package slidestest import ( "net/http" "net/http/httptest" "os" "path/filepath" "regexp" "strings" "testing" slides "amadan.net/oficina/slides/internal/slides" ) // hashedStylesheet is the fingerprinted URL shape a page links: // /static/tokens.<16 hex>.css. var hashedStylesheet = regexp.MustCompile(`/static/tokens\.[0-9a-f]{16}\.css`) // The scaffold shipped two tests about the placeholder index page and // said in AGENTS.md that sign-in would break both, because it does: // "/" is behind the session gate now, so a signed-out GET is a 303 with // no in its body. They are repointed at /signin, the one screen // that is still public — the subject was never the placeholder's // wording, it was the framework's asset delivery, and that still earns // its place. The placeholder's own "does this render" assertion is // covered by TestSigninPageIsPublic (signin_test.go), against a page // this app actually has. func TestSigninLinksFingerprintedStylesheet(t *testing.T) { h := newApp(t) href := hashedStylesheet.FindString(get(t, h, "/signin").Body.String()) if href == "" { t.Fatal("the sign-in page links no fingerprinted stylesheet") } rec := get(t, h, href) if rec.Code != http.StatusOK { t.Fatalf("GET %s: status %d, want 200", href, rec.Code) } if got := rec.Header().Get("Cache-Control"); got != "public, max-age=31536000, immutable" { t.Errorf("GET %s: Cache-Control = %q, want immutable year", href, got) } } // The bare name keeps working, just never long-cached — so a changed // file always shows on an ordinary reload. func TestBareAssetNameStaysFresh(t *testing.T) { rec := get(t, newApp(t), "/static/tokens.css") if rec.Code != http.StatusOK { t.Fatalf("GET /static/tokens.css: status %d, want 200", rec.Code) } if got := rec.Header().Get("Cache-Control"); got != "no-cache" { t.Errorf("Cache-Control = %q, want no-cache", got) } } // Static assets are mounted outside the session gate on purpose: the // sign-in page has to be able to load its own stylesheet before anybody // is signed in, or the front door renders unstyled. func TestStaticAssetsAreReachableSignedOut(t *testing.T) { rec := get(t, newApp(t), "/static/theme.css") if rec.Code != http.StatusOK { t.Fatalf("GET /static/theme.css signed out: status %d, want 200", rec.Code) } } // ErrorPage is what main.go points Options.ErrorPage at (and the seam // to wire into Ctx.ErrorPage as handlers grow); called directly, it // pins that a panic's page is the framework's error-page partial, not // a stray net/http default. func TestErrorPageRendersFrameworkPartial(t *testing.T) { rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Accept", "text/html") localised(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slides.ErrorPage(w, r, http.StatusInternalServerError, "abc123") })).ServeHTTP(rec, req) if rec.Code != http.StatusInternalServerError { t.Fatalf("ErrorPage: status %d, want %d", rec.Code, http.StatusInternalServerError) } body := rec.Body.String() if !strings.Contains(body, "Something went wrong") { t.Errorf("ErrorPage body missing the 500 title:\n%s", body) } if !strings.Contains(body, "abc123") { t.Errorf("ErrorPage body missing the ref:\n%s", body) } } // Every page failure a browser can navigate to is rendered inside Slides' // layout and bar. A fetch that did not ask for HTML remains plain text; the // split is by Accept rather than by an accident of which route failed. func TestNavigableErrorsUseTheSharedErrorPage(t *testing.T) { a := newApp(t) signedIn := signedIn(t, a, allowedAddress) // The user row is created on the first guarded request; optional chrome // deliberately will not create it while merely resolving an error page. mustGet(t, a, "/", http.StatusOK, signedIn) type errorCase struct { name, path, title string cookies []*http.Cookie } for _, tc := range []errorCase{ {"unknown route, signed out", "/no/such/page", "Page not found · Slides", nil}, {"unknown route, signed in", "/no/such/page", "Page not found · Slides", signedIn}, {"post-only route, signed out", "/signout", "Page not found · Slides", nil}, {"post-only route, signed in", "/signout", "Page not found · Slides", signedIn}, {"missing deck", "/d/23456789ab", "Deck not available · Slides", signedIn}, {"missing share link", "/s/doesnotexist", "Deck not available · Slides", nil}, } { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, tc.path, nil) req.Header.Set("Accept", "text/html,application/xhtml+xml") for _, c := range tc.cookies { req.AddCookie(c) } rec := httptest.NewRecorder() a.ServeHTTP(rec, req) body := rec.Body.String() if rec.Code != http.StatusNotFound || !strings.HasPrefix(rec.Header().Get("Content-Type"), "text/html") { t.Fatalf("status %d, Content-Type %q; want HTML 404", rec.Code, rec.Header().Get("Content-Type")) } if title := regexp.MustCompile(`([^<]*)`).FindStringSubmatch(body); title == nil || title[1] != tc.title { t.Fatalf("title %v, want %q", title, tc.title) } if !strings.Contains(body, "
") { t.Fatal("error is outside the shared layout and bar") } if account := strings.Contains(body, "