返回 DeepSeek-Reasonix
inbox_queue_test.go
根目录 / desktop / inbox_queue_test.go
1 package main
2
3 import (
4 "net/http"
5 "net/http/httptest"
6 "path/filepath"
7 "testing"
8
9 "reasonix/internal/config"
10 "reasonix/internal/control"
11 "reasonix/internal/event"
12 "reasonix/internal/serve"
13 "reasonix/internal/servecontract"
14 "reasonix/internal/sessioninbox"
15 )
16
17 func TestRemoteInboxQueueCapabilitiesAndIdentity(t *testing.T) {
18 isolateDesktopUserDirs(t)
19 dir := t.TempDir()
20 path := filepath.Join(dir, "session.jsonl")
21 ctrl := control.New(control.Options{SessionDir: dir, SessionPath: path, Sink: event.Discard})
22 defer ctrl.Close()
23 if err := ctrl.SetInboxPaused(true); err != nil {
24 t.Fatal(err)
25 }
26 receipt, err := ctrl.EnqueueInbox(control.InboxRequest{Submit: "remote full text", Idempotency: "remote"})
27 if err != nil {
28 t.Fatal(err)
29 }
30 server := httptest.NewServer(operatorServeHandler(serve.New(ctrl, nil, config.ServeConfig{})))
31 defer server.Close()
32 a, tab := remoteRuntimeTestApp(server.Client())
33 tab.base, tab.routing.currentPath, tab.session.path = server.URL, path, path
34 target, err := a.CaptureInboxTarget(tab.id, path)
35 if err != nil {
36 t.Fatal(err)
37 }
38 request := control.InboxQueueRequest{Kind: "read", ItemID: receipt.ItemID}
39 unsupported, err := a.InboxQueueForTarget(target, request)
40 if err != nil || unsupported.Reason != "unsupported" {
41 t.Fatalf("old remote: %+v %v", unsupported, err)
42 }
43 tab.capabilities[servecontract.InboxMutationsV1] = true
44 read, err := a.InboxQueueForTarget(target, request)
45 if err != nil || read.Edit == nil || read.Edit.Text != "remote full text" {
46 t.Fatalf("read: %+v %v", read, err)
47 }
48 request = control.InboxQueueRequest{Kind: "edit", ItemID: receipt.ItemID, ContentVersion: read.Edit.ContentVersion, Text: "edited remote"}
49 saved, err := a.InboxQueueForTarget(target, request)
50 if err != nil || saved.Outcome != "applied" || !saved.Snapshot.Paused {
51 t.Fatalf("save: %+v %v", saved, err)
52 }
53 tab.selectionRevision++
54 stale, err := a.InboxQueueForTarget(target, request)
55 if err != nil || stale.Reason != "session_changed" {
56 t.Fatalf("stale target: %+v %v", stale, err)
57 }
58 _, env, _ := ctrl.ReadInboxItem(receipt.ItemID)
59 if env.SubmitText != "edited remote" {
60 t.Fatal("remote queue changed unexpectedly")
61 }
62 tab.selectionRevision--
63 ctrl.SetSessionPath(filepath.Join(dir, "other.jsonl"))
64 stale, err = a.InboxQueueForTarget(target, control.InboxQueueRequest{Kind: "pause", Paused: true})
65 if err != nil || stale.Reason != "session_changed" {
66 t.Fatalf("remote foreground switch: %+v %v", stale, err)
67 }
68 }
69
70 func TestTargetGuidanceQueuesEndedTurnAndRejectsReplacementSession(t *testing.T) {
71 isolateDesktopUserDirs(t)
72 dir := t.TempDir()
73 path := filepath.Join(dir, "session.jsonl")
74 ctrl := control.New(control.Options{SessionDir: dir, SessionPath: path, Sink: event.Discard})
75 cleanupExactTurnController(t, ctrl)
76 if err := ctrl.SetInboxPaused(true); err != nil {
77 t.Fatal(err)
78 }
79 tab := &WorkspaceTab{ID: "tab", Ctrl: ctrl, SessionPath: path, SessionGeneration: 1, Ready: true}
80 a := &App{tabs: map[string]*WorkspaceTab{"tab": tab}}
81 target, err := a.CaptureInboxTarget("tab", path)
82 if err != nil {
83 t.Fatal(err)
84 }
85 request := control.InboxQueueRequest{Kind: "enqueue_steer", TurnID: "ended-turn", Text: "preserve guidance", Display: "preserve guidance", IdempotencyKey: "one-guidance"}
86 result, err := a.InboxQueueForTarget(target, request)
87 if err != nil || result.Receipt == nil || result.Receipt.Disposition != sessioninbox.DispositionQueuedFollowup {
88 t.Fatalf("ended turn: %+v %v", result, err)
89 }
90 duplicate, err := a.InboxQueueForTarget(target, request)
91 if err != nil || duplicate.Receipt == nil || duplicate.Receipt.ItemID != result.Receipt.ItemID || len(ctrl.InboxSnapshot().Items) != 1 {
92 t.Fatalf("duplicate: %+v %v", duplicate, err)
93 }
94 confirmed, err := a.LookupInboxFollowupForTarget(target, request.IdempotencyKey)
95 if err != nil || confirmed.ItemID != result.Receipt.ItemID {
96 t.Fatalf("receipt recovery: %+v %v", confirmed, err)
97 }
98 tab.SessionGeneration++
99 request.IdempotencyKey = "stale-input"
100 stale, err := a.InboxQueueForTarget(target, request)
101 if err != nil || stale.Reason != "session_changed" || len(ctrl.InboxSnapshot().Items) != 1 {
102 t.Fatalf("replacement fence: %+v %v", stale, err)
103 }
104 // Legacy tab-only calls cannot prove session ownership and keep their guard.
105 if _, err := a.EnqueueInboxSteerForTurn("tab", "ended-turn", "unsafe", "unsafe", "unsafe"); err == nil {
106 t.Fatal("legacy request bypassed the session fence")
107 }
108 }
109
110 // operatorServeHandler stands in for the remote client, which holds the
111 // launch token a serve requires for mutations.
112 func operatorServeHandler(s *serve.Server) http.Handler {
113 h := s.Handler()
114 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
115 r.Header.Set("Authorization", "Bearer "+s.AuthToken())
116 h.ServeHTTP(w, r)
117 })
118 }
119
119 lines GO