oficina / calendar Public

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

Plain git — no account needed to clone.

Download

Download this file

1package calendartest
2
3import (
4 "bytes"
5 "context"
6 "io"
7 "log/slog"
8 "net/http"
9 "net/url"
10 "path/filepath"
11 "regexp"
12 "strings"
13 "testing"
14 "time"
15
16 calendar "amadan.net/oficina/calendar/internal/calendar"
17)
18
19// The sweep through the router (booking spec §8), and the two rules
20// about what must never reach a log.
21
22func TestTheTickExpiresHoldsAndWakesForThem(t *testing.T) {
23 t.Setenv("CARLOS_ADMIN_TOKEN", "test-token")
24 app, _, path, _ := publishedPage(t)
25 h := app.Handler()
26 _, starts := futureDaySlots(t, h, path)
27 strangerPost(t, h, path, holdForm(starts[0], "A", "a@example.com"))
28 if _, err := app.DBForTest().Exec(`UPDATE bookings SET hold_expires_at = ?`, time.Now().Add(-time.Minute).Unix()); err != nil {
29 t.Fatal(err)
30 }
31 if !app.NextDue().Before(time.Now()) {
32 t.Error("NextDue does not see the expired hold")
33 }
34 if rec := tick(t, h); rec.Code != http.StatusNoContent {
35 t.Fatalf("tick: %d", rec.Code)
36 }
37 if again := strangerPost(t, h, path, holdForm(starts[0], "B", "b@example.com")); again.Code != 200 {
38 t.Errorf("the slot did not come back after the sweep: %d", again.Code)
39 }
40}
41
42func TestTheTickRetriesOwedMailAndDrainOnRequestDoesOneThing(t *testing.T) {
43 t.Setenv("CARLOS_ADMIN_TOKEN", "test-token")
44 app, mailer, path, _ := publishedPage(t)
45 h := app.Handler()
46 _, starts := futureDaySlots(t, h, path)
47 strangerPost(t, h, path, holdForm(starts[0], "Doc", "doc@tito.io"))
48 link := reManage.FindString(mailer.body(0))
49 // The host's mailbox bounces for now.
50 mailer.refuse = "host@example.com"
51 strangerPost(t, h, link+"/confirm", nil)
52 if mailer.count() != 1 {
53 t.Fatalf("mails after a refused confirm = %d, want the hold mail only", mailer.count())
54 }
55 if !app.NextDue().Before(time.Now().Add(6 * time.Minute)) {
56 t.Error("an owed mail is not on NextDue")
57 }
58 // Age the row past its lease and let the tick retry it, now that
59 // the mailbox is back.
60 mailer.refuse = ""
61 app.DBForTest().Exec(`UPDATE booking_mails SET due_at = ? WHERE sent_at IS NULL`, time.Now().Add(-time.Second).Unix())
62 if rec := tick(t, h); rec.Code != http.StatusNoContent {
63 t.Fatalf("tick: %d", rec.Code)
64 }
65 if mailer.count() != 2 || mailer.to(1) != "host@example.com" {
66 t.Errorf("the tick did not deliver the owed host mail: %d to %q", mailer.count(), mailer.to(1))
67 }
68
69 // DrainOnRequest: a signed-in page view does one thing. Queue two
70 // mails (a reschedule), refuse nothing, age them, load one page.
71 session := app.SignInForTest(t, "host@example.com")
72 picker := strangerGet(t, h, link+"/reschedule?day="+time.Now().UTC().AddDate(0, 0, 1).Format("2006-01-02")+"&zone=UTC").Body.String()
73 rev := regexp.MustCompile(`name="rev" value="(\d+)"`).FindStringSubmatch(picker)
74 next := reStart.FindAllStringSubmatch(picker, -1)
75 mailer.refuse = "nobody-yet" // stop the immediate try from sending
76 mailer.refuse = "@" // refuse everything for the immediate try
77 strangerPost(t, h, link+"/reschedule", url.Values{"start": {next[1][1]}, "zone": {"UTC"}, "rev": {rev[1]}})
78 mailer.refuse = ""
79 before := mailer.count()
80 app.DBForTest().Exec(`UPDATE booking_mails SET due_at = ? WHERE sent_at IS NULL`, time.Now().Add(-time.Second).Unix())
81 getAs(t, h, session, "/today")
82 if mailer.count() != before+1 {
83 t.Errorf("a page view sent %d mails, want exactly one", mailer.count()-before)
84 }
85}
86
87// No address, no token, no title, in any log line, after a failed send.
88func TestNothingSealedReachesTheLog(t *testing.T) {
89 var buf bytes.Buffer
90 mailer := &pickyMailer{refuse: "dermot"}
91 app, err := calendar.New(calendar.Config{
92 DBPath: filepath.Join(t.TempDir(), "calendar.db"), Env: "development", Origin: testOrigin,
93 Mailer: mailer, Logger: slog.New(slog.NewTextHandler(&buf, nil)),
94 })
95 if err != nil {
96 t.Fatal(err)
97 }
98 t.Cleanup(func() { app.Close() })
99 session := app.SignInForTest(t, "host@example.com")
100 h := app.Handler()
101 postAs(t, h, session, "/settings", url.Values{"zone": {"UTC"}, "week_start": {"1"},
102 "default_reminder_min": {"10"}, "default_all_day_min": {"0"}, "handle": {"paul"}})
103 postAs(t, h, session, "/settings/booking", url.Values{"title": {"Interrogation"}, "availability": {"every day 9-5"},
104 "duration_min": {"30"}, "buffer_min": {"0"}, "notice_min": {"0"}, "horizon_days": {"30"}})
105 postAs(t, h, session, "/settings/booking/1/publish", url.Values{"publish": {"1"}})
106 path := regexp.MustCompile(`/book/paul/[a-z0-9]{8}`).FindString(getAs(t, h, session, "/settings/booking").Body.String())
107 _, starts := futureDaySlots(t, h, path)
108 strangerPost(t, h, path, holdForm(starts[0], "Dermot Lacey", "dermot@elsewhere.example"))
109
110 log := buf.String()
111 if !strings.Contains(log, "booking.mail") {
112 t.Fatalf("the failed send was not logged at all:\n%s", log)
113 }
114 for _, never := range []string{"dermot", "Dermot", "Interrogation"} {
115 if strings.Contains(log, never) {
116 t.Errorf("the log carries %q:\n%s", never, log)
117 }
118 }
119 if regexp.MustCompile(`[0-9a-f]{64}`).MatchString(log) {
120 t.Error("the log carries something shaped like a token")
121 }
122 _ = io.Discard
123 _ = context.Background
124}
125