test(db): cover delete/empty-changes/empty-history; clarify map_row naming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 07:56:05 +02:00
parent 87b016a56c
commit c67b588188
2 changed files with 39 additions and 3 deletions
+4 -3
View File
@@ -41,6 +41,7 @@ pub async fn history_for<'e, E>(
where where
E: sqlx::PgExecutor<'e>, E: sqlx::PgExecutor<'e>,
{ {
// TODO: add LIMIT/keyset pagination before exposing history_for via the API.
let rows = sqlx::query( let rows = sqlx::query(
"SELECT seq, at, actor_kind, actor_id, action, entity_type, entity_id, changes \ "SELECT seq, at, actor_kind, actor_id, action, entity_type, entity_id, changes \
FROM audit_log \ FROM audit_log \
@@ -60,7 +61,7 @@ fn map_row(row: sqlx::postgres::PgRow) -> Result<AuditEntry, sqlx::Error> {
let at: time::OffsetDateTime = row.try_get("at")?; let at: time::OffsetDateTime = row.try_get("at")?;
let actor_kind: String = row.try_get("actor_kind")?; let actor_kind: String = row.try_get("actor_kind")?;
let actor_id: Option<Uuid> = row.try_get("actor_id")?; let actor_id: Option<Uuid> = 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_type: String = row.try_get("entity_type")?;
let entity_id: Uuid = row.try_get("entity_id")?; let entity_id: Uuid = row.try_get("entity_id")?;
let changes: sqlx::types::Json<Vec<FieldChange>> = row.try_get("changes")?; let changes: sqlx::types::Json<Vec<FieldChange>> = row.try_get("changes")?;
@@ -77,8 +78,8 @@ fn map_row(row: sqlx::postgres::PgRow) -> Result<AuditEntry, sqlx::Error> {
} }
}; };
let action = domain::AuditAction::from_db(&action) let action = domain::AuditAction::from_db(&action_str)
.ok_or_else(|| sqlx::Error::Decode(format!("unknown action: {action}").into()))?; .ok_or_else(|| sqlx::Error::Decode(format!("unknown action: {action_str}").into()))?;
Ok(AuditEntry { Ok(AuditEntry {
seq, seq,
+35
View File
@@ -68,3 +68,38 @@ async fn history_is_scoped_to_one_entity(pool: PgPool) {
assert_eq!(only_a.len(), 1); assert_eq!(only_a.len(), 1);
assert_eq!(only_a[0].entity_id, a); 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());
}