package audit_test import ( "context" "strings" "testing" "time" "github.com/jfraeys/fetch_ml/internal/audit" ) // mockLogger implements the logger interface for testing type mockLogger struct { errors []string warns []string } func (m *mockLogger) Error(msg string, keysAndValues ...any) { m.errors = append(m.errors, msg) } func (m *mockLogger) Warn(msg string, keysAndValues ...any) { m.warns = append(m.warns, msg) } func TestLoggingAlerter_CriticalAlert(t *testing.T) { mock := &mockLogger{} alerter := audit.NewLoggingAlerter(mock) alert := audit.TamperAlert{ DetectedAt: time.Now(), Severity: "critical", Description: "TAMPERING DETECTED in audit log", FilePath: "/var/log/audit/audit.log", ExpectedHash: "abc123", ActualHash: "def456", } err := alerter.Alert(context.Background(), alert) if err != nil { t.Fatalf("Alert failed: %v", err) } if len(mock.errors) != 1 { t.Errorf("Expected 1 error log, got %d", len(mock.errors)) } if !strings.Contains(mock.errors[0], "TAMPERING DETECTED") { t.Errorf("Expected error to contain 'TAMPERING DETECTED', got %s", mock.errors[0]) } if len(mock.warns) != 0 { t.Errorf("Expected 0 warn logs, got %d", len(mock.warns)) } } func TestLoggingAlerter_WarningAlert(t *testing.T) { mock := &mockLogger{} alerter := audit.NewLoggingAlerter(mock) alert := audit.TamperAlert{ DetectedAt: time.Now(), Severity: "warning", Description: "Potential tampering detected", FilePath: "/var/log/audit/audit.log", } err := alerter.Alert(context.Background(), alert) if err != nil { t.Fatalf("Alert failed: %v", err) } if len(mock.warns) != 1 { t.Errorf("Expected 1 warn log, got %d", len(mock.warns)) } if !strings.Contains(mock.warns[0], "Potential tampering") { t.Errorf("Expected warn to contain 'Potential tampering', got %s", mock.warns[0]) } if len(mock.errors) != 0 { t.Errorf("Expected 0 error logs, got %d", len(mock.errors)) } } func TestLoggingAlerter_NilLogger(t *testing.T) { alerter := audit.NewLoggingAlerter(nil) alert := audit.TamperAlert{ DetectedAt: time.Now(), Severity: "critical", Description: "Test", } // Should not panic or error err := alerter.Alert(context.Background(), alert) if err != nil { t.Fatalf("Alert with nil logger failed: %v", err) } } func TestMultiAlerter(t *testing.T) { mock1 := &mockLogger{} mock2 := &mockLogger{} alerter1 := audit.NewLoggingAlerter(mock1) alerter2 := audit.NewLoggingAlerter(mock2) multi := audit.NewMultiAlerter(alerter1, alerter2) alert := audit.TamperAlert{ DetectedAt: time.Now(), Severity: "critical", Description: "Test multi", } err := multi.Alert(context.Background(), alert) if err != nil { t.Fatalf("Multi alert failed: %v", err) } // Both alerters should have logged if len(mock1.errors) != 1 { t.Errorf("Expected alerter1 to have 1 error, got %d", len(mock1.errors)) } if len(mock2.errors) != 1 { t.Errorf("Expected alerter2 to have 1 error, got %d", len(mock2.errors)) } } func TestMultiAlerter_Empty(t *testing.T) { multi := audit.NewMultiAlerter() alert := audit.TamperAlert{ DetectedAt: time.Now(), Severity: "critical", Description: "Test empty", } // Should not error with no alerters err := multi.Alert(context.Background(), alert) if err != nil { t.Fatalf("Multi alert with no alerters failed: %v", err) } } func TestTamperAlert_Struct(t *testing.T) { now := time.Now() alert := audit.TamperAlert{ DetectedAt: now, Severity: "critical", Description: "Test description", ExpectedHash: "expected", ActualHash: "actual", FilePath: "/path/to/file", } if alert.DetectedAt != now { t.Error("DetectedAt mismatch") } if alert.Severity != "critical" { t.Error("Severity mismatch") } if alert.Description != "Test description" { t.Error("Description mismatch") } if alert.ExpectedHash != "expected" { t.Error("ExpectedHash mismatch") } if alert.ActualHash != "actual" { t.Error("ActualHash mismatch") } if alert.FilePath != "/path/to/file" { t.Error("FilePath mismatch") } }