oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "bufio"
5 "io"
6 "strings"
7 "testing"
8 "time"
9)
10
11// A grant is checked when the stream opens and never again, so a
12// revoked guest keeps receiving cells on the connection they already
13// hold — for up to maxStreamLife, and every edit resets the idle timer
14// that would otherwise end it.
15//
16// Found by an independent review (Codex, 2026-09-05). It is the quiet
17// shape of leak the stream gate was written to prevent: the gate was
18// real, and it ran once.
19func TestARevokedGuestStopsReceivingCells(t *testing.T) {
20 app, cookies := signIn(t)
21 s, cells := newSheet(t, app, cookies, "Budget")
22 ada := guestOn(t, app, s.ID, "ada@elsewhere.example", SheetViewer)
23 adaCookies := signInAs(t, app, "ada@elsewhere.example")
24
25 srv := liveServer(t, app)
26 resp := openStream(t, srv, s, adaCookies)
27 waitForSubscriber(t, app, s.TabID)
28
29 // Still granted: this edit must arrive. Without it a later silence
30 // would prove nothing — the stream might simply never have worked.
31 postCell(t, app, cookies, cells, `{"r":0,"c":0,"raw":"before"}`)
32 if line := readEventWithin(t, resp.Body, 5*time.Second); !strings.Contains(line, "before") {
33 t.Fatalf("the guest did not receive the first edit: %q", line)
34 }
35
36 if err := app.revoke(s.ID, ada.ID); err != nil {
37 t.Fatalf("revoke: %v", err)
38 }
39 postCell(t, app, cookies, cells, `{"r":0,"c":1,"raw":"CONFIDENTIAL"}`)
40
41 if got := readDataOrSilence(resp.Body, 3*time.Second); strings.Contains(got, "CONFIDENTIAL") {
42 t.Fatalf("a revoked guest received a cell written after the revoke: %q", got)
43 }
44}
45
46// readDataOrSilence returns the next SSE data line, or "" if the stream
47// says nothing in time.
48//
49// Unlike readEventWithin it does not fail on silence, because silence
50// is the passing case here. It reads in a goroutine and abandons it on
51// timeout rather than blocking the test.
52func readDataOrSilence(body io.Reader, d time.Duration) string {
53 ch := make(chan string, 1)
54 go func() {
55 sc := bufio.NewScanner(body)
56 for sc.Scan() {
57 if strings.HasPrefix(sc.Text(), "data: ") {
58 ch <- sc.Text()
59 return
60 }
61 }
62 ch <- ""
63 }()
64 select {
65 case line := <-ch:
66 return line
67 case <-time.After(d):
68 return ""
69 }
70}
71