返回 DeepSeek-Reasonix
runtime_state_reconcile_test.go
根目录 / desktop / runtime_state_reconcile_test.go
1 package main
2
3 import (
4 "encoding/json"
5 "net/http"
6 "os"
7 "path/filepath"
8 "reflect"
9 "sync"
10 "testing"
11
12 "reasonix/internal/agent"
13 )
14
15 func TestRemoteRuntimeMissingSelectedSessionRequiresOwnConfirmation(t *testing.T) {
16 isolateDesktopUserDirs(t)
17 body := `{"schemaVersion":1,"sessions":[]}`
18 client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
19 return remoteRuntimeTestResponse(req, http.StatusOK, body), nil
20 })}
21 a, tab := remoteRuntimeTestApp(client)
22 old := remoteRuntimeTestSnapshot("retired", 7, "executing")
23 acceptRemoteRuntimeStateLocked(tab, runtimeRemoteTestPath, old, true)
24 if _, err := a.SyncRuntimeState(); err != nil {
25 t.Fatal(err)
26 }
27 view := a.GetRuntimeStateSnapshot().Sessions[0]
28 if view.Freshness != "unknown" || !reflect.DeepEqual(view.State, old) {
29 t.Fatalf("missing runtime became authoritative: %+v", view)
30 }
31 other := remoteRuntimeTestSnapshot("other", 1, "idle")
32 acceptRemoteRuntimeStateLocked(tab, "/sessions/other.jsonl", other, true)
33 if tab.runtimeUnknown[runtimeRemoteTestPath] == 0 {
34 t.Fatal("another session cleared uncertainty")
35 }
36 body = remoteRuntimeTestPayload(t, old)
37 if _, err := a.SyncRuntimeState(); err != nil {
38 t.Fatal(err)
39 }
40 view = a.GetRuntimeStateSnapshot().Sessions[0]
41 if view.Freshness != "synced" || !reflect.DeepEqual(view.State, old) {
42 t.Fatalf("identical authority did not confirm: %+v", view)
43 }
44 }
45
46 func TestRemoteRuntimeGETCannotConfirmLaterUncertainty(t *testing.T) {
47 isolateDesktopUserDirs(t)
48 entered, release := make(chan struct{}), make(chan struct{})
49 enterOnce, releaseOnce := sync.OnceFunc(func() { close(entered) }), sync.OnceFunc(func() { close(release) })
50 defer releaseOnce()
51 state := remoteRuntimeTestSnapshot("controller", 3, "executing")
52 body := remoteRuntimeTestPayload(t, state)
53 client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
54 enterOnce()
55 <-release
56 return remoteRuntimeTestResponse(req, 200, body), nil
57 })}
58 a, tab := remoteRuntimeTestApp(client)
59 acceptRemoteRuntimeStateLocked(tab, runtimeRemoteTestPath, state, true)
60 result := make(chan error, 1)
61 go func() { _, err := a.SyncRuntimeState(); result <- err }()
62 <-entered
63 a.remoteTabMu.Lock()
64 markRemoteRuntimeUnknownLocked(tab, runtimeRemoteTestPath)
65 a.remoteTabMu.Unlock()
66 releaseOnce()
67 awaitRemoteRuntimeSync(t, result)
68 if tab.runtimeUnknown[runtimeRemoteTestPath] == 0 {
69 t.Fatal("old GET erased a later failure without changed facts")
70 }
71 if _, err := a.SyncRuntimeState(); err != nil {
72 t.Fatal(err)
73 }
74 if tab.runtimeUnknown[runtimeRemoteTestPath] != 0 {
75 t.Fatal("fresh GET did not restore confidence")
76 }
77 }
78
79 func TestRemoteRuntimeLegacyRecoveryConfirmsOnlySelectedSession(t *testing.T) {
80 isolateDesktopUserDirs(t)
81 client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
82 if req.URL.Path == "/runtime-states" {
83 return remoteRuntimeTestResponse(req, 404, "unsupported"), nil
84 }
85 return remoteRuntimeTestResponse(req, 200, `{"sessionPath":"/sessions/current.jsonl","running":false}`), nil
86 })}
87 a, tab := remoteRuntimeTestApp(client)
88 state := remoteRuntimeTestSnapshot("previous-server", 1, "executing")
89 acceptRemoteRuntimeStateLocked(tab, runtimeRemoteTestPath, state, true)
90 acceptRemoteRuntimeStateLocked(tab, "/sessions/background.jsonl", state, true)
91 markRemoteRuntimeUnknownLocked(tab, runtimeRemoteTestPath)
92 markRemoteRuntimeUnknownLocked(tab, "/sessions/background.jsonl")
93 if _, err := a.SyncRuntimeState(); err != nil {
94 t.Fatal(err)
95 }
96 for _, session := range a.GetRuntimeStateSnapshot().Sessions {
97 if session.Open {
98 if session.State.SchemaVersion != 0 || session.State.Running || session.Freshness != "synced" {
99 t.Fatalf("legacy recovery = %+v", session)
100 }
101 } else if session.Freshness != "unknown" {
102 t.Fatal("legacy foreground status confirmed background")
103 }
104 }
105 }
106
107 func TestRuntimeProjectionDoesNotReadPreviewFiles(t *testing.T) {
108 isolateDesktopUserDirs(t)
109 dir := t.TempDir()
110 first, second := filepath.Join(dir, "first.jsonl"), filepath.Join(dir, "second.jsonl")
111 a := &App{tabs: map[string]*WorkspaceTab{
112 "one": {ID: "one", Scope: "project", WorkspaceRoot: dir, TopicID: "topic", SessionPath: first},
113 "two": {ID: "two", Scope: "project", WorkspaceRoot: dir, TopicID: "topic", SessionPath: second},
114 }}
115 if err := os.WriteFile(agent.BranchMetaPath(first), []byte(`{"preview":"before"}`), 0o600); err != nil {
116 t.Fatal(err)
117 }
118 before := a.GetRuntimeStateSnapshot()
119 if err := os.WriteFile(agent.BranchMetaPath(first), []byte(`{"preview":"after"}`), 0o600); err != nil {
120 t.Fatal(err)
121 }
122 for range 20 {
123 if after := a.GetRuntimeStateSnapshot(); !reflect.DeepEqual(before, after) {
124 t.Fatalf("unstable memory projection: %+v", after)
125 }
126 }
127 }
128
129 func TestRemoteRuntimeWireRequiredFacts(t *testing.T) {
130 valid := remoteRuntimeTestJSON(t, remoteRuntimeTestSnapshot("controller", 2, "idle"))
131 if state, err := decodeRemoteRuntimeState(json.RawMessage(valid)); err != nil || state.Running || state.BackgroundJobs != 0 {
132 t.Fatalf("explicit false/zero rejected: %+v %v", state, err)
133 }
134 for _, field := range []string{"schemaVersion", "runtimeEpoch", "revision", "phase", "running", "pendingPrompt", "cancelRequested", "cancellable", "backgroundJobs"} {
135 for _, replacement := range []string{"missing", "null", `[]`} {
136 t.Run(field+"/"+replacement, func(t *testing.T) {
137 var fields map[string]json.RawMessage
138 if err := json.Unmarshal([]byte(valid), &fields); err != nil {
139 t.Fatal(err)
140 }
141 if replacement == "missing" {
142 delete(fields, field)
143 } else {
144 fields[field] = json.RawMessage(replacement)
145 }
146 raw, err := json.Marshal(fields)
147 if err != nil {
148 t.Fatal(err)
149 }
150 if _, err := decodeRemoteRuntimeState(raw); err == nil {
151 t.Fatal("accepted absent or malformed required fact")
152 }
153 var status remoteTabStatusPayload
154 if err := json.Unmarshal([]byte(`{"running":false,"runtimeState":`+string(raw)+`}`), &status); err == nil {
155 t.Fatal("status downgraded malformed schema 1 to legacy flags")
156 }
157 })
158 }
159 }
160 }
161
162 func TestRemoteRuntimeInvalidFullSnapshotCannotPrune(t *testing.T) {
163 isolateDesktopUserDirs(t)
164 client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
165 return remoteRuntimeTestResponse(req, 200, `{"schemaVersion":1,"sessions":[{"sessionPath":"/sessions/current.jsonl","state":{"schemaVersion":1,"runtimeEpoch":"controller","revision":2,"phase":"idle"}}]}`), nil
166 })}
167 a, tab := remoteRuntimeTestApp(client)
168 old := remoteRuntimeTestSnapshot("controller", 1, "executing")
169 acceptRemoteRuntimeStateLocked(tab, runtimeRemoteTestPath, old, true)
170 acceptRemoteRuntimeStateLocked(tab, "/sessions/background.jsonl", old, true)
171 if _, err := a.SyncRuntimeState(); err == nil {
172 t.Fatal("invalid payload accepted")
173 }
174 if len(tab.runtimeStates) != 2 || !reflect.DeepEqual(tab.runtimeStates[runtimeRemoteTestPath], old) {
175 t.Fatal("invalid full GET replaced or pruned facts")
176 }
177 for _, view := range a.GetRuntimeStateSnapshot().Sessions {
178 if view.Freshness != "unknown" {
179 t.Fatal("invalid full GET cleared uncertainty")
180 }
181 }
182 a.acceptRemoteRuntimeFrame(tab.id, tab.gen, runtimeRemoteTestPath, json.RawMessage(`{"runtimeState":{"schemaVersion":1,"runtimeEpoch":"controller","revision":2,"phase":"idle"}}`))
183 if !reflect.DeepEqual(tab.runtimeStates[runtimeRemoteTestPath], old) {
184 t.Fatal("invalid SSE replaced facts")
185 }
186 }
187
187 lines GO