返回 DeepSeek-Reasonix
session_target_regression_test.go
根目录 / desktop / session_target_regression_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "reasonix/internal/agent"
7 "reasonix/internal/config"
8 "reasonix/internal/provider"
9 "reasonix/internal/session"
10 "strings"
11 "testing"
12 "time"
13 )
14
15 func attachReviewTitleTarget(t *testing.T, app *App, runtime *session.Runtime, topic string) {
16 t.Helper()
17 workspace, err := app.ensureDesktopWorkspace(t.Context(), "global", "")
18 if err != nil {
19 t.Fatal(err)
20 }
21 if err := app.workspaceRegistry().AttachSession(t.Context(), "", workspace, runtime.Ref().SessionID, ""); err != nil {
22 t.Fatal(err)
23 }
24 if err := app.workspaceRegistry().EnsureSessionTopic(t.Context(), runtime.Ref().SessionID, topic, ""); err != nil {
25 t.Fatal(err)
26 }
27 appendSessionTestMessage(t, runtime, "probe-user", provider.Message{ID: "probe-user", Role: provider.RoleUser, Origin: provider.MessageOriginUser, Content: "durable target content"})
28 if _, err := runtime.Session().Flush(t.Context()); err != nil {
29 t.Fatal(err)
30 }
31 }
32
33 func TestAIRenameColdSessionWithoutAnyController(t *testing.T) {
34 app, _, runtime, _, _ := newCanonicalTitleFixture(t)
35 attachReviewTitleTarget(t, app, runtime, "cold-target")
36 app.tabs = map[string]*WorkspaceTab{}
37 app.activeTabID = ""
38 _, err := app.AIRenameSession(sessionRoute(runtime.Ref().SessionID))
39 if err != nil {
40 t.Fatalf("durable target still requires a controller: %v", err)
41 }
42 }
43
44 func TestSessionTargetExplicitRefMustNotMatchSiblingTopicController(t *testing.T) {
45 app, _, original, _, _ := newCanonicalTitleFixture(t)
46 attachReviewTitleTarget(t, app, original, "topic-canonical")
47 sibling, err := app.desktopSessionService("").Create(t.Context(), session.CreateOptions{SessionID: "sibling-review"})
48 if err != nil {
49 t.Fatal(err)
50 }
51 attachReviewTitleTarget(t, app, sibling, "topic-canonical")
52 ref := sibling.Ref()
53 target, err := app.resolveSessionTarget(sessionTargetSelector{Ref: &ref, TopicID: "topic-canonical"})
54 if err != nil {
55 t.Fatal(err)
56 }
57 if target.Controller != nil {
58 actual, _ := target.Controller.SessionRef()
59 if actual != ref {
60 t.Fatalf("explicit ref %s attached sibling controller %s", ref.SessionID, actual.SessionID)
61 }
62 }
63 if _, err := app.AIRenameSession("topic-canonical"); err == nil {
64 t.Fatal("ambiguous topic-only rename must not choose an arbitrary session")
65 }
66 if _, err := app.AIRenameSession(sessionRoute(ref.SessionID)); err != nil {
67 t.Fatal(err)
68 }
69 info, err := app.desktopSessionService("").Query().Stat(t.Context(), original.Ref())
70 if err != nil || info.Title != "" {
71 t.Fatalf("renaming explicit sibling changed the original: %+v, %v", info, err)
72 }
73 }
74
75 func TestSessionTargetInvalidHighPriorityRefDoesNotFallBack(t *testing.T) {
76 app, _, _, _, path := newCanonicalTitleFixture(t)
77 target, err := app.resolveSessionTarget(SessionSelector{
78 Ref: &session.SessionRef{HostID: localDesktopHostID},
79 SessionPath: path,
80 })
81 if err == nil || !strings.Contains(err.Error(), "session_operation:target_not_found:") {
82 t.Fatalf("invalid explicit ref resolved target %+v with err %v", target, err)
83 }
84 }
85
86 func TestSessionTargetRemoteRefDoesNotEnterLocalResolver(t *testing.T) {
87 app := NewApp()
88 ref := session.SessionRef{HostID: "remote-host", SessionID: "remote-session"}
89 _, err := app.resolveSessionTarget(SessionSelector{Ref: &ref, SessionPath: "/must/not/fallback.jsonl", TopicID: "fallback"})
90 if err == nil || !strings.Contains(err.Error(), "session_operation:unsupported:") {
91 t.Fatalf("remote target error = %v, want structured unsupported", err)
92 }
93 }
94
95 func TestSessionTargetTopicRejectsMultipleLegacySessions(t *testing.T) {
96 isolateDesktopUserDirs(t)
97 dir := config.SessionDir()
98 if err := os.MkdirAll(dir, 0o700); err != nil {
99 t.Fatal(err)
100 }
101 for _, name := range []string{"one", "two"} {
102 path := writeTargetHistoryFixture(t, dir, name, name)
103 if err := agent.UpdateBranchMeta(path, false, func(meta *agent.BranchMeta) error {
104 meta.TopicID = "shared-legacy-topic"
105 return nil
106 }); err != nil {
107 t.Fatal(err)
108 }
109 }
110 app := NewApp()
111 installSessionCatalogForTest(t, app, dir, "global", "")
112 target, err := app.resolveSessionTarget(SessionSelector{TopicID: "shared-legacy-topic"})
113 if err == nil || !strings.Contains(err.Error(), "session_operation:ambiguous_target:") {
114 t.Fatalf("ambiguous legacy topic resolved target %+v with err %v", target, err)
115 }
116 }
117
118 func TestAIRenameArchiveDuringColdRenameRejectsCompletion(t *testing.T) {
119 app, _, _, prov, path := newCanonicalTitleFixture(t)
120 runtime, err := app.desktopSessionService("").Create(t.Context(), session.CreateOptions{SessionID: "archive-target", CWD: globalWorkspaceRoot()})
121 if err != nil {
122 t.Fatal(err)
123 }
124 attachReviewTitleTarget(t, app, runtime, "cold-target")
125 generator := newDesktopSessionTitleController(filepath.Dir(path), path, prov)
126 t.Cleanup(generator.Close)
127 app.tabs["test"].Ctrl = generator
128 app.tabs["test"].TopicID = "other-topic"
129 app.tabs["test"].SessionID = "other-session"
130 prov.started = make(chan struct{})
131 prov.chunks = make(chan provider.Chunk, 2)
132 result := make(chan error, 1)
133 go func() { _, err := app.AIRenameSession("cold-target"); result <- err }()
134 select {
135 case <-prov.started:
136 case err := <-result:
137 t.Fatalf("rename stopped early: %v", err)
138 case <-time.After(5 * time.Second):
139 t.Fatal("provider did not start")
140 }
141 if err := app.ArchiveCanonicalSession(runtime.Ref()); err != nil {
142 t.Fatal(err)
143 }
144 prov.chunks <- provider.Chunk{Type: provider.ChunkText, Text: "stale renamed archive"}
145 prov.chunks <- provider.Chunk{Type: provider.ChunkDone}
146 close(prov.chunks)
147 if err := <-result; err == nil {
148 t.Fatal("AI rename succeeded after target was archived")
149 }
150 }
151
151 lines GO