返回 DeepSeek-Reasonix
project_tree_activity_ttl_test.go
根目录 / desktop / project_tree_activity_ttl_test.go
1 package main
2
3 import (
4 "os"
5 "testing"
6 "time"
7
8 "reasonix/internal/control"
9 "reasonix/internal/event"
10 )
11
12 func TestReapStaleTopicActivityStatus(t *testing.T) {
13 isolateDesktopUserDirs(t)
14 now := time.Now()
15 cases := []struct {
16 name string
17 status string
18 age time.Duration
19 wantStatus string
20 }{
21 {"fresh thinking kept", topicStatusThinking, time.Minute, topicStatusThinking},
22 {"fresh streaming kept", topicStatusStreaming, 5 * time.Minute, topicStatusStreaming},
23 {"stale thinking reaped", topicStatusThinking, topicActivityStatusTTL + time.Minute, ""},
24 {"stale streaming reaped", topicStatusStreaming, topicActivityStatusTTL + time.Minute, ""},
25 {"waiting confirmation never reaped", topicStatusWaitingConfirmation, topicActivityStatusTTL + time.Minute, topicStatusWaitingConfirmation},
26 {"error status never reaped", topicStatusError, topicActivityStatusTTL + time.Minute, topicStatusError},
27 {"zero timestamp never reaped", topicStatusThinking, 0, topicStatusThinking},
28 }
29 for _, tc := range cases {
30 t.Run(tc.name, func(t *testing.T) {
31 app := NewApp()
32 tab := &WorkspaceTab{
33 ID: "tab",
34 Scope: "global",
35 TopicID: "topic_ttl",
36 Ready: true,
37 ActivityStatus: tc.status,
38 disabledMCP: map[string]ServerView{},
39 }
40 if tc.age > 0 {
41 app.projectTreeRuntime.setActivityAt(tab.ID, now.Add(-tc.age))
42 }
43 app.tabs[tab.ID] = tab
44 app.reapStaleTopicActivityStatus(now)
45 if tab.ActivityStatus != tc.wantStatus {
46 t.Fatalf("status after reap = %q, want %q", tab.ActivityStatus, tc.wantStatus)
47 }
48 })
49 }
50 }
51
52 func TestReconcileTabActivityStatus(t *testing.T) {
53 isolateDesktopUserDirs(t)
54 dir := desktopSessionDir(globalTabWorkspaceRoot())
55 if err := os.MkdirAll(dir, 0o755); err != nil {
56 t.Fatalf("mkdir sessions: %v", err)
57 }
58 path := writeTopicSessionWithPrompt(t, dir, "reconcile.jsonl", "topic_reconcile", "Reconcile", "", "prompt", time.Now())
59
60 newTab := func(ctrl control.SessionAPI) *WorkspaceTab {
61 return &WorkspaceTab{
62 ID: "tab",
63 Scope: "global",
64 WorkspaceRoot: globalTabWorkspaceRoot(),
65 TopicID: "topic_reconcile",
66 SessionPath: path,
67 Ctrl: ctrl,
68 Ready: true,
69 ActivityStatus: topicStatusThinking,
70 disabledMCP: map[string]ServerView{},
71 }
72 }
73
74 t.Run("clears status the idle controller does not corroborate", func(t *testing.T) {
75 app := NewApp()
76 ctrl := control.New(control.Options{SessionDir: dir, SessionPath: path, Label: "idle", Sink: event.Discard})
77 defer ctrl.Close()
78 tab := newTab(ctrl)
79 app.tabs[tab.ID] = tab
80 if !app.reconcileTabActivityStatus(tab) {
81 t.Fatal("reconcile should clear the stale status")
82 }
83 if tab.ActivityStatus != "" {
84 t.Fatalf("status after reconcile = %q, want cleared", tab.ActivityStatus)
85 }
86 })
87
88 t.Run("keeps status while the controller is running", func(t *testing.T) {
89 app := NewApp()
90 runner := &blockingRunner{started: make(chan struct{}), release: make(chan struct{})}
91 ctrl := control.New(control.Options{Runner: runner, SessionDir: dir, SessionPath: path, Label: "running", Sink: event.Discard})
92 defer ctrl.Close()
93 tab := newTab(ctrl)
94 app.tabs[tab.ID] = tab
95 ctrl.Submit("keep running")
96 <-runner.started
97 if app.reconcileTabActivityStatus(tab) {
98 t.Fatal("reconcile must not clear the status of a running turn")
99 }
100 if tab.ActivityStatus != topicStatusThinking {
101 t.Fatalf("status after reconcile = %q, want %q", tab.ActivityStatus, topicStatusThinking)
102 }
103 close(runner.release)
104 waitNotRunning(t, ctrl)
105 })
106
107 t.Run("ignores non-live statuses", func(t *testing.T) {
108 app := NewApp()
109 ctrl := control.New(control.Options{SessionDir: dir, SessionPath: path, Label: "idle", Sink: event.Discard})
110 defer ctrl.Close()
111 tab := newTab(ctrl)
112 tab.ActivityStatus = topicStatusWaitingConfirmation
113 app.tabs[tab.ID] = tab
114 if app.reconcileTabActivityStatus(tab) {
115 t.Fatal("reconcile must not touch waiting_confirmation")
116 }
117 })
118 }
119
119 lines GO