返回 DeepSeek-Reasonix
new_session_meta_test.go
根目录 / desktop / new_session_meta_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "reasonix/internal/agent"
10 )
11
12 func TestPinNewEmptySessionBranchMetaStoresBinding(t *testing.T) {
13 isolateDesktopUserDirs(t)
14
15 projectRoot := t.TempDir()
16 tests := []struct {
17 name string
18 scope string
19 workspaceRoot string
20 topicID string
21 topicTitle string
22 wantRoot string
23 }{
24 {
25 name: "project",
26 scope: "project",
27 workspaceRoot: projectRoot,
28 topicID: "topic_project",
29 topicTitle: "Project topic",
30 wantRoot: normalizeProjectRoot(projectRoot),
31 },
32 {
33 name: "global",
34 scope: "global",
35 workspaceRoot: globalWorkspaceRoot(),
36 topicID: "topic_global",
37 topicTitle: "Global topic",
38 },
39 }
40
41 for _, tt := range tests {
42 t.Run(tt.name, func(t *testing.T) {
43 path, err := createEmptySessionFile(t.TempDir(), "test-model")
44 if err != nil {
45 t.Fatalf("createEmptySessionFile: %v", err)
46 }
47 err = pinNewEmptySessionBranchMeta(
48 path,
49 tt.scope,
50 tt.workspaceRoot,
51 tt.topicID,
52 tt.topicTitle,
53 )
54 if err != nil {
55 t.Fatalf("pinNewEmptySessionBranchMeta: %v", err)
56 }
57 meta, ok, err := agent.LoadBranchMeta(path)
58 if err != nil || !ok {
59 t.Fatalf("LoadBranchMeta(%q): ok=%v err=%v", path, ok, err)
60 }
61 if meta.Scope != tt.scope ||
62 meta.WorkspaceRoot != tt.wantRoot ||
63 meta.TopicID != tt.topicID ||
64 meta.TopicTitle != tt.topicTitle {
65 t.Fatalf(
66 "branch meta binding = scope %q root %q topic %q title %q, want %q %q %q %q",
67 meta.Scope,
68 meta.WorkspaceRoot,
69 meta.TopicID,
70 meta.TopicTitle,
71 tt.scope,
72 tt.wantRoot,
73 tt.topicID,
74 tt.topicTitle,
75 )
76 }
77 })
78 }
79 }
80
81 func TestPinnedSessionMetaKeepsProjectBindingOutsideKnownDirectories(t *testing.T) {
82 isolateDesktopUserDirs(t)
83
84 projectRoot := t.TempDir()
85 topicID := "topic_unknown_dir"
86 topicTitle := "Unknown directory"
87 sessionPath, err := createEmptySessionFile(t.TempDir(), "test-model")
88 if err != nil {
89 t.Fatalf("createEmptySessionFile: %v", err)
90 }
91 if err := pinNewEmptySessionBranchMeta(
92 sessionPath,
93 "project",
94 projectRoot,
95 topicID,
96 topicTitle,
97 ); err != nil {
98 t.Fatalf("pinNewEmptySessionBranchMeta: %v", err)
99 }
100
101 app := NewApp()
102 tab := &WorkspaceTab{
103 ID: "tab_unknown_dir",
104 Scope: "project",
105 WorkspaceRoot: projectRoot,
106 TopicID: topicID,
107 TopicTitle: topicTitle,
108 SessionPath: sessionPath,
109 disabledMCP: map[string]ServerView{},
110 }
111 app.tabs[tab.ID] = tab
112 app.tabOrder = []string{tab.ID}
113 app.activeTabID = tab.ID
114
115 resolved, ok := app.reconcileTabWithPinnedSessionMeta(tab)
116 if !ok || !sameDesktopPath(resolved, sessionPath) {
117 t.Fatalf("reconcile binding = %q, %v, want %q, true", resolved, ok, sessionPath)
118 }
119 if tab.Scope != "project" || !sameProjectRoot(tab.WorkspaceRoot, projectRoot) {
120 t.Fatalf("reconciled tab binding = %q/%q, want project/%q", tab.Scope, tab.WorkspaceRoot, projectRoot)
121 }
122 }
123
124 func TestPinSessionBranchMetaReturnsCorruptSidecarError(t *testing.T) {
125 isolateDesktopUserDirs(t)
126
127 sessionPath := filepath.Join(t.TempDir(), "corrupt.jsonl")
128 if err := os.WriteFile(sessionPath, nil, 0o644); err != nil {
129 t.Fatalf("write session: %v", err)
130 }
131 metaPath := agent.BranchMetaPath(sessionPath)
132 const corruptMeta = `{"scope":`
133 if err := os.WriteFile(metaPath, []byte(corruptMeta), 0o644); err != nil {
134 t.Fatalf("write corrupt branch meta: %v", err)
135 }
136
137 if err := pinSessionBranchMeta(sessionPath, "project", t.TempDir(), "topic", "Topic"); err == nil {
138 t.Fatal("pinSessionBranchMeta error = nil, want corrupt sidecar error")
139 }
140 got, err := os.ReadFile(metaPath)
141 if err != nil {
142 t.Fatalf("read corrupt branch meta: %v", err)
143 }
144 if string(got) != corruptMeta {
145 t.Fatalf("corrupt branch meta was overwritten: %q", got)
146 }
147 }
148
149 func TestPinNewEmptySessionBranchMetaCleansUpRejectedBinding(t *testing.T) {
150 isolateDesktopUserDirs(t)
151
152 dir := t.TempDir()
153 path, err := createEmptySessionFile(dir, "test-model")
154 if err != nil {
155 t.Fatalf("createEmptySessionFile: %v", err)
156 }
157 if err := pinNewEmptySessionBranchMeta(
158 path,
159 "project",
160 "",
161 "topic",
162 "Topic",
163 ); err == nil {
164 t.Fatal("pinNewEmptySessionBranchMeta error = nil, want missing project root error")
165 }
166
167 entries, err := os.ReadDir(dir)
168 if err != nil {
169 t.Fatalf("read session dir: %v", err)
170 }
171 for _, entry := range entries {
172 name := entry.Name()
173 if strings.HasSuffix(name, ".jsonl") || strings.HasSuffix(name, ".meta") {
174 t.Fatalf("rejected binding left session artifact %q", name)
175 }
176 }
177 }
178
179 func TestPinSessionBranchMetaPreservesExistingFields(t *testing.T) {
180 isolateDesktopUserDirs(t)
181
182 sessionPath := filepath.Join(t.TempDir(), "existing.jsonl")
183 if err := os.WriteFile(sessionPath, nil, 0o644); err != nil {
184 t.Fatalf("write session: %v", err)
185 }
186 existing := agent.BranchMeta{
187 CustomTitle: "Keep title",
188 Model: "provider/model",
189 Revision: 7,
190 ContentDigest: "digest",
191 WriterID: "writer",
192 SchemaVersion: agent.BranchMetaCountsVersion,
193 Turns: 3,
194 Preview: "Keep preview",
195 RecoveryReason: "keep recovery",
196 }
197 if err := agent.SaveBranchMetaPreserveUpdated(sessionPath, existing); err != nil {
198 t.Fatalf("save existing branch meta: %v", err)
199 }
200
201 projectRoot := t.TempDir()
202 if err := pinSessionBranchMeta(sessionPath, "project", projectRoot, "topic", "Topic"); err != nil {
203 t.Fatalf("pinSessionBranchMeta: %v", err)
204 }
205 got, ok, err := agent.LoadBranchMeta(sessionPath)
206 if err != nil || !ok {
207 t.Fatalf("LoadBranchMeta: ok=%v err=%v", ok, err)
208 }
209 if got.CustomTitle != existing.CustomTitle ||
210 got.Model != existing.Model ||
211 got.Revision != existing.Revision ||
212 got.ContentDigest != existing.ContentDigest ||
213 got.WriterID != existing.WriterID ||
214 got.SchemaVersion != existing.SchemaVersion ||
215 got.Turns != existing.Turns ||
216 got.Preview != existing.Preview ||
217 got.RecoveryReason != existing.RecoveryReason {
218 t.Fatalf("pinning dropped existing fields: got %+v, existing %+v", got, existing)
219 }
220 }
221
221 lines GO