aboutsummaryrefslogtreecommitdiff
path: root/internal/web/apifeed_test.go
blob: 97b657e8f9474839ac4e23d676ed21baf63ed635 (plain) (blame)
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)
	}
}