oficina / docs Public

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

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "amadan.net/oficina/home/roster"
5 "bufio"
6 "context"
7 "io"
8 "net/http"
9 "net/http/httptest"
10 "net/url"
11 "strings"
12 "testing"
13 "time"
14)
15
16func teamOpenActualStream(t *testing.T, f *teamRouterFixture, b *routerBrowser, srv *httptest.Server) *http.Response {
17 t.Helper()
18 r, e := http.NewRequestWithContext(t.Context(), "GET", srv.URL+"/d/"+f.own.Sqid+"/stream", nil)
19 if e != nil {
20 t.Fatal(e)
21 }
22 r.Host = strings.TrimPrefix(bridgeOrigin, "https://")
23 for _, c := range b.cookies {
24 r.AddCookie(c)
25 }
26 res, e := srv.Client().Do(r)
27 if e != nil {
28 t.Fatal(e)
29 }
30 t.Cleanup(func() { res.Body.Close() })
31 if res.StatusCode != 200 {
32 raw, _ := io.ReadAll(res.Body)
33 t.Fatal("stream refused", res.StatusCode, res.Request.URL.String(), string(raw))
34 }
35 reader := bufio.NewReader(res.Body)
36 name, _ := nextEvent(t, reader, time.Second)
37 if name != "update" {
38 t.Fatal("initial sequence event missing")
39 }
40 // Keep the same buffered reader: it may already contain the next frame.
41 res.Body = struct {
42 io.Reader
43 io.Closer
44 }{reader, res.Body}
45 return res
46}
47func TestTeamStreamActualSessionWriterWaitCannotExtendOriginalLease(t *testing.T) {
48 f := newTeamRouterFixture(t)
49 b := f.browser()
50 b.localSignin(t, "alice@example.com")
51 b.linkProof(t, teamSubject('a'), "alice@example.com")
52 routerRedirect(t, b.do("POST", "/account/home/confirm", ""), "/")
53 if r := b.do("GET", "/d/"+f.own.Sqid, ""); r.Code != 200 {
54 t.Fatal("initial membership unavailable", r.Code)
55 }
56 streamDurations(t, f.app, 40*time.Millisecond, 5*time.Second)
57 f.directory.change(nil, true)
58 f.clock.step(59 * time.Second)
59 if e := f.app.store.team.cache.Refresh(t.Context()); e == nil {
60 t.Fatal("failed directory refresh accepted")
61 }
62 srv := httptest.NewServer(f.app.Handler())
63 defer srv.Close()
64 res := teamOpenActualStream(t, f, b, srv)
65 defer res.Body.Close()
66 writer := f.app.db.Writer()
67 tx, e := writer.BeginTx(t.Context(), nil)
68 if e != nil {
69 t.Fatal(e)
70 }
71 defer tx.Rollback()
72 baseline := writer.Stats().WaitCount
73 done := make(chan struct{})
74 go func() { io.Copy(io.Discard, res.Body); close(done) }()
75 deadline := time.Now().Add(300 * time.Millisecond)
76 for writer.Stats().WaitCount == baseline && time.Now().Before(deadline) {
77 time.Sleep(time.Millisecond)
78 }
79 if writer.Stats().WaitCount == baseline {
80 t.Fatal("actual session reader never queued behind writer")
81 }
82 select {
83 case <-done:
84 case <-time.After(1300 * time.Millisecond):
85 t.Fatal("writer wait held stream beyond its original lease")
86 }
87 // The stream ended while the writer remains locked; cancellation did not need
88 // the transaction to finish and did not mint a new session.
89 if e = tx.Rollback(); e != nil {
90 t.Fatal(e)
91 }
92 if _, ok := f.sessions.From(b.request("GET", "/", "")); !ok {
93 t.Fatal("stream cancellation revoked legacy session")
94 }
95}
96func TestTeamStreamHealthyPublicationClosesOriginalLeaseAndReconnects(t *testing.T) {
97 f := newTeamRouterFixture(t)
98 b := f.browser()
99 b.localSignin(t, "alice@example.com")
100 b.linkProof(t, teamSubject('a'), "alice@example.com")
101 routerRedirect(t, b.do("POST", "/account/home/confirm", ""), "/")
102 streamDurations(t, f.app, 30*time.Millisecond, 5*time.Second)
103 srv := httptest.NewServer(f.app.Handler())
104 defer srv.Close()
105 res := teamOpenActualStream(t, f, b, srv)
106 defer res.Body.Close()
107 f.clock.step(roster.RetryAfter)
108 if e := f.app.store.team.cache.Refresh(context.Background()); e != nil {
109 t.Fatal(e)
110 }
111 done := make(chan struct{})
112 go func() { io.Copy(io.Discard, res.Body); close(done) }()
113 select {
114 case <-done:
115 case <-time.After(time.Second):
116 t.Fatal("old stream renewed instead of closing after publication")
117 }
118 fresh := teamOpenActualStream(t, f, b, srv)
119 fresh.Body.Close()
120}
121
122func TestTeamStreamGuestGrantAndActualSessionRevocation(t *testing.T) {
123 for _, reason := range []string{"grant", "session"} {
124 t.Run(reason, func(t *testing.T) {
125 f := newTeamRouterFixture(t)
126 guest := f.browser()
127 guest.localSignin(t, "alice@example.com")
128 owner := f.browser()
129 owner.localSignin(t, "bob@example.com")
130 owner.linkProof(t, teamSubject('b'), "bob@example.com")
131 routerRedirect(t, owner.do("POST", "/account/home/confirm", ""), "/")
132 streamDurations(t, f.app, 30*time.Millisecond, 5*time.Second)
133 srv := httptest.NewServer(f.app.Handler())
134 defer srv.Close()
135 shared := *f
136 shared.own = f.shared
137 res := teamOpenActualStream(t, &shared, guest, srv)
138 defer res.Body.Close()
139 if reason == "grant" {
140 routerRedirect(t, owner.do("POST", "/d/"+f.shared.Sqid+"/share/revoke", url.Values{"address": {"alice@example.com"}}.Encode()), "/d/"+f.shared.Sqid+"/share")
141 } else {
142 signoutChained(t, guest.do("POST", "/signout", ""))
143 }
144 done := make(chan struct{})
145 go func() { io.Copy(io.Discard, res.Body); close(done) }()
146 select {
147 case <-done:
148 case <-time.After(time.Second):
149 t.Fatal("live stream survived actual " + reason + " revocation")
150 }
151 })
152 }
153}
154