返回 DeepSeek-Reasonix
historical_reconciliation_test.go
根目录 / desktop / historical_reconciliation_test.go
1 package main
2
3 import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "reflect"
8 "testing"
9
10 "reasonix/desktop/internal/workspacestate"
11 "reasonix/internal/session"
12 )
13
14 func rewriteHistoricalRegistry(t *testing.T, app *App, change func(*workspacestate.State)) {
15 t.Helper()
16 state, err := app.workspaceRegistry().Load(t.Context())
17 if err != nil {
18 t.Fatal(err)
19 }
20 change(&state)
21 body, err := json.Marshal(state)
22 if err != nil {
23 t.Fatal(err)
24 }
25 if err := os.WriteFile(app.workspaceRegistry().Path(), body, 0600); err != nil {
26 t.Fatal(err)
27 }
28 }
29
30 func TestHistoricalDiscoveryRepairsAdoptionAndKeepsOneLifecycle(t *testing.T) {
31 for _, damage := range []string{"mapping", "aliases", "target", "deleted"} {
32 t.Run(damage, func(t *testing.T) {
33 app, selector, _ := historicalArchiveFixture(t)
34 result, err := app.ImportHistoricalSession(selector.Source.SourceKey)
35 if err != nil {
36 t.Fatal(err)
37 }
38 if damage == "mapping" || damage == "aliases" {
39 appendMigrationTestMessage(t, app.desktopSessionService(""), result.Session, "continued new conversation")
40 }
41 title, pinned := "My retained title", true
42 if err := app.workspaceRegistry().UpdatePresentation(t.Context(), []string{result.Session.SessionID}, &title, &pinned); err != nil {
43 t.Fatal(err)
44 }
45 before, err := app.desktopSessionService("").Query().Snapshot(t.Context(), result.Session)
46 if err != nil {
47 t.Fatal(err)
48 }
49 if damage == "deleted" {
50 if err := app.ArchiveCanonicalSession(result.Session); err != nil {
51 t.Fatal(err)
52 }
53 // Simulate a previous build that retained its source after purge.
54 state, err := app.workspaceRegistry().Load(t.Context())
55 if err != nil {
56 t.Fatal(err)
57 }
58 if err := app.workspaceRegistry().BeginPurge(t.Context(), result.Session.SessionID, state.Generation); err != nil {
59 t.Fatal(err)
60 }
61 }
62 app.stopHistoricalImports()
63 app.closeSessionServices()
64 if damage == "target" {
65 if err := os.Rename(filepath.Join(app.desktopSessions.root, result.Session.SessionID), filepath.Join(t.TempDir(), "removed-target")); err != nil {
66 t.Fatal(err)
67 }
68 } else {
69 rewriteHistoricalRegistry(t, app, func(state *workspacestate.State) {
70 mapping := state.SourceMappings[selector.Source.SourceKey]
71 state.SourceMappings = map[string]workspacestate.SourceMapping{}
72 if damage == "aliases" {
73 mapping.SourceKey = "previous-key"
74 state.SourceMappings[mapping.SourceKey] = mapping
75 mapping.SourceKey, mapping.SessionID = "retired-key", "retired-copy"
76 state.SourceMappings[mapping.SourceKey] = mapping
77 retired := state.SessionStates[result.Session.SessionID]
78 retired.Lifecycle = workspacestate.Deleted
79 state.SessionStates[mapping.SessionID] = retired
80 }
81 for id, op := range state.PendingOperations {
82 if op.Kind == "import" {
83 delete(state.PendingOperations, id)
84 }
85 }
86 })
87 }
88 app = newHistoricalLifecycleApp(t)
89 if _, err := app.ListHistoricalSessions(); err != nil {
90 t.Fatal(err)
91 }
92 state, err := app.workspaceRegistry().Load(t.Context())
93 if err != nil {
94 t.Fatal(err)
95 }
96 mapping, found, err := state.ResolveSource(selector.Source.SourceKey)
97 if err != nil || !found || mapping.SessionID != result.Session.SessionID {
98 t.Fatalf("adoption not repaired: %+v %v %v", mapping, found, err)
99 }
100 if p := state.Presentation[result.Session.SessionID]; p.Title != title || !p.Pinned {
101 t.Fatalf("presentation overwritten: %+v", p)
102 }
103 page, err := app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
104 if err != nil {
105 t.Fatal(err)
106 }
107 if damage == "deleted" {
108 if len(page.Items) != 0 {
109 t.Fatalf("deleted source resurrected: %+v", page.Items)
110 }
111 return
112 }
113 if len(page.Items) != 1 || page.Items[0].Session == nil || *page.Items[0].Session != result.Session {
114 t.Fatalf("expected only b: %+v", page.Items)
115 }
116 after, err := app.desktopSessionService("").Query().Snapshot(t.Context(), result.Session)
117 if err != nil {
118 t.Fatal(err)
119 }
120 if (damage == "mapping" || damage == "aliases") && !reflect.DeepEqual(before, after) {
121 t.Fatal("receipt recovery overwrote continued target content")
122 }
123 if err := app.ArchiveCanonicalSession(result.Session); err != nil {
124 t.Fatal(err)
125 }
126 if err := app.PurgeCanonicalSession(result.Session); err != nil {
127 t.Fatal(err)
128 }
129 app.stopHistoricalImports()
130 app.closeSessionServices()
131 app = newHistoricalLifecycleApp(t)
132 if _, err := app.ListHistoricalSessions(); err != nil {
133 t.Fatal(err)
134 }
135 page, err = app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
136 if err != nil || len(page.Items) != 0 {
137 t.Fatalf("purged source returned: %+v %v", page.Items, err)
138 }
139 if _, err := os.Stat(selector.Source.Path); !os.IsNotExist(err) {
140 t.Fatalf("exclusive source was not purged: %v", err)
141 }
142 })
143 }
144 }
145
146 func TestHistoricalDiscoveryHidesUnrecoverableSource(t *testing.T) {
147 app, selector, _ := historicalArchiveFixture(t)
148 if err := os.WriteFile(filepath.Join(selector.Source.Path, "manifest.json"), []byte("broken manifest"), 0600); err != nil {
149 t.Fatal(err)
150 }
151 if _, err := app.ListHistoricalSessions(); err != nil {
152 t.Fatal(err)
153 }
154 page, err := app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
155 if err != nil || len(page.Items) != 0 {
156 t.Fatalf("unrecoverable source shown: %+v %v", page.Items, err)
157 }
158 if _, err := os.Stat(selector.Source.Path); err != nil {
159 t.Fatal("hiding destroyed the source", err)
160 }
161 }
162
163 func TestCanonicalSidebarOmitsMissingMember(t *testing.T) {
164 isolateDesktopUserDirs(t)
165 app := newHistoricalLifecycleApp(t)
166 workspace, err := app.ensureDesktopWorkspace(t.Context(), "global", "")
167 if err != nil {
168 t.Fatal(err)
169 }
170 if err := app.workspaceRegistry().AttachSession(t.Context(), "", workspace, "missing", ""); err != nil {
171 t.Fatal(err)
172 }
173 page, err := app.ListProjectTopics(ProjectTopicPageRequest{Scope: "global", Limit: 50})
174 if err != nil || len(page.Items) != 0 {
175 t.Fatalf("missing member shown: %+v %v", page.Items, err)
176 }
177 if _, err := app.desktopSessionService("").Query().Stat(t.Context(), session.SessionRef{HostID: localDesktopHostID, SessionID: "missing"}); err == nil {
178 t.Fatal("created an empty replacement")
179 }
180 }
181
181 lines GO