Files

101 lines
2.5 KiB
Rust

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"
);
}