返回 DeepSeek-Reasonix
work_mode_test.go
根目录 / internal / cli / work_mode_test.go
1 package cli
2
3 import (
4 "strings"
5 "sync"
6 "testing"
7 "time"
8
9 "github.com/charmbracelet/x/ansi"
10
11 "reasonix/internal/boot"
12 "reasonix/internal/control"
13 "reasonix/internal/event"
14 "reasonix/internal/i18n"
15 "reasonix/internal/provider"
16 )
17
18 func resetPresetDeprecationForTest() {
19 presetDeprecationOnce = sync.Once{}
20 }
21
22 func committedNotices(m chatTUI) string {
23 if m.pendingCommit == nil {
24 return ""
25 }
26 return ansi.Strip(strings.Join(*m.pendingCommit, "\n"))
27 }
28
29 func TestParseAgentPresetAcceptsLegacyLabels(t *testing.T) {
30 for input, want := range map[string]string{
31 "economy": "standard",
32 "light": "standard",
33 "eco": "standard",
34 "lite": "standard",
35 "balanced": "standard",
36 "full": "standard",
37 "standard": "standard",
38 "delivery": "standard",
39 "deliver": "standard",
40 } {
41 got, ok := parseAgentPreset(input)
42 if !ok || got != want {
43 t.Errorf("parseAgentPreset(%q) = %q, %v; want %q, true", input, got, ok, want)
44 }
45 }
46 if _, ok := parseAgentPreset("unknown"); ok {
47 t.Fatal("unknown preset should be rejected")
48 }
49 }
50
51 func TestRetiredPresetCommandsAreHiddenFromCompletion(t *testing.T) {
52 m := newTestChatTUI()
53 for _, command := range []string{"/preset", "/profile", "/work-mode"} {
54 if hasLabel(m.slashItems(), command) {
55 t.Fatalf("retired compatibility command %q appeared in slash completion", command)
56 }
57 }
58 for _, input := range []string{"/preset ", "/work-mode ", "/profile "} {
59 if _, _, ok := m.slashArgItems(input); ok {
60 t.Fatalf("%q offered retired execution-mode argument completion", input)
61 }
62 }
63 }
64
65 func TestConsumeDeprecatedModeFlagsKeepsCompatibilityOutOfPublicFlagSets(t *testing.T) {
66 clean, mode, err := consumeDeprecatedModeFlags([]string{
67 "--model", "deepseek", "--profile=delivery", "fix it", "--preset", "light",
68 }, "profile", "preset")
69 if err != nil {
70 t.Fatal(err)
71 }
72 if mode != "delivery" {
73 t.Fatalf("mode = %q, want profile precedence delivery", mode)
74 }
75 if got := strings.Join(clean, " "); got != "--model deepseek fix it" {
76 t.Fatalf("clean args = %q", got)
77 }
78
79 clean, mode, err = consumeDeprecatedModeFlags([]string{"run", "--", "--profile", "delivery"}, "profile")
80 if err != nil || mode != "" || strings.Join(clean, " ") != "run -- --profile delivery" {
81 t.Fatalf("post-boundary args: clean=%v mode=%q err=%v", clean, mode, err)
82 }
83 if _, _, err := consumeDeprecatedModeFlags([]string{"--profile"}, "profile"); err == nil {
84 t.Fatal("missing compatibility flag value must fail")
85 }
86 }
87
88 func TestLegacyModeFlagsAreHiddenFromCommandHelp(t *testing.T) {
89 tests := []struct {
90 name string
91 run func() int
92 }{
93 {name: "run", run: func() int { return runAgent([]string{"--help"}, "dev") }},
94 {name: "chat", run: func() int { return chatREPL([]string{"--help"}, "dev") }},
95 {name: "web", run: func() int { return runWeb([]string{"--help"}) }},
96 {name: "acp", run: func() int { return acpCommand([]string{"--help"}, "dev") }},
97 }
98 for _, tt := range tests {
99 t.Run(tt.name, func(t *testing.T) {
100 out := captureStdout(t, func() {
101 if code := tt.run(); code != 0 {
102 t.Fatalf("help exit code = %d, want 0", code)
103 }
104 })
105 if strings.Contains(out, "--profile") || strings.Contains(out, "--preset") {
106 t.Fatalf("legacy mode flag leaked into help:\n%s", out)
107 }
108 })
109 }
110 }
111
112 func TestRetiredPresetCommandsAreHiddenFromHelp(t *testing.T) {
113 for _, command := range []string{"/preset", "/profile", "/work-mode"} {
114 if hasLabel(builtinHelpItems(), command) {
115 t.Fatalf("built-in help listed retired command %q", command)
116 }
117 }
118 }
119
120 func TestPresetTagStaysHiddenForLegacyInputs(t *testing.T) {
121 ctrl := newOwnedTestController(t, control.Options{})
122 m := newChatTUI(ctrl, "", make(chan event.Event, 1), 80)
123 if tag := m.presetTag(); tag != "" {
124 t.Fatalf("standard floor should stay quiet, got %q", tag)
125 }
126 if err := ctrl.SetQualityFloor(control.QualityFloorDelivery); err != nil {
127 t.Fatal(err)
128 }
129 if tag := m.presetTag(); tag != "" {
130 t.Fatalf("retired delivery setting surfaced a footer tag: %q", tag)
131 }
132 }
133
134 func TestPresetCommandIsInPlaceCompatibilityNoOp(t *testing.T) {
135 resetPresetDeprecationForTest()
136 oldCtrl := newOwnedTestController(t, control.Options{Label: "old"})
137 oldCtrl.SetToolApprovalMode(control.ToolApprovalAuto)
138 oldCtrl.SetPlanMode(true)
139 m := newChatTUI(oldCtrl, "", make(chan event.Event, 1), 100)
140 m.modelRef = "provider/model"
141 builds := 0
142 m.buildController = func(controllerBuildSpec, []provider.Message, string, control.SessionAPI) (*control.Controller, error) {
143 builds++
144 return newOwnedTestController(t, control.Options{Label: "new"}), nil
145 }
146
147 cmd := m.runWorkModeCommand("/preset delivery")
148 if cmd != nil {
149 t.Fatal("/preset must not schedule a controller rebuild")
150 }
151 if m.ctrl != oldCtrl {
152 t.Fatal("controller must stay the same instance")
153 }
154 if m.ctrl.AgentPreset() != boot.AgentPresetStandard {
155 t.Fatalf("controller preset = %q, want standard", m.ctrl.AgentPreset())
156 }
157 if builds != 0 {
158 t.Fatalf("unexpected rebuilds: %d", builds)
159 }
160 if out := committedNotices(m); !strings.Contains(out, i18n.M.WorkModeDeprecatedNotice) {
161 t.Fatalf("legacy /preset missing retirement notice:\n%s", out)
162 }
163
164 if cmd := m.runWorkModeCommand("/preset light"); cmd != nil {
165 t.Fatal("second /preset must not rebuild")
166 }
167 if m.ctrl.AgentPreset() != boot.AgentPresetStandard {
168 t.Fatalf("light must fold to standard, got %q", m.ctrl.AgentPreset())
169 }
170 }
171
172 func TestPresetCommandRejectsInvalidValue(t *testing.T) {
173 resetPresetDeprecationForTest()
174 m := newTestChatTUI()
175 m.ctrl = newOwnedTestController(t, control.Options{Label: "model"})
176 m.modelRef = "provider/model"
177 builds := 0
178 m.buildController = func(controllerBuildSpec, []provider.Message, string, control.SessionAPI) (*control.Controller, error) {
179 builds++
180 return newOwnedTestController(t, control.Options{Label: "new"}), nil
181 }
182
183 if cmd := m.runWorkModeCommand("/preset unknown"); cmd != nil {
184 t.Fatal("invalid /preset unexpectedly scheduled a rebuild")
185 }
186 out := committedNotices(m)
187 if !strings.Contains(out, i18n.M.WorkModeUsage) {
188 t.Fatalf("invalid /preset missing usage:\n%s", out)
189 }
190 if m.ctrl.AgentPreset() != boot.AgentPresetStandard {
191 t.Fatalf("controller preset = %q, want standard", m.ctrl.AgentPreset())
192 }
193 if builds != 0 {
194 t.Fatalf("rejected preset request triggered %d builds", builds)
195 }
196 }
197
198 func TestPresetCommandSwitchesWhenBusy(t *testing.T) {
199 resetPresetDeprecationForTest()
200 m := newTestChatTUI()
201 m.ctrl = newOwnedTestController(t, control.Options{Label: "model"})
202 m.modelRef = "provider/model"
203 m.pendingApproval = &event.Approval{ID: "approval", Tool: "bash"}
204 builds := 0
205 m.buildController = func(controllerBuildSpec, []provider.Message, string, control.SessionAPI) (*control.Controller, error) {
206 builds++
207 return newOwnedTestController(t, control.Options{Label: "new"}), nil
208 }
209 if cmd := m.runWorkModeCommand("/preset light"); cmd != nil {
210 t.Fatal("busy /preset must not rebuild")
211 }
212 if m.ctrl.AgentPreset() != boot.AgentPresetStandard {
213 t.Fatalf("busy /preset AgentPreset = %q, want standard (light folds)", m.ctrl.AgentPreset())
214 }
215 if builds != 0 {
216 t.Fatalf("busy /preset triggered %d builds", builds)
217 }
218 }
219
220 func TestPresetCommandSwitchesDuringRunningTurn(t *testing.T) {
221 resetPresetDeprecationForTest()
222 runner := &blockingTurnRunner{started: make(chan struct{})}
223 ctrl := newOwnedTestController(t, control.Options{Runner: runner, Sink: event.Discard, SessionDir: t.TempDir(), Label: "model"})
224 ctrl.Send("keep running")
225 <-runner.started
226 t.Cleanup(func() {
227 ctrl.Cancel()
228 deadline := time.Now().Add(2 * time.Second)
229 for ctrl.Running() && time.Now().Before(deadline) {
230 time.Sleep(time.Millisecond)
231 }
232 })
233
234 m := newTestChatTUI()
235 m.ctrl = ctrl
236 m.modelRef = "provider/model"
237 builds := 0
238 m.buildController = func(controllerBuildSpec, []provider.Message, string, control.SessionAPI) (*control.Controller, error) {
239 builds++
240 return newOwnedTestController(t, control.Options{}), nil
241 }
242 if cmd := m.runWorkModeCommand("/preset delivery"); cmd != nil {
243 t.Fatal("running-turn /preset must not rebuild")
244 }
245 if m.ctrl.AgentPreset() != boot.AgentPresetStandard {
246 t.Fatalf("running-turn /preset AgentPreset = %q, want standard", m.ctrl.AgentPreset())
247 }
248 if builds != 0 {
249 t.Fatalf("running-turn /preset triggered %d builds", builds)
250 }
251 if out := committedNotices(m); !strings.Contains(out, i18n.M.WorkModeDeprecatedNotice) {
252 t.Fatalf("running-turn /preset missing retirement notice:\n%s", out)
253 }
254 }
255
255 lines GO