1package tools
2
3import (
4 "context"
5 "encoding/json"
6 "testing"
7
8 "github.com/bnema/zut/packages/core"
9 "github.com/bnema/zut/packages/provider"
10)
11
12func TestUpdatePlanToolReturnsPlanUpdate(t *testing.T) {
13 tool := &UpdatePlanTool{}
14 result, err := tool.Execute(context.Background(), json.RawMessage(`{
15 "explanation":"Finished discovery",
16 "plan":[
17 {"step":"Map the implementation","status":"completed"},
18 {"step":"Add the built-in tool","status":"in_progress"},
19 {"step":"Verify the behavior","status":"pending"}
20 ]
21 }`), nil)
22 if err != nil {
23 t.Fatal(err)
24 }
25 if result.IsError {
26 t.Fatalf("result = %#v, want success", result)
27 }
28 update, ok := result.Details.(core.PlanUpdate)
29 if !ok {
30 t.Fatalf("details = %T, want core.PlanUpdate", result.Details)
31 }
32 if update.Explanation == nil || *update.Explanation != "Finished discovery" || len(update.Plan) != 3 {
33 t.Fatalf("update = %#v", update)
34 }
35 if update.Plan[1].Status != core.PlanInProgress {
36 t.Fatalf("second status = %q, want %q", update.Plan[1].Status, core.PlanInProgress)
37 }
38 if len(result.Content) != 1 {
39 t.Fatalf("content = %#v, want one text block", result.Content)
40 }
41 text, ok := result.Content[0].(provider.TextBlock)
42 if !ok || text.Text != "Plan updated" {
43 t.Fatalf("content = %#v, want Plan updated", result.Content)
44 }
45}
46
47func TestUpdatePlanToolAcceptsEmptyPlan(t *testing.T) {
48 result, err := (&UpdatePlanTool{}).Execute(context.Background(), json.RawMessage(`{"plan":[]}`), nil)
49 if err != nil {
50 t.Fatal(err)
51 }
52 if result.IsError {
53 t.Fatalf("result = %#v, want success", result)
54 }
55 update, ok := result.Details.(core.PlanUpdate)
56 if !ok || update.Explanation != nil || len(update.Plan) != 0 {
57 t.Fatalf("details = %#v, want empty plan update without explanation", result.Details)
58 }
59}
60
61func TestUpdatePlanToolAcceptsMultipleInProgressSteps(t *testing.T) {
62 result, err := (&UpdatePlanTool{}).Execute(context.Background(), json.RawMessage(`{"plan":[{"step":"one","status":"in_progress"},{"step":"two","status":"in_progress"}]}`), nil)
63 if err != nil {
64 t.Fatal(err)
65 }
66 if result.IsError {
67 t.Fatalf("result = %#v, want Codex-compatible acceptance", result)
68 }
69}
70
71func TestUpdatePlanToolRejectsMalformedArguments(t *testing.T) {
72 tests := []string{
73 `{}`,
74 `{"plan":[{"status":"pending"}]}`,
75 `{"plan":[{"step":"work"}]}`,
76 `{"plan":[{"step":"work","status":"unknown"}]}`,
77 `{"plan":[],"unexpected":true}`,
78 `{"plan":[{"step":"work","status":"pending","unexpected":true}]}`,
79 `{"plan":[]} trailing`,
80 }
81 for _, raw := range tests {
82 t.Run(raw, func(t *testing.T) {
83 result, err := (&UpdatePlanTool{}).Execute(context.Background(), json.RawMessage(raw), nil)
84 if err != nil {
85 t.Fatal(err)
86 }
87 if !result.IsError {
88 t.Fatalf("result = %#v, want tool error", result)
89 }
90 if _, ok := result.Details.(core.PlanUpdate); ok {
91 t.Fatalf("invalid result exposed plan update: %#v", result.Details)
92 }
93 })
94 }
95}