返回 DeepSeek-Reasonix
session_archive_source_visibility_test.go
根目录 / desktop / session_archive_source_visibility_test.go
1 package main
2
3 import (
4 "path/filepath"
5 "slices"
6 "testing"
7 "time"
8
9 "reasonix/desktop/internal/workspacestate"
10 "reasonix/internal/agent"
11 "reasonix/internal/provider"
12 "reasonix/internal/session"
13 )
14
15 func TestMigratedSingleHeadArchiveDoesNotResurrectLegacyRow(t *testing.T) {
16 isolateDesktopUserDirs(t)
17 path, _, head := migrationSingleDAGFixture(t)
18 before, err := desktopSourceFingerprint(path)
19 if err != nil {
20 t.Fatal(err)
21 }
22 app := NewApp()
23 app.ctx = t.Context()
24 pinDesktopSessionRoot(t, app)
25 installNoopRuntimeEvents(app)
26 t.Cleanup(app.closeSessionServices)
27 if err := app.migrateDesktopSessionsV5(t.Context()); err != nil {
28 t.Fatal(err)
29 }
30 installSessionCatalogForTest(t, app, filepath.Dir(path), "global", "")
31 state, err := app.workspaceRegistry().Load(t.Context())
32 if err != nil {
33 t.Fatal(err)
34 }
35 mapping, ok := state.SourceMappings[desktopSourceKey(path, head)]
36 if !ok {
37 t.Fatal("upgrade did not record the historical head")
38 }
39 ref := session.SessionRef{HostID: localDesktopHostID, SessionID: mapping.SessionID}
40 if aliases := sourceAliases(state, mapping.WorkspaceID, mapping.SessionID); !slices.Contains(aliases, "path\x00"+path) {
41 t.Fatalf("canonical identity is missing its displayed path alias: %q", aliases)
42 }
43 // Metadata-only pages identify an ordinary row by its path source key,
44 // whereas the migration receipt records the selected DAG head.
45 lazyAlias := "source\x00local\x00" + desktopSourceKey(path, "")
46 if aliases := sourceAliases(state, mapping.WorkspaceID, mapping.SessionID); !slices.Contains(aliases, lazyAlias) {
47 t.Fatalf("canonical identity cannot replace its lazy page row: %q", aliases)
48 }
49 assertLists := func(want int) {
50 t.Helper()
51 page, err := app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
52 if err != nil || len(page.Items) != want {
53 t.Errorf("sidebar = %+v, %v; want %d rows", page.Items, err, want)
54 }
55 if rows := app.listSessionsFromDir(filepath.Dir(path), ""); len(rows) != want {
56 t.Errorf("history = %+v; want %d rows", rows, want)
57 }
58 }
59 assertLists(1)
60 if result, err := app.ArchiveSessionTarget(SessionSelector{SessionPath: path}); err != nil || !result.Committed {
61 t.Fatalf("archive historical path = %+v, %v", result, err)
62 } else if !slices.Contains(result.IdentityAliases, lazyAlias) {
63 t.Fatalf("archive receipt cannot fence its lazy page row: %+v", result)
64 }
65 assertLists(0)
66 root := app.desktopSessions.root
67 app.stopSessionCatalog(time.Second)
68 app.closeSessionServices()
69 app = NewApp()
70 app.ctx = t.Context()
71 app.desktopSessions.root = root
72 installNoopRuntimeEvents(app)
73 t.Cleanup(app.closeSessionServices)
74 if err := app.migrateDesktopSessionsV5(t.Context()); err != nil {
75 t.Fatal(err)
76 }
77 installSessionCatalogForTest(t, app, filepath.Dir(path), "global", "")
78 assertLists(0)
79 if result, err := app.RestoreSessionTarget(SessionSelector{Ref: &ref}); err != nil || !result.Committed {
80 t.Fatalf("restore = %+v, %v", result, err)
81 }
82 assertLists(1)
83 if after, err := desktopSourceFingerprint(path); err != nil || after != before {
84 t.Fatalf("lifecycle changed retained history: %v", err)
85 }
86 }
87
88 func TestArchivedHistoricalHeadDoesNotHideUnadoptedSibling(t *testing.T) {
89 isolateDesktopUserDirs(t)
90 path, legacy, _ := migrationSingleDAGFixture(t)
91 head, err := legacy.ForkHead(path, legacy.Snapshot()[1].ID, agent.HeadKindFork, "sibling")
92 if err != nil {
93 t.Fatal(err)
94 }
95 legacy.Add(provider.Message{Role: provider.RoleAssistant, Content: "sibling answer"})
96 if err := legacy.Save(path); err != nil {
97 t.Fatal(err)
98 }
99 before, err := desktopSourceFingerprint(path)
100 if err != nil {
101 t.Fatal(err)
102 }
103 app := NewApp()
104 app.ctx = t.Context()
105 pinDesktopSessionRoot(t, app)
106 installNoopRuntimeEvents(app)
107 t.Cleanup(app.closeSessionServices)
108 installSessionCatalogForTest(t, app, filepath.Dir(path), "global", "")
109 selector := SessionSelector{Source: &SessionSourceRef{HostID: localDesktopHostID, Path: path, HeadID: head}}
110 if result, err := app.ArchiveSessionTarget(selector); err != nil || !result.Committed {
111 t.Fatalf("archive one head = %+v, %v", result, err)
112 }
113 page, err := app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
114 if err != nil || len(page.Items) != 1 || page.Items[0].Source == nil || page.Items[0].Source.HeadID == head {
115 t.Fatalf("unadopted sidebar sibling = %+v, %v", page.Items, err)
116 }
117 rows := app.listSessionsFromDir(filepath.Dir(path), "")
118 if len(rows) != 1 || rows[0].Source == nil || rows[0].Source.HeadID == head {
119 t.Fatalf("unadopted history sibling = %+v", rows)
120 }
121 if result, err := app.RestoreSessionTarget(selector); err != nil || !result.Committed {
122 t.Fatalf("restore one head = %+v, %v", result, err)
123 }
124 page, err = app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
125 if err != nil || len(page.Items) != 2 {
126 t.Fatalf("restored heads = %+v, %v", page.Items, err)
127 }
128 if result, err := app.ArchiveSessionTarget(selector); err != nil || !result.Committed {
129 t.Fatalf("rearchive one head = %+v, %v", result, err)
130 }
131 state, err := app.workspaceRegistry().Load(t.Context())
132 if err != nil {
133 t.Fatal(err)
134 }
135 mapping, found, err := state.ResolveSource(desktopSourceKey(path, head))
136 if err != nil || !found {
137 t.Fatalf("adopted head missing: %v", err)
138 }
139 ref := session.SessionRef{HostID: localDesktopHostID, SessionID: mapping.SessionID}
140 aliases := sourceAliases(state, mapping.WorkspaceID, mapping.SessionID)
141 if slices.Contains(aliases, "path\x00"+path) || slices.Contains(aliases, "source\x00local\x00"+desktopSourceKey(path, "")) {
142 t.Fatalf("one head claimed the independent sibling's path identity: %q", aliases)
143 }
144 if err := app.PurgeCanonicalSession(ref); err != nil {
145 t.Fatal(err)
146 }
147 if after, err := desktopSourceFingerprint(path); err != nil || after != before {
148 t.Fatalf("purge damaged independent legacy head: %v", err)
149 }
150 page, err = app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
151 if err != nil || len(page.Items) != 1 || page.Items[0].Source == nil || page.Items[0].Source.HeadID == head {
152 t.Fatalf("purge hid sibling or revived deleted head: %+v %v", page.Items, err)
153 }
154 state, err = app.workspaceRegistry().Load(t.Context())
155 if err != nil || state.SessionStates[ref.SessionID].Lifecycle != workspacestate.Deleted {
156 t.Fatalf("head tombstone lost: %v", err)
157 }
158 }
159
159 lines GO