返回 DeepSeek-Reasonix
historical_source_admission_test.go
根目录 / desktop / historical_source_admission_test.go
1 package main
2
3 import (
4 "errors"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9
10 "reasonix/internal/agent"
11 "reasonix/internal/config"
12 "reasonix/internal/control"
13 "reasonix/internal/provider"
14 "reasonix/internal/session"
15 )
16
17 func restoreHistoricalSourcePendingTab(t *testing.T) (*App, *WorkspaceTab, string) {
18 t.Helper()
19 app := newSavedTabReconcileTestApp(t)
20 t.Cleanup(app.closeSessionServices)
21 path := filepath.Join(config.SessionDir(), "pending.jsonl")
22 if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
23 t.Fatal(err)
24 }
25 legacy := agent.NewSession("system")
26 legacy.Add(provider.Message{ID: "old", Role: provider.RoleUser, Content: "original work"})
27 if err := legacy.Save(path); err != nil {
28 t.Fatal(err)
29 }
30 entry := desktopTabEntry{ID: "pending", Scope: "global", TopicID: "topic", SessionPath: path}
31 file := desktopTabsFile{Tabs: []desktopTabEntry{entry}, ActiveTab: entry.ID, TabOrder: []string{entry.ID}}
32 if err := os.MkdirAll(desktopConfigDir(), 0o700); err != nil {
33 t.Fatal(err)
34 }
35 if err := os.WriteFile(filepath.Join(desktopConfigDir(), tabsFileName), mustMarshalJSON(t, file), 0o600); err != nil {
36 t.Fatal(err)
37 }
38 finishSavedTabMigration(app)
39 app.tabsRestored = make(chan struct{})
40 app.restoreOrBuildTabs()
41 tab := waitForTabReady(t, app, entry.ID)
42 native, ok := tab.Ctrl.(*control.Controller)
43 if !ok || !native.NativeLegacySession() || agent.CanonicalSessionPath(tab.SessionPath) != agent.CanonicalSessionPath(path) {
44 t.Fatalf("pending source did not restore as its native runtime: path=%q ctrl=%T", tab.SessionPath, tab.Ctrl)
45 }
46 return app, tab, path
47 }
48
49 func TestHistoricalSourcePendingTabRefusesSendWithTypedReason(t *testing.T) {
50 app, tab, path := restoreHistoricalSourcePendingTab(t)
51
52 err := app.SubmitToTabWithID(tab.ID, "continue", "submission-1")
53 var refusal *SessionOperationError
54 if !errors.As(err, &refusal) || refusal.Code != sessionOperationHistoricalSourcePending {
55 t.Fatalf("send was not refused with the historical-source reason: %v", err)
56 }
57 if !errors.Is(err, control.ErrSubmissionNotAccepted) {
58 t.Fatalf("refusal does not say the submission was not accepted: %v", err)
59 }
60 if errors.Is(err, control.ErrSubmissionIdentityUnavailable) {
61 t.Fatalf("send reached the controller before being refused: %v", err)
62 }
63 if tab.Ctrl.RuntimeStatus().Running {
64 t.Fatal("refused send started a turn")
65 }
66
67 meta := app.metaForTab(tab.ID)
68 if meta.HistoricalSource == nil || agent.CanonicalSessionPath(meta.HistoricalSource.Path) != agent.CanonicalSessionPath(path) {
69 t.Fatalf("meta does not report the pending source, so the composer stays enabled: %+v", meta.HistoricalSource)
70 }
71 for _, listed := range app.ListTabs() {
72 if listed.ID == tab.ID && listed.HistoricalSource == nil {
73 t.Fatal("tab list does not report the pending source")
74 }
75 }
76 }
77
78 func TestHistoricalSourcePendingTabPreparesIntoCanonicalSession(t *testing.T) {
79 app, tab, _ := restoreHistoricalSourcePendingTab(t)
80 source := app.metaForTab(tab.ID).HistoricalSource
81 if source == nil {
82 t.Fatal("meta does not report the pending source")
83 }
84 view, err := app.PrepareSession(SessionSelector{Source: source})
85 if err != nil {
86 t.Fatal(err)
87 }
88 deadline := time.Now().Add(10 * time.Second)
89 for view.Status != "ready" && view.Status != "failed" && view.Status != "blocked" && time.Now().Before(deadline) {
90 time.Sleep(20 * time.Millisecond)
91 if view, err = app.GetSessionPreparation(view.OperationID); err != nil {
92 t.Fatal(err)
93 }
94 }
95 if view.Status != "ready" || view.Target == nil {
96 t.Fatalf("pending source could not be prepared while its tab is open: %+v", view)
97 }
98 }
99
100 func TestNativeLegacyRuntimeRefusesIdentifiedSendBeforeController(t *testing.T) {
101 dir := t.TempDir()
102 path := filepath.Join(dir, "native.jsonl")
103 ctrl := control.New(control.Options{Sink: &tabEventSink{tabID: "tab"}, SessionDir: dir, SessionPath: path, NativeLegacySession: true})
104 defer ctrl.Close()
105 tab := &WorkspaceTab{ID: "tab", Scope: "global", Ready: true, Ctrl: ctrl, sink: &tabEventSink{tabID: "tab"}, SessionPath: path}
106 app := &App{tabs: map[string]*WorkspaceTab{tab.ID: tab}, activeTabID: tab.ID}
107
108 err := app.SubmitToTabWithID(tab.ID, "continue", "submission-1")
109 var refusal *SessionOperationError
110 if !errors.As(err, &refusal) || refusal.Code != sessionOperationHistoricalSourcePending {
111 t.Fatalf("send was not refused with the historical-source reason: %v", err)
112 }
113 if errors.Is(err, control.ErrSubmissionIdentityUnavailable) {
114 t.Fatalf("send reached the controller before being refused: %v", err)
115 }
116 }
117
118 // Importing a previewed legacy source and moving to the imported session keeps
119 // the source byte-for-byte, so the source is not reported as updated.
120 func TestHistoricalPreviewImportLeavesSourceUntouched(t *testing.T) {
121 app, tab, path := restoreHistoricalSourcePendingTab(t)
122 before, err := desktopSourceFingerprint(path)
123 if err != nil {
124 t.Fatal(err)
125 }
126 view, err := app.PrepareSession(SessionSelector{Source: app.metaForTab(tab.ID).HistoricalSource})
127 if err != nil {
128 t.Fatal(err)
129 }
130 for deadline := time.Now().Add(10 * time.Second); view.Status != "ready" && view.Status != "failed"; {
131 if time.Now().After(deadline) {
132 t.Fatalf("preparation did not settle: %+v", view)
133 }
134 time.Sleep(20 * time.Millisecond)
135 view, _ = app.GetSessionPreparation(view.OperationID)
136 }
137 if view.Status != "ready" {
138 t.Fatalf("preparation = %+v", view)
139 }
140 if _, err := app.OpenSession(session.SessionRef{HostID: view.Target.HostID, SessionID: view.Target.SessionID}); err != nil {
141 t.Fatal(err)
142 }
143 if after, err := desktopSourceFingerprint(path); err != nil || after != before {
144 t.Fatalf("opening the imported session rewrote the legacy source (err=%v)", err)
145 }
146 for deadline := time.Now().Add(10 * time.Second); ; {
147 update, err := app.CheckHistoricalSourceUpdate(SessionSelector{Ref: view.Target})
148 if err != nil {
149 t.Fatal(err)
150 }
151 if update.Status != "checking" {
152 if update.Status != "unchanged" {
153 t.Fatalf("source update = %q, want unchanged", update.Status)
154 }
155 return
156 }
157 if time.Now().After(deadline) {
158 t.Fatal("source update check did not settle")
159 }
160 time.Sleep(20 * time.Millisecond)
161 }
162 }
163
163 lines GO