package store_test import ( "context" "errors" "path/filepath" "testing" "time" "g.fc.run/misc/byre/internal/api" "g.fc.run/misc/byre/internal/store" ) func TestReviewLifecycle(t *testing.T) { ctx := context.Background() st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) if err != nil { t.Fatalf("open store: %v", err) } t.Cleanup(func() { st.Close() }) now := time.Now().UTC() _, err = st.EnsureUser(ctx, "alice", "Alice") if err != nil { t.Fatalf("ensure user: %v", err) } err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) if err != nil { t.Fatalf("create project: %v", err) } err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "t1", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") if err != nil { t.Fatalf("create task: %v", err) } for _, s := range []struct { id string role api.AgentSessionRole }{{"impl", api.Implementer}, {"rev1", api.Reviewer}, {"rev2", api.Reviewer}} { err = st.CreateSession(ctx, api.AgentSession{Id: s.id, TaskId: "t1", Vendor: api.Claude, VendorSessionId: s.id, State: api.AgentSessionStateIdle, Role: s.role, CreatedBy: "alice", CreatedAt: now}, "p1") if err != nil { t.Fatalf("create session: %v", err) } } sessions, err := st.ListSessions(ctx, "t1") if err != nil || len(sessions) != 1 || sessions[0].Id != "impl" { t.Fatalf("task sessions %+v %v, want only the implementer", sessions, err) } snap := func(n int, at time.Time) { t.Helper() err := st.CreateSnapshot(ctx, api.Snapshot{Id: "s" + string(rune('0'+n)), TaskId: "t1", AgentSessionId: "impl", N: n, CommitSha: "c", TreeSha: "tree" + string(rune('0'+n)), EventSeq: 1, Trigger: api.SnapshotTriggerTurnEnd, CreatedAt: at}, "p1") if err != nil { t.Fatalf("create snapshot %d: %v", n, err) } } snap(1, now) asked := now.Add(time.Second) err = st.CreateInboxItem(ctx, api.InboxItem{Id: "q1", ProjectId: "p1", TaskId: "t1", AgentSessionId: "impl", ThreadId: "q1", Kind: api.Question, Priority: api.Blocked, Blocking: true, Payload: map[string]any{}, State: api.Open, CreatedAt: asked}) if err != nil { t.Fatalf("create question: %v", err) } snap(2, asked.Add(time.Second)) for _, r := range []api.Review{ {Id: "r1", TaskId: "t1", SnapshotN: 1, AgentSessionId: "rev1", Vendor: "codex", Model: "m", State: api.ReviewStateRunning, CreatedAt: now}, {Id: "r2", TaskId: "t1", SnapshotN: 2, AgentSessionId: "rev2", Vendor: "codex", Model: "m", State: api.ReviewStateRunning, CreatedAt: now}, } { err = st.CreateReview(ctx, r) if err != nil { t.Fatalf("create review: %v", err) } } byRev, err := st.ReviewBySession(ctx, "rev2") if err != nil || byRev.Id != "r2" { t.Fatalf("review by session %+v %v", byRev, err) } err = st.FinishReview(ctx, "r2", api.ReviewStateDone, map[string]any{"summary": "fine", "findings": []any{}}, "") if err != nil { t.Fatalf("finish: %v", err) } err = st.FinishReview(ctx, "r2", api.ReviewStateFailed, nil, "late") if !errors.Is(err, store.ErrNotFound) { t.Fatalf("finishing a finished review: %v, want not found", err) } item, err := st.GetInboxItem(ctx, "q1") if err != nil { t.Fatalf("get question: %v", err) } if len(item.Reviews) != 1 || item.Reviews[0].Id != "r2" || item.Reviews[0].Result == nil || (*item.Reviews[0].Result)["summary"] != "fine" { t.Fatalf("question's reviews %+v, want the review of the snapshot after it", item.Reviews) } running, err := st.RunningReviews(ctx) if err != nil || len(running) != 1 || running[0].Id != "r1" { t.Fatalf("running reviews %+v %v", running, err) } }