返回 DeepSeek-Reasonix
manual_session_upgrade_test.go
根目录 / desktop / manual_session_upgrade_test.go
1 package main
2
3 import (
4 "bytes"
5 "database/sql"
6 "encoding/json"
7 "os"
8 "path/filepath"
9 "reasonix/desktop/internal/draftstate"
10 "reasonix/desktop/internal/workspacestate"
11 "testing"
12 )
13
14 func copyRollbackFixture(t *testing.T, version, name string) string {
15 t.Helper()
16 data, err := os.ReadFile(filepath.Join("testdata", "manual-session-upgrade", version, name))
17 if err != nil {
18 t.Fatal(err)
19 }
20 destination := filepath.Join(t.TempDir(), name)
21 if err := os.WriteFile(destination, data, 0600); err != nil {
22 t.Fatal(err)
23 }
24 return destination
25 }
26
27 func TestTaggedWorkspaceFixturesSurviveManualCreationUpgrade(t *testing.T) {
28 for _, version := range []string{"1.38.9", "1.38.10", "1.38.11"} {
29 t.Run(version, func(t *testing.T) {
30 path := copyRollbackFixture(t, version, "workspace-state.json")
31 data, err := os.ReadFile(path)
32 if err != nil {
33 t.Fatal(err)
34 }
35 var original map[string]json.RawMessage
36 if err := json.Unmarshal(data, &original); err != nil {
37 t.Fatal(err)
38 }
39 original["futureRollbackFixture"] = json.RawMessage(`{"keep":true}`)
40 data, _ = json.Marshal(original)
41 if err := os.WriteFile(path, data, 0600); err != nil {
42 t.Fatal(err)
43 }
44 store := workspacestate.NewStore(path)
45 state, err := store.Load(t.Context())
46 if err != nil {
47 t.Fatal(err)
48 }
49 if len(state.Workspaces["global"].SessionIDs) != 1 || state.Workspaces["global"].SessionIDs[0] != "existing-session" {
50 t.Fatalf("history ownership lost: %+v", state)
51 }
52 if state.PendingCreates["reserved-session"].OperationID != "interrupted-create" {
53 t.Fatal("reservation lost")
54 }
55 if err := store.AttachSession(t.Context(), "interrupted-create", "global", "reserved-session", ""); err != nil {
56 t.Fatal(err)
57 }
58 after, err := os.ReadFile(path)
59 if err != nil {
60 t.Fatal(err)
61 }
62 if !bytes.Contains(after, []byte("futureRollbackFixture")) {
63 t.Fatal("future metadata lost on write")
64 }
65 reopened, err := workspacestate.NewStore(path).Load(t.Context())
66 if err != nil || reopened.Version != 3 || len(reopened.Workspaces["global"].SessionIDs) != 2 {
67 t.Fatalf("reopen=%+v %v", reopened, err)
68 }
69 })
70 }
71 }
72
73 func TestTaggedDraftFixturePreservesAllRecoveryIdentities(t *testing.T) {
74 for _, version := range []string{"main-v2-pr10469", "main-v2-pr10572", "1.38.11"} {
75 t.Run(version, func(t *testing.T) { verifyTaggedDraftRecovery(t, version) })
76 }
77 }
78
79 func verifyTaggedDraftRecovery(t *testing.T, version string) {
80 t.Helper()
81 isolateDesktopUserDirs(t)
82 path := copyRollbackFixture(t, version, "drafts.sqlite")
83 store := draftstate.New(path)
84 defer store.Close()
85 settingsOnly, err := store.Get(t.Context(), "draft-settings-only")
86 if err != nil || settingsOnly.Status != "active" || !bytes.Contains([]byte(settingsOnly.SettingsJSON), []byte("fixture/model")) {
87 t.Fatalf("settings-only draft lost: %+v %v", settingsOnly, err)
88 }
89 converted, err := store.Get(t.Context(), "draft-converted")
90 if err != nil || converted.Status != "converted" {
91 t.Fatalf("converted draft revived: %+v %v", converted, err)
92 }
93 db, err := sql.Open("sqlite", path)
94 if err != nil {
95 t.Fatal(err)
96 }
97 defer db.Close()
98 var conflicts int
99 if err := db.QueryRow(`SELECT COUNT(*) FROM conflicts WHERE draft_id='draft-editable' AND content_json LIKE '%losing historical writer%'`).Scan(&conflicts); err != nil || conflicts != 1 {
100 t.Fatalf("historical conflicts=%d %v", conflicts, err)
101 }
102 for _, phase := range []string{"editable", "reserved", "starting", "dispatching", "dispatching_shell", "dispatch_unknown", "accepted", "cancel_requested", "resume_required", "failed", "cancelled"} {
103 draft, err := store.Get(t.Context(), "draft-"+phase)
104 if err != nil {
105 t.Fatal(err)
106 }
107 if !bytes.Contains([]byte(draft.ContentJSON), []byte("unsent upgrade fixture")) {
108 t.Fatal("unsent content lost")
109 }
110 if phase == "editable" {
111 continue
112 }
113 operation, err := store.Operation(t.Context(), "operation-"+phase)
114 if err != nil {
115 t.Fatal(err)
116 }
117 if operation.SessionID != "session-"+phase || operation.TopicID != "topic-"+phase || operation.SubmissionID != "submit-"+phase || operation.Phase != phase {
118 t.Fatalf("identity/phase changed: %+v", operation)
119 }
120 app := NewApp()
121 settings, err := app.draftOperationSettings(operation)
122 if err != nil || settings.Model != "fixture/frozen" || settings.ToolApprovalMode != "read-only" {
123 t.Fatalf("frozen historical snapshot changed: %+v %v", settings, err)
124 }
125 if !bytes.Contains([]byte(operation.RequestJSON), []byte(`"futureSnapshot":{"preserve":true}`)) {
126 t.Fatal("opaque snapshot data lost")
127 }
128 }
129 }
130
130 lines GO