返回 DeepSeek-Reasonix
session_archive_last_tab_test.go
根目录 / desktop / session_archive_last_tab_test.go
1 package main
2
3 import (
4 "os"
5 "testing"
6
7 "reasonix/desktop/internal/workspacestate"
8 "reasonix/internal/config"
9 )
10
11 // Archiving the only visible session hands the surface to the draft landing.
12 // A replacement blank session would be registered as a real sidebar row that
13 // the user can archive again, so the workspace could never become empty.
14 func TestArchiveLastVisibleSessionLeavesNoReplacementBlankSession(t *testing.T) {
15 a, ref := lifecycleFixture(t)
16 a.tabs = map[string]*WorkspaceTab{
17 "only": {ID: "only", Scope: "global", WorkspaceRoot: globalWorkspaceRoot(), TopicID: "canonical-" + ref.SessionID,
18 SessionID: ref.SessionID, Ready: true, disabledMCP: map[string]ServerView{}},
19 }
20 a.tabOrder = []string{"only"}
21 a.activeTabID = "only"
22
23 archived, err := a.ArchiveSessionTarget(SessionSelector{Ref: &ref})
24 if err != nil || !archived.Committed {
25 t.Fatalf("ArchiveSessionTarget = %+v, %v", archived, err)
26 }
27 assertNoVisibleRuntime(t, a)
28 state, err := a.workspaceRegistry().Load(t.Context())
29 if err != nil {
30 t.Fatal(err)
31 }
32 if got := state.SessionStates[ref.SessionID].Lifecycle; got != workspacestate.Archived {
33 t.Fatalf("archived lifecycle = %q", got)
34 }
35 for id, sessionState := range state.SessionStates {
36 if sessionState.Lifecycle == workspacestate.Active {
37 t.Fatalf("archive registered replacement session %q", id)
38 }
39 }
40 page, err := a.ListWorkspaceSessions(workspacestate.GlobalWorkspaceID, "", "", 50, false)
41 if err != nil || len(page.Sessions) != 0 {
42 t.Fatalf("workspace still lists sessions after archiving its only one: %+v, %v", page.Sessions, err)
43 }
44 }
45
46 // The legacy topic archive shares the fallback mechanism with canonical
47 // archive, so it must leave the same empty surface.
48 func TestTrashLastTopicLeavesNoReplacementBlankSession(t *testing.T) {
49 isolateDesktopUserDirs(t)
50 projectRoot := t.TempDir()
51 topicID := "topic_trash_last"
52 if err := addProject(projectRoot, ""); err != nil {
53 t.Fatalf("add project: %v", err)
54 }
55 if err := setTopicTitle(projectRoot, topicID, "Trash last"); err != nil {
56 t.Fatalf("set topic title: %v", err)
57 }
58 dir := config.SessionDir()
59 if err := os.MkdirAll(dir, 0o755); err != nil {
60 t.Fatalf("mkdir sessions: %v", err)
61 }
62 sessionPath := writeTopicSession(t, dir, "trash-last.jsonl", topicID, "Trash last", projectRoot)
63 ctrl := controllerWithContent(t, sessionPath)
64 tab := &WorkspaceTab{ID: "only", Scope: "project", WorkspaceRoot: projectRoot, TopicID: topicID,
65 TopicTitle: "Trash last", SessionPath: sessionPath, Ctrl: ctrl, Ready: true, disabledMCP: map[string]ServerView{}}
66 app := NewApp()
67 app.ctx = t.Context()
68 installNoopRuntimeEvents(app)
69 t.Cleanup(app.closeSessionServices)
70 app.tabs = map[string]*WorkspaceTab{tab.ID: tab}
71 app.tabOrder = []string{tab.ID}
72 app.activeTabID = tab.ID
73
74 if err := app.TrashTopic(topicID); err != nil {
75 t.Fatalf("TrashTopic: %v", err)
76 }
77 assertNoVisibleRuntime(t, app)
78 }
79
80 func assertNoVisibleRuntime(t *testing.T, a *App) {
81 t.Helper()
82 a.mu.RLock()
83 defer a.mu.RUnlock()
84 if len(a.tabs) != 0 || len(a.tabOrder) != 0 || a.activeTabID != "" {
85 ids := make([]string, 0, len(a.tabs))
86 for id, tab := range a.tabs {
87 ids = append(ids, id+"/topic="+tab.TopicID+"/session="+tab.SessionID)
88 }
89 t.Fatalf("removing the last visible session opened a replacement runtime: tabs=%v order=%v active=%q", ids, a.tabOrder, a.activeTabID)
90 }
91 }
92
92 lines GO