Skip to content

Commit 5df3be7

Browse files
test: add more auth, relationship, and pruning tests
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent caae58a commit 5df3be7

8 files changed

Lines changed: 476 additions & 38 deletions

File tree

‎src/app/core/entities.rs‎

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
use anyhow::{Result, bail};
2+
use quick_cache::sync::Cache;
3+
use std::sync::Arc;
24

35
use crate::app::{SqlitePool, models};
46
use crate::utils::validate;
57

68
#[derive(Clone)]
79
pub struct LiwanEntities {
810
pool: SqlitePool,
11+
existing: Arc<Cache<String, ()>>,
912
}
1013

1114
impl LiwanEntities {
1215
pub fn new(pool: SqlitePool) -> Self {
13-
Self { pool }
16+
Self { pool, existing: Arc::new(Cache::new(512)) }
1417
}
1518

1619
/// Get all entities
@@ -34,13 +37,20 @@ impl LiwanEntities {
3437
"insert into entities (id, display_name) values (:id, :display_name)",
3538
rusqlite::named_params! { ":id": entity.id, ":display_name": entity.display_name },
3639
)?;
37-
for project_id in initial_projects {
38-
tx.execute(
39-
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
40-
rusqlite::named_params! { ":project_id": project_id, ":entity_id": entity.id },
41-
)?;
40+
{
41+
let mut exists = tx.prepare_cached("select 1 from projects where id = ? limit 1")?;
42+
for project_id in initial_projects {
43+
if !exists.exists([project_id])? {
44+
bail!("project not found: {project_id}");
45+
}
46+
tx.execute(
47+
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
48+
rusqlite::named_params! { ":project_id": project_id, ":entity_id": entity.id },
49+
)?;
50+
}
4251
}
4352
tx.commit()?;
53+
self.existing.insert(entity.id.clone(), ());
4454
Ok(())
4555
}
4656

@@ -57,11 +67,17 @@ impl LiwanEntities {
5767
let mut conn = self.pool.get()?;
5868
let tx = conn.transaction()?;
5969
tx.execute("delete from project_entities where entity_id = ?", rusqlite::params![entity_id])?;
60-
for project_id in project_ids {
61-
tx.execute(
62-
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
63-
rusqlite::named_params! { ":project_id": project_id, ":entity_id": entity_id },
64-
)?;
70+
{
71+
let mut exists = tx.prepare_cached("select 1 from projects where id = ? limit 1")?;
72+
for project_id in project_ids {
73+
if !exists.exists([project_id])? {
74+
bail!("project not found: {project_id}");
75+
}
76+
tx.execute(
77+
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
78+
rusqlite::named_params! { ":project_id": project_id, ":entity_id": entity_id },
79+
)?;
80+
}
6581
}
6682
tx.commit()?;
6783
Ok(())
@@ -75,6 +91,7 @@ impl LiwanEntities {
7591
tx.execute("delete from entities where id = ?", rusqlite::params![id])?;
7692
tx.execute("delete from project_entities where entity_id = ?", rusqlite::params![id])?;
7793
tx.commit()?;
94+
self.existing.remove(id);
7895
Ok(())
7996
}
8097

@@ -98,8 +115,16 @@ impl LiwanEntities {
98115

99116
/// Check if an entity exists
100117
pub fn exists(&self, id: &str) -> Result<bool> {
118+
if self.existing.get(id).is_some() {
119+
return Ok(true);
120+
}
121+
101122
let conn = self.pool.get()?;
102123
let mut stmt = conn.prepare_cached("select 1 from entities where id = ? limit 1")?;
103-
Ok(stmt.exists([id])?)
124+
let exists = stmt.exists([id])?;
125+
if exists {
126+
self.existing.insert(id.to_string(), ());
127+
}
128+
Ok(exists)
104129
}
105130
}

‎src/app/core/projects.rs‎

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ impl LiwanProjects {
1818
let mut conn = self.pool.get()?;
1919
let tx = conn.transaction()?;
2020
tx.execute("delete from project_entities where project_id = ?", rusqlite::params![project_id])?;
21-
for entity_id in entity_ids {
22-
tx.execute(
23-
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
24-
rusqlite::named_params! { ":project_id": project_id, ":entity_id": entity_id },
25-
)?;
21+
{
22+
let mut exists = tx.prepare_cached("select 1 from entities where id = ? limit 1")?;
23+
for entity_id in entity_ids {
24+
if !exists.exists([entity_id])? {
25+
bail!("entity not found: {entity_id}");
26+
}
27+
tx.execute(
28+
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
29+
rusqlite::named_params! { ":project_id": project_id, ":entity_id": entity_id },
30+
)?;
31+
}
2632
}
2733
tx.commit()?;
2834
Ok(())
@@ -99,11 +105,17 @@ impl LiwanProjects {
99105
":secret": project.secret,
100106
},
101107
)?;
102-
for entity_id in initial_entities {
103-
tx.execute(
104-
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
105-
rusqlite::named_params! { ":project_id": project.id, ":entity_id": entity_id },
106-
)?;
108+
{
109+
let mut exists = tx.prepare_cached("select 1 from entities where id = ? limit 1")?;
110+
for entity_id in initial_entities {
111+
if !exists.exists([entity_id])? {
112+
bail!("entity not found: {entity_id}");
113+
}
114+
tx.execute(
115+
"insert into project_entities (project_id, entity_id) values (:project_id, :entity_id)",
116+
rusqlite::named_params! { ":project_id": project.id, ":entity_id": entity_id },
117+
)?;
118+
}
107119
}
108120
tx.commit()?;
109121
Ok(project.clone())

‎src/web/routes/admin.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ async fn remove_user(
303303
http_bail!(StatusCode::FORBIDDEN, "Forbidden")
304304
}
305305

306-
if username == session_user.username {
306+
if username.eq_ignore_ascii_case(&session_user.username) {
307307
http_bail!(StatusCode::FORBIDDEN, "Cannot delete own user")
308308
}
309309

@@ -355,7 +355,7 @@ async fn project_create_handler(
355355
},
356356
project.entities.as_slice(),
357357
)
358-
.http_err("Failed to create project", StatusCode::INTERNAL_SERVER_ERROR)?;
358+
.http_err("Failed to create project", StatusCode::BAD_REQUEST)?;
359359

360360
Ok(empty_response())
361361
}
@@ -385,7 +385,7 @@ async fn project_update_handler(
385385
if let Some(entities) = req.entities {
386386
app.projects
387387
.update_entities(&project_id, entities.as_slice())
388-
.http_err("Failed to update project entities", StatusCode::INTERNAL_SERVER_ERROR)?;
388+
.http_err("Failed to update project entities", StatusCode::BAD_REQUEST)?;
389389
}
390390

391391
Ok(empty_response())
@@ -627,7 +627,7 @@ async fn entity_create_handler(
627627
&Entity { id: entity.id.clone(), display_name: entity.display_name.clone() },
628628
entity.projects.as_slice(),
629629
)
630-
.http_err("Failed to create entity", StatusCode::INTERNAL_SERVER_ERROR)?;
630+
.http_err("Failed to create entity", StatusCode::BAD_REQUEST)?;
631631

632632
Ok(Json(EntityResponse { id: entity.id, display_name: entity.display_name, projects: Vec::new() }))
633633
}
@@ -651,7 +651,7 @@ async fn entity_update_handler(
651651
if let Some(projects) = entity.projects {
652652
app.entities
653653
.update_projects(&entity_id, projects.as_slice())
654-
.http_err("Failed to update entity projects", StatusCode::INTERNAL_SERVER_ERROR)?;
654+
.http_err("Failed to update entity projects", StatusCode::BAD_REQUEST)?;
655655
}
656656

657657
Ok(empty_response())

‎src/web/routes/event.rs‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use http::StatusCode;
2020
use schemars::JsonSchema;
2121
use std::net::IpAddr;
2222
use std::str::FromStr;
23-
use std::sync::{Arc, LazyLock};
23+
use std::sync::Arc;
2424
use tower_governor::GovernorLayer;
2525
use tower_governor::governor::GovernorConfigBuilder;
2626
use url::Url;
@@ -144,9 +144,6 @@ fn extract_utm(url: &mut Url) -> Utm {
144144
}
145145
}
146146

147-
static EXISTING_ENTITIES: LazyLock<quick_cache::sync::Cache<String, ()>> =
148-
LazyLock::new(|| quick_cache::sync::Cache::new(512));
149-
150147
async fn event_handler(
151148
state: State<RouterState>,
152149
ClientIp(ip): ClientIp,
@@ -198,11 +195,8 @@ fn process_event(
198195
let referrer = referrer.map(|r| r.trim_start_matches("www.").to_string()); // remove www. prefix
199196
let referrer = referrer.filter(|r| r.trim().len() > 3); // ignore empty or short referrers
200197

201-
if EXISTING_ENTITIES.get(&event.entity_id).is_none() {
202-
if !app.entities.exists(&event.entity_id).unwrap_or(false) {
203-
return Ok(None);
204-
}
205-
EXISTING_ENTITIES.insert(event.entity_id.clone(), ());
198+
if !app.entities.exists(&event.entity_id).unwrap_or(false) {
199+
return Ok(None);
206200
}
207201

208202
let settings = app.settings.resolved_for_entity(&event.entity_id);

0 commit comments

Comments
 (0)