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:
@@ -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,
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user