use db::Db; use db::audit; use domain::{AuditAction, AuditActor, NewAuditEvent}; use sqlx::PgPool; use uuid::Uuid; fn sample() -> NewAuditEvent { NewAuditEvent { actor: AuditActor::System, action: AuditAction::Created, entity_type: "object".into(), entity_id: Uuid::new_v4(), changes: vec![], } } async fn count(pool: &PgPool) -> i64 { sqlx::query_scalar("SELECT count(*) FROM audit_log") .fetch_one(pool) .await .unwrap() } #[sqlx::test] async fn update_delete_truncate_are_rejected(pool: PgPool) { let db = Db::from_pool(pool); audit::record(db.pool(), &sample()).await.unwrap(); // Each failing statement poisons its connection (Postgres enters aborted-transaction // state). Acquire a fresh connection per statement so later assertions are independent. let update_err = sqlx::query("UPDATE audit_log SET action = 'deleted'") .execute(db.pool()) .await .unwrap_err() .to_string(); assert!( update_err.contains("audit_log is append-only"), "UPDATE must be rejected by the trigger, got: {update_err}" ); let delete_err = sqlx::query("DELETE FROM audit_log") .execute(db.pool()) .await .unwrap_err() .to_string(); assert!( delete_err.contains("audit_log is append-only"), "DELETE must be rejected by the trigger, got: {delete_err}" ); let truncate_err = sqlx::query("TRUNCATE audit_log") .execute(db.pool()) .await .unwrap_err() .to_string(); assert!( truncate_err.contains("audit_log is append-only"), "TRUNCATE must be rejected by the trigger, got: {truncate_err}" ); assert_eq!(count(db.pool()).await, 1, "the row is still there"); } #[sqlx::test] async fn record_rolls_back_with_caller_transaction(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); audit::record(&mut *tx, &sample()).await.unwrap(); tx.rollback().await.unwrap(); assert_eq!( count(db.pool()).await, 0, "a rolled-back audit record must not persist" ); } #[sqlx::test] async fn record_commits_with_caller_transaction(pool: PgPool) { let db = Db::from_pool(pool); let mut tx = db.pool().begin().await.unwrap(); audit::record(&mut *tx, &sample()).await.unwrap(); tx.commit().await.unwrap(); assert_eq!( count(db.pool()).await, 1, "a committed audit record persists" ); }