返回 DeepSeek-Reasonix
manual_session_creation_test.go
根目录 / desktop / manual_session_creation_test.go
1 package main
2
3 import (
4 "encoding/json"
5 "testing"
6
7 "reasonix/desktop/internal/sessionui"
8 "reasonix/internal/session"
9 )
10
11 func newManualSessionTestApp(t *testing.T) *App {
12 t.Helper()
13 isolateDesktopUserDirs(t)
14 a := NewApp()
15 a.ctx = t.Context()
16 installNoopRuntimeEvents(a)
17 t.Cleanup(func() {
18 if err := a.stopManualCreations(); err != nil {
19 t.Fatal(err)
20 }
21 for _, tab := range a.tabs {
22 if tab.Ctrl != nil {
23 tab.Ctrl.Close()
24 if closed, ok := tab.Ctrl.(interface{ Closed() <-chan struct{} }); ok {
25 <-closed.Closed()
26 }
27 }
28 }
29 a.closeSessionServices()
30 _ = a.sessionUI.Close()
31 _ = a.desktopDrafts.Close()
32 })
33 return a
34 }
35
36 func TestManualCreationUniqueIdempotentAndVisibleBeforeFirstSend(t *testing.T) {
37 a := newManualSessionTestApp(t)
38 for _, id := range []string{"manual-test-one", "manual-test-two", "manual-test-three"} {
39 first, err := a.BeginManualSessionCreation(ManualSessionCreationRequest{OperationID: id, Scope: "global"})
40 if err != nil {
41 t.Fatal(err)
42 }
43 retry, err := a.BeginManualSessionCreation(ManualSessionCreationRequest{OperationID: id, Scope: "global"})
44 if err != nil || retry.Ref != first.Ref {
45 t.Fatalf("replayed identity=%+v %v", retry, err)
46 }
47 }
48 a.manualCreationTasks.Wait()
49 for _, id := range []string{"manual-test-one", "manual-test-two", "manual-test-three"} {
50 view, err := a.GetManualSessionCreation(id)
51 if err != nil || view.Phase != "ready" {
52 t.Fatalf("runtime did not start: %+v %v", view, err)
53 }
54 }
55 state, err := a.workspaceRegistry().Load(t.Context())
56 if err != nil {
57 t.Fatal(err)
58 }
59 if len(state.Workspaces["global"].SessionIDs) != 3 {
60 t.Fatalf("session ids=%v", state.Workspaces["global"].SessionIDs)
61 }
62 for _, id := range state.Workspaces["global"].SessionIDs {
63 snapshot, err := a.desktopSessionService("").Query().Snapshot(t.Context(), session.SessionRef{HostID: "local", SessionID: id})
64 if err != nil {
65 t.Fatal(err)
66 }
67 for _, message := range snapshot.Projection.Messages {
68 if message.Role != "system" {
69 t.Fatal("creation submitted a non-system message")
70 }
71 }
72 }
73 if drafts, err := a.ListSessionDraftSummaries(); err != nil || len(drafts) != 0 {
74 t.Fatalf("new path created drafts: %+v %v", drafts, err)
75 }
76 }
77
78 func TestComposerRestartConflictAndUnknownSubmission(t *testing.T) {
79 a := newManualSessionTestApp(t)
80 op, err := a.BeginManualSessionCreation(ManualSessionCreationRequest{OperationID: "composer-test-create", Scope: "global"})
81 if err != nil {
82 t.Fatal(err)
83 }
84 a.manualCreationTasks.Wait()
85 input, err := a.GetSessionComposerState(op.Ref)
86 if err != nil {
87 t.Fatal(err)
88 }
89 req := SessionComposerSaveRequest{Ref: op.Ref, ExpectedRevision: input.Revision, ContentVersion: 1, ContentJSON: `{"text":"keep me","attachments":[],"future":{"keep":true}}`}
90 saved, err := a.SaveSessionComposerState(req)
91 if err != nil || saved.Conflict {
92 t.Fatalf("save=%+v %v", saved, err)
93 }
94 conflict, err := a.SaveSessionComposerState(req)
95 if err != nil || !conflict.Conflict {
96 t.Fatalf("CAS=%+v %v", conflict, err)
97 }
98 path := a.sessionUI.Path()
99 _ = a.sessionUI.Close()
100 a.sessionUI = sessionui.New(path)
101 restored, err := a.GetSessionComposerState(op.Ref)
102 if err != nil || restored.ContentJSON != req.ContentJSON {
103 t.Fatalf("restored=%+v %v", restored, err)
104 }
105 pending, err := a.BeginSessionComposerSubmission(op.Ref, saved.Revision, "composer-pending", `{"input":"hello"}`)
106 if err != nil {
107 t.Fatal(err)
108 }
109 unknown, err := a.GetSessionComposerState(op.Ref)
110 if err != nil || unknown.SubmissionPhase != "unknown" || unknown.ContentJSON != req.ContentJSON {
111 t.Fatalf("unknown=%+v %v", unknown, err)
112 }
113 req.ExpectedRevision = pending.Revision
114 if _, err := a.SaveSessionComposerState(req); err == nil {
115 t.Fatal("pending input was overwritten")
116 }
117 accepted, err := a.CompleteSessionComposerSubmission(op.Ref, "composer-pending", "accepted")
118 if err != nil || accepted.ContentJSON != "{}" {
119 t.Fatalf("accepted=%+v %v", accepted, err)
120 }
121 if !json.Valid([]byte(accepted.ContentJSON)) {
122 t.Fatal("invalid saved content")
123 }
124 }
125
126 func TestPreviousDraftAPIWillNotCreateNewRecords(t *testing.T) {
127 a := newManualSessionTestApp(t)
128 if _, err := a.OpenSessionDraftForTarget("global", ""); err == nil {
129 t.Fatal("legacy API created a draft")
130 }
131 rows, err := a.ListSessionDraftSummaries()
132 if err != nil || len(rows) != 0 {
133 t.Fatalf("legacy rows=%+v %v", rows, err)
134 }
135 }
136
137 func TestManualCreationCancelledBuildRetainsIdentityForRetry(t *testing.T) {
138 a := newManualSessionTestApp(t)
139 started, release := make(chan struct{}), make(chan struct{})
140 a.tabBuildStartHook = func(string) { close(started); <-release }
141 operation, err := a.BeginManualSessionCreation(ManualSessionCreationRequest{OperationID: "cancelled-build-operation", Scope: "global"})
142 if err != nil {
143 t.Fatal(err)
144 }
145 <-started
146 a.shuttingDown.Store(true)
147 a.cancelAllTabBuilds()
148 close(release)
149 a.manualCreationTasks.Wait()
150 failed, err := a.GetManualSessionCreation(operation.OperationID)
151 if err != nil || failed.Phase != "starting" || failed.Ref != operation.Ref {
152 t.Fatalf("cancelled creation=%+v %v", failed, err)
153 }
154 a.shuttingDown.Store(false)
155 a.tabBuildStartHook = nil
156 if _, err := a.RetryManualSessionCreation(operation.OperationID); err != nil {
157 t.Fatal(err)
158 }
159 a.manualCreationTasks.Wait()
160 retried, err := a.GetManualSessionCreation(operation.OperationID)
161 if err != nil || retried.Phase != "ready" || retried.Ref != operation.Ref {
162 t.Fatalf("retry=%+v %v", retried, err)
163 }
164 state, err := a.workspaceRegistry().Load(t.Context())
165 if err != nil || len(state.Workspaces["global"].SessionIDs) != 1 {
166 t.Fatalf("retry created replacement: %+v %v", state, err)
167 }
168 }
169
169 lines GO