返回 DeepSeek-Reasonix
cli_flags_test.go
根目录 / internal / cli / cli_flags_test.go
1 package cli
2
3 import (
4 "path/filepath"
5 "reflect"
6 "strings"
7 "testing"
8
9 "github.com/spf13/pflag"
10 "reasonix/internal/agent"
11 "reasonix/internal/provider"
12 )
13
14 func TestSplitAllowedToolRules(t *testing.T) {
15 got, err := splitAllowedToolRules([]string{
16 "Bash(git *) Edit,read_file",
17 "Bash(go test ./...) Edit(docs/**)",
18 "Edit",
19 })
20 if err != nil {
21 t.Fatalf("splitAllowedToolRules: %v", err)
22 }
23 want := []string{"Bash(git *)", "Edit", "read_file", "Bash(go test ./...)", "Edit(docs/**)"}
24 if !reflect.DeepEqual(got, want) {
25 t.Fatalf("rules = %#v, want %#v", got, want)
26 }
27 }
28
29 func TestSplitAllowedToolRulesRejectsUnbalancedParentheses(t *testing.T) {
30 for _, input := range []string{"Bash(git *", "Bash(git *))"} {
31 if _, err := splitAllowedToolRules([]string{input}); err == nil {
32 t.Fatalf("splitAllowedToolRules(%q) unexpectedly succeeded", input)
33 }
34 }
35 }
36
37 func TestRegisterContinueFlagShorthandParses(t *testing.T) {
38 cases := []struct {
39 args []string
40 want bool
41 }{
42 {[]string{"-c"}, true},
43 {[]string{"-c=true"}, true},
44 {[]string{"--continue"}, true},
45 {[]string{"--continue=true"}, true},
46 {[]string{}, false},
47 }
48 for _, tc := range cases {
49 fs := pflag.NewFlagSet("reasonix", pflag.ContinueOnError)
50 cont := registerContinueFlag(fs)
51 if err := fs.Parse(tc.args); err != nil {
52 t.Fatalf("Parse(%#v): %v", tc.args, err)
53 }
54 if *cont != tc.want {
55 t.Fatalf("Parse(%#v) continue = %v, want %v", tc.args, *cont, tc.want)
56 }
57 }
58 }
59
60 // Regression guard: registering the shorthand with BoolVar instead of BoolP
61 // leaves "-c" unparseable ("unknown shorthand flag") while accidentally
62 // accepting "--c" as a long flag name (the pre-fix bug, #7156/#7171).
63 func TestRegisterContinueFlagRejectsAccidentalLongC(t *testing.T) {
64 fs := pflag.NewFlagSet("reasonix", pflag.ContinueOnError)
65 cont := registerContinueFlag(fs)
66 if err := fs.Parse([]string{"--c"}); err == nil {
67 t.Fatalf("Parse(--c) should fail: --c must not exist as a long flag name")
68 }
69 if *cont {
70 t.Fatalf("--c unexpectedly set the continue flag")
71 }
72 }
73
74 func TestNormalizeOptionalResumeArg(t *testing.T) {
75 got := normalizeOptionalResumeArg([]string{"--model", "x", "--resume", "session-id", "--copy"})
76 want := []string{"--model", "x", "--resume=session-id", "--copy"}
77 if !reflect.DeepEqual(got, want) {
78 t.Fatalf("normalized args = %#v, want %#v", got, want)
79 }
80 got = normalizeOptionalResumeArg([]string{"-r", "--copy"})
81 if !reflect.DeepEqual(got, []string{"-r", "--copy"}) {
82 t.Fatalf("bare resume args = %#v", got)
83 }
84 }
85
86 func TestHasLeadingPrintFlag(t *testing.T) {
87 cases := []struct {
88 args []string
89 want bool
90 }{
91 {[]string{"-p", "task"}, true},
92 {[]string{"--print", "task"}, true},
93 {[]string{"--model", "x", "-p", "task"}, true},
94 {[]string{"--effort", "max", "--print"}, true},
95 {[]string{"--model", "x", "task"}, false},
96 {[]string{"--", "-p"}, false}, // after -- it is a literal prompt token
97 {[]string{"--model", "x", "--", "-p"}, false},
98 }
99 for _, tc := range cases {
100 if got := hasLeadingPrintFlag(tc.args); got != tc.want {
101 t.Fatalf("hasLeadingPrintFlag(%#v) = %v, want %v", tc.args, got, tc.want)
102 }
103 }
104 }
105
106 func TestStripLeadingPrintFlag(t *testing.T) {
107 cases := []struct {
108 args []string
109 want []string
110 }{
111 {[]string{"-p", "task"}, []string{"task"}},
112 {[]string{"--model", "x", "-p", "task"}, []string{"--model", "x", "task"}},
113 {[]string{"--print", "--model", "x"}, []string{"--model", "x"}},
114 // Only the first print token is dropped; a later "--print" after "--" is prompt text.
115 {[]string{"-p", "--", "--print"}, []string{"--", "--print"}},
116 {[]string{"--model", "x", "task"}, []string{"--model", "x", "task"}},
117 }
118 for _, tc := range cases {
119 if got := stripLeadingPrintFlag(tc.args); !reflect.DeepEqual(got, tc.want) {
120 t.Fatalf("stripLeadingPrintFlag(%#v) = %#v, want %#v", tc.args, got, tc.want)
121 }
122 }
123 }
124
125 func TestResolveSessionQueryByMachineSessionID(t *testing.T) {
126 identityKey := installMachineTestIdentity(t)
127 dir := t.TempDir()
128 path := saveQueryTestSession(t, dir, "opaque-branch.jsonl", "resume by machine id")
129 machineID := machineSessionIDWithKey(agent.BranchID(path), identityKey)
130 if machineID == "" || !looksLikeMachineSessionID(machineID) {
131 t.Fatalf("machine session id = %q", machineID)
132 }
133
134 got, err := resolveSessionQuery(dir, machineID)
135 if err != nil || got != path {
136 t.Fatalf("resolve by machine id = (%q, %v), want %q", got, err, path)
137 }
138 missing := "session_" + strings.Repeat("0", 32)
139 if _, err := resolveSessionQuery(dir, missing); err == nil || !strings.Contains(err.Error(), "no session") {
140 t.Fatalf("missing machine id error = %v", err)
141 }
142 }
143
144 func TestResolveSessionQueryByIDAndPreview(t *testing.T) {
145 dir := t.TempDir()
146 first := saveQueryTestSession(t, dir, "alpha-session.jsonl", "fix provider configuration")
147 _ = saveQueryTestSession(t, dir, "beta-session.jsonl", "improve terminal picker")
148
149 got, err := resolveSessionQuery(dir, "alpha-session")
150 if err != nil || got != first {
151 t.Fatalf("resolve by ID = (%q, %v), want %q", got, err, first)
152 }
153 got, err = resolveSessionQuery(dir, "provider configuration")
154 if err != nil || got != first {
155 t.Fatalf("resolve by preview = (%q, %v), want %q", got, err, first)
156 }
157 if _, err := resolveSessionQuery(dir, "session"); err == nil || !strings.Contains(err.Error(), "ambiguous") {
158 t.Fatalf("ambiguous query error = %v", err)
159 }
160 if _, err := resolveSessionQuery(dir, "missing"); err == nil || !strings.Contains(err.Error(), "no session") {
161 t.Fatalf("missing query error = %v", err)
162 }
163 }
164
165 func saveQueryTestSession(t *testing.T, dir, name, prompt string) string {
166 t.Helper()
167 path := filepath.Join(dir, name)
168 session := agent.NewSession("")
169 session.Add(provider.Message{Role: provider.RoleUser, Content: prompt})
170 session.Add(provider.Message{Role: provider.RoleAssistant, Content: "done"})
171 if err := session.Save(path); err != nil {
172 t.Fatal(err)
173 }
174 return path
175 }
176
176 lines GO