package store import ( "os" "path/filepath" "strings" "testing" ) func auditFixture(t *testing.T) string { t.Helper() return filepath.Join(t.TempDir(), "audit.jsonl") } func sampleSummary(id string) SessionSummary { return SessionSummary{ SessionID: id, NextAction: "write plan", SuccessCond: "plan committed", Outcome: "completed", StartedUnix: 1000, EndedUnix: 2000, SwitchCount: 3, Buckets: []BucketTotal{ {Class: "code", Title: "antidrift", Seconds: 540}, {Class: "firefox", Title: "docs", Seconds: 120}, }, } } func TestAppendSessionChainsAndVerifies(t *testing.T) { path := auditFixture(t) for i := 0; i < 3; i++ { if err := AppendSession(path, sampleSummary("session-"+string(rune('a'+i)))); err != nil { t.Fatalf("append %d: %v", i, err) } } if err := VerifyChain(path); err != nil { t.Fatalf("chain should verify: %v", err) } } func TestGenesisPrevHashIsZeros(t *testing.T) { path := auditFixture(t) if err := AppendSession(path, sampleSummary("session-a")); err != nil { t.Fatalf("append: %v", err) } data, _ := os.ReadFile(path) if !strings.Contains(string(data), strings.Repeat("0", 64)) { t.Fatalf("genesis prev_hash should be 64 zeros, got: %s", data) } } func TestVerifyEmptyChain(t *testing.T) { if err := VerifyChain(auditFixture(t)); err != nil { t.Fatalf("empty/missing chain should verify, got %v", err) } } func TestTamperDetected(t *testing.T) { path := auditFixture(t) _ = AppendSession(path, sampleSummary("session-a")) _ = AppendSession(path, sampleSummary("session-b")) _ = AppendSession(path, sampleSummary("session-c")) // Corrupt the middle line's switch_count. data, _ := os.ReadFile(path) lines := strings.Split(strings.TrimRight(string(data), "\n"), "\n") lines[1] = strings.Replace(lines[1], `"switch_count":3`, `"switch_count":99`, 1) if err := os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), 0o644); err != nil { t.Fatalf("rewrite: %v", err) } err := VerifyChain(path) if err == nil || !strings.Contains(err.Error(), "line 2") { t.Fatalf("tamper on line 2 should be reported, got %v", err) } }