fetch_ml/internal/security/audit_test.go
Jeremie Fraeys 61660dc925
refactor: co-locate security, storage, telemetry, tracking, worker tests
Move unit tests from tests/unit/ to internal/ following Go conventions:

Security tests:
- tests/unit/security/* -> internal/security/* (audit, config_integrity, filetype, gpu_audit, hipaa_validation, manifest_filename, path_traversal, resource_quota, secrets)

Storage tests:
- tests/unit/storage/* -> internal/storage/* (db, experiment_metadata)

Telemetry tests:
- tests/unit/telemetry/* -> internal/telemetry/* (telemetry)

Tracking tests:
- tests/unit/reproducibility/* -> internal/tracking/* (config_hash, environment_capture)

Worker tests:
- tests/unit/worker/* -> internal/worker/* (artifacts, config, hash_bench, plugins/jupyter_task, plugins/vllm, prewarm_v1, run_manifest_execution, snapshot_stage, snapshot_store, worker)

Update import paths in test files to reflect new locations.
2026-03-12 16:37:03 -04:00

149 lines
4.2 KiB
Go

package security_test
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, "")
}