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 "context"
5 "net/http/httptest"
6 "net/url"
7 "path/filepath"
8 "sync"
9 "testing"
10 "time"
11
12 rdb "amadan.net/rastrillo/rastrillo/db"
13 "amadan.net/rastrillo/rastrillo/migrate"
14
15 "amadan.net/oficina/sheets/internal/sheets/seal"
16)
17
18// blockingMailer holds every Send until release is closed — an SMTP
19// relay that takes as long as the test likes.
20type blockingMailer struct {
21 release chan struct{}
22 mu sync.Mutex
23 to []string
24}
25
26func (m *blockingMailer) Send(_ context.Context, to, _, _ string) error {
27 <-m.release
28 m.mu.Lock()
29 m.to = append(m.to, to)
30 m.mu.Unlock()
31 return nil
32}
33
34func (m *blockingMailer) sent() []string {
35 m.mu.Lock()
36 defer m.mu.Unlock()
37 return append([]string(nil), m.to...)
38}
39
40// TestSigninNeverWaitsForTheMailer: when the send ran inside the
41// request, an admitted address waited for SMTP before its ?sent=1 and a
42// stranger did not (Astra measured 252 ms against 0.35 ms on Slides,
43// which had the same shape), so the team was readable off a stopwatch.
44// With the mailer blocked, every POST — admitted, stranger, and both
45// with force=1 — must answer, and the admitted links must then go out.
46func TestSigninNeverWaitsForTheMailer(t *testing.T) {
47 d, err := rdb.Open(filepath.Join(t.TempDir(), "test.db"), quietLogger())
48 if err != nil {
49 t.Fatal(err)
50 }
51 t.Cleanup(func() { d.Close() })
52 if _, err := migrate.Apply(context.Background(), d, BootSchema); err != nil {
53 t.Fatal(err)
54 }
55 sealer, err := seal.New(make([]byte, dekBytes))
56 if err != nil {
57 t.Fatal(err)
58 }
59 app := New(Config{DB: d.G, Sealer: sealer, Origin: testOrigin, Admit: Admits([]string{signedInAddress})})
60 bm := &blockingMailer{release: make(chan struct{})}
61 a, err := NewAuth(d.Writer(), AuthEnv{Origin: testOrigin, InstanceKey: "test-instance-key", Owners: []string{signedInAddress}, Mailer: bm}, quietLogger(), app)
62 if err != nil {
63 t.Fatal(err)
64 }
65 t.Cleanup(func() { CloseSigninMail(a) })
66 app.UseAuth(a)
67 var once sync.Once
68 free := func() { once.Do(func() { close(bm.release) }) }
69 t.Cleanup(free)
70 h := app.Router()
71
72 for _, form := range []url.Values{
73 {"address": {signedInAddress}},
74 {"address": {"stranger@example.com"}},
75 {"address": {signedInAddress}, "force": {"1"}},
76 {"address": {"stranger@example.com"}, "force": {"1"}},
77 } {
78 done := make(chan *httptest.ResponseRecorder, 1)
79 go func() { done <- keymailPost(t, h, form) }()
80 select {
81 case rec := <-done:
82 if rec.Code != 303 || rec.Header().Get("Location") != "/signin?sent=1" {
83 t.Errorf("POST %v: %d %q, want 303 /signin?sent=1", form, rec.Code, rec.Header().Get("Location"))
84 }
85 case <-time.After(2 * time.Second):
86 t.Errorf("POST %v waited on the mailer: the response time says the address is admitted", form)
87 free()
88 <-done
89 }
90 }
91
92 free()
93 deadline := time.Now().Add(5 * time.Second)
94 for len(bm.sent()) < 2 && time.Now().Before(deadline) {
95 time.Sleep(10 * time.Millisecond)
96 }
97 time.Sleep(50 * time.Millisecond)
98 if got := bm.sent(); len(got) != 2 || got[0] != signedInAddress || got[1] != signedInAddress {
99 t.Fatalf("mail sent = %v, want two links, both to %s", got, signedInAddress)
100 }
101}
102