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.New("capture failed"), "failed"},
19 {errors.Join(context.Canceled, errors.New("capture failed")), "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 TestFormatCompletionUpdateIdentifiesActiveSiblings(t *testing.T) {
49 got := FormatCompletionUpdateWithActive(
50 []Completion{{AgentID: "finished", Status: "completed"}},
51 []string{"worker-a", "worker-b"},
52 "continue",
53 )
54 for _, want := range []string{"finished: completed", "Still running: worker-a worker-b", "continue"} {
55 if !strings.Contains(got, want) {
56 t.Fatalf("completion update %q missing %q", got, want)
57 }
58 }
59}
60
61func TestCompletionTrackerDropsReportsAfterCancellationReset(t *testing.T) {
62 tracker := NewCompletionTracker()
63 if !tracker.TrackResident("child", "turn-1") {
64 t.Fatal("TrackResident returned false")
65 }
66 tracker.Reset()
67 if tracker.Report(Completion{AgentID: "child", TurnID: "turn-1"}) {
68 t.Fatal("Report returned true after reset")
69 }
70 got, err := tracker.WaitIdle(context.Background())
71 if err != nil {
72 t.Fatal(err)
73 }
74 if len(got) != 0 {
75 t.Fatalf("stale completions = %#v", got)
76 }
77}
78
79func TestCompletionTrackerWaitReadyDoesNotWaitForSiblings(t *testing.T) {
80 tracker := NewCompletionTracker()
81 tracker.TrackResident("a", "turn-a")
82 tracker.TrackResident("b", "turn-b")
83 want := Completion{AgentID: "a", TurnID: "turn-a", Summary: "result"}
84 tracker.Report(want)
85 ctx, cancel := context.WithCancel(context.Background())
86 cancel()
87 got, err := tracker.WaitReady(ctx)
88 if err != nil || len(got) != 1 || got[0] != want {
89 t.Fatalf("WaitReady = %#v, %v, want ready result despite pending sibling", got, err)
90 }
91 if _, err := tracker.WaitReady(ctx); !errors.Is(err, context.Canceled) {
92 t.Fatalf("second WaitReady error = %v, want cancellation with no duplicate result", err)
93 }
94 tracker.Report(Completion{AgentID: "b", TurnID: "turn-b"})
95 got, err = tracker.WaitIdle(context.Background())
96 if err != nil || len(got) != 1 || got[0].AgentID != "b" {
97 t.Fatalf("WaitIdle = %#v, %v, want only remaining sibling", got, err)
98 }
99 got, err = tracker.WaitReady(context.Background())
100 if err != nil || len(got) != 0 {
101 t.Fatalf("idle WaitReady = %#v, %v", got, err)
102 }
103}
104
105func TestCompletionTrackerMatchesTerminalReportsToAcceptedTurns(t *testing.T) {
106 tracker := NewCompletionTracker()
107 if !tracker.TrackResident("child", "turn-1") || !tracker.TrackResident("child", "turn-2") {
108 t.Fatal("TrackResident returned false")
109 }
110 if tracker.Report(Completion{AgentID: "child", TurnID: "other"}) {
111 t.Fatal("Report accepted an unknown turn")
112 }
113 if !tracker.Report(Completion{AgentID: "child", TurnID: "turn-1"}) {
114 t.Fatal("Report rejected an accepted turn")
115 }
116 if tracker.Report(Completion{AgentID: "child", TurnID: "turn-1"}) {
117 t.Fatal("Report accepted a duplicate terminal result")
118 }
119 if got := tracker.Pending(); got != 1 {
120 t.Fatalf("Pending = %d, want 1", got)
121 }
122 if !tracker.Report(Completion{AgentID: "child", TurnID: "turn-2"}) {
123 t.Fatal("Report rejected second accepted turn")
124 }
125 got, err := tracker.WaitIdle(context.Background())
126 if err != nil {
127 t.Fatal(err)
128 }
129 if len(got) != 2 {
130 t.Fatalf("completions = %#v, want 2", got)
131 }
132}