返回 DeepSeek-Reasonix
inbox_target_transient_test.go
根目录 / desktop / inbox_target_transient_test.go
1 package main
2
3 import (
4 "errors"
5 "net/http"
6 "strings"
7 "testing"
8 )
9
10 func codedCode(t *testing.T, err error) (string, map[string]any) {
11 t.Helper()
12 var coded interface {
13 error
14 RPCErrorData() map[string]any
15 }
16 if !errors.As(err, &coded) {
17 t.Fatalf("error %v is not an inboxCodedError", err)
18 }
19 return strings.TrimPrefix(coded.Error(), "reasonix_error:"), coded.RPCErrorData()
20 }
21
22 // A fence that clears by itself must be machine-distinguishable from a
23 // permanent refusal: the renderer keeps the message queued and retries it,
24 // while permanent failures still surface verbatim.
25 func TestInboxTargetFenceClassifiesTransientStates(t *testing.T) {
26 cases := []struct {
27 name string
28 mutate func(*remoteTab)
29 }{
30 {"reconnecting", func(tab *remoteTab) { tab.state = "reconnecting" }},
31 {"connecting", func(tab *remoteTab) { tab.state = "connecting" }},
32 {"rehydrating", func(tab *remoteTab) { tab.routing.rehydratingPath = "/sessions/other.jsonl" }},
33 {"unrouted", func(tab *remoteTab) { tab.routing.currentPath = "" }},
34 {"route_moved", func(tab *remoteTab) { tab.routing.currentPath = "/sessions/other.jsonl" }},
35 }
36 for _, tc := range cases {
37 t.Run(tc.name, func(t *testing.T) {
38 a, tab := remoteRuntimeTestApp(&http.Client{})
39 tc.mutate(tab)
40 _, err := a.CaptureInboxTarget(tab.id, runtimeRemoteTestPath)
41 if err == nil {
42 t.Fatal("fence admitted an unsettled tab")
43 }
44 code, data := codedCode(t, err)
45 if code != "inbox_target_transient" || data["transient"] != true {
46 t.Fatalf("code=%q data=%v, want transient", code, data)
47 }
48 if !strings.HasPrefix(err.Error(), "reasonix_error:"+"inbox_target_transient") {
49 t.Fatalf("wire error = %q", err.Error())
50 }
51 })
52 }
53 }
54
55 // The enqueue boundary classifies the same fence, so a follow-up captured
56 // before the tab moved is retryable rather than permanently failed.
57 func TestInboxEnqueueClassifiesTransientTargetMismatch(t *testing.T) {
58 a, tab := remoteRuntimeTestApp(&http.Client{})
59 target, err := a.CaptureInboxTarget(tab.id, runtimeRemoteTestPath)
60 if err != nil {
61 t.Fatalf("capture failed: %v", err)
62 }
63 tab.routing.rehydratingPath = "/sessions/other.jsonl"
64 _, err = a.EnqueueInboxFollowupForTarget(target, "queued text", "queued text", nil, "transient-key")
65 if err == nil {
66 t.Fatal("enqueue admitted a rehydrating tab")
67 }
68 code, data := codedCode(t, err)
69 if code != "inbox_target_transient" || data["transient"] != true {
70 t.Fatalf("code=%q data=%v, want transient", code, data)
71 }
72 }
73
74 // Permanent refusals keep the historical code and must not look retryable.
75 func TestInboxPermanentFailuresStayNonTransient(t *testing.T) {
76 a, tab := remoteRuntimeTestApp(&http.Client{})
77 target, err := a.CaptureInboxTarget(tab.id, runtimeRemoteTestPath)
78 if err != nil {
79 t.Fatalf("capture failed: %v", err)
80 }
81 _, err = a.EnqueueInboxFollowupForTarget(target, "text", "text", nil, "")
82 if err == nil {
83 t.Fatal("enqueue accepted an empty idempotency key")
84 }
85 code, data := codedCode(t, err)
86 if code != "inbox_not_submitted" || data != nil {
87 t.Fatalf("code=%q data=%v, want permanent inbox_not_submitted", code, data)
88 }
89 }
90
91 // A settled tab still admits the same call: the classification must not turn
92 // the fence into a blanket refusal.
93 func TestInboxTargetFenceAdmitsSettledTab(t *testing.T) {
94 a, tab := remoteRuntimeTestApp(&http.Client{})
95 if _, err := a.CaptureInboxTarget(tab.id, runtimeRemoteTestPath); err != nil {
96 t.Fatalf("settled capture failed: %v", err)
97 }
98 }
99
99 lines GO