oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package hub
2
3import (
4 "context"
5 "testing"
6 "time"
7)
8
9// Guest isolation (spec §4.3, ruling 9, and two of §13's slice-2 gates).
10//
11// Five predicates in admission.go carry the rule that a guest learns
12// nothing about anyone it is not already in the mesh with, and that a
13// peer which has only knocked learns nothing at all. Whole-branch
14// review W-1 mutated each of the five away and found the whole suite
15// still green, so the tests below exist to kill exactly those five
16// mutations. Each one names the mutation it is written against: a test
17// whose subject is an audience filter is otherwise indistinguishable
18// from a test of the happy path, and the happy path is what the
19// pre-existing tests already covered.
20//
21// The shape that makes them discriminating is a room with someone still
22// waiting in it. Every one of these leaks is invisible when the guest
23// under test is the only guest in the room.
24
25// guestRoom builds that shape: one admitted member, one guest admitted
26// after it, and one guest still waiting, with every subscriber drained
27// so a test asserts only on what happens next.
28func guestRoom(t *testing.T, h *Hub) (member, admittedGuest, waitingGuest OpenResult) {
29 t.Helper()
30 ctx := context.Background()
31 member, _ = h.Open("r1", "sm", 1, "Mia", KindMember)
32 if err := h.Join(ctx, "r1", "sm", member.Peer); err != nil {
33 t.Fatalf("member join: %v", err)
34 }
35 waitingGuest, _ = h.Open("r1", "sw", 101, "Wes", KindGuest)
36 if err := h.Join(ctx, "r1", "sw", waitingGuest.Peer); err != nil {
37 t.Fatalf("waiting guest join: %v", err)
38 }
39 admittedGuest, _ = h.Open("r1", "sg", 100, "Gus", KindGuest)
40 if err := h.Join(ctx, "r1", "sg", admittedGuest.Peer); err != nil {
41 t.Fatalf("guest join: %v", err)
42 }
43 if err := h.Admit(ctx, "r1", "sm", member.Peer, admittedGuest.Peer); err != nil {
44 t.Fatalf("admit: %v", err)
45 }
46 drain(member.Sub)
47 drain(admittedGuest.Sub)
48 drain(waitingGuest.Sub)
49 return member, admittedGuest, waitingGuest
50}
51
52// countOf reports how many of the drained events carry one type.
53func countOf(ms []Message, typ string) int {
54 n := 0
55 for _, m := range ms {
56 if m.Type == typ {
57 n++
58 }
59 }
60 return n
61}
62
63// W-1 row 1 — mutation: rosterFor's `viewer.Kind == KindMember` → true.
64//
65// §13: "roster carrying the waiting queue to members and not to
66// guests". The roster an admitted guest is sent must never carry the
67// waiting queue, which is the display name of everyone knocking. The
68// guest is admitted here while another guest is already waiting, so
69// the Waiting field has something to leak — the pre-existing test
70// admitted the only guest in the room, where an empty queue proved
71// nothing about the filter.
72func TestAnAdmittedGuestIsNeverToldWhoIsWaiting(t *testing.T) {
73 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
74 h := testHub(&now)
75 ctx := context.Background()
76 m, _ := h.Open("r1", "sm", 1, "Mia", KindMember)
77 h.Join(ctx, "r1", "sm", m.Peer)
78 w, _ := h.Open("r1", "sw", 101, "Wes", KindGuest)
79 h.Join(ctx, "r1", "sw", w.Peer)
80 g, _ := h.Open("r1", "sg", 100, "Gus", KindGuest)
81 h.Join(ctx, "r1", "sg", g.Peer)
82 drain(g.Sub)
83
84 if err := h.Admit(ctx, "r1", "sm", m.Peer, g.Peer); err != nil {
85 t.Fatal(err)
86 }
87 ev := drain(g.Sub)
88 if len(ev) != 1 || ev[0].Type != TypeRoster {
89 t.Fatalf("the admitted guest got %v, want one roster", types(ev))
90 }
91 if n := len(ev[0].Roster.Waiting); n != 0 {
92 t.Fatalf("the admitted guest was handed %d waiting peer(s): %+v", n, ev[0].Roster.Waiting)
93 }
94
95 // The same moment from a member's side, so the test also says what
96 // the rule is for: the queue exists, and a member does see it.
97 m2, _ := h.Open("r1", "sm2", 2, "Moe", KindMember)
98 h.Join(ctx, "r1", "sm2", m2.Peer)
99 mev := drain(m2.Sub)
100 if len(mev) != 1 || mev[0].Type != TypeRoster {
101 t.Fatalf("the joining member got %v, want one roster", types(mev))
102 }
103 if len(mev[0].Roster.Waiting) != 1 || mev[0].Roster.Waiting[0].Peer != w.Peer {
104 t.Fatalf("a member's roster must carry the queue, got %+v", mev[0].Roster.Waiting)
105 }
106}
107
108// W-1 row 2 — mutation: rosterFor's `!p.Admitted` → `!p.Joined`.
109//
110// roster.peers is the mesh: everyone the viewer should be negotiating a
111// connection with. A peer that has only knocked is not in it, for
112// anyone — a member sees it in Waiting and nowhere else, and a guest
113// does not see it at all.
114func TestTheRosterMeshListsAdmittedPeersOnly(t *testing.T) {
115 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
116 h := testHub(&now)
117 ctx := context.Background()
118 m, g, w := guestRoom(t, h)
119
120 // A member joining now is sent the authoritative snapshot.
121 m2, _ := h.Open("r1", "sm2", 2, "Moe", KindMember)
122 if err := h.Join(ctx, "r1", "sm2", m2.Peer); err != nil {
123 t.Fatal(err)
124 }
125 mev := drain(m2.Sub)
126 if len(mev) != 1 || mev[0].Type != TypeRoster {
127 t.Fatalf("joining member got %v", types(mev))
128 }
129 assertMeshExcludesWaiting(t, "the joining member", mev[0].Roster, w.Peer,
130 m.Peer, g.Peer, m2.Peer)
131
132 // And the same snapshot as an admitted guest sees it, which is the
133 // half with no Waiting field to carry the knock instead.
134 g2, _ := h.Open("r1", "sg2", 102, "Gil", KindGuest)
135 h.Join(ctx, "r1", "sg2", g2.Peer)
136 drain(g2.Sub)
137 if err := h.Admit(ctx, "r1", "sm", m.Peer, g2.Peer); err != nil {
138 t.Fatal(err)
139 }
140 gev := drain(g2.Sub)
141 if len(gev) != 1 || gev[0].Type != TypeRoster {
142 t.Fatalf("admitted guest got %v", types(gev))
143 }
144 assertMeshExcludesWaiting(t, "the admitted guest", gev[0].Roster, w.Peer,
145 m.Peer, g.Peer, m2.Peer, g2.Peer)
146}
147
148func assertMeshExcludesWaiting(t *testing.T, who string, ro *Roster, waiting PeerID, want ...PeerID) {
149 t.Helper()
150 got := map[PeerID]bool{}
151 for _, p := range ro.Peers {
152 got[p.Peer] = true
153 }
154 if got[waiting] {
155 t.Fatalf("%s: roster.peers listed a peer that has only knocked (%s): %+v", who, waiting, ro.Peers)
156 }
157 if len(got) != len(want) {
158 t.Fatalf("%s: roster.peers has %d entries, want %d: %+v", who, len(got), len(want), ro.Peers)
159 }
160 for _, id := range want {
161 if !got[id] {
162 t.Fatalf("%s: roster.peers is missing %s: %+v", who, id, ro.Peers)
163 }
164 }
165}
166
167// W-1 row 3 — mutation: admitLocked's `q.Admitted` dropped from the
168// fan-out condition.
169//
170// §13: "a waiting guest receiving nothing but hello and its own
171// outcome". Every admission after the knock — another guest let in, a
172// member arriving — must leave the waiting subscriber silent, because
173// a `peer` event is an instruction to open a connection to someone the
174// guest has no business reaching.
175func TestAWaitingGuestReceivesNoPeerEventForAnyoneElsesAdmission(t *testing.T) {
176 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
177 h := testHub(&now)
178 ctx := context.Background()
179 m, _, w := guestRoom(t, h)
180
181 // Another guest is let in while this one is still waiting.
182 g2, _ := h.Open("r1", "sg2", 102, "Gil", KindGuest)
183 h.Join(ctx, "r1", "sg2", g2.Peer)
184 if err := h.Admit(ctx, "r1", "sm", m.Peer, g2.Peer); err != nil {
185 t.Fatal(err)
186 }
187 if ev := drain(w.Sub); len(ev) != 0 {
188 t.Fatalf("the waiting guest was told about another admission: %v (%+v)", types(ev), ev)
189 }
190
191 // And a member arrives, which is an admission too.
192 m2, _ := h.Open("r1", "sm2", 2, "Moe", KindMember)
193 if err := h.Join(ctx, "r1", "sm2", m2.Peer); err != nil {
194 t.Fatal(err)
195 }
196 if ev := drain(w.Sub); len(ev) != 0 {
197 t.Fatalf("the waiting guest was told a member joined: %v (%+v)", types(ev), ev)
198 }
199
200 // The rule is an audience, not silence: the peer already in the mesh
201 // was told both times.
202 if n := countOf(drain(m.Sub), TypePeer); n != 2 {
203 t.Fatalf("the member in the mesh got %d peer events, want 2", n)
204 }
205}
206
207// W-1 row 4 — mutation: refreshRostersLocked's `q.Admitted` dropped.
208//
209// A kind change owes every *admitted* peer a fresh roster (§2.4). A
210// waiting guest is owed nothing, and the roster it would be handed is
211// the whole mesh — plus, when the changed peer is a member, the queue.
212func TestAKindChangeNeverHandsAWaitingGuestTheRoster(t *testing.T) {
213 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
214 kinds := map[int64]Kind{1: KindMember, 100: KindGuest, 101: KindGuest}
215 h := New(Config{
216 Now: func() time.Time { return now },
217 StandingFor: func(ctx context.Context, room string, personID int64) (Kind, bool, error) {
218 return kinds[personID], true, nil
219 },
220 })
221 ctx := context.Background()
222 m, g, w := guestRoom(t, h)
223
224 // The admitted guest is added to the team mid-call: the tick
225 // re-derives its kind, and the change refreshes the rosters.
226 kinds[100] = KindMember
227 h.Tick(ctx)
228
229 if ev := drain(w.Sub); len(ev) != 0 {
230 t.Fatalf("a kind change reached the waiting guest: %v (%+v)", types(ev), ev)
231 }
232 // The peers that were owed the refresh got it, so the test is not
233 // passing because nothing happened at all.
234 for _, c := range []struct {
235 who string
236 r OpenResult
237 }{{"the member", m}, {"the promoted guest", g}} {
238 ev := drain(c.r.Sub)
239 if len(ev) != 1 || ev[0].Type != TypeRoster {
240 t.Fatalf("%s got %v, want the refreshed roster", c.who, types(ev))
241 }
242 }
243}
244
245// W-1 row 5 — mutation: knock's audience `admittedMembers` →
246// `admittedPeers`.
247//
248// A knock carries the display name of the person asking to come in.
249// Only a member can act on it, and only a member may learn it: an
250// admitted guest is in the mesh, not on the door. knock-gone has the
251// same audience for the same reason.
252func TestAKnockReachesAdmittedMembersOnly(t *testing.T) {
253 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
254 h := testHub(&now)
255 ctx := context.Background()
256 m, g, w := guestRoom(t, h)
257
258 newcomer, _ := h.Open("r1", "sn", 103, "Nan", KindGuest)
259 if err := h.Join(ctx, "r1", "sn", newcomer.Peer); err != nil {
260 t.Fatal(err)
261 }
262
263 mev := drain(m.Sub)
264 if len(mev) != 1 || mev[0].Type != TypeKnock || mev[0].From != newcomer.Peer || mev[0].Name != "Nan" {
265 t.Fatalf("the member did not get the knock: %v (%+v)", types(mev), mev)
266 }
267 if ev := drain(g.Sub); len(ev) != 0 {
268 t.Fatalf("an admitted guest learned who is knocking: %v (%+v)", types(ev), ev)
269 }
270 if ev := drain(w.Sub); len(ev) != 0 {
271 t.Fatalf("a waiting guest learned who is knocking: %v (%+v)", types(ev), ev)
272 }
273
274 if err := h.Admit(ctx, "r1", "sm", m.Peer, newcomer.Peer); err != nil {
275 t.Fatal(err)
276 }
277 got := types(drain(m.Sub))
278 if len(got) != 2 || got[0] != TypePeer || got[1] != TypeKnockGone {
279 t.Fatalf("the member got %v, want peer then knock-gone", got)
280 }
281 if ev := types(drain(g.Sub)); len(ev) != 1 || ev[0] != TypePeer {
282 t.Fatalf("the admitted guest got %v, want the peer event and no knock-gone", ev)
283 }
284 if ev := drain(w.Sub); len(ev) != 0 {
285 t.Fatalf("the waiting guest got %v", types(ev))
286 }
287}
288
289// Review W-5: `Remove`'s refusal to take its own actor as a target. The
290// guard survived being deleted, and what it stops is worth a test — a
291// member removing itself would end its own participation through the
292// removal path, which announces `gone` and writes no tombstone, so the
293// tab would find itself out of the room with no way to resume and no
294// reason given. Leaving is `leave`, and it is a different ending.
295func TestAMemberCannotRemoveItself(t *testing.T) {
296 now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
297 h := testHub(&now)
298 ctx := context.Background()
299 m, g, _ := guestRoom(t, h)
300
301 if err := h.Remove(ctx, "r1", "sm", m.Peer, m.Peer); err != ErrUnknownPeer {
302 t.Fatalf("a member removing itself: %v, want ErrUnknownPeer", err)
303 }
304 if p, ok := h.Find("r1", "sm", m.Peer); !ok || !p.Admitted {
305 t.Fatal("the member removed itself")
306 }
307 if ev := drain(m.Sub); len(ev) != 0 {
308 t.Fatalf("the room was told something: %v", types(ev))
309 }
310 // The refusal is the same sentinel an unknown target gets, so it
311 // tells the caller nothing about itself either.
312 if err := h.Remove(ctx, "r1", "sm", m.Peer, PeerID("0123456789abcdef0123456789abcdef")); err != ErrUnknownPeer {
313 t.Fatalf("an unknown target: %v", err)
314 }
315 // And a real other peer is still removable, so the guard is not
316 // simply refusing everything.
317 if err := h.Remove(ctx, "r1", "sm", m.Peer, g.Peer); err != nil {
318 t.Fatalf("removing someone else: %v", err)
319 }
320}
321