1package agent
2
3import (
4 "testing"
5
6 "github.com/bnema/zut/packages/agent/subagents"
7 "github.com/google/uuid"
8)
9
10func TestParseArgsSubagentAndReasoning(t *testing.T) {
11 args, err := ParseArgs([]string{"--subagent-worker", "/tmp/in.sock", "--subagent", "reviewer", "--reasoning", "high", "task"})
12 if err != nil {
13 t.Fatal(err)
14 }
15 if args.Mode != ModeSubagentWorker || args.Subagent != "reviewer" || args.Reasoning != "high" || args.Prompt != "task" {
16 t.Fatalf("parsed args = mode=%q subagent=%q reasoning=%q prompt=%q", args.Mode, args.Subagent, args.Reasoning, args.Prompt)
17 }
18}
19
20func TestParseArgsNoLSP(t *testing.T) {
21 args, err := ParseArgs([]string{"--no-lsp"})
22 if err != nil {
23 t.Fatal(err)
24 }
25 if !args.NoLSP {
26 t.Fatal("--no-lsp did not disable LSP")
27 }
28}
29
30func TestParseArgsResumeOptionalSessionID(t *testing.T) {
31 id := uuid.New()
32
33 picker, err := ParseArgs([]string{"--resume"})
34 if err != nil {
35 t.Fatal(err)
36 }
37 if !picker.Resume || picker.ResumeSessionID != "" || picker.Prompt != "" {
38 t.Fatalf("no-argument resume = %#v, want picker behavior", picker)
39 }
40
41 args, err := ParseArgs([]string{"--resume", id.String(), "continue this"})
42 if err != nil {
43 t.Fatal(err)
44 }
45 if !args.Resume || args.ResumeSessionID != id.String() || args.Prompt != "continue this" {
46 t.Fatalf("resume UUID args = %#v", args)
47 }
48
49 prompt, err := ParseArgs([]string{"--resume", "not-a-session-id"})
50 if err != nil {
51 t.Fatal(err)
52 }
53 if prompt.ResumeSessionID != "" || prompt.Prompt != "not-a-session-id" {
54 t.Fatalf("non-UUID resume argument = %#v, want existing prompt behavior", prompt)
55 }
56}
57
58func TestParseArgsAllowsLeadingDashPromptAfterTerminator(t *testing.T) {
59 args, err := ParseArgs([]string{"--subagent-worker", "/tmp/in.sock", "--", "--inspect", "auth flow"})
60 if err != nil {
61 t.Fatal(err)
62 }
63 if args.Prompt != "--inspect auth flow" {
64 t.Fatalf("prompt = %q, want leading-dash task preserved", args.Prompt)
65 }
66}
67
68func TestParseArgsTemperatureAllowsZero(t *testing.T) {
69 args, err := ParseArgs([]string{"--temperature", "0"})
70 if err != nil {
71 t.Fatalf("ParseArgs returned %v", err)
72 }
73 if args.Temperature == nil || *args.Temperature != 0 {
74 t.Fatalf("Temperature = %v; want 0", args.Temperature)
75 }
76}
77
78func TestParseArgsTemperatureRejectsOutOfRange(t *testing.T) {
79 if _, err := ParseArgs([]string{"--temperature", "2.1"}); err == nil {
80 t.Fatal("ParseArgs accepted out-of-range temperature")
81 }
82}
83
84func TestParseArgsYes(t *testing.T) {
85 for _, flag := range []string{"-y", "--yes"} {
86 args, err := ParseArgs([]string{flag, "--print", "hi"})
87 if err != nil {
88 t.Fatalf("ParseArgs(%q): %v", flag, err)
89 }
90 if !args.Yes {
91 t.Fatalf("ParseArgs(%q): Yes = false", flag)
92 }
93 if args.Mode != ModePrint || args.Prompt != "hi" {
94 t.Fatalf("ParseArgs(%q): Mode=%q Prompt=%q", flag, args.Mode, args.Prompt)
95 }
96 }
97}
98
99func TestParseArgsStatsRequiresPrintMode(t *testing.T) {
100 args, err := ParseArgs([]string{"-p", "--stats", "stats.json", "hi"})
101 if err != nil {
102 t.Fatal(err)
103 }
104 if args.StatsPath != "stats.json" || args.Mode != ModePrint {
105 t.Fatalf("StatsPath=%q Mode=%q", args.StatsPath, args.Mode)
106 }
107
108 if _, err := ParseArgs([]string{"--stats", "stats.json", "hi"}); err == nil {
109 t.Fatal("ParseArgs accepted --stats without print mode")
110 }
111}
112
113func TestParseArgsStream(t *testing.T) {
114 args, err := ParseArgs([]string{"--stream", "hi"})
115 if err != nil {
116 t.Fatal(err)
117 }
118 if args.Mode != ModeStream || args.Prompt != "hi" {
119 t.Fatalf("Mode=%q Prompt=%q", args.Mode, args.Prompt)
120 }
121}
122
123func TestParseArgsWebSearchPolicyIsWorkerOnlyRegardlessOfOrder(t *testing.T) {
124 for _, argv := range [][]string{
125 {"--web-search-policy", "allow"},
126 {"--web-search-policy", "allow", "--subagent-worker", "/tmp/in.sock", "--print"},
127 {"--subagent-worker", "/tmp/in.sock", "--web-search-policy", "deny", "--json"},
128 } {
129 if _, err := ParseArgs(argv); err == nil {
130 t.Fatalf("ordinary CLI mode accepted internal policy from %q", argv)
131 }
132 }
133
134 for _, tc := range []struct {
135 name string
136 argv []string
137 policy subagents.WebSearchPolicy
138 }{
139 {
140 name: "policy before worker",
141 argv: []string{"--web-search-policy", "allow", "--subagent-worker", "/tmp/in.sock", "--tools", "web_search"},
142 policy: subagents.WebSearchAllow,
143 },
144 {
145 name: "policy after worker",
146 argv: []string{"--print", "--subagent-worker", "/tmp/in.sock", "--web-search-policy", "deny"},
147 policy: subagents.WebSearchDeny,
148 },
149 } {
150 t.Run(tc.name, func(t *testing.T) {
151 worker, err := ParseArgs(tc.argv)
152 if err != nil {
153 t.Fatal(err)
154 }
155 if worker.Mode != ModeSubagentWorker || worker.WebSearchPolicy != tc.policy {
156 t.Fatalf("worker policy args = %#v", worker)
157 }
158 })
159 }
160}
161
162func TestParseArgsTracksExplicitToolListProvenance(t *testing.T) {
163 absent, err := ParseArgs(nil)
164 if err != nil {
165 t.Fatal(err)
166 }
167 if absent.ToolsSet || len(absent.Tools) != 0 {
168 t.Fatalf("absent --tools = %#v", absent)
169 }
170
171 empty, err := ParseArgs([]string{"--tools", ""})
172 if err != nil {
173 t.Fatal(err)
174 }
175 if !empty.ToolsSet || len(empty.Tools) != 0 {
176 t.Fatalf("empty --tools = %#v", empty)
177 }
178
179 selected, err := ParseArgs([]string{"--tools", "read, web_search"})
180 if err != nil {
181 t.Fatal(err)
182 }
183 if !selected.ToolsSet || len(selected.Tools) != 2 || selected.Tools[1] != "web_search" {
184 t.Fatalf("selected --tools = %#v", selected)
185 }
186}