package audit import ( "testing" "time" ) func TestEventValidateRequiresTypeSourceTimestamp(t *testing.T) { t.Parallel() // Empty type should return an error e := Event{Source: SourceEdge, Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)} err := e.Validate() if err == nil { t.Fatal("expected error for missing event type") } if got := err.Error(); got != "audit event type is required" { t.Fatalf("expected 'audit event type is required', got %q", got) } // Empty source should return an error e = Event{Type: TypeExecutionRequested, Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)} err = e.Validate() if err == nil { t.Fatal("expected error for missing source") } if got := err.Error(); got != "audit event source is required" { t.Fatalf("expected 'audit event source is required', got %q", got) } // Zero timestamp should return an error e = Event{Type: TypeExecutionRequested, Source: SourceNode} err = e.Validate() if err == nil { t.Fatal("expected error for zero timestamp") } if got := err.Error(); got != "audit event timestamp is required" { t.Fatalf("expected 'audit event timestamp is required', got %q", got) } // All required fields present should pass e = Event{ Type: TypeExecutionCompleted, Source: SourceControlPlane, Timestamp: time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC), } if err := e.Validate(); err != nil { t.Fatalf("unexpected error: %v", err) } } func TestEventValidateAcceptsStructuredExecutionAuditEvent(t *testing.T) { t.Parallel() ts := time.Date(2026, 6, 1, 15, 30, 0, 0, time.UTC) e := Event{ EventID: "evt-001", Type: TypeExecutionPolicyEvaluated, Source: SourceEdge, ActorID: "user-42", RequestID: "req-100", RunID: "run-200", SessionID: "sess-300", EdgeID: "edge-1", NodeID: "node-5", Adapter: "k8s", Target: "deploy/web", CredentialRef: MetadataCredentialReference, PolicyDecision: DecisionAllow, PolicyRule: "rule-default-allow", Timestamp: ts, Status: "passed", Metadata: map[string]string{ "extra": "value", }, } if err := e.Validate(); err != nil { t.Fatalf("unexpected validation error: %v", err) } } func TestEventValidateAcceptsTerminalAuditEvent(t *testing.T) { t.Parallel() ts := time.Date(2026, 6, 2, 10, 0, 0, 0, time.UTC) e := Event{ Type: TypeTerminalSessionClosed, Source: SourceNode, EventID: "evt-term-01", NodeID: "node-abc", Timestamp: ts, } if err := e.Validate(); err != nil { t.Fatalf("unexpected validation error: %v", err) } } func TestEventValidateAcceptsBootstrapAuditEvent(t *testing.T) { t.Parallel() ts := time.Date(2026, 6, 3, 8, 0, 0, 0, time.UTC) e := Event{ Type: TypeBootstrapEnrollmentRequested, Source: SourceControlPlane, EventID: "evt-boot-01", Timestamp: ts, } if err := e.Validate(); err != nil { t.Fatalf("unexpected validation error: %v", err) } } func TestCopyMetadataDefensivelyCopiesMap(t *testing.T) { t.Parallel() // nil metadata should return nil e := Event{ Type: TypeExecutionRequested, Source: SourceEdge, Timestamp: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), } if got := e.CopyMetadata(); got != nil { t.Fatalf("expected nil for nil metadata, got %v", got) } // Non-nil metadata should be defensively copied original := map[string]string{ "foo": "bar", "key": "val", } e.Metadata = original copied := e.CopyMetadata() if len(copied) != len(original) { t.Fatalf("expected copied map length %d, got %d", len(original), len(copied)) } if copied["foo"] != "bar" { t.Fatalf("expected copied['foo'] == 'bar', got %q", copied["foo"]) } if copied["key"] != "val" { t.Fatalf("expected copied['key'] == 'val', got %q", copied["key"]) } // Modifying the copy must not affect the original copied["foo"] = "changed" if original["foo"] != "bar" { t.Fatal("original metadata was mutated by changing the copy") } // Modifying the original must not affect the copy original["key"] = "changed" if copied["key"] != "val" { t.Fatal("copied metadata was mutated by changing the original") } } func TestAuditPolicyDefaultsAvoidRawTerminalTranscript(t *testing.T) { t.Parallel() // MetadataCredentialReference should not contain raw secret text if MetadataCredentialReference == "" { t.Fatal("MetadataCredentialReference should not be empty") } if MetadataRedactionMarker == "" { t.Fatal("MetadataRedactionMarker should not be empty") } // Neither should contain raw terminal transcript or secret content if MetadataCredentialReference != "credential_ref" { t.Fatalf("expected 'credential_ref', got %q", MetadataCredentialReference) } if MetadataRedactionMarker != "redaction_marker" { t.Fatalf("expected 'redaction_marker', got %q", MetadataRedactionMarker) } } func TestRequiredEventTypesMatchMilestoneDraft(t *testing.T) { t.Parallel() expected := []struct { name string got EventType want string }{ {"TypeExecutionRequested", TypeExecutionRequested, "execution.requested"}, {"TypeExecutionPolicyEvaluated", TypeExecutionPolicyEvaluated, "execution.policy_evaluated"}, {"TypeExecutionDispatched", TypeExecutionDispatched, "execution.dispatched"}, {"TypeExecutionStarted", TypeExecutionStarted, "execution.started"}, {"TypeExecutionCompleted", TypeExecutionCompleted, "execution.completed"}, {"TypeExecutionFailed", TypeExecutionFailed, "execution.failed"}, {"TypeExecutionCanceled", TypeExecutionCanceled, "execution.canceled"}, {"TypeTerminalSessionRequested", TypeTerminalSessionRequested, "terminal.session_requested"}, {"TypeTerminalSessionStarted", TypeTerminalSessionStarted, "terminal.session_started"}, {"TypeTerminalCommandSubmitted", TypeTerminalCommandSubmitted, "terminal.command_submitted"}, {"TypeTerminalCommandCompleted", TypeTerminalCommandCompleted, "terminal.command_completed"}, {"TypeTerminalSessionClosed", TypeTerminalSessionClosed, "terminal.session_closed"}, {"TypeBootstrapEnrollmentRequested", TypeBootstrapEnrollmentRequested, "bootstrap.enrollment_requested"}, {"TypeBootstrapCredentialIssued", TypeBootstrapCredentialIssued, "bootstrap.credential_issued"}, {"TypeBootstrapNodeRegistered", TypeBootstrapNodeRegistered, "bootstrap.node_registered"}, {"TypeBootstrapEnrollmentFailed", TypeBootstrapEnrollmentFailed, "bootstrap.enrollment_failed"}, {"TypeBootstrapRevoked", TypeBootstrapRevoked, "bootstrap.revoked"}, } if len(expected) != 17 { t.Fatalf("expected 17 event type constants, got %d", len(expected)) } seen := map[string]string{} for _, tt := range expected { if string(tt.got) != tt.want { t.Fatalf("%s = %q, want %q", tt.name, tt.got, tt.want) } if previous, ok := seen[tt.want]; ok { t.Fatalf("duplicate event type value %q for %s and %s", tt.want, previous, tt.name) } seen[tt.want] = tt.name } } func TestSourceConstants(t *testing.T) { t.Parallel() expectedSources := []Source{SourceControlPlane, SourceEdge, SourceNode} for _, s := range expectedSources { if string(s) == "" { t.Fatalf("source constant resolved to empty string: %v", s) } } } func TestPolicyDecisionConstants(t *testing.T) { t.Parallel() expectedDecisions := []PolicyDecision{DecisionAllow, DecisionDeny, DecisionWarn} for _, d := range expectedDecisions { if string(d) == "" { t.Fatalf("policy decision constant resolved to empty string: %v", d) } } } func TestRetentionDefaults(t *testing.T) { t.Parallel() if DefaultEdgeLocalRetentionDays != 30 { t.Fatalf("expected DefaultEdgeLocalRetentionDays == 30, got %d", DefaultEdgeLocalRetentionDays) } if DefaultControlPlaneMetadataRetentionDays != 90 { t.Fatalf("expected DefaultControlPlaneMetadataRetentionDays == 90, got %d", DefaultControlPlaneMetadataRetentionDays) } }