返回 DeepSeek-Reasonix
legacy_draft_retirement_test.go
根目录 / desktop / legacy_draft_retirement_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "reasonix/desktop/internal/workspacestate"
9 "reasonix/internal/config"
10 )
11
12 func corruptRetiredDraftDatabase(t *testing.T) {
13 t.Helper()
14 path := config.DesktopDraftStatePath()
15 content := []byte("unreadable historical drafts must not block current sessions")
16 if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
17 t.Fatal(err)
18 }
19 if err := os.WriteFile(path, content, 0600); err != nil {
20 t.Fatal(err)
21 }
22 t.Cleanup(func() {
23 got, err := os.ReadFile(path)
24 if err != nil || string(got) != string(content) {
25 t.Fatalf("historical draft database changed: %q %v", got, err)
26 }
27 })
28 }
29
30 func TestManualCreationIgnoresRetiredDraftDatabase(t *testing.T) {
31 app := newManualSessionTestApp(t)
32 corruptRetiredDraftDatabase(t)
33 const operationID = "manual-with-retired-drafts"
34 if _, err := app.BeginManualSessionCreation(ManualSessionCreationRequest{OperationID: operationID, Scope: "global"}); err != nil {
35 t.Fatal(err)
36 }
37 app.manualCreationTasks.Wait()
38 view, err := app.GetManualSessionCreation(operationID)
39 if err != nil || view.Phase != "ready" {
40 t.Fatalf("formal creation depended on retired drafts: %+v %v", view, err)
41 }
42 }
43
44 func TestReconcileSavedTabsIgnoresRetiredDraftDatabase(t *testing.T) {
45 app := newSavedTabReconcileTestApp(t)
46 finishSavedTabMigration(app)
47 corruptRetiredDraftDatabase(t)
48 root := t.TempDir()
49 ref, workspaceID := createLegacyCleanupSession(t, app, root, "formal-history", true)
50 file := desktopTabsFile{Tabs: []desktopTabEntry{savedProjectTab("formal", ref.SessionID, "draft-op-previous", root, workspaceID)}, ActiveTab: "formal"}
51 got, changed := app.reconcileSavedTabs(t.Context(), file)
52 if !changed || len(got.Tabs) != 1 || got.Tabs[0].SessionID != ref.SessionID || got.Tabs[0].CreateOperationID != "" || got.Tabs[0].restoreBlocked {
53 t.Fatalf("formal session was not restored independently of retired draft: %+v", got)
54 }
55 if _, err := app.desktopSessionService("").Query().Snapshot(t.Context(), ref); err != nil {
56 t.Fatalf("formal history lost: %v", err)
57 }
58 }
59
60 func TestReconcileSavedTabsDropsRetiredDraftPendingCreate(t *testing.T) {
61 app := newSavedTabReconcileTestApp(t)
62 finishSavedTabMigration(app)
63 root := t.TempDir()
64 workspaceID, err := app.ensureDesktopWorkspace(t.Context(), "project", root)
65 if err != nil {
66 t.Fatal(err)
67 }
68 const operationID = "draft-op-unfinished"
69 if err := app.workspaceRegistry().BeginCreate(t.Context(), workspacestate.PendingCreate{OperationID: operationID, WorkspaceID: workspaceID, SessionID: "never-created"}); err != nil {
70 t.Fatal(err)
71 }
72 file := desktopTabsFile{Tabs: []desktopTabEntry{savedProjectTab("retired", "", operationID, root, workspaceID)}, ActiveTab: "retired"}
73 got, changed := app.reconcileSavedTabs(t.Context(), file)
74 if !changed || len(got.Tabs) != 0 {
75 t.Fatalf("unfinished draft became a recovery prompt: %+v", got)
76 }
77 }
78
79 func TestRetiredDraftsStayRetiredWhenTabsWriteFails(t *testing.T) {
80 app := newSavedTabReconcileTestApp(t)
81 finishSavedTabMigration(app)
82 corruptRetiredDraftDatabase(t)
83 root := t.TempDir()
84 ref, workspaceID := createLegacyCleanupSession(t, app, root, "formal-write-failure", true)
85 file := desktopTabsFile{Tabs: []desktopTabEntry{
86 savedProjectTab("formal", ref.SessionID, "draft-op-completed", root, workspaceID),
87 savedProjectTab("abandoned", "never-created", "draft-op-abandoned", root, workspaceID),
88 }, ActiveTab: "abandoned", TabOrder: []string{"abandoned", "formal"}}
89 // A directory at the atomic write's temporary path deterministically fails
90 // persistence, without relying on OS-specific permission behavior.
91 if err := os.MkdirAll(filepath.Join(desktopConfigDir(), tabsFileName+".tmp"), 0700); err != nil {
92 t.Fatal(err)
93 }
94 got, _, current := app.reconcileTabsBeforeRestore(t.Context(), file, app.tabsSnapshotVersion())
95 if !current || len(got.Tabs) != 1 || got.Tabs[0].ID != "formal" || got.Tabs[0].SessionID != ref.SessionID || got.Tabs[0].CreateOperationID != "" {
96 t.Fatalf("failed tab write revived retired draft ownership: %+v", got)
97 }
98 if got.ActiveTab != "formal" || len(got.TabOrder) != 1 || got.TabOrder[0] != "formal" {
99 t.Fatalf("failed tab write retained abandoned selection: %+v", got)
100 }
101 }
102
103 func TestRetiredDraftIdentityRepairWriteFailureStaysBlocked(t *testing.T) {
104 app := newSavedTabReconcileTestApp(t)
105 finishSavedTabMigration(app)
106 corruptRetiredDraftDatabase(t)
107 root := t.TempDir()
108 ref, workspaceID := createLegacyCleanupSession(t, app, root, "formal-route-repair", true)
109 entry := savedProjectTab("formal", "", "draft-op-completed", root, workspaceID)
110 entry.SessionPath = sessionRoute(ref.SessionID)
111 file := desktopTabsFile{Tabs: []desktopTabEntry{entry}, ActiveTab: entry.ID}
112 if err := os.MkdirAll(filepath.Join(desktopConfigDir(), tabsFileName+".tmp"), 0700); err != nil {
113 t.Fatal(err)
114 }
115 got, _, current := app.reconcileTabsBeforeRestore(t.Context(), file, app.tabsSnapshotVersion())
116 if !current || len(got.Tabs) != 1 || got.Tabs[0].CreateOperationID != "" || !got.Tabs[0].restoreBlocked {
117 t.Fatalf("unpersisted retirement repair was not blocked: %+v", got)
118 }
119 if prepareRestoredTabIdentity(&WorkspaceTab{}, got.Tabs[0]) {
120 t.Fatal("unpersisted identity repair started a runtime")
121 }
122 }
123
123 lines GO