package web import ( "net" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" "github.com/lukaszkasprzak/lectio/internal/bible" "github.com/lukaszkasprzak/lectio/internal/config" "github.com/lukaszkasprzak/lectio/internal/liturgy" ) // TestServer exercises NewServer's handler tree end to end via httptest. Every // reading is computed offline (no network, no cache, no fixture server). func TestServer(t *testing.T) { srv := NewServer(config.Default()) t.Run("index page", func(t *testing.T) { rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/?date=2026-07-22&v=wuj", nil)) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } body := rec.Body.String() // config.Default() -> UILanguage "en", so the gospel heading's part // label is localised to "Gospel" (render.LocalizeHeading). if !strings.Contains(body, "Gospel") { t.Errorf("body missing reading heading: %q", body) } // ?v=wuj -> the gospel is rendered from the Wujek corpus ("grobu" is // distinctly Polish Wujek verse text, not Latin/English). if !strings.Contains(body, "grobu") { t.Errorf("body missing Wujek verse text: %q", body) } if !strings.Contains(body, "htmx") { t.Errorf("body missing htmx reference") } if !strings.Contains(body, `id="theme"`) { t.Errorf("body missing theme ") } // The offline engine localises the celebration name to the UI language // (en): 2026-07-22 is Saint Mary Magdalene. if !strings.Contains(body, `class="dayinfo"`) || !strings.Contains(body, "Saint Mary Magdalene") { t.Errorf("body missing day-info header: %q", body) } }) t.Run("readings partial", func(t *testing.T) { rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/readings?date=2026-07-22&v=wuj&all=1", nil)) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } body := rec.Body.String() if strings.Contains(body, " today() if baseline.Code != http.StatusOK { t.Fatalf("baseline status = %d, want 200", baseline.Code) } rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/readings?date=../evil&v=wuj", nil)) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } if rec.Body.String() != baseline.Body.String() { t.Errorf("traversal date did not fall back to today() identically to omitting date\n got: %q\nwant: %q", rec.Body.String(), baseline.Body.String()) } }) t.Run("date query with many ../ segments falls back to today, same as omitting date", func(t *testing.T) { baseline := httptest.NewRecorder() srv.ServeHTTP(baseline, httptest.NewRequest("GET", "/readings?v=wuj", nil)) // no date -> today() rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/readings?date=../../../../etc/hostname&v=wuj", nil)) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } if rec.Body.String() != baseline.Body.String() { t.Errorf("traversal-shaped date did not behave identically to omitting date\n got: %q\nwant: %q", rec.Body.String(), baseline.Body.String()) } }) } // TestBTCheckboxRemoved checks index.html no longer renders a "bt" version // checkbox (the niedziela.pl corpus was retired; bibleVersions is now // wuj,vul,grb,drb) for either lectionary, while the real version boxes remain. func TestBTCheckboxRemoved(t *testing.T) { srv := NewServer(config.Default()) for _, lect := range []string{"traditional", "new"} { rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/?lectionary="+lect, nil)) b := rec.Body.String() if strings.Contains(b, `id="ver-bt"`) { t.Errorf("%s: bt checkbox should no longer be rendered", lect) } if !strings.Contains(b, `id="ver-wuj"`) { t.Errorf("%s: wuj checkbox missing", lect) } } } func TestReaderDefault(t *testing.T) { srv := NewServer(config.Default()) rec := httptest.NewRecorder() srv.ServeHTTP(rec, httptest.NewRequest("GET", "/reader", nil)) if rec.Code != 200 { t.Fatalf("status %d", rec.Code) } body := rec.Body.String() if !strings.Contains(body, `id="reader-root"`) || !strings.Contains(body, `id="pane"`) { t.Errorf("reader page missing root/pane") } if !strings.Contains(body, "Genesis") { // book