package workflow import ( "os" "path/filepath" "strings" "testing" "time" "github.com/ajhahnde/eeco/internal/queue" ) func TestLoadHistory_MissingFileIsEmpty(t *testing.T) { dir := t.TempDir() h, err := LoadHistory(dir) if err != nil { t.Fatalf("missing file must not error, got %v", err) } if len(h.Records) != 0 { t.Errorf("missing file: got %d records, want 0", len(h.Records)) } } func TestLoadHistory_CorruptFileDegradesToEmpty(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, HistoryFilename), []byte("{not json"), 0o644); err != nil { t.Fatal(err) } h, err := LoadHistory(dir) if err != nil { t.Fatalf("corrupt file must degrade silently, got %v", err) } if len(h.Records) != 0 { t.Errorf("corrupt file: got %d records, want 0", len(h.Records)) } } func TestHistory_SaveLoadRoundTrip(t *testing.T) { dir := t.TempDir() in := History{Records: []HistoryRecord{ { SignalKind: SignalCommitType, SignalKey: "fix", CountAtProposal: 5, QueueKind: "evolve", QueueTitle: "Workflow candidate: fix-workflow", ProposedAt: "2026-05-24T10:00:00Z", }, { SignalKind: SignalCommitType, SignalKey: "docs", CountAtProposal: 4, QueueKind: "evolve", QueueTitle: "Workflow candidate: docs-workflow", ProposedAt: "2026-05-24T10:00:00Z", Resolved: true, ResolvedAt: "2026-05-25T09:00:00Z", }, }} if err := SaveHistory(dir, in); err != nil { t.Fatal(err) } out, err := LoadHistory(dir) if err != nil { t.Fatal(err) } if len(out.Records) != len(in.Records) { t.Fatalf("len mismatch: got %d, want %d", len(out.Records), len(in.Records)) } for i, r := range in.Records { if out.Records[i] != r { t.Errorf("record %d round-trip: got %+v, want %+v", i, out.Records[i], r) } } } func TestHistory_OmitemptyForResolvedDefaults(t *testing.T) { dir := t.TempDir() in := History{Records: []HistoryRecord{{ SignalKind: SignalCommitType, SignalKey: "fix", CountAtProposal: 3, QueueKind: "evolve", QueueTitle: "Workflow candidate: fix-workflow", ProposedAt: "2026-05-24T10:00:00Z", }}} if err := SaveHistory(dir, in); err != nil { t.Fatal(err) } b, err := os.ReadFile(filepath.Join(dir, HistoryFilename)) if err != nil { t.Fatal(err) } s := string(b) if strings.Contains(s, "\"resolved\"") || strings.Contains(s, "\"resolved_at\"") { t.Errorf("default record must omit resolved fields on wire:\n%s", s) } } func TestHasProposed(t *testing.T) { h := History{Records: []HistoryRecord{ {SignalKind: SignalCommitType, SignalKey: "fix"}, {SignalKind: SignalCommitType, SignalKey: "docs", Resolved: true}, }} cases := []struct { kind, key string want bool }{ {SignalCommitType, "fix", true}, {SignalCommitType, "docs", true}, {SignalCommitType, "feat", false}, {"other-kind", "fix", false}, } for _, tc := range cases { if got := h.HasProposed(tc.kind, tc.key); got != tc.want { t.Errorf("HasProposed(%q,%q) = %v, want %v", tc.kind, tc.key, got, tc.want) } } } func TestReconcileHistory_TicksOpenItemToResolved(t *testing.T) { dir := t.TempDir() // Pre-fill queue with an open item, then mark it resolved by // rewriting the queue file with `- [x]`. if err := queue.Append(dir, queue.Item{ Kind: "evolve", Title: "Workflow candidate: fix-workflow", Project: "proj", Date: time.Now(), }); err != nil { t.Fatal(err) } body, err := os.ReadFile(filepath.Join(dir, queue.Filename)) if err != nil { t.Fatal(err) } rewritten := strings.Replace(string(body), "- [ ] **evolve**", "- [x] **evolve**", 1) if err := os.WriteFile(filepath.Join(dir, queue.Filename), []byte(rewritten), 0o644); err != nil { t.Fatal(err) } h := History{Records: []HistoryRecord{{ SignalKind: SignalCommitType, SignalKey: "fix", QueueKind: "evolve", QueueTitle: "Workflow candidate: fix-workflow", }}} now := time.Date(2026, 5, 25, 12, 0, 0, 0, time.UTC) out, changed := ReconcileHistory(dir, h, now) if !changed { t.Fatalf("changed must be true when an open item flipped") } if !out.Records[0].Resolved { t.Errorf("record must flip to resolved") } if out.Records[0].ResolvedAt == "" { t.Errorf("ResolvedAt must be set on flip") } } func TestReconcileHistory_AlreadyResolvedStays(t *testing.T) { dir := t.TempDir() h := History{Records: []HistoryRecord{{ SignalKind: SignalCommitType, SignalKey: "fix", QueueKind: "evolve", QueueTitle: "Workflow candidate: fix-workflow", Resolved: true, ResolvedAt: "2026-05-20T10:00:00Z", }}} out, changed := ReconcileHistory(dir, h, time.Now()) if changed { t.Errorf("an already-resolved record must not trigger change") } if out.Records[0].ResolvedAt != "2026-05-20T10:00:00Z" { t.Errorf("ResolvedAt must not be overwritten: got %q", out.Records[0].ResolvedAt) } } func TestReconcileHistory_OpenItemStaysUnresolved(t *testing.T) { dir := t.TempDir() if err := queue.Append(dir, queue.Item{ Kind: "evolve", Title: "Workflow candidate: fix-workflow", Project: "proj", Date: time.Now(), }); err != nil { t.Fatal(err) } h := History{Records: []HistoryRecord{{ SignalKind: SignalCommitType, SignalKey: "fix", QueueKind: "evolve", QueueTitle: "Workflow candidate: fix-workflow", }}} out, changed := ReconcileHistory(dir, h, time.Now()) if changed { t.Errorf("an open queue item must not flip the record") } if out.Records[0].Resolved { t.Errorf("record must stay unresolved while queue row is open") } } func TestQueueResolved_MissingFile(t *testing.T) { dir := t.TempDir() ok, err := queue.Resolved(dir, "evolve", "missing") if err != nil { t.Fatalf("missing queue must not error, got %v", err) } if ok { t.Errorf("missing queue: got ok=true, want false") } }