package security import ( "log/slog" "testing" "time" "github.com/jfraeys/fetch_ml/internal/audit" "github.com/jfraeys/fetch_ml/internal/logging" ) func TestAuditLogger_ChainIntegrity(t *testing.T) { logger := logging.NewLogger(slog.LevelInfo, false) al, err := audit.NewLogger(true, "", logger) if err != nil { t.Fatalf("Failed to create audit logger: %v", err) } defer al.Close() // Log several events events := []audit.Event{ {EventType: audit.EventFileRead, UserID: "user1", Resource: "/data/file1.txt"}, {EventType: audit.EventFileWrite, UserID: "user1", Resource: "/data/file2.txt"}, {EventType: audit.EventFileDelete, UserID: "user2", Resource: "/data/file3.txt"}, } var loggedEvents []audit.Event for _, e := range events { al.Log(e) // In real scenario, we'd read back from file // For unit test, we just verify no panic and hashes are set loggedEvents = append(loggedEvents, e) } // Verify chain integrity would work if we had the actual events with hashes // This is a simplified test } func TestAuditLogger_VerifyChain(t *testing.T) { logger := logging.NewLogger(slog.LevelInfo, false) al, err := audit.NewLogger(true, "", logger) if err != nil { t.Fatalf("Failed to create audit logger: %v", err) } defer al.Close() // Create a valid chain of events with real hashes events := []audit.Event{ { EventType: audit.EventAuthSuccess, UserID: "user1", Timestamp: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC), SequenceNum: 1, PrevHash: "", }, { EventType: audit.EventFileRead, UserID: "user1", Resource: "/data/file.txt", Timestamp: time.Date(2026, 1, 1, 12, 1, 0, 0, time.UTC), SequenceNum: 2, }, { EventType: audit.EventFileWrite, UserID: "user1", Resource: "/data/output.txt", Timestamp: time.Date(2026, 1, 1, 12, 2, 0, 0, time.UTC), SequenceNum: 3, }, } // Calculate real hashes for each event for i := range events { if i > 0 { events[i].PrevHash = events[i-1].EventHash } events[i].EventHash = al.CalculateEventHash(events[i]) } // Test verification with valid chain - should pass tamperedSeq, err := al.VerifyChain(events) if err != nil { t.Errorf("VerifyChain failed for valid chain: %v", err) } if tamperedSeq != -1 { t.Errorf("Expected valid chain (tamperedSeq=-1), got %d", tamperedSeq) } // Test tamper detection - modify an event hash tamperedEvents := make([]audit.Event, len(events)) copy(tamperedEvents, events) tamperedEvents[1].EventHash = "tampered_hash_1234567890abcdef" tamperedSeq, err = al.VerifyChain(tamperedEvents) if err == nil { t.Error("Expected error for tampered chain, got nil") } if tamperedSeq != 2 { t.Errorf("Expected tamperedSeq=2, got %d", tamperedSeq) } // Test chain break detection - modify prev_hash brokenEvents := make([]audit.Event, len(events)) copy(brokenEvents, events) brokenEvents[1].PrevHash = "wrong_prev_hash_1234567890abcdef" tamperedSeq, err = al.VerifyChain(brokenEvents) if err == nil { t.Error("Expected error for broken chain, got nil") } if tamperedSeq != 2 { t.Errorf("Expected tamperedSeq=2 for broken chain, got %d", tamperedSeq) } } func TestAuditLogger_LogFileAccess(t *testing.T) { logger := logging.NewLogger(slog.LevelInfo, false) al, err := audit.NewLogger(true, "", logger) if err != nil { t.Fatalf("Failed to create audit logger: %v", err) } defer al.Close() // Test file read logging al.LogFileAccess(audit.EventFileRead, "user1", "/data/dataset.csv", "192.168.1.1", true, "") // Test file write logging al.LogFileAccess(audit.EventFileWrite, "user1", "/data/output.txt", "192.168.1.1", true, "") // Test file delete logging al.LogFileAccess(audit.EventFileDelete, "user2", "/data/old.txt", "192.168.1.2", false, "permission denied") } func TestAuditLogger_Disabled(t *testing.T) { logger := logging.NewLogger(slog.LevelInfo, false) al, err := audit.NewLogger(false, "", logger) if err != nil { t.Fatalf("Failed to create audit logger: %v", err) } defer al.Close() // When disabled, logging should not panic al.Log(audit.Event{ EventType: audit.EventAuthSuccess, UserID: "user1", }) al.LogFileAccess(audit.EventFileRead, "user1", "/data/file.txt", "", true, "") }