1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
package web
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/lukaszkasprzak/lectio/internal/config"
)
// TestAPICalendarJSON exercises GET /api/calendar.json's success path: a
// single ?date= resolves to exactly one day, with the correct Content-Type,
// nosniff, and a body that parses as the lectio.calendar/1 envelope.
func TestAPICalendarJSON(t *testing.T) {
srv := NewServer(config.Default())
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/api/calendar.json?date=2026-01-06", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
}
if ct := rec.Header().Get("Content-Type"); ct != "application/json; charset=utf-8" {
t.Errorf("Content-Type = %q", ct)
}
if ns := rec.Header().Get("X-Content-Type-Options"); ns != "nosniff" {
t.Errorf("X-Content-Type-Options = %q, want nosniff", ns)
}
var out struct {
Schema string `json:"schema"`
Form string `json:"form"`
Days []struct {
Date string `json:"date"`
} `json:"days"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
t.Fatalf("body did not parse as JSON: %v; body=%s", err, rec.Body.String())
}
if len(out.Days) != 1 {
t.Fatalf("got %d days, want 1", len(out.Days))
}
}
// TestCalendarICS exercises GET /calendar.ics's success path: a whole ?year=
// resolves to a VCALENDAR body, with the correct Content-Type and nosniff.
func TestCalendarICS(t *testing.T) {
srv := NewServer(config.Default())
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/calendar.ics?year=2026", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
}
if ct := rec.Header().Get("Content-Type"); ct != "text/calendar; charset=utf-8" {
t.Errorf("Content-Type = %q", ct)
}
if ns := rec.Header().Get("X-Content-Type-Options"); ns != "nosniff" {
t.Errorf("X-Content-Type-Options = %q, want nosniff", ns)
}
if !strings.Contains(rec.Body.String(), "BEGIN:VCALENDAR") {
t.Errorf("body missing BEGIN:VCALENDAR")
}
}
// TestAPICalendarJSONBadDate: an unparsable date is rejected with 400 (D3).
func TestAPICalendarJSONBadDate(t *testing.T) {
srv := NewServer(config.Default())
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/api/calendar.json?date=not-a-date", nil))
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", rec.Code)
}
}
// TestAPICalendarJSONBadForm: an out-of-enum form is rejected with 400, not
// silently defaulted (D3).
func TestAPICalendarJSONBadForm(t *testing.T) {
srv := NewServer(config.Default())
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/api/calendar.json?from=2026-01-01&to=2026-01-02&form=bogus", nil))
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", rec.Code)
}
}
// TestCalendarICSOverCap: a range exceeding the 1830-day web cap (D1) is
// rejected with 400 BEFORE any day is built -- the body must not contain a
// VEVENT, proving the request never reached compute.
func TestCalendarICSOverCap(t *testing.T) {
srv := NewServer(config.Default())
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/calendar.ics?from=2000-01-01&to=2100-01-01", nil))
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", rec.Code)
}
if strings.Contains(rec.Body.String(), "BEGIN:VEVENT") {
t.Errorf("over-cap request produced a VEVENT: %s", rec.Body.String())
}
}
// TestAPICalendarJSONInverted: from after to is rejected with 400.
func TestAPICalendarJSONInverted(t *testing.T) {
srv := NewServer(config.Default())
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/api/calendar.json?from=2026-02-01&to=2026-01-01", nil))
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want 400", rec.Code)
}
}
|