auditlog 0.1.0

Audit trail for your data models — an ORM-agnostic core with a pluggable, async sqlx backend (SQLite & Postgres).
Documentation
//! Tests for *process-global* configuration: the master auditing switch and the global
//! `max_audits`. These mutate shared statics, so they live in their own test binary (separate
//! process from `behavior.rs`) and run as a single serial function to avoid intra-file races.

use auditlog::{
    AuditId, AuditOptions, Auditable, SqlxBackend, ValueMap, config, set_auditing_enabled,
    with_auditing,
};
use serde_json::json;

struct GUser {
    id: i64,
    name: String,
    status: i64,
}

impl Auditable for GUser {
    fn auditable_type() -> &'static str {
        "GUser"
    }
    fn auditable_id(&self) -> AuditId {
        self.id.into()
    }
    fn audited_attributes(&self) -> ValueMap {
        let mut m = ValueMap::new();
        m.insert("id".into(), json!(self.id));
        m.insert("name".into(), json!(self.name));
        m.insert("status".into(), json!(self.status));
        m
    }
    fn audit_options() -> AuditOptions {
        AuditOptions::default()
    }
}

async fn backend() -> SqlxBackend {
    let b = SqlxBackend::connect_sqlite("sqlite::memory:")
        .await
        .unwrap();
    b.migrate().await.unwrap();
    b
}

#[tokio::test]
async fn process_global_state() {
    // ---- global master switch ----
    {
        let b = backend().await;
        let u = GUser {
            id: 1,
            name: "A".into(),
            status: 0,
        };

        set_auditing_enabled(false);
        assert!(u.audited_create(&b).await.unwrap().is_none());

        // with_auditing must NOT re-enable when the global master switch is off
        with_auditing(async { u.audited_create(&b).await })
            .await
            .unwrap();
        assert_eq!(GUser::audits(&b, 1).await.unwrap().len(), 0);

        set_auditing_enabled(true);
        assert!(u.audited_create(&b).await.unwrap().is_some());
        assert_eq!(GUser::audits(&b, 1).await.unwrap().len(), 1);
    }

    // ---- global max_audits ----
    {
        let b = backend().await;
        config(|c| c.max_audits = Some(2));

        let mut u = GUser {
            id: 2,
            name: "Foobar".into(),
            status: 0,
        };
        u.audited_create(&b).await.unwrap(); // v1
        let o1 = GUser {
            id: 2,
            name: "Foobar".into(),
            status: 0,
        };
        u.name = "Awesome".into();
        u.audited_update(&b, &o1).await.unwrap(); // v2
        let o2 = GUser {
            id: 2,
            name: "Awesome".into(),
            status: 0,
        };
        u.status = 5;
        u.audited_update(&b, &o2).await.unwrap(); // v3

        let audits = GUser::audits(&b, 2).await.unwrap();
        assert_eq!(audits.len(), 2);
        assert_eq!(
            audits.iter().map(|a| a.version).collect::<Vec<_>>(),
            vec![2, 3]
        );

        // reset for any later tests / other runs in this process
        config(|c| c.max_audits = None);
    }
}