Skip to main content

mvm_core/
audit.rs

1use serde::{Deserialize, Serialize};
2
3/// Audit event types for per-tenant audit logging.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub enum AuditAction {
6    InstanceCreated,
7    InstanceStarted,
8    InstanceStopped,
9    InstanceWarmed,
10    InstanceSlept,
11    InstanceWoken,
12    InstanceDestroyed,
13    PoolCreated,
14    PoolBuilt,
15    PoolDestroyed,
16    TenantCreated,
17    TenantDestroyed,
18    QuotaExceeded,
19    SecretsRotated,
20    SnapshotCreated,
21    SnapshotRestored,
22    SnapshotDeleted,
23    TransitionDeferred,
24    MinRuntimeOverridden,
25}
26
27/// A single audit log entry.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct AuditEntry {
30    pub timestamp: String,
31    pub tenant_id: String,
32    pub pool_id: Option<String>,
33    pub instance_id: Option<String>,
34    pub action: AuditAction,
35    pub detail: Option<String>,
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_audit_entry_serialization() {
44        let entry = AuditEntry {
45            timestamp: "2025-01-01T00:00:00Z".to_string(),
46            tenant_id: "acme".to_string(),
47            pool_id: Some("workers".to_string()),
48            instance_id: Some("i-abc123".to_string()),
49            action: AuditAction::InstanceStarted,
50            detail: Some("pid=12345".to_string()),
51        };
52
53        let json = serde_json::to_string(&entry).unwrap();
54        assert!(json.contains("\"tenant_id\":\"acme\""));
55        assert!(json.contains("\"InstanceStarted\""));
56    }
57
58    #[test]
59    fn test_audit_entry_no_optionals() {
60        let entry = AuditEntry {
61            timestamp: "2025-01-01T00:00:00Z".to_string(),
62            tenant_id: "acme".to_string(),
63            pool_id: None,
64            instance_id: None,
65            action: AuditAction::TenantCreated,
66            detail: None,
67        };
68
69        let json = serde_json::to_string(&entry).unwrap();
70        assert!(json.contains("\"pool_id\":null"));
71    }
72
73    #[test]
74    fn test_all_audit_actions_serialize() {
75        let actions = vec![
76            AuditAction::InstanceCreated,
77            AuditAction::InstanceStarted,
78            AuditAction::InstanceStopped,
79            AuditAction::InstanceWarmed,
80            AuditAction::InstanceSlept,
81            AuditAction::InstanceWoken,
82            AuditAction::InstanceDestroyed,
83            AuditAction::PoolCreated,
84            AuditAction::PoolBuilt,
85            AuditAction::PoolDestroyed,
86            AuditAction::TenantCreated,
87            AuditAction::TenantDestroyed,
88            AuditAction::QuotaExceeded,
89            AuditAction::SecretsRotated,
90            AuditAction::SnapshotCreated,
91            AuditAction::SnapshotRestored,
92            AuditAction::SnapshotDeleted,
93            AuditAction::TransitionDeferred,
94            AuditAction::MinRuntimeOverridden,
95        ];
96
97        for action in actions {
98            let json = serde_json::to_string(&action).unwrap();
99            assert!(!json.is_empty());
100        }
101    }
102}