pub fn record_audit(
conn: &rusqlite::Connection,
action: &str,
entity_type: &str,
entity_id: &str,
actor: Option<&str>,
details: Option<&str>,
) {
let result = conn.execute(
"INSERT INTO audit_log (action, entity_type, entity_id, actor, details) VALUES (?, ?, ?, ?, ?)",
rusqlite::params![action, entity_type, entity_id, actor, details],
);
if let Err(e) = result {
tracing::warn!(
action,
entity_type,
entity_id,
error = %e,
"Failed to write audit log entry"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_audit_succeeds_on_valid_table() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT NOT NULL,
entity_type TEXT NOT NULL,
entity_id TEXT NOT NULL,
actor TEXT,
details TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)",
)
.unwrap();
record_audit(&conn, "create", "order", "ord-123", Some("admin"), Some("{\"total\":99.99}"));
let count: i64 =
conn.query_row("SELECT COUNT(*) FROM audit_log", [], |r| r.get(0)).unwrap();
assert_eq!(count, 1);
let action: String =
conn.query_row("SELECT action FROM audit_log WHERE id = 1", [], |r| r.get(0)).unwrap();
assert_eq!(action, "create");
}
#[test]
fn record_audit_does_not_panic_on_missing_table() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
record_audit(&conn, "delete", "customer", "cust-456", None, None);
}
}