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 "net/http"
5 "net/http/httptest"
6 "net/url"
7 "strings"
8 "testing"
9 "time"
10
11 calendar "amadan.net/oficina/calendar/internal/calendar"
12)
13
14// The caps, the gate and the day bound (booking spec §4.2, §7).
15
16func fromIP(t *testing.T, h http.Handler, ip, target string, form url.Values) *httptest.ResponseRecorder {
17 t.Helper()
18 method := http.MethodGet
19 if form != nil {
20 method = http.MethodPost
21 }
22 req := calendar.RequestForTest(nil, method, target, form)
23 req.Header.Set("X-Forwarded-For", ip)
24 rec := httptest.NewRecorder()
25 h.ServeHTTP(rec, req)
26 return rec
27}
28
29func TestTheSixthOpenHoldFromOneIPIs429(t *testing.T) {
30 app, _, path, _ := publishedPage(t)
31 h := app.Handler()
32 _, starts := futureDaySlots(t, h, path)
33 for i := 0; i < 5; i++ {
34 rec := fromIP(t, h, "203.0.113.9", path, holdForm(starts[i], "S", "s"+string(rune('a'+i))+"@example.com"))
35 if rec.Code != 200 {
36 t.Fatalf("hold %d: %d", i, rec.Code)
37 }
38 }
39 rec := fromIP(t, h, "203.0.113.9", path, holdForm(starts[5], "S", "sx@example.com"))
40 if rec.Code != http.StatusTooManyRequests {
41 t.Errorf("sixth hold from one IP: %d, want 429", rec.Code)
42 }
43 // A different IP is fine.
44 if rec := fromIP(t, h, "203.0.113.10", path, holdForm(starts[5], "S", "sy@example.com")); rec.Code != 200 {
45 t.Errorf("hold from another IP: %d", rec.Code)
46 }
47}
48
49func TestTheThirdOpenHoldForOneAddressIs429(t *testing.T) {
50 app, _, path, _ := publishedPage(t)
51 h := app.Handler()
52 _, starts := futureDaySlots(t, h, path)
53 for i := 0; i < 2; i++ {
54 fromIP(t, h, "203.0.113."+string(rune('1'+i)), path, holdForm(starts[i], "S", "same@example.com"))
55 }
56 if rec := fromIP(t, h, "203.0.113.5", path, holdForm(starts[2], "S", "same@example.com")); rec.Code != http.StatusTooManyRequests {
57 t.Errorf("third hold for one address: %d, want 429", rec.Code)
58 }
59}
60
61func TestTheGateRefusesTheSixtyFirstRequestInAMinute(t *testing.T) {
62 app, _, path, _ := publishedPage(t)
63 h := app.Handler()
64 var last int
65 for i := 0; i < 61; i++ {
66 last = fromIP(t, h, "198.51.100.7", path, nil).Code
67 }
68 if last != http.StatusTooManyRequests {
69 t.Errorf("61st request: %d, want 429", last)
70 }
71 if rec := fromIP(t, h, "198.51.100.8", path, nil); rec.Code != 200 {
72 t.Errorf("another IP after the gate closed on one: %d", rec.Code)
73 }
74}
75
76func TestADayOutsideTheHorizonIs404AndAStrangeZoneFallsBack(t *testing.T) {
77 app, _, path, _ := publishedPage(t)
78 h := app.Handler()
79 far := time.Now().AddDate(0, 0, 60).Format("2006-01-02")
80 if rec := strangerGet(t, h, path+"?day="+far); rec.Code != http.StatusNotFound {
81 t.Errorf("a day past the horizon: %d, want 404", rec.Code)
82 }
83 past := time.Now().AddDate(0, 0, -10).Format("2006-01-02")
84 if rec := strangerGet(t, h, path+"?day="+past); rec.Code != http.StatusNotFound {
85 t.Errorf("a day in the past: %d, want 404", rec.Code)
86 }
87 body := strangerGet(t, h, path+"?zone=Nowhere/Town").Body.String()
88 if !strings.Contains(body, `<option value="UTC" selected`) {
89 t.Error("an unknown zone did not fall back to the host's")
90 }
91}
92
93func TestAnOversizedFormIs422(t *testing.T) {
94 app, _, path, _ := publishedPage(t)
95 h := app.Handler()
96 _, starts := futureDaySlots(t, h, path)
97 form := holdForm(starts[0], "A", "a@example.com")
98 form.Set("note", strings.Repeat("x", 20<<10))
99 if rec := strangerPost(t, h, path, form); rec.Code != http.StatusUnprocessableEntity {
100 t.Errorf("20 KiB form: %d, want 422", rec.Code)
101 }
102 form.Set("note", strings.Repeat("é", 1500)) // 1500 runes, 3000 bytes: over the rune limit
103 if rec := strangerPost(t, h, path, form); rec.Code != http.StatusUnprocessableEntity || !strings.Contains(rec.Body.String(), "shorter") {
104 t.Errorf("a 1500-rune note: %d", rec.Code)
105 }
106}
107