oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "bytes"
5 "context"
6 "encoding/base64"
7 "io"
8 "log/slog"
9 "net/http/httptest"
10 "net/url"
11 "path/filepath"
12 "sync"
13 "testing"
14 "time"
15
16 "amadan.net/rastrillo/rastrillo/db"
17)
18
19// blockingMailer holds every Send until release is closed — an SMTP
20// relay that takes as long as the test likes.
21type blockingMailer struct {
22 release chan struct{}
23 mu sync.Mutex
24 to []string
25}
26
27func (m *blockingMailer) Send(_ context.Context, to, _, _ string) error {
28 <-m.release
29 m.mu.Lock()
30 m.to = append(m.to, to)
31 m.mu.Unlock()
32 return nil
33}
34
35func (m *blockingMailer) sent() []string {
36 m.mu.Lock()
37 defer m.mu.Unlock()
38 return append([]string(nil), m.to...)
39}
40
41// TestSigninNeverWaitsForTheMailer: when the send ran inside the
42// request, a member waited for SMTP before its ?sent=1 and a stranger
43// did not (Astra measured 252 ms against 0.35 ms on Slides, which had
44// the same shape). With the mailer blocked, every POST — member,
45// stranger, and both with force=1 — must answer, and the member's
46// links must then go out.
47func TestSigninNeverWaitsForTheMailer(t *testing.T) {
48 logger := slog.New(slog.NewTextHandler(io.Discard, nil))
49 d, err := db.Open(filepath.Join(t.TempDir(), "calls.db"), logger)
50 if err != nil {
51 t.Fatal(err)
52 }
53 t.Cleanup(func() { d.Close() })
54 bm := &blockingMailer{release: make(chan struct{})}
55 var once sync.Once
56 free := func() { once.Do(func() { close(bm.release) }) }
57 t.Cleanup(free)
58 mux, _, err := buildForTest(t, d, Config{Origin: keymailOrigin, InstanceKey: "test-only-instance",
59 DEK: base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{3}, 32)), AllowedAddresses: []string{plainMember}, Mailer: bm, Logger: logger})
60 if err != nil {
61 t.Fatal(err)
62 }
63 f := &keymailFixture{handler: mux}
64
65 for _, form := range []url.Values{
66 {"address": {plainMember}},
67 {"address": {"someone@else.test"}},
68 {"address": {plainMember}, "force": {"1"}},
69 {"address": {"someone@else.test"}, "force": {"1"}},
70 } {
71 done := make(chan *httptest.ResponseRecorder, 1)
72 go func() { done <- f.post(form) }()
73 select {
74 case rec := <-done:
75 if rec.Code != 303 || rec.Header().Get("Location") != "/signin?sent=1" {
76 t.Errorf("POST %v: %d %q, want 303 /signin?sent=1", form, rec.Code, rec.Header().Get("Location"))
77 }
78 case <-time.After(2 * time.Second):
79 t.Errorf("POST %v waited on the mailer: the response time says the address is a member", form)
80 free()
81 <-done
82 }
83 }
84
85 free()
86 deadline := time.Now().Add(5 * time.Second)
87 for len(bm.sent()) < 2 && time.Now().Before(deadline) {
88 time.Sleep(10 * time.Millisecond)
89 }
90 time.Sleep(50 * time.Millisecond)
91 if got := bm.sent(); len(got) != 2 || got[0] != plainMember || got[1] != plainMember {
92 t.Fatalf("mail sent = %v, want two links, both to %s", got, plainMember)
93 }
94}
95