oficina / slides Public

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

Plain git — no account needed to clone.

Download

Download this file

1package slidestest
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "net/url"
9 "strings"
10 "sync"
11 "testing"
12 "time"
13)
14
15// blockingMailer holds every Send until release is closed — an SMTP
16// relay that takes as long as the test likes.
17type blockingMailer struct {
18 release chan struct{}
19 mu sync.Mutex
20 to []string
21}
22
23func (m *blockingMailer) Send(_ context.Context, to, _, _ string) error {
24 <-m.release
25 m.mu.Lock()
26 m.to = append(m.to, to)
27 m.mu.Unlock()
28 return nil
29}
30
31func (m *blockingMailer) sent() []string {
32 m.mu.Lock()
33 defer m.mu.Unlock()
34 return append([]string(nil), m.to...)
35}
36
37// TestSigninNeverWaitsForTheMailer is the timing half of the no-oracle
38// guarantee (Astra on #20, P1). When the send ran inside the request, a
39// permitted address waited for SMTP before its ?sent=1 and a denied one
40// did not — 252 ms against 0.35 ms, the allowlist readable off a
41// stopwatch, and force=1 made Keymail addresses readable the same way.
42//
43// Here the mailer never answers until the test says so. Every POST —
44// permitted, denied, and both again with force=1 — must come back while
45// it is still blocked, and the permitted links must then go out.
46func TestSigninNeverWaitsForTheMailer(t *testing.T) {
47 bm := &blockingMailer{release: make(chan struct{})}
48 cfg, _ := newConfig(t)
49 cfg.Mailer = bm
50 app := newAppWith(t, cfg, nil)
51 var once sync.Once
52 free := func() { once.Do(func() { close(bm.release) }) }
53 t.Cleanup(free) // before the app's own Close, which drains the queue
54
55 posts := []url.Values{
56 {"address": {allowedAddress}},
57 {"address": {deniedAddress}},
58 {"address": {allowedAddress}, "force": {"1"}},
59 {"address": {deniedAddress}, "force": {"1"}},
60 }
61 for _, form := range posts {
62 done := make(chan *httptest.ResponseRecorder, 1)
63 go func() { done <- post(t, app, "/signin", form) }()
64 select {
65 case rec := <-done:
66 if rec.Code != 303 || rec.Header().Get("Location") != "/signin?sent=1" {
67 t.Errorf("POST %v: %d %q, want 303 /signin?sent=1", form, rec.Code, rec.Header().Get("Location"))
68 }
69 case <-time.After(2 * time.Second):
70 t.Errorf("POST %v waited on the mailer: the response time says the address is permitted", form)
71 free()
72 <-done
73 }
74 }
75
76 free()
77 deadline := time.Now().Add(5 * time.Second)
78 for len(bm.sent()) < 2 && time.Now().Before(deadline) {
79 time.Sleep(10 * time.Millisecond)
80 }
81 time.Sleep(50 * time.Millisecond) // and nothing more arrives
82 got := bm.sent()
83 if len(got) != 2 || got[0] != allowedAddress || got[1] != allowedAddress {
84 t.Fatalf("mail sent = %v, want two links, both to %s", got, allowedAddress)
85 }
86}
87
88// TestAFullMailQueueAnswersLikeAnEmptyOne: the queue holds 64 links.
89// With the mailer stuck on the first (permitted) link, enough POSTs to
90// overflow it — each from its own address and IP, so no budget stands
91// in — must every one be answered exactly as the first was, and at
92// once. Every address's link is queued, permitted or not, so a refused
93// address fills the queue as surely as a permitted one (Astra on #20,
94// round 2).
95func TestAFullMailQueueAnswersLikeAnEmptyOne(t *testing.T) {
96 bm := &blockingMailer{release: make(chan struct{})}
97 cfg, _ := newConfig(t)
98 cfg.Mailer = bm
99 app := newAppWith(t, cfg, nil)
100 var once sync.Once
101 free := func() { once.Do(func() { close(bm.release) }) }
102 t.Cleanup(free)
103
104 postFrom := func(i int, address string) *httptest.ResponseRecorder {
105 req := httptest.NewRequest(http.MethodPost, "/signin", strings.NewReader(url.Values{"address": {address}}.Encode()))
106 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
107 req.Header.Set("Origin", testOrigin)
108 req.Header.Set("Sec-Fetch-Site", "same-origin")
109 req.RemoteAddr = fmt.Sprintf("198.51.100.%d:1234", i%250+1)
110 rec := httptest.NewRecorder()
111 app.ServeHTTP(rec, req)
112 return rec
113 }
114 first := postFrom(0, allowedAddress)
115 for i := 1; i <= 80; i++ {
116 start := time.Now()
117 rec := postFrom(i, fmt.Sprintf("stranger%d@else.test", i))
118 if took := time.Since(start); took > time.Second {
119 t.Fatalf("POST %d took %v with the queue full", i, took)
120 }
121 if rec.Code != first.Code || rec.Header().Get("Location") != first.Header().Get("Location") || rec.Body.String() != first.Body.String() {
122 t.Fatalf("POST %d answered %d %q, want exactly the first answer %d %q", i, rec.Code, rec.Header().Get("Location"), first.Code, first.Header().Get("Location"))
123 }
124 }
125}
126