返回 DeepSeek-Reasonix
remote_identity_notice_test.go
根目录 / desktop / remote_identity_notice_test.go
1 package main
2
3 import (
4 "net/http"
5 "net/http/httptest"
6 "sync/atomic"
7 "testing"
8 )
9
10 // identityOwnershipServe answers /ownership for an identity-routed session,
11 // records the selector each probe asked about, and runs duringProbe while the
12 // answer is still in flight.
13 func identityOwnershipServe(t *testing.T, holder string, asked *atomic.Value, duringProbe func()) *httptest.Server {
14 t.Helper()
15 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16 if r.URL.Path != "/ownership" {
17 w.WriteHeader(http.StatusNotFound)
18 return
19 }
20 asked.Store(r.URL.Query().Get("session"))
21 if duringProbe != nil {
22 duringProbe()
23 }
24 w.Header().Set("Content-Type", "application/json")
25 _, _ = w.Write([]byte(`{"available":true,"holder":"` + holder + `","sessionPath":"session-id:held"}`))
26 }))
27 t.Cleanup(srv.Close)
28 return srv
29 }
30
31 func identityNoticeTab(srv *httptest.Server) (*App, *remoteTab) {
32 tab := &remoteTab{
33 id: "remote-1", state: "ready", gen: 4, client: srv.Client(), base: srv.URL,
34 routing: remoteTabSessionRouting{currentPath: "session-id:held", running: map[string]bool{}},
35 session: remoteTabSessionState{sessionID: "held"},
36 }
37 return &App{remoteTabs: map[string]*remoteTab{tab.id: tab}}, tab
38 }
39
40 // Serve now delivers takeover and reclaim notices on identity routes, which it
41 // previously dropped for this subscriber. The tab must route such a notice to
42 // the ownership probe under its identity route and adopt the answer.
43 func TestIdentityRoutedTakeoverNoticeProbesAndPinsSpectator(t *testing.T) {
44 var asked atomic.Value
45 srv := identityOwnershipServe(t, "external", &asked, nil)
46 a, tab := identityNoticeTab(srv)
47
48 a.probeSpectatorAfterNotice(tab.id, tab.gen, tab.client, tab.base, "session-id:held")
49
50 if got, _ := asked.Load().(string); got != "session-id:held" {
51 t.Fatalf("ownership probe asked about %q, want the identity route", got)
52 }
53 a.remoteTabMu.Lock()
54 takenOver := tab.session.takenOver
55 a.remoteTabMu.Unlock()
56 if !takenOver {
57 t.Fatal("an identity-routed takeover notice did not pin the spectator state")
58 }
59 }
60
61 // A reclaimed notice for a session that is no longer locally owned clears the
62 // pin, so ownership returning through a notice does not leave a stale banner.
63 func TestIdentityRoutedReclaimedNoticeClearsSpectator(t *testing.T) {
64 var asked atomic.Value
65 srv := identityOwnershipServe(t, "free", &asked, nil)
66 a, tab := identityNoticeTab(srv)
67 tab.session.takenOver = true
68
69 a.probeSpectatorAfterNotice(tab.id, tab.gen, tab.client, tab.base, "session-id:held")
70
71 a.remoteTabMu.Lock()
72 takenOver := tab.session.takenOver
73 a.remoteTabMu.Unlock()
74 if takenOver {
75 t.Fatal("a reclaimed notice left the spectator banner pinned")
76 }
77 }
78
79 // A notice for a different session must not probe or re-pin this tab.
80 func TestIdentityRoutedNoticeForAnotherSessionIsIgnored(t *testing.T) {
81 var asked atomic.Value
82 srv := identityOwnershipServe(t, "external", &asked, nil)
83 a, tab := identityNoticeTab(srv)
84
85 a.probeSpectatorAfterNotice(tab.id, tab.gen, tab.client, tab.base, "session-id:other")
86
87 if asked.Load() != nil {
88 t.Fatal("a notice for another session probed ownership for this tab")
89 }
90 a.remoteTabMu.Lock()
91 takenOver := tab.session.takenOver
92 a.remoteTabMu.Unlock()
93 if takenOver {
94 t.Fatal("a notice for another session pinned the spectator banner")
95 }
96 }
97
98 // Now that these notices arrive on identity routes, one can land around an
99 // explicit take-back. A probe whose answer predates the reclaim must not
100 // re-pin the banner after ownership returned — the fence the reclaim epoch
101 // already applies to in-flight status payloads.
102 func TestOwnershipProbeCannotRepinAcrossACompletedReclaim(t *testing.T) {
103 var asked atomic.Value
104 var a *App
105 var tab *remoteTab
106 // The reclaim completes while the stale "still externally held" answer is
107 // in flight, so the ordering needs no timing assumptions.
108 srv := identityOwnershipServe(t, "external", &asked, func() {
109 a.remoteTabMu.Lock()
110 tab.session.takenOver = false
111 tab.ownership.reclaimRevision = tab.runtime.revision + 1
112 a.remoteTabMu.Unlock()
113 })
114 a, tab = identityNoticeTab(srv)
115 tab.session.takenOver = true
116
117 a.probeSpectatorAfterNotice(tab.id, tab.gen, tab.client, tab.base, "session-id:held")
118
119 a.remoteTabMu.Lock()
120 takenOver := tab.session.takenOver
121 a.remoteTabMu.Unlock()
122 if takenOver {
123 t.Fatal("a pre-reclaim ownership probe re-pinned the spectator banner")
124 }
125 }
126
126 lines GO