package httpx import ( "net/http" "strings" "testing" ) func TestLoginPageRenders(t *testing.T) { s := newTestServer(t) rec := get(t, s.Handler(), "/login") if rec.Code != http.StatusOK { t.Fatalf("status = %d", rec.Code) } body := rec.Body.String() for _, want := range []string{"login-form", `id="email"`, `id="password"`, "/static/js/login.js"} { if !strings.Contains(body, want) { t.Errorf("login page missing %q", want) } } // OIDC is not configured → no SSO button if strings.Contains(body, "Continue with SSO") { t.Error("SSO button shown although OIDC is disabled") } if csp := rec.Header().Get("Content-Security-Policy"); !strings.Contains(csp, "script-src 'self'") { t.Errorf("CSP missing or weak: %q", csp) } if rec.Header().Get("X-Frame-Options") != "DENY" { t.Error("X-Frame-Options missing") } } func TestRegisterPageRenders(t *testing.T) { s := newTestServer(t) rec := get(t, s.Handler(), "/register") if rec.Code != http.StatusOK { t.Fatalf("status = %d", rec.Code) } if !strings.Contains(rec.Body.String(), "register-form") { t.Error("register form missing") } } func TestAnonymousPageRedirectsToLogin(t *testing.T) { s := newTestServer(t) for _, path := range []string{"/", "/profile", "/board", "/admin", "/messages"} { rec := get(t, s.Handler(), path) if rec.Code != http.StatusFound { t.Errorf("%s: status = %d, want 302", path, rec.Code) continue } loc := rec.Header().Get("Location") if !strings.HasPrefix(loc, "/login?next=") { t.Errorf("%s: redirect to %q", path, loc) } } } func TestStaticAssetsServed(t *testing.T) { s := newTestServer(t) tests := map[string]string{ "/static/css/app.css": "--bg:#ffffff", // Y2K white-paper light theme "/static/js/theme.js": "data-default-theme", "/static/js/api.js": "X-CSRF-Token", "/static/js/login.js": "auth/login", "/static/js/nav.js": "nav-theme", } for path, want := range tests { rec := get(t, s.Handler(), path) if rec.Code != http.StatusOK { t.Errorf("%s: status = %d", path, rec.Code) continue } if !strings.Contains(rec.Body.String(), want) { t.Errorf("%s: missing %q", path, want) } } } func TestThemeTokensPresent(t *testing.T) { s := newTestServer(t) rec := get(t, s.Handler(), "/static/css/app.css") css := rec.Body.String() // spot-check both palettes (Y2K rework, user-requested deviation from // the §10 beige — see DECISIONS.md) and the square-edge radius for _, tok := range []string{ "--bg:#ffffff", "--accent:#7a4a14", // light: white paper, brown ink "--bg:#171209", "--accent:#d9a548", // dark "--radius:2px", } { if !strings.Contains(css, tok) { t.Errorf("css missing design token %q", tok) } } } func TestInitialsFunc(t *testing.T) { tests := []struct{ in, want string }{ {"Jane Doe", "JD"}, {"single", "S"}, {"", "?"}, {" spaced out ", "SO"}, } f := templateFuncs["initials"].(func(string) string) for _, tt := range tests { if got := f(tt.in); got != tt.want { t.Errorf("initials(%q) = %q, want %q", tt.in, got, tt.want) } } }