返回 DeepSeek-Reasonix
session_workspace_duplicate_identity_test.go
根目录 / desktop / session_workspace_duplicate_identity_test.go
1 package main
2
3 import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "slices"
8 "testing"
9
10 "reasonix/desktop/internal/workspacestate"
11 "reasonix/internal/config"
12 )
13
14 // The repair removes the losing record, while a saved tab can still name it.
15 // That tab must be restored against the directory's surviving owner instead of
16 // being dropped as a stale presentation of a workspace that no longer exists.
17 func TestSavedTabNamingAbsorbedWorkspaceIsRestored(t *testing.T) {
18 app := newSavedTabReconcileTestApp(t)
19 finishSavedTabMigration(app)
20 root, err := ensureGlobalWorkspaceRoot()
21 if err != nil {
22 t.Fatal(err)
23 }
24 // Registered while Global has no record: the project record takes the
25 // directory, the way a build without physical identity left it.
26 ref, duplicate := createLegacyCleanupSession(t, app, root, "kept-session", true)
27 if duplicate == workspacestate.GlobalWorkspaceID {
28 t.Fatalf("fixture did not register a separate project record: %q", duplicate)
29 }
30 path := config.DesktopWorkspaceStatePath()
31 body, err := os.ReadFile(path)
32 if err != nil {
33 t.Fatal(err)
34 }
35 var document map[string]any
36 if err := json.Unmarshal(body, &document); err != nil {
37 t.Fatal(err)
38 }
39 document["workspaceIds"] = append(document["workspaceIds"].([]any), workspacestate.GlobalWorkspaceID)
40 document["workspaces"].(map[string]any)[workspacestate.GlobalWorkspaceID] = map[string]any{
41 "id": workspacestate.GlobalWorkspaceID, "root": root,
42 "title": "Global", "visible": true, "sessionIds": []string{},
43 }
44 if body, err = json.Marshal(document); err != nil {
45 t.Fatal(err)
46 }
47 if err := os.WriteFile(path, append(body, '\n'), 0o600); err != nil {
48 t.Fatal(err)
49 }
50
51 if owner, err := app.ensureDesktopWorkspace(t.Context(), "global", ""); err != nil || owner != workspacestate.GlobalWorkspaceID {
52 t.Fatalf("repair resolved %q: %v", owner, err)
53 }
54 state, err := app.workspaceRegistry().Load(t.Context())
55 if err != nil {
56 t.Fatal(err)
57 }
58 if _, stranded := state.Workspaces[duplicate]; stranded || !slices.Contains(state.Workspaces[workspacestate.GlobalWorkspaceID].SessionIDs, ref.SessionID) {
59 t.Fatalf("session did not move to the surviving owner: %+v", state.Workspaces)
60 }
61 file := desktopTabsFile{Tabs: []desktopTabEntry{savedProjectTab("kept", ref.SessionID, "", root, duplicate)}, ActiveTab: "kept"}
62 got, _ := app.reconcileSavedTabs(t.Context(), file)
63 if len(got.Tabs) != 1 || got.Tabs[0].SessionID != ref.SessionID {
64 t.Fatalf("saved tab naming the absorbed workspace was dropped: %+v", got.Tabs)
65 }
66 }
67
68 // A registry written before physical workspace identity could hold both the
69 // Global folder and an ordinary project for that same directory. Every sidebar
70 // projection over it resolves an owner, so an unrepaired duplicate strands the
71 // Global folder and the project together.
72 func TestProjectTopicsRecoverFromDuplicateGlobalWorkspaceIdentity(t *testing.T) {
73 isolateDesktopUserDirs(t)
74 root, err := ensureGlobalWorkspaceRoot()
75 if err != nil {
76 t.Fatal(err)
77 }
78 duplicate := desktopWorkspaceID("project", root)
79 path := config.DesktopWorkspaceStatePath()
80 if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
81 t.Fatal(err)
82 }
83 body, err := json.Marshal(map[string]any{
84 "version": 3, "initialized": true, "sessionStates": map[string]any{},
85 "workspaceIds": []string{workspacestate.GlobalWorkspaceID, duplicate},
86 "workspaces": map[string]any{
87 workspacestate.GlobalWorkspaceID: map[string]any{
88 "id": workspacestate.GlobalWorkspaceID, "root": root,
89 "title": "Global", "visible": true, "sessionIds": []string{},
90 },
91 duplicate: map[string]any{
92 "id": duplicate, "root": root,
93 "title": "global-workspace", "visible": true, "sessionIds": []string{},
94 },
95 },
96 })
97 if err != nil {
98 t.Fatal(err)
99 }
100 if err := os.WriteFile(path, append(body, '\n'), 0o600); err != nil {
101 t.Fatal(err)
102 }
103 app := NewApp()
104 app.ctx = t.Context()
105 t.Cleanup(app.closeSessionServices)
106 for _, req := range []ProjectTopicPageRequest{
107 {Scope: "global", Limit: 50},
108 {Scope: "project", WorkspaceRoot: root, Limit: 50},
109 } {
110 if _, err := app.ListProjectTopics(req); err != nil {
111 t.Fatalf("list %s topics: %v", req.Scope, err)
112 }
113 }
114 state, err := app.workspaceRegistry().Load(t.Context())
115 if err != nil {
116 t.Fatal(err)
117 }
118 if _, stranded := state.Workspaces[duplicate]; stranded || len(state.Workspaces) != 1 {
119 t.Fatalf("duplicate directory owner survived: %+v", state.Workspaces)
120 }
121 global := state.Workspaces[workspacestate.GlobalWorkspaceID]
122 if global.Title != "Global" || !global.Visible {
123 t.Fatalf("global lost its presentation: %+v", global)
124 }
125 }
126
126 lines GO