返回 DeepSeek-Reasonix
session_identity_guard_test.go
根目录 / desktop / session_identity_guard_test.go
1 package main
2
3 import (
4 "errors"
5 "os"
6 "path/filepath"
7 "reflect"
8 "testing"
9
10 "reasonix/desktop/internal/workspacestate"
11 "reasonix/internal/store"
12 )
13
14 func TestRestoreBlockedIdentityPreservesPinsWithoutSidecars(t *testing.T) {
15 app := newSavedTabReconcileTestApp(t)
16 finishSavedTabMigration(app)
17 path := filepath.Join(t.TempDir(), "session-id:unverified")
18 entry := desktopTabEntry{ID: "blocked", Scope: "global", SessionPath: path, Goal: "saved goal", PinnedFiles: []string{"README.md"}}
19 file := desktopTabsFile{Tabs: []desktopTabEntry{entry}, ActiveTab: entry.ID, TabOrder: []string{entry.ID}}
20 if err := os.MkdirAll(desktopConfigDir(), 0o700); err != nil {
21 t.Fatal(err)
22 }
23 if err := os.WriteFile(filepath.Join(desktopConfigDir(), tabsFileName), mustMarshalJSON(t, file), 0o600); err != nil {
24 t.Fatal(err)
25 }
26 app.tabsRestored = make(chan struct{})
27 app.restoreOrBuildTabs()
28 tab := app.tabs[entry.ID]
29 if tab == nil || tab.StartupErr == "" || tab.Ctrl != nil {
30 t.Fatalf("blocked restore published an unsafe runtime: %+v", tab)
31 }
32 if got := persistedDesktopTabEntry(tab); got.SessionPath != path || got.Goal != entry.Goal || !reflect.DeepEqual(got.PinnedFiles, entry.PinnedFiles) {
33 t.Fatalf("blocked restore lost saved input: %+v", got)
34 }
35 artifacts, err := os.ReadDir(filepath.Dir(path))
36 if err != nil || len(artifacts) != 0 {
37 t.Fatalf("blocked restore created artifacts: %v, %v", artifacts, err)
38 }
39 }
40
41 func TestRestorePinnedContextRejectsUnverifiedLocators(t *testing.T) {
42 for _, path := range []string{"session-id:valid", "session-id:bad/id", filepath.Join(t.TempDir(), "session-id:unverified")} {
43 t.Run(path, func(t *testing.T) {
44 tab := &WorkspaceTab{SessionPath: path}
45 restoreTabPinnedContext(tab, []string{"legacy.md"})
46 if got := tab.pendingLegacyPinnedFilesForPersistence(); !reflect.DeepEqual(got, []string{"legacy.md"}) {
47 t.Fatalf("unverified input was consumed: %v", got)
48 }
49 if _, err := os.Stat(store.SessionPinnedContext(path)); err == nil {
50 t.Fatal("unverified locator created pinned sidecar")
51 }
52 })
53 }
54 }
55
56 func TestReconcileSavedTabsRejectsPendingOperationIdentityConflicts(t *testing.T) {
57 for _, pseudo := range []bool{false, true} {
58 for _, mapping := range []bool{false, true} {
59 name := "exact"
60 if pseudo {
61 name = "pseudo"
62 }
63 if mapping {
64 name += "-mapping"
65 }
66 t.Run(name, func(t *testing.T) {
67 app := newSavedTabReconcileTestApp(t)
68 root := t.TempDir()
69 ref, workspaceID := createLegacyCleanupSession(t, app, root, "session-A", true)
70 op := workspacestate.Operation{ID: "operation", Kind: "import", Lifecycle: workspacestate.Active, WorkspaceID: workspaceID, SessionIDs: []string{"session-B"}}
71 if mapping {
72 op.SessionIDs = []string{ref.SessionID}
73 op.Mapping = &workspacestate.SourceMapping{SessionID: "session-B"}
74 }
75 if err := app.workspaceRegistry().BeginOperation(t.Context(), op); err != nil {
76 t.Fatal(err)
77 }
78 before, err := app.workspaceRegistry().Load(t.Context())
79 if err != nil {
80 t.Fatal(err)
81 }
82 finishSavedTabMigration(app)
83 entry := savedProjectTab("conflict", "", op.ID, root, workspaceID)
84 entry.SessionPath = sessionRoute(ref.SessionID)
85 if pseudo {
86 entry.SessionPath = `C:\old\` + entry.SessionPath
87 }
88 got, changed := app.reconcileSavedTabs(t.Context(), desktopTabsFile{Tabs: []desktopTabEntry{entry}})
89 if changed || len(got.Tabs) != 1 || !got.Tabs[0].restoreBlocked {
90 t.Fatalf("pending conflict was accepted: changed=%v tabs=%+v", changed, got.Tabs)
91 }
92 if got.Tabs[0].SessionID != entry.SessionID || got.Tabs[0].SessionPath != entry.SessionPath || got.Tabs[0].CreateOperationID != op.ID {
93 t.Fatalf("pending conflict changed saved identity: %+v", got.Tabs[0])
94 }
95 after, err := app.workspaceRegistry().Load(t.Context())
96 if err != nil || !reflect.DeepEqual(before, after) {
97 t.Fatalf("conflict mutated registry: %v", err)
98 }
99 })
100 }
101 }
102 }
103
104 func TestReconcileSavedTabsAcceptsMatchingPendingOperationIdentity(t *testing.T) {
105 app := newSavedTabReconcileTestApp(t)
106 root := t.TempDir()
107 ref, workspaceID := createLegacyCleanupSession(t, app, root, "session-A", true)
108 op := workspacestate.Operation{ID: "operation", Kind: "import", Lifecycle: workspacestate.Active, WorkspaceID: workspaceID, SessionIDs: []string{ref.SessionID}}
109 if err := app.workspaceRegistry().BeginOperation(t.Context(), op); err != nil {
110 t.Fatal(err)
111 }
112 finishSavedTabMigration(app)
113 entry := savedProjectTab("matching", "", op.ID, root, workspaceID)
114 entry.SessionPath = sessionRoute(ref.SessionID)
115 got, changed := app.reconcileSavedTabs(t.Context(), desktopTabsFile{Tabs: []desktopTabEntry{entry}})
116 if !changed || len(got.Tabs) != 1 || got.Tabs[0].restoreBlocked || got.Tabs[0].SessionID != ref.SessionID || got.Tabs[0].SessionPath != "" {
117 t.Fatalf("matching pending identity was rejected: changed=%v tabs=%+v", changed, got.Tabs)
118 }
119 }
120
121 func TestHiddenTabPruneRejectsCanonicalIdentityConflict(t *testing.T) {
122 app := newSavedTabReconcileTestApp(t)
123 root := t.TempDir()
124 ref, workspaceID := createLegacyCleanupSession(t, app, root, "session-A", true)
125 before, err := app.workspaceRegistry().Load(t.Context())
126 if err != nil {
127 t.Fatal(err)
128 }
129 for _, path := range []string{"session-id:session-B", "session-id:bad/id", filepath.Join(t.TempDir(), "session-id:session-B"), filepath.Join(t.TempDir(), "legacy.jsonl")} {
130 t.Run(path, func(t *testing.T) {
131 tab := &WorkspaceTab{ID: "conflict", Scope: "project", WorkspaceRoot: root, SessionWorkspace: desktopTabWorkspace{ID: workspaceID}, SessionID: ref.SessionID, SessionPath: path}
132 app.tabs[tab.ID] = tab
133 for _, save := range []func() error{
134 func() error { return app.persistHiddenTabBeforePrune(tab.ID, tab) },
135 func() error { return app.saveTabSessionMeta(tab, sessionRoute(ref.SessionID)) },
136 } {
137 var identityErr *sessionLocatorError
138 if err := save(); !errors.As(err, &identityErr) {
139 t.Fatalf("conflicting identity accepted: %v", err)
140 }
141 }
142 if tab.SessionID != ref.SessionID || tab.SessionPath != path {
143 t.Fatal("failed persistence changed tab identity")
144 }
145 })
146 }
147 after, err := app.workspaceRegistry().Load(t.Context())
148 if err != nil || !reflect.DeepEqual(before, after) {
149 t.Fatalf("failed prune mutated registry: %v", err)
150 }
151 }
152
152 lines GO