feat(web): Sessions, Rate-Limiting, Security-Header, Templates und eingebettete Assets
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
parent
e49882b8a8
commit
bb2bc223b9
16 changed files with 1292 additions and 0 deletions
89
internal/web/render_test.go
Normal file
89
internal/web/render_test.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testRenderer(t *testing.T) *Renderer {
|
||||
t.Helper()
|
||||
rn, err := NewRenderer(Brand{Title: "VPN-Portal Firma GmbH", Version: "1.2.3",
|
||||
SupportContact: "it@firma.de"})
|
||||
if err != nil {
|
||||
t.Fatalf("NewRenderer: %v", err)
|
||||
}
|
||||
return rn
|
||||
}
|
||||
|
||||
func TestRenderLoginPage(t *testing.T) {
|
||||
rn := testRenderer(t)
|
||||
rec := httptest.NewRecorder()
|
||||
rn.Render(rec, httptest.NewRequest(http.MethodGet, "/login", nil), http.StatusOK,
|
||||
"login", PageData{Brand: rn.brand})
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("Code = %d", rec.Code)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{
|
||||
"<!DOCTYPE html>", `lang="de"`, "VPN-Portal Firma GmbH",
|
||||
S["login_submit"], S["login_username_hint"], "1.2.3",
|
||||
`name="password"`, `type="password"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("Login-Seite enthält %q nicht", want)
|
||||
}
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/html") {
|
||||
t.Errorf("Content-Type = %q", ct)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderEscapesUserContent(t *testing.T) {
|
||||
rn := testRenderer(t)
|
||||
rec := httptest.NewRecorder()
|
||||
rn.Render(rec, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusOK,
|
||||
"error", PageData{
|
||||
Brand: rn.brand,
|
||||
Flash: `<script>alert(1)</script>`,
|
||||
Data: errorPageData{Title: "T", Message: `"><img src=x onerror=alert(1)>`},
|
||||
})
|
||||
body := rec.Body.String()
|
||||
if strings.Contains(body, "<script>alert(1)</script>") {
|
||||
t.Error("Flash-Text wurde nicht escapt")
|
||||
}
|
||||
if strings.Contains(body, "onerror=alert(1)>") {
|
||||
t.Error("Fehlermeldung wurde nicht escapt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderSetsStatusCode(t *testing.T) {
|
||||
rn := testRenderer(t)
|
||||
rec := httptest.NewRecorder()
|
||||
rn.Render(rec, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusServiceUnavailable,
|
||||
"error", PageData{Brand: rn.brand, Data: errorPageData{Title: "x", Message: "y"}})
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("Code = %d, want 503", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllTemplatesParse(t *testing.T) {
|
||||
rn := testRenderer(t)
|
||||
for _, page := range pageNames {
|
||||
if rn.pages[page] == nil {
|
||||
t.Errorf("Template %q wurde nicht geladen", page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderUnknownPageDoesNotPanic(t *testing.T) {
|
||||
rn := testRenderer(t)
|
||||
rec := httptest.NewRecorder()
|
||||
rn.Render(rec, httptest.NewRequest(http.MethodGet, "/", nil), http.StatusOK,
|
||||
"gibtsnicht", PageData{Brand: rn.brand})
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("Code = %d, want 500", rec.Code)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue