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 "context"
5 "io"
6 "log/slog"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "amadan.net/rastrillo/rastrillo/db"
12)
13
14// testApp builds the whole app over a fresh temp database and hands back
15// the *app itself, which NewForTest deliberately does not. These two
16// tables are about internals — Authorize and standingFor — that no HTTP
17// request reaches directly.
18func testApp(t *testing.T, members ...string) *app {
19 t.Helper()
20 logger := slog.New(slog.NewTextHandler(io.Discard, nil))
21 d, err := db.Open(filepath.Join(t.TempDir(), "a.db"), logger)
22 if err != nil {
23 t.Fatal(err)
24 }
25 t.Cleanup(func() { d.Close() })
26 _, a, err := buildForTest(t, d, Config{
27 Env: EnvDevelopment, Origin: "http://app.test",
28 AllowedAddresses: members, Logger: logger,
29 })
30 if err != nil {
31 t.Fatal(err)
32 }
33 now := time.Date(2026, 9, 8, 10, 0, 0, 0, time.UTC)
34 a.store.clk.set(func() time.Time { return now })
35 return a
36}
37
38// Review C1-8: refusing an outsider in front of Begin never exercises
39// Authorize, so nothing proved that Authorize itself refuses an address
40// with no pending row. Spec §3.2: on the allowlist, or holding an
41// unexpired pending row whose link is still live.
42func TestAuthorizeAdmitsMembersAndLivePendingRowsOnly(t *testing.T) {
43 a := testApp(t, "paul@example.com")
44 ctx := context.Background()
45 m, tok, err := a.store.createMeeting(ctx, "x", nil, "person:1")
46 if err != nil {
47 t.Fatal(err)
48 }
49 link, _, _, _ := a.store.liveLink(ctx, tok)
50
51 if a.authorize("unknown@example.com") {
52 t.Fatal("an address with no pending row was admitted")
53 }
54 if !a.authorize("paul@example.com") {
55 t.Fatal("an allowlisted address was refused")
56 }
57 if !a.authorize("PAUL@Example.com ") {
58 t.Fatal("an allowlisted address was refused on case or whitespace")
59 }
60 if err := a.store.writePending(ctx, "guest@example.com", link); err != nil {
61 t.Fatal(err)
62 }
63 if !a.authorize("guest@example.com") {
64 t.Fatal("an address with a live pending row was refused")
65 }
66 if _, err := a.store.replaceLink(ctx, m.ID, "person:1"); err != nil {
67 t.Fatal(err)
68 }
69 if a.authorize("guest@example.com") {
70 t.Fatal("an address whose link was replaced was still admitted")
71 }
72}
73
74// Review C1-8: the one-per-request entry authority (spec §2.3), asserted
75// directly rather than through the holding page's status code.
76func TestStandingTable(t *testing.T) {
77 ctx := context.Background()
78 for _, tc := range []struct {
79 name string
80 allowlisted bool
81 grant bool
82 expired bool
83 want Standing
84 }{
85 {name: "allowlisted, no grant", allowlisted: true, want: StandingMember},
86 {name: "allowlisted, live grant", allowlisted: true, grant: true, want: StandingMember},
87 {name: "allowlisted, expired grant", allowlisted: true, grant: true, expired: true, want: StandingMember},
88 {name: "outsider, no grant", want: StandingNone},
89 {name: "outsider, live grant", grant: true, want: StandingGuest},
90 {name: "outsider, expired grant", grant: true, expired: true, want: StandingNone},
91 } {
92 t.Run(tc.name, func(t *testing.T) {
93 const address = "someone@example.com"
94 members := []string{"other@example.com"}
95 if tc.allowlisted {
96 members = append(members, address)
97 }
98 a := testApp(t, members...)
99 m, _, err := a.store.createMeeting(ctx, "x", nil, "person:1")
100 if err != nil {
101 t.Fatal(err)
102 }
103 p, err := a.store.ensurePerson(ctx, address)
104 if err != nil {
105 t.Fatal(err)
106 }
107 if tc.grant {
108 if _, err := a.store.mintGrant(ctx, m, p.ID); err != nil {
109 t.Fatal(err)
110 }
111 }
112 if tc.expired {
113 after := a.store.now().Add(grantLife + time.Minute)
114 a.store.clk.set(func() time.Time { return after })
115 }
116 got, err := a.standingFor(ctx, p, m)
117 if err != nil {
118 t.Fatal(err)
119 }
120 if got != tc.want {
121 t.Fatalf("standing %s, want %s", got, tc.want)
122 }
123 })
124 }
125}
126