feat(db): add append-only audit repository (record, history_for)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 07:52:16 +02:00
parent 01c42837d1
commit 87b016a56c
4 changed files with 168 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
use db::Db;
use db::audit;
use domain::{AuditAction, AuditActor, FieldChange, NewAuditEvent};
use serde_json::json;
use sqlx::PgPool;
use uuid::Uuid;
fn created(entity_id: Uuid, name: &str) -> NewAuditEvent {
NewAuditEvent {
actor: AuditActor::System,
action: AuditAction::Created,
entity_type: "object".into(),
entity_id,
changes: vec![FieldChange {
field: "name".into(),
before: None,
after: Some(json!(name)),
}],
}
}
#[sqlx::test]
async fn records_and_reads_back_history_in_order(pool: PgPool) {
let db = Db::from_pool(pool);
let id = Uuid::new_v4();
let user = Uuid::new_v4();
audit::record(db.pool(), &created(id, "Vase"))
.await
.unwrap();
audit::record(
db.pool(),
&NewAuditEvent {
actor: AuditActor::User(user),
action: AuditAction::Updated,
entity_type: "object".into(),
entity_id: id,
changes: vec![FieldChange {
field: "name".into(),
before: Some(json!("Vase")),
after: Some(json!("Roman Vase")),
}],
},
)
.await
.unwrap();
let history = audit::history_for(db.pool(), "object", id).await.unwrap();
assert_eq!(history.len(), 2);
assert_eq!(history[0].action, AuditAction::Created);
assert_eq!(history[0].actor, AuditActor::System);
assert_eq!(history[1].action, AuditAction::Updated);
assert_eq!(history[1].actor, AuditActor::User(user));
assert!(history[0].seq < history[1].seq, "ordered by seq");
assert_eq!(history[1].changes[0].field, "name");
assert_eq!(history[1].changes[0].after, Some(json!("Roman Vase")));
}
#[sqlx::test]
async fn history_is_scoped_to_one_entity(pool: PgPool) {
let db = Db::from_pool(pool);
let a = Uuid::new_v4();
let b = Uuid::new_v4();
audit::record(db.pool(), &created(a, "A")).await.unwrap();
audit::record(db.pool(), &created(b, "B")).await.unwrap();
let only_a = audit::history_for(db.pool(), "object", a).await.unwrap();
assert_eq!(only_a.len(), 1);
assert_eq!(only_a[0].entity_id, a);
}