返回 DeepSeek-Reasonix
initial_goal_submit_target_test.go
根目录 / desktop / initial_goal_submit_target_test.go
1 package main
2
3 import (
4 "errors"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "reasonix/internal/control"
11 )
12
13 type initialGoalSubmitRecorder struct {
14 control.SessionAPI
15 display string
16 input string
17 invocations []control.InvocationRequest
18 }
19
20 func TestSubmitInitialGoalStopsBeforeProviderWhenPersistenceFails(t *testing.T) {
21 app := testAppWithOrderedTabs(t, "a", "a")
22 base := control.New(control.Options{Label: "test"})
23 defer base.Close()
24 recorder := &initialGoalSubmitRecorder{SessionAPI: base}
25 tab := app.tabs["a"]
26 tab.Ctrl = &rejectingGoalSession{SessionAPI: recorder, err: errors.New("disk full")}
27
28 _, err := app.SubmitInitialGoalToTab(
29 tab.ID, "ship the fix", "ship the fix", "ship the fix", nil,
30 "normal", "ask",
31 )
32 if err == nil || !strings.Contains(err.Error(), "disk full") {
33 t.Fatalf("SubmitInitialGoalToTab error = %v, want persistence failure", err)
34 }
35 if recorder.input != "" || recorder.display != "" {
36 t.Fatalf("provider submission escaped failed goal mutation: display=%q input=%q", recorder.display, recorder.input)
37 }
38 if tab.goal != "" {
39 t.Fatalf("tab goal = %q, want unpublished", tab.goal)
40 }
41 }
42
43 func TestSubmitInitialGoalRejectsMissingImageBeforeGoalMutation(t *testing.T) {
44 workspace := t.TempDir()
45 ctrl := control.New(control.Options{WorkspaceRoot: workspace})
46 t.Cleanup(ctrl.Close)
47 tab := &WorkspaceTab{
48 ID: "a",
49 Scope: "project",
50 WorkspaceRoot: workspace,
51 TopicID: "topic_a",
52 TopicTitle: "A",
53 Ready: true,
54 Ctrl: ctrl,
55 disabledMCP: map[string]ServerView{},
56 }
57 app := &App{tabs: map[string]*WorkspaceTab{"a": tab}, tabOrder: []string{"a"}, activeTabID: "a"}
58
59 _, err := app.SubmitInitialGoalToTab(
60 tab.ID,
61 "inspect the image",
62 "inspect missing.png",
63 "inspect @.reasonix/attachments/missing.png",
64 nil,
65 "normal",
66 "ask",
67 )
68 if err == nil || err.Error() != "reasonix_error:image_attachment_unreadable" {
69 t.Fatalf("error = %v, want stable image failure", err)
70 }
71 if tab.goal != "" || ctrl.Goal() != "" {
72 t.Fatalf("rejected image mutated goal: tab=%q controller=%q", tab.goal, ctrl.Goal())
73 }
74 if ctrl.Running() {
75 t.Fatal("rejected image started a turn")
76 }
77 if _, statErr := os.Stat(filepath.Join(workspace, ".reasonix", "attachments")); !os.IsNotExist(statErr) {
78 t.Fatalf("failed image read created attachment directory: %v", statErr)
79 }
80 }
81
82 func (r *initialGoalSubmitRecorder) SubmitDisplay(display, input string) {
83 r.display = display
84 r.input = input
85 }
86
87 func (r *initialGoalSubmitRecorder) SubmitInvocationDisplay(
88 display, input string,
89 invocations []control.InvocationRequest,
90 ) {
91 r.display = display
92 r.input = input
93 r.invocations = append([]control.InvocationRequest(nil), invocations...)
94 }
95
96 func TestSubmitInitialGoalAcceptsCurrentLocalTarget(t *testing.T) {
97 app := testAppWithOrderedTabs(t, "a", "a")
98 ctrl := control.New(control.Options{Label: "test"})
99 defer ctrl.Close()
100 recorder := &initialGoalSubmitRecorder{SessionAPI: ctrl}
101 tab := app.tabs["a"]
102 tab.Ctrl = recorder
103
104 _, err := app.SubmitInitialGoalToTab(
105 tab.ID,
106 "ship the fix",
107 "/ui-ux-pro-max ship the fix",
108 "ship the fix",
109 []InvocationRequest{{Name: "ui-ux-pro-max", Kind: "skill", Offset: 4}},
110 "normal",
111 "ask",
112 )
113 if err != nil {
114 t.Fatal(err)
115 }
116 if tab.goal != "ship the fix" {
117 t.Fatalf("tab goal = %q, want %q", tab.goal, "ship the fix")
118 }
119 if got := ctrl.Goal(); got != "ship the fix" {
120 t.Fatalf("controller goal = %q, want %q", got, "ship the fix")
121 }
122 if recorder.display != "/ui-ux-pro-max ship the fix" || recorder.input != "ship the fix" {
123 t.Fatalf("recorded submit = display %q input %q", recorder.display, recorder.input)
124 }
125 if len(recorder.invocations) != 1 {
126 t.Fatalf("recorded invocations = %+v, want one", recorder.invocations)
127 }
128 if got := recorder.invocations[0]; got.Name != "ui-ux-pro-max" || got.Kind != "skill" || got.Offset != 4 {
129 t.Fatalf("recorded invocation = %+v", got)
130 }
131 }
132
133 func TestSubmitInitialGoalAppliesToolApprovalProfileBeforeSubmit(t *testing.T) {
134 app := testAppWithOrderedTabs(t, "a", "a")
135 ctrl := control.New(control.Options{Label: "test"})
136 defer ctrl.Close()
137 recorder := &initialGoalSubmitRecorder{SessionAPI: ctrl}
138 tab := app.tabs["a"]
139 tab.Ctrl = recorder
140
141 _, err := app.SubmitInitialGoalToTab(
142 tab.ID,
143 "ship with YOLO",
144 "ship with YOLO",
145 "ship with YOLO",
146 nil,
147 "normal",
148 string(control.ToolApprovalYolo),
149 )
150 if err != nil {
151 t.Fatal(err)
152 }
153 if tab.toolApprovalMode != string(control.ToolApprovalYolo) {
154 t.Fatalf("tab approval mode = %q, want %q", tab.toolApprovalMode, control.ToolApprovalYolo)
155 }
156 if got := recorder.ToolApprovalMode(); got != string(control.ToolApprovalYolo) {
157 t.Fatalf("controller approval mode = %q, want %q", got, control.ToolApprovalYolo)
158 }
159 if recorder.input != "ship with YOLO" {
160 t.Fatalf("recorded input = %q, want first Goal input", recorder.input)
161 }
162 }
163
163 lines GO