fetch_ml/internal/audit/alert_test.go
Jeremie Fraeys a4e2ecdbe6
refactor: co-locate api, audit, auth tests with source code
Move unit tests from tests/unit/ to internal/ following Go conventions:
- tests/unit/api/* -> internal/api/* (WebSocket handlers, helpers, duplicate detection)
- tests/unit/audit/* -> internal/audit/* (alert, sealed, verifier tests)
- tests/unit/auth/* -> internal/auth/* (API key, keychain, user manager)
- tests/unit/crypto/kms/* -> internal/auth/kms/* (cache, protocol tests)

Update import paths in test files to reflect new locations.

Benefits:
- Tests live alongside the code they test
- Easier navigation and maintenance
- Clearer package boundaries
- Follows standard Go project layout
2026-03-12 16:34:54 -04:00

171 lines
4 KiB
Go

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")
}
}