返回 DeepSeek-Reasonix
session_workspace_navigation_test.go
根目录 / desktop / session_workspace_navigation_test.go
1 package main
2
3 import (
4 "context"
5 "errors"
6 "testing"
7
8 "reasonix/internal/boot"
9 "reasonix/internal/control"
10 "reasonix/internal/event"
11 "reasonix/internal/session"
12 )
13
14 func TestOpenSessionSelectsLocalTabAfterRemote(t *testing.T) {
15 app, _, target, _, _ := canonicalWorkspaceOpenFixture(t)
16 app.remoteTabMu.Lock()
17 app.remoteTabs = map[string]*remoteTab{
18 "remote": {id: "remote", ref: RemoteTabRef{HostID: "box", Workspace: "/work"}, state: "disconnected"},
19 }
20 app.remoteTabLayout = remoteTabLayoutState{activeID: "remote", order: []string{"remote"}, stripOrder: []string{"remote"}}
21 app.remoteTabMu.Unlock()
22
23 if _, err := app.OpenSession(target.Ref()); err != nil {
24 t.Fatal(err)
25 }
26 localActive, remoteActive := false, false
27 for _, tab := range app.ListTabs() {
28 if tab.Remote == nil && tab.SessionID == target.Ref().SessionID {
29 localActive = tab.Active
30 }
31 if tab.Remote != nil {
32 remoteActive = tab.Active
33 }
34 }
35 if !localActive || remoteActive {
36 t.Fatalf("OpenSession left localActive=%v remoteActive=%v", localActive, remoteActive)
37 }
38 }
39
40 func TestCanonicalNavigationLastRequestWins(t *testing.T) {
41 app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t)
42 ctrl := tab.Ctrl
43 tab.Ctrl = &activeNavigationController{SessionAPI: ctrl, IdentityLifecycle: ctrl.(control.IdentityLifecycle), status: control.RuntimeStatus{Running: true}}
44 source := session.SessionRef{HostID: localDesktopHostID, SessionID: tab.SessionID}
45 app.runtimeRebuildMu.Lock()
46 locked := true
47 defer func() {
48 if locked {
49 app.runtimeRebuildMu.Unlock()
50 }
51 }()
52 first, second := make(chan error, 1), make(chan error, 1)
53 go func() { _, err := app.OpenSession(target.Ref()); first <- err }()
54 waitFor(t, "first navigation admission", func() bool { return app.desktopSessions.navigationSeq.Load() == 1 })
55 go func() { _, err := app.OpenSession(source); second <- err }()
56 waitFor(t, "second navigation admission", func() bool { return app.desktopSessions.navigationSeq.Load() == 2 })
57 app.runtimeRebuildMu.Unlock()
58 locked = false
59 if err := <-first; !errors.Is(err, errSessionNavigationSuperseded) {
60 t.Fatalf("first open = %v", err)
61 }
62 if err := <-second; err != nil {
63 t.Fatalf("last open = %v", err)
64 }
65 if tab.SessionID != source.SessionID {
66 t.Fatal("superseded open won")
67 }
68 }
69
70 func TestTicketedCanonicalNavigationKeepsOriginalIntent(t *testing.T) {
71 app, source, target, _, _ := canonicalWorkspaceOpenFixture(t)
72 app.readyHook = func() {}
73 installNoopRuntimeEvents(app, source.sink)
74 events := newActivationEventRecorder(app)
75 t.Cleanup(func() { app.shutdown(context.Background()) })
76 ticket, err := app.StartTopicActivation(TopicActivationRequest{SessionPath: sessionRoute(target.Ref().SessionID)})
77 if err != nil {
78 t.Fatal(err)
79 }
80 if got := app.desktopSessions.navigationSeq.Load(); got != 1 {
81 t.Fatalf("one navigation claimed %d intents; a nested open can overtake a newer queued request", got)
82 }
83 events.waitFor(t, activationEventFor(ticket.RequestID, "ready"))
84 }
85
86 func TestDelayedCanonicalNavigationCannotReclaimSupersededIntent(t *testing.T) {
87 app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t)
88 intent := app.desktopSessions.navigationSeq.Add(1)
89 if err := app.SetActiveTab(tab.ID); err != nil {
90 t.Fatal(err)
91 }
92 before := tab.SessionID
93 if _, err := app.openTopicSessionWithNavigation("project", tab.WorkspaceRoot, "", sessionRoute(target.Ref().SessionID), intent); !errors.Is(err, errSessionNavigationSuperseded) {
94 t.Fatalf("late adopted source open = %v", err)
95 }
96 if tab.SessionID != before {
97 t.Fatal("late source adoption displaced the newer selection")
98 }
99 }
100
101 func TestCanonicalOpenReattachesDetachedWorkspace(t *testing.T) {
102 app, tab, target, root, workspaceID := canonicalWorkspaceOpenFixture(t)
103 ctrl, err := app.buildTabControllerBoot(app.ctx, boot.Options{Model: tab.model, WorkspaceRoot: root, SessionDir: desktopSessionDir(root), Sink: event.Discard})
104 if err != nil {
105 t.Fatal(err)
106 }
107 t.Cleanup(ctrl.Close)
108 if _, err := ctrl.(control.IdentityLifecycle).OpenSession(t.Context(), target.Ref()); err != nil {
109 t.Fatal(err)
110 }
111 detached := &WorkspaceTab{ID: "detached-B", Scope: "project", WorkspaceRoot: root, SessionID: target.Ref().SessionID, Ready: true, Ctrl: ctrl, model: tab.model, sink: &tabEventSink{tabID: "detached-B", app: app}}
112 detached.SessionWorkspace.ID = workspaceID
113 key := sessionRuntimeKey(sessionRoute(target.Ref().SessionID))
114 app.ensureDetachedSessionsLocked()
115 app.detachedSessions[key] = detached
116 app.newSessionRuntimeLocked(detached, key)
117 if _, err := app.OpenSession(target.Ref()); err != nil {
118 t.Fatal(err)
119 }
120 if tab.Ctrl != ctrl || !sameDesktopPath(tab.WorkspaceRoot, root) || app.detachedSessions[key] != nil {
121 t.Fatal("did not transfer the exact detached owner")
122 }
123 if runtime := app.runtimeBySessionKey[key]; runtime == nil || runtime.Owner != tab {
124 t.Fatal("runtime map has a stale owner")
125 }
126 }
127
128 func TestCanonicalLegacyResumeUsesTargetWorkspace(t *testing.T) {
129 for _, page := range []bool{false, true} {
130 t.Run(map[bool]string{false: "messages", true: "page"}[page], func(t *testing.T) {
131 app, tab, target, root, _ := canonicalWorkspaceOpenFixture(t)
132 tab.HistoricalSource = &SessionSourceRef{Path: "/fixture/old.jsonl"}
133 var err error
134 if page {
135 _, err = app.ResumeSessionPageForTab(tab.ID, sessionRoute(target.Ref().SessionID), 32)
136 } else {
137 _, err = app.ResumeSessionForTab(tab.ID, sessionRoute(target.Ref().SessionID))
138 }
139 if err != nil {
140 t.Fatal(err)
141 }
142 if !sameDesktopPath(tab.WorkspaceRoot, root) {
143 t.Fatal("compatibility entry kept source workspace")
144 }
145 if tab.HistoricalSource != nil {
146 t.Fatal("compatibility entry retained the preparation action")
147 }
148 })
149 }
150 }
151
152 func TestCanonicalOpenGlobalWorkspace(t *testing.T) {
153 app, tab, _, _, _ := canonicalWorkspaceOpenFixture(t)
154 workspaceID, err := app.ensureDesktopWorkspace(t.Context(), "global", "")
155 if err != nil {
156 t.Fatal(err)
157 }
158 target, err := app.desktopSessionService("").Create(t.Context(), session.CreateOptions{SessionID: "global-target", CWD: globalWorkspaceRoot(), Origin: session.SessionOriginNew})
159 if err != nil {
160 t.Fatal(err)
161 }
162 if err := app.workspaceRegistry().AttachSession(t.Context(), "", workspaceID, target.Ref().SessionID, ""); err != nil {
163 t.Fatal(err)
164 }
165 if _, err := app.OpenSession(target.Ref()); err != nil {
166 t.Fatal(err)
167 }
168 if tab.Scope != "global" || tab.SessionWorkspace.ID != workspaceID || !sameDesktopPath(tab.WorkspaceRoot, globalWorkspaceRoot()) {
169 t.Fatal("global target inherited project context")
170 }
171 }
172
172 lines GO