From c67b5881881f5543a69f89e03ea5c82bb6351937 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Tue, 2 Jun 2026 07:56:05 +0200 Subject: [PATCH] test(db): cover delete/empty-changes/empty-history; clarify map_row naming Co-Authored-By: Claude Sonnet 4.6 --- crates/db/src/audit.rs | 7 ++++--- crates/db/tests/audit.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/crates/db/src/audit.rs b/crates/db/src/audit.rs index 1d7bcea..d53f5d9 100644 --- a/crates/db/src/audit.rs +++ b/crates/db/src/audit.rs @@ -41,6 +41,7 @@ pub async fn history_for<'e, E>( where E: sqlx::PgExecutor<'e>, { + // TODO: add LIMIT/keyset pagination before exposing history_for via the API. let rows = sqlx::query( "SELECT seq, at, actor_kind, actor_id, action, entity_type, entity_id, changes \ FROM audit_log \ @@ -60,7 +61,7 @@ fn map_row(row: sqlx::postgres::PgRow) -> Result { let at: time::OffsetDateTime = row.try_get("at")?; let actor_kind: String = row.try_get("actor_kind")?; let actor_id: Option = row.try_get("actor_id")?; - let action: String = row.try_get("action")?; + let action_str: String = row.try_get("action")?; let entity_type: String = row.try_get("entity_type")?; let entity_id: Uuid = row.try_get("entity_id")?; let changes: sqlx::types::Json> = row.try_get("changes")?; @@ -77,8 +78,8 @@ fn map_row(row: sqlx::postgres::PgRow) -> Result { } }; - let action = domain::AuditAction::from_db(&action) - .ok_or_else(|| sqlx::Error::Decode(format!("unknown action: {action}").into()))?; + let action = domain::AuditAction::from_db(&action_str) + .ok_or_else(|| sqlx::Error::Decode(format!("unknown action: {action_str}").into()))?; Ok(AuditEntry { seq, diff --git a/crates/db/tests/audit.rs b/crates/db/tests/audit.rs index d5ce510..c7b4b38 100644 --- a/crates/db/tests/audit.rs +++ b/crates/db/tests/audit.rs @@ -68,3 +68,38 @@ async fn history_is_scoped_to_one_entity(pool: PgPool) { assert_eq!(only_a.len(), 1); assert_eq!(only_a[0].entity_id, a); } + +#[sqlx::test] +async fn deleted_action_with_empty_changes_round_trips(pool: PgPool) { + let db = Db::from_pool(pool); + let id = Uuid::new_v4(); + + audit::record( + db.pool(), + &NewAuditEvent { + actor: AuditActor::System, + action: AuditAction::Deleted, + entity_type: "object".into(), + entity_id: id, + changes: vec![], + }, + ) + .await + .unwrap(); + + let history = audit::history_for(db.pool(), "object", id).await.unwrap(); + assert_eq!(history.len(), 1); + assert_eq!(history[0].action, AuditAction::Deleted); + assert!(history[0].changes.is_empty()); +} + +#[sqlx::test] +async fn history_is_empty_for_unknown_entity(pool: PgPool) { + let db = Db::from_pool(pool); + + let history = audit::history_for(db.pool(), "object", Uuid::new_v4()) + .await + .unwrap(); + + assert!(history.is_empty()); +}