sz-orm-audit 3.5.0

SQL audit log: execution audit trail with case-insensitive sensitive-keyword masking (password/token/credit_card) and JSON flush
Documentation
use sz_orm_audit::*;

fn make_ctx(sql: &str, user: &str, ts: i64) -> SqlAuditContext {
    SqlAuditContext {
        sql: sql.to_string(),
        user: user.to_string(),
        timestamp: ts,
    }
}

#[test]
fn test_sql_auditor_log_and_get() {
    let auditor = SqlAuditor::new();
    auditor.log(&make_ctx("SELECT * FROM users", "alice", 1000));
    auditor.log(&make_ctx("INSERT INTO logs VALUES(1)", "bob", 2000));
    let logs = auditor.get_logs();
    assert_eq!(logs.len(), 2);
    assert_eq!(logs[0].user, "alice");
    assert_eq!(logs[1].user, "bob");
}

#[test]
fn test_sql_auditor_empty() {
    let auditor = SqlAuditor::new();
    assert!(auditor.get_logs().is_empty());
}

#[test]
fn test_sql_auditor_mask_sensitive_password() {
    let auditor = SqlAuditor::new();
    let masked = auditor.mask_sensitive("WHERE password = 'secret123'");
    assert!(masked.contains("******"));
    assert!(!masked.contains("password"));
}

#[test]
fn test_sql_auditor_mask_sensitive_token() {
    let auditor = SqlAuditor::new();
    let masked = auditor.mask_sensitive("SET token = 'abc'");
    assert!(masked.contains("******"));
}

#[test]
fn test_sql_auditor_mask_sensitive_credit_card() {
    let auditor = SqlAuditor::new();
    let masked = auditor.mask_sensitive("credit_card = '1234'");
    assert!(masked.contains("******"));
}

#[test]
fn test_sql_auditor_mask_sensitive_case_insensitive() {
    let auditor = SqlAuditor::new();
    let masked = auditor.mask_sensitive("WHERE PASSWORD = 'x'");
    assert!(masked.contains("******"));
}

#[test]
fn test_sql_auditor_mask_not_in_identifier() {
    let auditor = SqlAuditor::new();
    let masked = auditor.mask_sensitive("SELECT password_hash FROM users");
    assert!(!masked.contains("******"));
    assert!(masked.contains("password_hash"));
}

#[test]
fn test_sql_auditor_mask_no_sensitive() {
    let auditor = SqlAuditor::new();
    let masked = auditor.mask_sensitive("SELECT id, name FROM users");
    assert_eq!(masked, "SELECT id, name FROM users");
}

#[test]
fn test_sql_auditor_log_masks_automatically() {
    let auditor = SqlAuditor::new();
    auditor.log(&make_ctx("WHERE password = 'secret'", "user", 1000));
    let logs = auditor.get_logs();
    assert!(logs[0].sql.contains("******"));
    assert!(!logs[0].sql.contains("password"));
}

#[test]
fn test_audit_rules_default_allows_all() {
    let rules = AuditRules::new();
    assert!(rules.should_audit("SELECT * FROM users"));
    assert!(rules.should_audit("DROP TABLE users"));
}

#[test]
fn test_audit_rules_allow_list() {
    let rules = AuditRules::new().allow("select").allow("insert");
    assert!(rules.should_audit("SELECT * FROM users"));
    assert!(rules.should_audit("INSERT INTO t VALUES(1)"));
    assert!(!rules.should_audit("DROP TABLE users"));
}

#[test]
fn test_audit_rules_deny_list() {
    let rules = AuditRules::new().deny("drop").deny("delete");
    assert!(!rules.should_audit("DROP TABLE users"));
    assert!(!rules.should_audit("DELETE FROM users"));
    assert!(rules.should_audit("SELECT * FROM users"));
}

#[test]
fn test_audit_rules_deny_overrides_allow() {
    let rules = AuditRules::new().allow("select").deny("select");
    assert!(!rules.should_audit("SELECT * FROM users"));
}

#[test]
fn test_audit_rules_case_insensitive() {
    let rules = AuditRules::new().deny("DROP");
    assert!(!rules.should_audit("drop table users"));
}

#[test]
fn test_audit_rules_counts() {
    let rules = AuditRules::new().allow("a").allow("b").deny("c");
    assert_eq!(rules.allow_count(), 2);
    assert_eq!(rules.deny_count(), 1);
}

#[test]
fn test_rotation_policy_none() {
    let policy = RotationPolicy::none();
    assert_eq!(policy.max_entries, 0);
    assert_eq!(policy.max_age_ms, 0);
}

#[test]
fn test_rotation_policy_by_size() {
    let policy = RotationPolicy::by_size(100);
    assert_eq!(policy.max_entries, 100);
    assert_eq!(policy.max_age_ms, 0);
}

#[test]
fn test_rotation_policy_by_age() {
    let policy = RotationPolicy::by_age(60000);
    assert_eq!(policy.max_entries, 0);
    assert_eq!(policy.max_age_ms, 60000);
}

#[test]
fn test_rotation_policy_by_size_and_age() {
    let policy = RotationPolicy::by_size_and_age(100, 60000);
    assert_eq!(policy.max_entries, 100);
    assert_eq!(policy.max_age_ms, 60000);
}

#[test]
fn test_rotating_auditor_basic() {
    let auditor = RotatingAuditor::with_max_entries(10);
    assert!(auditor.is_empty());
    auditor.log(&make_ctx("SELECT 1", "user", 1000));
    assert_eq!(auditor.len(), 1);
    assert!(!auditor.is_empty());
}

#[test]
fn test_rotating_auditor_rotation_by_size() {
    let auditor = RotatingAuditor::with_max_entries(3);
    auditor.log(&make_ctx("SQL1", "u", 1000));
    auditor.log(&make_ctx("SQL2", "u", 1000));
    auditor.log(&make_ctx("SQL3", "u", 1000));
    auditor.log(&make_ctx("SQL4", "u", 1000));
    assert_eq!(auditor.rotation_count(), 1);
    assert_eq!(auditor.len(), 1);
}

#[test]
fn test_rotating_auditor_with_rules() {
    let rules = AuditRules::new().deny("drop");
    let auditor = RotatingAuditor::new(RotationPolicy::none(), rules);
    let logged = auditor.log(&make_ctx("DROP TABLE x", "u", 1000));
    assert!(!logged);
    assert_eq!(auditor.len(), 0);
    let logged2 = auditor.log(&make_ctx("SELECT 1", "u", 1000));
    assert!(logged2);
    assert_eq!(auditor.len(), 1);
}

#[test]
fn test_rotating_auditor_manual_rotate() {
    let auditor = RotatingAuditor::with_max_entries(100);
    auditor.log(&make_ctx("SQL1", "u", 1000));
    auditor.log(&make_ctx("SQL2", "u", 1000));
    let cleared = auditor.rotate();
    assert_eq!(cleared, 2);
    assert!(auditor.is_empty());
    assert_eq!(auditor.rotation_count(), 1);
}

#[test]
fn test_async_audit_writer() {
    let writer = AsyncAuditWriter::new();
    writer.log(&make_ctx("SELECT 1", "user", 1000)).unwrap();
    writer.log(&make_ctx("SELECT 2", "user", 2000)).unwrap();
    let logs = writer.shutdown().unwrap();
    assert_eq!(logs.len(), 2);
}

#[test]
fn test_async_audit_writer_masks_sensitive() {
    let writer = AsyncAuditWriter::new();
    writer
        .log(&make_ctx("WHERE password = 'x'", "user", 1000))
        .unwrap();
    let logs = writer.shutdown().unwrap();
    assert!(logs[0].sql.contains("******"));
}

#[test]
fn test_async_audit_writer_double_shutdown() {
    let writer = AsyncAuditWriter::new();
    writer.shutdown().unwrap();
    let result = writer.shutdown();
    assert!(result.is_err());
}

#[test]
fn test_audit_query_by_user() {
    let query = AuditQuery::new().by_user("alice");
    assert_eq!(query.user, Some("alice".to_string()));
}

#[test]
fn test_audit_query_by_time_range() {
    let query = AuditQuery::new().by_time_range(1000, 2000);
    assert_eq!(query.from_ts, Some(1000));
    assert_eq!(query.to_ts, Some(2000));
}

#[test]
fn test_audit_query_by_sql_contains() {
    let query = AuditQuery::new().by_sql_contains("INSERT");
    assert_eq!(query.sql_contains, Some("INSERT".to_string()));
}

#[test]
fn test_audit_query_with_limit() {
    let query = AuditQuery::new().with_limit(10);
    assert_eq!(query.limit, 10);
}

#[test]
fn test_audit_query_filter() {
    let logs = vec![
        make_ctx("SELECT 1", "alice", 1000),
        make_ctx("INSERT 1", "bob", 2000),
        make_ctx("SELECT 2", "alice", 3000),
    ];
    let query = AuditQuery::new().by_user("alice");
    let filtered = query.filter(&logs);
    assert_eq!(filtered.len(), 2);
}

#[test]
fn test_audit_query_filter_with_limit() {
    let logs = vec![
        make_ctx("SQL1", "u", 1000),
        make_ctx("SQL2", "u", 2000),
        make_ctx("SQL3", "u", 3000),
    ];
    let query = AuditQuery::new().with_limit(2);
    let filtered = query.filter(&logs);
    assert_eq!(filtered.len(), 2);
}

#[test]
fn test_audit_query_filter_time_range() {
    let logs = vec![
        make_ctx("SQL1", "u", 1000),
        make_ctx("SQL2", "u", 2000),
        make_ctx("SQL3", "u", 3000),
    ];
    let query = AuditQuery::new().by_time_range(1500, 2500);
    let filtered = query.filter(&logs);
    assert_eq!(filtered.len(), 1);
}

#[test]
fn test_audit_query_filter_sql_contains() {
    let logs = vec![
        make_ctx("SELECT * FROM t", "u", 1000),
        make_ctx("INSERT INTO t", "u", 2000),
    ];
    let query = AuditQuery::new().by_sql_contains("insert");
    let filtered = query.filter(&logs);
    assert_eq!(filtered.len(), 1);
}