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