//! SOX Audit Trail Completeness Tests //! //! Validates that all critical operations have complete audit trails with //! all required fields (WHO, WHAT, WHEN, WHERE, WHY, RESULT) as required //! by SOX compliance. use chrono::Utc; use rust_decimal::Decimal; use std::collections::HashMap; use trading_engine::compliance::audit_trails::{ AuditEventType, AuditTrailEngine, AuditTrailConfig, OrderDetails, ExecutionDetails, RiskLevel, TransactionAuditEvent, }; #[tokio::test] async fn test_configuration_change_audit_completeness() { // Test that configuration changes are fully audited with all 6 fields let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); let mut metadata = HashMap::new(); metadata.insert("config_key".to_string(), serde_json::json!("max_position_size")); metadata.insert("old_value".to_string(), serde_json::json!(1000000)); metadata.insert("new_value".to_string(), serde_json::json!(2000000)); metadata.insert("reason".to_string(), serde_json::json!("Risk limit increase")); let event = TransactionAuditEvent { event_id: "CFG-001".to_string(), timestamp: Utc::now(), timestamp_nanos: 0, event_type: AuditEventType::SystemEvent, transaction_id: "TXN-CFG-001".to_string(), order_id: "N/A".to_string(), actor: "admin_user".to_string(), // WHO session_id: Some("session-123".to_string()), client_ip: Some("192.168.1.100".to_string()), // WHERE details: trading_engine::compliance::audit_trails::AuditEventDetails { symbol: None, quantity: None, price: None, side: None, order_type: None, venue: None, account_id: None, strategy_id: None, metadata: metadata.clone(), // WHAT + WHY performance_metrics: None, }, before_state: Some(serde_json::json!({"max_position_size": 1000000})), after_state: Some(serde_json::json!({"max_position_size": 2000000})), compliance_tags: vec!["SOX".to_string(), "CONFIG_CHANGE".to_string()], risk_level: RiskLevel::High, digital_signature: None, checksum: String::new(), }; // Validate all 6 fields are present assert!(event.actor != "", "WHO field missing (actor)"); assert!(event.client_ip.is_some(), "WHERE field missing (client_ip)"); assert!(event.before_state.is_some() && event.after_state.is_some(), "WHAT field missing (state tracking)"); assert!(metadata.contains_key("reason"), "WHY field missing (reason/context)"); assert!(event.timestamp.timestamp() > 0, "WHEN field missing (timestamp)"); let result = audit_engine.log_event(event); assert!(result.is_ok(), "RESULT field missing (success/failure)"); } #[tokio::test] async fn test_order_lifecycle_audit() { // Test complete order lifecycle: creation → execution → settlement let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); // 1. Order creation let order_details = OrderDetails { transaction_id: "TXN-ORDER-001".to_string(), user_id: "trader_123".to_string(), session_id: Some("session-456".to_string()), client_ip: Some("10.0.0.50".to_string()), symbol: "AAPL".to_string(), quantity: Decimal::new(100, 0), price: Some(Decimal::new(15000, 2)), side: "BUY".to_string(), order_type: "LIMIT".to_string(), venue: Some("NASDAQ".to_string()), account_id: "ACC-001".to_string(), strategy_id: Some("STRAT-MM-001".to_string()), metadata: HashMap::new(), }; let create_result = audit_engine.log_order_created("ORD-001", &order_details); assert!(create_result.is_ok(), "Order creation audit failed"); // 2. Order execution let execution_details = ExecutionDetails { transaction_id: "TXN-ORDER-001".to_string(), order_id: "ORD-001".to_string(), symbol: "AAPL".to_string(), executed_quantity: Decimal::new(100, 0), execution_price: Decimal::new(15050, 2), side: "BUY".to_string(), venue: "NASDAQ".to_string(), account_id: "ACC-001".to_string(), strategy_id: Some("STRAT-MM-001".to_string()), metadata: HashMap::new(), processing_latency_ns: 1200, queue_time_ns: 300, system_load: 0.45, memory_usage_bytes: 1024 * 1024 * 512, }; let exec_result = audit_engine.log_order_executed(&execution_details); assert!(exec_result.is_ok(), "Order execution audit failed"); // 3. Verify full lifecycle captured // In production, would query audit trail to verify both events present } #[tokio::test] async fn test_user_authentication_audit() { // Test user authentication events have complete audit trail let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); let auth_event = TransactionAuditEvent { event_id: "AUTH-001".to_string(), timestamp: Utc::now(), timestamp_nanos: 0, event_type: AuditEventType::UserAuthenticated, transaction_id: "TXN-AUTH-001".to_string(), order_id: "N/A".to_string(), actor: "user@example.com".to_string(), // WHO session_id: Some("session-789".to_string()), client_ip: Some("203.0.113.45".to_string()), // WHERE details: trading_engine::compliance::audit_trails::AuditEventDetails { symbol: None, quantity: None, price: None, side: None, order_type: None, venue: None, account_id: Some("ACC-002".to_string()), strategy_id: None, metadata: { let mut m = HashMap::new(); m.insert("auth_method".to_string(), serde_json::json!("MFA")); // WHAT m.insert("mfa_factor".to_string(), serde_json::json!("TOTP")); m.insert("reason".to_string(), serde_json::json!("Normal login")); // WHY m }, performance_metrics: None, }, before_state: None, after_state: Some(serde_json::json!({"authenticated": true, "roles": ["trader"]})), compliance_tags: vec!["SOX".to_string(), "AUTH".to_string()], risk_level: RiskLevel::Medium, digital_signature: None, checksum: String::new(), }; let result = audit_engine.log_event(auth_event); assert!(result.is_ok(), "Authentication audit failed"); } #[tokio::test] async fn test_position_modification_audit_with_state_tracking() { // Test position modifications capture before/after state let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); let position_event = TransactionAuditEvent { event_id: "POS-001".to_string(), timestamp: Utc::now(), timestamp_nanos: 0, event_type: AuditEventType::PositionUpdate, transaction_id: "TXN-POS-001".to_string(), order_id: "ORD-002".to_string(), actor: "system_reconciliation".to_string(), // WHO session_id: None, client_ip: None, details: trading_engine::compliance::audit_trails::AuditEventDetails { symbol: Some("TSLA".to_string()), quantity: Some(Decimal::new(50, 0)), price: Some(Decimal::new(20000, 2)), side: Some("BUY".to_string()), order_type: None, venue: Some("NASDAQ".to_string()), account_id: Some("ACC-001".to_string()), strategy_id: Some("STRAT-MM-001".to_string()), metadata: { let mut m = HashMap::new(); m.insert("reason".to_string(), serde_json::json!("Execution fill")); // WHY m.insert("reconciliation_source".to_string(), serde_json::json!("broker_feed")); m }, performance_metrics: None, }, before_state: Some(serde_json::json!({ "symbol": "TSLA", "quantity": 100, "avg_price": 195.50, "unrealized_pnl": 450.00 })), after_state: Some(serde_json::json!({ "symbol": "TSLA", "quantity": 150, "avg_price": 197.67, "unrealized_pnl": 675.00 })), compliance_tags: vec!["SOX".to_string(), "POSITION".to_string()], risk_level: RiskLevel::Medium, digital_signature: None, checksum: String::new(), }; // Validate state tracking assert!(position_event.before_state.is_some(), "Before state missing"); assert!(position_event.after_state.is_some(), "After state missing"); let result = audit_engine.log_event(position_event); assert!(result.is_ok(), "Position modification audit failed"); } #[tokio::test] async fn test_account_access_audit_with_ip_tracking() { // Test account access captures source IP (WHERE field) let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); let access_event = TransactionAuditEvent { event_id: "ACC-001".to_string(), timestamp: Utc::now(), timestamp_nanos: 0, event_type: AuditEventType::AccountModified, transaction_id: "TXN-ACC-001".to_string(), order_id: "N/A".to_string(), actor: "compliance_officer".to_string(), // WHO session_id: Some("session-999".to_string()), client_ip: Some("172.16.0.100".to_string()), // WHERE (critical for SOX) details: trading_engine::compliance::audit_trails::AuditEventDetails { symbol: None, quantity: None, price: None, side: None, order_type: None, venue: None, account_id: Some("ACC-003".to_string()), strategy_id: None, metadata: { let mut m = HashMap::new(); m.insert("action".to_string(), serde_json::json!("role_assignment")); // WHAT m.insert("reason".to_string(), serde_json::json!("New trader onboarding")); // WHY m.insert("role_added".to_string(), serde_json::json!("trader")); m }, performance_metrics: None, }, before_state: Some(serde_json::json!({"roles": ["viewer"]})), after_state: Some(serde_json::json!({"roles": ["viewer", "trader"]})), compliance_tags: vec!["SOX".to_string(), "ACCESS_CONTROL".to_string()], risk_level: RiskLevel::High, digital_signature: None, checksum: String::new(), }; // Validate IP tracking assert!(access_event.client_ip.is_some(), "Source IP (WHERE) missing"); assert_eq!(access_event.client_ip.as_ref().unwrap(), "172.16.0.100"); let result = audit_engine.log_event(access_event); assert!(result.is_ok(), "Account access audit failed"); } #[tokio::test] async fn test_audit_field_completeness_validation() { // Test validation of all 6 required audit fields let required_fields = vec!["WHO", "WHAT", "WHEN", "WHERE", "WHY", "RESULT"]; let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); let complete_event = TransactionAuditEvent { event_id: "TEST-001".to_string(), timestamp: Utc::now(), // WHEN timestamp_nanos: 0, event_type: AuditEventType::OrderCreated, transaction_id: "TXN-TEST-001".to_string(), order_id: "ORD-TEST-001".to_string(), actor: "test_user".to_string(), // WHO session_id: Some("test-session".to_string()), client_ip: Some("10.0.0.1".to_string()), // WHERE details: trading_engine::compliance::audit_trails::AuditEventDetails { symbol: Some("TEST".to_string()), quantity: Some(Decimal::new(10, 0)), price: Some(Decimal::new(100, 0)), side: Some("BUY".to_string()), order_type: Some("MARKET".to_string()), venue: Some("TEST".to_string()), account_id: Some("ACC-TEST".to_string()), strategy_id: None, metadata: { let mut m = HashMap::new(); m.insert("reason".to_string(), serde_json::json!("Test order")); // WHY m }, performance_metrics: None, }, before_state: None, after_state: Some(serde_json::json!({"status": "created"})), // WHAT compliance_tags: vec!["SOX".to_string()], risk_level: RiskLevel::Low, digital_signature: None, checksum: String::new(), }; // Validate all fields present assert!(complete_event.actor != "", "WHO field missing"); assert!(complete_event.after_state.is_some(), "WHAT field missing"); assert!(complete_event.timestamp.timestamp() > 0, "WHEN field missing"); assert!(complete_event.client_ip.is_some(), "WHERE field missing"); assert!(complete_event.details.metadata.contains_key("reason"), "WHY field missing"); let result = audit_engine.log_event(complete_event); // RESULT assert!(result.is_ok(), "RESULT field missing (success/failure tracking)"); // Confirm all 6 fields accounted for assert_eq!(required_fields.len(), 6, "SOX requires exactly 6 audit fields"); } #[tokio::test] async fn test_checksum_integrity_verification() { // Test SHA-256 checksum calculation for tamper detection let config = AuditTrailConfig::default(); let audit_engine = AuditTrailEngine::new(config); let order_details = OrderDetails { transaction_id: "TXN-CHECKSUM-001".to_string(), user_id: "trader_456".to_string(), session_id: Some("session-checksum".to_string()), client_ip: Some("10.0.0.99".to_string()), symbol: "MSFT".to_string(), quantity: Decimal::new(200, 0), price: Some(Decimal::new(30000, 2)), side: "SELL".to_string(), order_type: "LIMIT".to_string(), venue: Some("NASDAQ".to_string()), account_id: "ACC-CHECKSUM".to_string(), strategy_id: None, metadata: HashMap::new(), }; let result = audit_engine.log_order_created("ORD-CHECKSUM-001", &order_details); assert!(result.is_ok(), "Order audit with checksum failed"); // Checksum is automatically calculated by audit engine // In production, verification would: // 1. Read event from database // 2. Recalculate checksum // 3. Compare with stored checksum // 4. Detect tampering if mismatch } #[tokio::test] async fn test_encryption_compression_validation() { // Test encryption (AES-256-GCM) and compression (Gzip) support let mut config = AuditTrailConfig::default(); config.encryption_enabled = true; config.compression_enabled = true; let audit_engine = AuditTrailEngine::new(config.clone()); // Validate configuration assert!(config.encryption_enabled, "Encryption not enabled"); assert!(config.compression_enabled, "Compression not enabled"); assert_eq!(config.retention_days, 2555, "7-year retention not configured (SOX requirement)"); let order_details = OrderDetails { transaction_id: "TXN-ENC-001".to_string(), user_id: "trader_789".to_string(), session_id: None, client_ip: None, symbol: "GOOGL".to_string(), quantity: Decimal::new(50, 0), price: Some(Decimal::new(280000, 2)), side: "BUY".to_string(), order_type: "MARKET".to_string(), venue: Some("NASDAQ".to_string()), account_id: "ACC-ENC".to_string(), strategy_id: None, metadata: HashMap::new(), }; let result = audit_engine.log_order_created("ORD-ENC-001", &order_details); assert!(result.is_ok(), "Encrypted/compressed audit failed"); } #[tokio::test] async fn test_wal_crash_recovery() { // Test Write-Ahead Log (WAL) for crash recovery let mut config = AuditTrailConfig::default(); config.real_time_persistence = true; let audit_engine = AuditTrailEngine::new(config); let order_details = OrderDetails { transaction_id: "TXN-WAL-001".to_string(), user_id: "trader_wal".to_string(), session_id: None, client_ip: None, symbol: "AMZN".to_string(), quantity: Decimal::new(25, 0), price: Some(Decimal::new(130000, 2)), side: "BUY".to_string(), order_type: "LIMIT".to_string(), venue: Some("NASDAQ".to_string()), account_id: "ACC-WAL".to_string(), strategy_id: None, metadata: HashMap::new(), }; let result = audit_engine.log_order_created("ORD-WAL-001", &order_details); assert!(result.is_ok(), "WAL-backed audit failed"); // WAL ensures: // 1. Event written to WAL before acknowledgment // 2. Crash recovery replays WAL on restart // 3. No events lost even with system crash } #[tokio::test] async fn test_retention_period_enforcement() { // Test 7-year retention period (2,555 days) for SOX compliance let config = AuditTrailConfig::default(); // Validate retention configuration assert_eq!(config.retention_days, 2555, "SOX requires 7-year (2,555 day) retention"); let audit_engine = AuditTrailEngine::new(config); let order_details = OrderDetails { transaction_id: "TXN-RET-001".to_string(), user_id: "trader_retention".to_string(), session_id: None, client_ip: None, symbol: "NFLX".to_string(), quantity: Decimal::new(100, 0), price: Some(Decimal::new(45000, 2)), side: "SELL".to_string(), order_type: "MARKET".to_string(), venue: Some("NASDAQ".to_string()), account_id: "ACC-RET".to_string(), strategy_id: None, metadata: HashMap::new(), }; let result = audit_engine.log_order_created("ORD-RET-001", &order_details); assert!(result.is_ok(), "Retention-enforced audit failed"); // Retention manager ensures: // 1. Events kept for full 7-year period // 2. Automated archival after retention period // 3. No premature deletion (SOX violation) }