1package tools
2
3import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8
9 "github.com/bnema/zut/packages/core"
10)
11
12func TestUpdateGoalToolDescriptionRequiresGoalsForSubstantialWork(t *testing.T) {
13 description := (&UpdateGoalTool{}).Description()
14 for _, clause := range []string{
15 "requires substantial multi-step work",
16 "before substantial execution",
17 "Do not start a mission for brief questions, casual conversation",
18 "requests completed immediately",
19 "Incidental messages and interrupted turns do not end an active mission",
20 } {
21 if !strings.Contains(description, clause) {
22 t.Fatalf("update_goal description missing %q: %q", clause, description)
23 }
24 }
25}
26
27func TestUpdateGoalToolReturnsPersistableStatus(t *testing.T) {
28 tool := &UpdateGoalTool{}
29 raw, err := json.Marshal(map[string]string{"status": "complete"})
30 if err != nil {
31 t.Fatal(err)
32 }
33 result, err := tool.Execute(context.Background(), raw, nil)
34 if err != nil {
35 t.Fatal(err)
36 }
37 if result.IsError {
38 t.Fatalf("result = %#v, want success", result)
39 }
40 update, ok := GoalUpdateFromResult(result)
41 if !ok {
42 t.Fatalf("details = %#v, want goal update", result.Details)
43 }
44 if update.Status != core.GoalDone || update.Reason != "" {
45 t.Fatalf("update = %#v", update)
46 }
47}
48
49func TestUpdateGoalToolReturnsPauseAndResumeTransitions(t *testing.T) {
50 tool := &UpdateGoalTool{}
51 for _, test := range []struct {
52 name string
53 raw string
54 want core.GoalStatus
55 }{
56 {name: "pause", raw: `{"status":"paused"}`, want: core.GoalPaused},
57 {name: "resume", raw: `{"status":"active"}`, want: core.GoalActive},
58 } {
59 t.Run(test.name, func(t *testing.T) {
60 result, err := tool.Execute(context.Background(), json.RawMessage(test.raw), nil)
61 if err != nil {
62 t.Fatal(err)
63 }
64 update, ok := GoalUpdateFromResult(result)
65 if !ok || update.Status != test.want || update.Objective != "" {
66 t.Fatalf("update = %#v, ok = %v", update, ok)
67 }
68 })
69 }
70}
71
72func TestUpdateGoalToolReturnsPersistableManagerGoal(t *testing.T) {
73 tool := &UpdateGoalTool{}
74 result, err := tool.Execute(context.Background(), json.RawMessage(`{"status":"active","objective":"reproduce the reported failure"}`), nil)
75 if err != nil {
76 t.Fatal(err)
77 }
78 update, ok := GoalUpdateFromResult(result)
79 if !ok {
80 t.Fatalf("details = %#v, want goal update", result.Details)
81 }
82 if update.Status != core.GoalActive || update.Objective != "reproduce the reported failure" {
83 t.Fatalf("update = %#v", update)
84 }
85}
86
87func TestUpdateGoalToolReturnsPersistableSupersedingGoal(t *testing.T) {
88 tool := &UpdateGoalTool{}
89 result, err := tool.Execute(context.Background(), json.RawMessage(`{"status":"superseded","objective":"use the supported API","goal_id":"goal-1","mission_id":"mission-1"}`), nil)
90 if err != nil {
91 t.Fatal(err)
92 }
93 update, ok := GoalUpdateFromResult(result)
94 if !ok {
95 t.Fatalf("details = %#v, want superseding goal update", result.Details)
96 }
97 if update.Status != core.GoalSuperseded || update.Objective != "use the supported API" || update.GoalID != "goal-1" || update.MissionID != "mission-1" {
98 t.Fatalf("update = %#v", update)
99 }
100}
101
102func TestUpdateGoalToolRejectsUnknownFields(t *testing.T) {
103 tool := &UpdateGoalTool{}
104 result, err := tool.Execute(context.Background(), json.RawMessage(`{"status":"complete","unexpected":true}`), nil)
105 if err != nil {
106 t.Fatal(err)
107 }
108 if !result.IsError {
109 t.Fatalf("result = %#v, want tool error", result)
110 }
111}
112
113func TestGoalUpdateFromResultRejectsBlockedWithoutReason(t *testing.T) {
114 result := core.ToolResult{Details: GoalUpdate{Status: core.GoalBlocked}}
115 if _, ok := GoalUpdateFromResult(result); ok {
116 t.Fatal("blank-reason blocked update was accepted")
117 }
118
119 result.Details = GoalUpdate{Status: core.GoalBlocked, Reason: "needs user input"}
120 if _, ok := GoalUpdateFromResult(result); !ok {
121 t.Fatal("blocked update with reason was rejected")
122 }
123
124 result.Details = GoalUpdate{Status: core.GoalDone}
125 if _, ok := GoalUpdateFromResult(result); !ok {
126 t.Fatal("completed update without reason was rejected")
127 }
128}
129
130func TestUpdateGoalToolRejectsSupersededWithoutReplacement(t *testing.T) {
131 tool := &UpdateGoalTool{}
132 result, err := tool.Execute(context.Background(), json.RawMessage(`{"status":"superseded"}`), nil)
133 if err != nil {
134 t.Fatal(err)
135 }
136 if !result.IsError {
137 t.Fatalf("result = %#v, want tool error", result)
138 }
139}
140
141func TestUpdateGoalToolRejectsUnsupportedStatus(t *testing.T) {
142 tool := &UpdateGoalTool{}
143 raw := json.RawMessage(`{"status":"waiting"}`)
144 result, err := tool.Execute(context.Background(), raw, nil)
145 if err != nil {
146 t.Fatal(err)
147 }
148 if !result.IsError {
149 t.Fatalf("result = %#v, want tool error", result)
150 }
151 if _, ok := GoalUpdateFromResult(result); ok {
152 t.Fatal("invalid result exposed a goal update")
153 }
154}