aboutsummaryrefslogtreecommitdiff
path: root/internal/web/apifeed_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/apifeed_test.go')
-rw-r--r--internal/web/apifeed_test.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/internal/web/apifeed_test.go b/internal/web/apifeed_test.go
new file mode 100644
index 0000000..97b657e
--- /dev/null
+++ b/internal/web/apifeed_test.go
@@ -0,0 +1,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)
+ }
+}