package web_test import ( "encoding/json" "net/http" "net/http/httptest" "regexp" "strings" "testing" "git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/web" ) // fakeNav is the NavLister seam (SPEC-008 §7): the enabled module names. type fakeNav []string func (f fakeNav) Nav() []string { return f } // renderShell renders a page and returns the body. func renderShell(t *testing.T, p web.Page) string { t.Helper() rec := httptest.NewRecorder() if err := web.Render(rec, http.StatusOK, p); err != nil { t.Fatalf("web.Render: %v", err) } if got := rec.Header().Get("Content-Type"); !strings.HasPrefix(got, "text/html") { t.Errorf("Content-Type = %q, want text/html", got) } return rec.Body.String() } // ── the shell (SPEC-009 §4) ────────────────────────────────────────────────── // TestShellRendersContentAndNav: every page renders inside the shell, and the // nav comes from the injected NavLister — the registry seam. func TestShellRendersContentAndNav(t *testing.T) { web.SetNav(fakeNav{"home", "time", "absence"}) t.Cleanup(func() { web.SetNav(nil) }) body := renderShell(t, web.Page{ Title: "Dashboard", GUID: "guid-1", Active: "time", CSRF: "tok-123", Content: web.Markup(`

Hello

`), }) for _, want := range []string{ "", "Dashboard", `

Hello

`, `meta name="csrf-token" content="tok-123"`, `href="/static/app.css"`, `href="/static/manifest.webmanifest"`, "alert(1)`, Flash: `bad news`, FlashKind: "error", CSRF: `">`, Content: web.Markup(`

trusted

`), }) if strings.Contains(body, ``) { t.Error("the title was not escaped") } if strings.Contains(body, "bad news") { t.Error("the flash was not escaped") } if strings.Contains(body, ``) { t.Error("the CSRF token was not escaped into the meta tag") } if !strings.Contains(body, "

trusted

") { t.Error("explicit Markup was escaped — the content slot must render it") } // The banner carries its kind as a class. if !strings.Contains(body, `class="flash error"`) { t.Errorf("the error banner lost its kind:\n%s", body) } } // ── AC-3: no inline scripts ────────────────────────────────────────────────── // TestShellHasNoInlineScripts: the layout references same-origin files only. // The CSP is `script-src 'self'` (SPEC-003 §4) — one inline script would force // weakening it, so there must never be one (AC-3). func TestShellHasNoInlineScripts(t *testing.T) { body := renderShell(t, web.Page{GUID: "g", Content: web.Markup(`

x

`)}) for _, m := range scriptTag.FindAllString(body, -1) { if !strings.Contains(m, "src=") { t.Errorf("the shell emitted an inline