package audit import ( "fmt" "testing" "time" "github.com/stretchr/testify/assert" ) func TestLogPlannerDecision(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) err := logger.LogPlannerDecision("wf-1", "T1.1", "Approved for implementation", "Code meets standards", nil) assert.NoError(t, err) events, err := logger.GetAuditTrail() assert.NoError(t, err) assert.Equal(t, 1, len(events)) assert.Equal(t, "planner_decision", events[0].EventType) assert.Equal(t, "planner", events[0].Actor) } func TestLogJudgeVerdict(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) err := logger.LogJudgeVerdict("wf-1", "T1.1", "Verdict: Approved", "Code review passed", nil) assert.NoError(t, err) events, err := logger.GetAuditTrail() assert.NoError(t, err) assert.Equal(t, 1, len(events)) assert.Equal(t, "judge_verdict", events[0].EventType) } func TestLogImplementerChange(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) files := []string{"file1.go", "file2.go"} err := logger.LogImplementerChange("wf-1", "T1.1", "Implemented feature X", files, nil) assert.NoError(t, err) events, err := logger.GetAuditTrail() assert.NoError(t, err) assert.Equal(t, 1, len(events)) assert.Equal(t, "implementer_change", events[0].EventType) assert.NotNil(t, events[0].Output["files_modified"]) } func TestQueryByTask(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) logger.LogPlannerDecision("wf-1", "T1.1", "Decision 1", "Reason 1", nil) logger.LogPlannerDecision("wf-1", "T1.2", "Decision 2", "Reason 2", nil) logger.LogPlannerDecision("wf-1", "T1.1", "Decision 3", "Reason 3", nil) events, err := logger.QueryByTask("T1.1") assert.NoError(t, err) assert.Equal(t, 2, len(events)) events, err = logger.QueryByTask("T1.2") assert.NoError(t, err) assert.Equal(t, 1, len(events)) } func TestQueryByWorkflow(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) logger.LogPlannerDecision("wf-1", "T1.1", "Decision 1", "Reason 1", nil) logger.LogPlannerDecision("wf-2", "T1.1", "Decision 2", "Reason 2", nil) logger.LogPlannerDecision("wf-1", "T1.2", "Decision 3", "Reason 3", nil) events, err := logger.QueryByWorkflow("wf-1") assert.NoError(t, err) assert.Equal(t, 2, len(events)) events, err = logger.QueryByWorkflow("wf-2") assert.NoError(t, err) assert.Equal(t, 1, len(events)) } func TestQueryByActor(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) logger.LogPlannerDecision("wf-1", "T1.1", "Decision", "Reason", nil) logger.LogJudgeVerdict("wf-1", "T1.2", "Verdict", "Reason", nil) logger.LogPlannerDecision("wf-1", "T1.3", "Decision", "Reason", nil) events, err := logger.QueryByActor("planner") assert.NoError(t, err) assert.Equal(t, 2, len(events)) events, err = logger.QueryByActor("judge") assert.NoError(t, err) assert.Equal(t, 1, len(events)) } func TestQueryByTimeRange(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) before := time.Now().Add(-1 * time.Second) logger.LogPlannerDecision("wf-1", "T1.1", "Decision", "Reason", nil) middle := time.Now().Add(1 * time.Second) logger.LogPlannerDecision("wf-1", "T1.2", "Decision", "Reason", nil) events, err := logger.QueryByTimeRange(before, middle) assert.NoError(t, err) // At least one event should be in the range assert.Greater(t, len(events), 0) } func TestGetAuditTrail(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) logger.LogPlannerDecision("wf-1", "T1.1", "Decision 1", "Reason 1", nil) logger.LogJudgeVerdict("wf-1", "T1.2", "Verdict 1", "Reason 1", nil) logger.LogImplementerChange("wf-1", "T1.3", "Change 1", []string{}, nil) events, err := logger.GetAuditTrail() assert.NoError(t, err) assert.Equal(t, 3, len(events)) } func TestGetEventCount(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) count, err := logger.GetEventCount() assert.NoError(t, err) assert.Equal(t, 0, count) logger.LogPlannerDecision("wf-1", "T1.1", "Decision", "Reason", nil) logger.LogJudgeVerdict("wf-1", "T1.2", "Verdict", "Reason", nil) count, err = logger.GetEventCount() assert.NoError(t, err) assert.Equal(t, 2, count) } func TestEventImmutability(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) logger.LogPlannerDecision("wf-1", "T1.1", "Decision 1", "Reason 1", nil) events1, _ := logger.GetAuditTrail() logger.LogPlannerDecision("wf-1", "T1.2", "Decision 2", "Reason 2", nil) events2, _ := logger.GetAuditTrail() // First event should be unchanged assert.Equal(t, "Decision 1", events1[0].Action) assert.Equal(t, "Decision 1", events2[0].Action) // New event should be appended assert.Equal(t, 1, len(events1)) assert.Equal(t, 2, len(events2)) } func TestEventTimestamp(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) before := time.Now() logger.LogPlannerDecision("wf-1", "T1.1", "Decision", "Reason", nil) after := time.Now() events, _ := logger.GetAuditTrail() assert.True(t, events[0].Timestamp.After(before) || events[0].Timestamp.Equal(before)) assert.True(t, events[0].Timestamp.Before(after) || events[0].Timestamp.Equal(after)) } func TestEventID(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) logger.LogPlannerDecision("wf-1", "T1.1", "Decision", "Reason", nil) events, _ := logger.GetAuditTrail() assert.NotEmpty(t, events[0].EventID) } func TestMultipleWorkflows(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) for i := 0; i < 5; i++ { workflowID := fmt.Sprintf("wf-%d", i+1) logger.LogPlannerDecision(workflowID, "T1.1", "Decision", "Reason", nil) } events, _ := logger.GetAuditTrail() assert.Equal(t, 5, len(events)) } func TestMetadata(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) metadata := map[string]interface{}{ "retry_count": 2, "duration_ms": 1500, } logger.LogPlannerDecision("wf-1", "T1.1", "Decision", "Reason", metadata) events, _ := logger.GetAuditTrail() assert.NotNil(t, events[0].Metadata["retry_count"]) assert.NotNil(t, events[0].Metadata["duration_ms"]) } func TestEmptyQueries(t *testing.T) { tmpDir := t.TempDir() logger := NewAuditLogger(tmpDir) events, err := logger.QueryByTask("nonexistent") assert.NoError(t, err) assert.Nil(t, events) events, err = logger.QueryByWorkflow("nonexistent") assert.NoError(t, err) assert.Nil(t, events) }