返回 DeepSeek-Reasonix
session_workspace_registry_test.go
根目录 / desktop / session_workspace_registry_test.go
1 package main
2
3 import (
4 "context"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9 "testing"
10
11 "reasonix/desktop/internal/workspacestate"
12 "reasonix/internal/agent"
13 "reasonix/internal/config"
14 "reasonix/internal/control"
15 "reasonix/internal/event"
16 "reasonix/internal/provider"
17 "reasonix/internal/session"
18 )
19
20 type registrySessionCreator struct{ service *session.Service }
21
22 func (c registrySessionCreator) BindFreshSession(ctx context.Context, sessionID string) (session.SessionRef, error) {
23 runtime, err := c.service.Create(ctx, session.CreateOptions{SessionID: sessionID})
24 if err != nil {
25 return session.SessionRef{}, err
26 }
27 return runtime.Ref(), nil
28 }
29
30 func TestGlobalWorkspaceEquivalentRootAllowsCreateAndRestart(t *testing.T) {
31 aliases := []string{"cleaned"}
32 if runtime.GOOS == "windows" {
33 aliases = append(aliases, "case", "separators")
34 }
35 for _, alias := range aliases {
36 t.Run(alias, func(t *testing.T) {
37 isolateDesktopUserDirs(t)
38 root := globalWorkspaceRoot()
39 savedRoot := root + string(os.PathSeparator) + "."
40 switch alias {
41 case "case":
42 savedRoot = strings.ToUpper(root)
43 case "separators":
44 savedRoot = strings.ReplaceAll(root, `\`, "/")
45 }
46 var ref session.SessionRef
47 for attempt := range 2 {
48 app := NewApp()
49 app.ctx = t.Context()
50 t.Cleanup(app.closeSessionServices)
51 if attempt == 0 {
52 if err := app.workspaceRegistry().EnsureWorkspace(t.Context(), workspacestate.Workspace{
53 ID: workspacestate.GlobalWorkspaceID, Root: savedRoot, Title: "My Global", Visible: true,
54 }); err != nil {
55 t.Fatal(err)
56 }
57 }
58 ctrl := control.New(control.Options{
59 Executor: agent.New(nil, nil, agent.NewSession("test"), agent.Options{}, event.Discard),
60 SessionService: app.desktopSessionService(""), ExclusiveSession: true,
61 })
62 t.Cleanup(ctrl.Close)
63 got, workspaceID, err := app.bindTabCanonicalSession(t.Context(), ctrl, &config.Config{}, "global", "", ref.SessionID, "", "", false)
64 if err != nil || workspaceID != workspacestate.GlobalWorkspaceID {
65 t.Fatalf("create/reopen attempt %d: workspace=%q, err=%v", attempt, workspaceID, err)
66 }
67 if attempt == 0 {
68 ref = got
69 live, ok := app.desktopSessionService("").Runtime(ref)
70 if !ok {
71 t.Fatal("created session has no runtime")
72 }
73 appendSessionTestMessage(t, live, "user-message", provider.Message{ID: "user-message", Role: provider.RoleUser, Content: "preserved Global history"})
74 } else {
75 if got != ref {
76 t.Fatalf("restart replaced session: got=%v want=%v", got, ref)
77 }
78 page, err := app.ReadSessionHistory(ref, "", 10)
79 if err != nil || len(page.Messages) == 0 || page.Messages[len(page.Messages)-1].Content != "preserved Global history" {
80 t.Fatalf("reopened history=%+v err=%v", page, err)
81 }
82 if _, err := app.canonicalSessionWorkspace(t.Context(), ref); err != nil {
83 t.Fatalf("navigation membership: %v", err)
84 }
85 }
86 state, err := app.workspaceRegistry().Load(t.Context())
87 if err != nil {
88 t.Fatal(err)
89 }
90 workspace := state.Workspaces[workspaceID]
91 if workspace.Root != savedRoot || workspace.Title != "My Global" || len(workspace.SessionIDs) != 1 || workspace.SessionIDs[0] != ref.SessionID || len(state.PendingCreates) != 0 {
92 t.Fatalf("unexpected persisted workspace: %+v, pending=%+v", workspace, state.PendingCreates)
93 }
94 ctrl.Close()
95 app.closeSessionServices()
96 }
97 })
98 }
99 }
100
101 func (c registrySessionCreator) BindFreshSessionWithOptions(ctx context.Context, options session.CreateOptions) (session.SessionRef, error) {
102 runtime, err := c.service.Create(ctx, options)
103 if err != nil {
104 return session.SessionRef{}, err
105 }
106 return runtime.Ref(), nil
107 }
108
109 func TestFreshDesktopSessionIsDurableRegistryMemberBeforeReturn(t *testing.T) {
110 root := t.TempDir()
111 app := NewApp()
112 t.Cleanup(app.closeSessionServices)
113 app.desktopSessions.root = filepath.Join(root, "desktop-sessions-v5", "by-id")
114 app.desktopSessions.workspaceState = workspacestate.NewStore(filepath.Join(root, "desktop", "workspace-state-v1.json"))
115 service := app.desktopSessionService(filepath.Join(root, "old-project-sessions"))
116 project := filepath.Join(root, "project")
117
118 ref, workspaceID, err := app.bindFreshDesktopSession(t.Context(), "project", project, registrySessionCreator{service: service})
119 if err != nil {
120 t.Fatalf("bindFreshDesktopSession: %v", err)
121 }
122 state, err := app.desktopSessions.workspaceState.Load(t.Context())
123 if err != nil {
124 t.Fatalf("Load workspace state: %v", err)
125 }
126 workspace := state.Workspaces[workspaceID]
127 if len(workspace.SessionIDs) != 1 || workspace.SessionIDs[0] != ref.SessionID {
128 t.Fatalf("workspace sessions = %#v, ref = %#v", workspace.SessionIDs, ref)
129 }
130 if len(state.PendingCreates) != 0 {
131 t.Fatalf("pending creates = %#v", state.PendingCreates)
132 }
133 page, err := service.Query().List(t.Context(), "", 10)
134 if err != nil {
135 t.Fatalf("List sessions: %v", err)
136 }
137 if len(page.Sessions) != 1 || page.Sessions[0].SessionID != ref.SessionID || page.Sessions[0].CWD != project {
138 t.Fatalf("sessions = %#v", page.Sessions)
139 }
140 }
141
142 func TestWorkspaceRegistryRejectsSessionHeaderFromDifferentWorkspace(t *testing.T) {
143 root := t.TempDir()
144 app := NewApp()
145 t.Cleanup(app.closeSessionServices)
146 app.desktopSessions.root = filepath.Join(root, "desktop-sessions-v5", "by-id")
147 app.desktopSessions.workspaceState = workspacestate.NewStore(filepath.Join(root, "desktop", "workspace-state-v1.json"))
148 service := app.desktopSessionService("")
149 projectA := filepath.Join(root, "project-a")
150 projectB := filepath.Join(root, "project-b")
151 runtime, err := service.Create(t.Context(), session.CreateOptions{
152 SessionID: "belongs-to-a", CWD: projectA, Origin: session.SessionOriginNew,
153 })
154 if err != nil {
155 t.Fatal(err)
156 }
157
158 if _, err := app.attachDesktopSession(t.Context(), "project", projectB, runtime.Ref()); err == nil {
159 t.Fatal("attaching a session to a workspace that disagrees with its immutable header succeeded")
160 }
161 state, err := app.workspaceRegistry().Load(t.Context())
162 if err != nil {
163 t.Fatal(err)
164 }
165 workspace := state.Workspaces[desktopWorkspaceID("project", projectB)]
166 if len(workspace.SessionIDs) != 0 {
167 t.Fatalf("mismatched workspace members = %#v", workspace.SessionIDs)
168 }
169 }
170
170 lines GO