1package subagents
2
3import (
4 "context"
5 "errors"
6 "strings"
7 "testing"
8)
9
10func TestResidentCompletionProjection(t *testing.T) {
11 for _, tc := range []struct {
12 err error
13 status string
14 }{
15 {nil, "completed"},
16 {errors.New("failure"), "failed"},
17 {context.Canceled, "interrupted"},
18 {errors.Join(ErrBudgetExceeded, errors.New("capture failed")), "budget_exhausted"},
19 {errors.Join(context.Canceled, ErrBudgetExceeded), "interrupted"},
20 } {
21 t.Run(tc.status, func(t *testing.T) {
22 got := (ResidentCompletion{ChildID: "child", TurnID: "turn", Task: "task", Summary: "saved progress", Err: tc.err}).Completion()
23 if got.Status != tc.status || got.AgentID != "child" || got.TurnID != "turn" || got.Task != "task" || got.Summary != "saved progress" {
24 t.Fatalf("completion = %#v", got)
25 }
26 if tc.err != nil && got.Error != tc.err.Error() {
27 t.Fatalf("error = %q, want %q", got.Error, tc.err.Error())
28 }
29 text := FormatCompletionUpdate([]Completion{got}, "")
30 label := "partial: "
31 if tc.err == nil {
32 label = "final: "
33 }
34 if !strings.Contains(text, label+"saved progress") {
35 t.Fatalf("update = %q", text)
36 }
37 })
38 }
39}
40
41func TestFormatCompletionUpdateIncludesFinalSummary(t *testing.T) {
42 got := FormatCompletionUpdate([]Completion{{AgentID: "child", Status: "completed", Task: "review", Summary: "found the regression"}}, "")
43 if !strings.Contains(got, "final: found the regression") {
44 t.Fatalf("completion update = %q", got)
45 }
46}
47
48func TestCompletionTrackerDropsReportsAfterCancellationReset(t *testing.T) {
49 tracker := NewCompletionTracker()
50 if !tracker.TrackResident("child", "turn-1") {
51 t.Fatal("TrackResident returned false")
52 }
53 tracker.Reset()
54 if tracker.Report(Completion{AgentID: "child", TurnID: "turn-1"}) {
55 t.Fatal("Report returned true after reset")
56 }
57 got, err := tracker.WaitIdle(context.Background())
58 if err != nil {
59 t.Fatal(err)
60 }
61 if len(got) != 0 {
62 t.Fatalf("stale completions = %#v", got)
63 }
64}
65
66func TestCompletionTrackerWaitReadyDoesNotWaitForSiblings(t *testing.T) {
67 tracker := NewCompletionTracker()
68 tracker.TrackResident("a", "turn-a")
69 tracker.TrackResident("b", "turn-b")
70 want := Completion{AgentID: "a", TurnID: "turn-a", Summary: "result"}
71 tracker.Report(want)
72 ctx, cancel := context.WithCancel(context.Background())
73 cancel()
74 got, err := tracker.WaitReady(ctx)
75 if err != nil || len(got) != 1 || got[0] != want {
76 t.Fatalf("WaitReady = %#v, %v, want ready result despite pending sibling", got, err)
77 }
78 if _, err := tracker.WaitReady(ctx); !errors.Is(err, context.Canceled) {
79 t.Fatalf("second WaitReady error = %v, want cancellation with no duplicate result", err)
80 }
81 tracker.Report(Completion{AgentID: "b", TurnID: "turn-b"})
82 got, err = tracker.WaitIdle(context.Background())
83 if err != nil || len(got) != 1 || got[0].AgentID != "b" {
84 t.Fatalf("WaitIdle = %#v, %v, want only remaining sibling", got, err)
85 }
86 got, err = tracker.WaitReady(context.Background())
87 if err != nil || len(got) != 0 {
88 t.Fatalf("idle WaitReady = %#v, %v", got, err)
89 }
90}
91
92func TestCompletionTrackerMatchesTerminalReportsToAcceptedTurns(t *testing.T) {
93 tracker := NewCompletionTracker()
94 if !tracker.TrackResident("child", "turn-1") || !tracker.TrackResident("child", "turn-2") {
95 t.Fatal("TrackResident returned false")
96 }
97 if tracker.Report(Completion{AgentID: "child", TurnID: "other"}) {
98 t.Fatal("Report accepted an unknown turn")
99 }
100 if !tracker.Report(Completion{AgentID: "child", TurnID: "turn-1"}) {
101 t.Fatal("Report rejected an accepted turn")
102 }
103 if tracker.Report(Completion{AgentID: "child", TurnID: "turn-1"}) {
104 t.Fatal("Report accepted a duplicate terminal result")
105 }
106 if got := tracker.Pending(); got != 1 {
107 t.Fatalf("Pending = %d, want 1", got)
108 }
109 if !tracker.Report(Completion{AgentID: "child", TurnID: "turn-2"}) {
110 t.Fatal("Report rejected second accepted turn")
111 }
112 got, err := tracker.WaitIdle(context.Background())
113 if err != nil {
114 t.Fatal(err)
115 }
116 if len(got) != 2 {
117 t.Fatalf("completions = %#v, want 2", got)
118 }
119}