favoritter/internal/render/render_test.go
Ole-Morten Duesund a8f3aa6f7e test: add comprehensive test suite (44 → 169 tests) and v1.1 plan
Add 125 new test functions across 10 new test files, covering:
- CSRF middleware (8 tests): double-submit cookie validation
- Auth middleware (12 tests): SessionLoader, RequireAdmin, context helpers
- API handlers (28 tests): auth, faves CRUD, tags, users, export/import
- Web handlers (41 tests): signup, login, password reset, fave CRUD,
  admin panel, feeds, import/export, profiles, settings
- Config (8 tests): env parsing, defaults, trusted proxies, normalization
- Database (6 tests): migrations, PRAGMAs, idempotency, seeding
- Image processing (10 tests): JPEG/PNG, resize, EXIF strip, path traversal
- Render (6 tests): page/error/partial rendering, template functions
- Settings store (3 tests): CRUD operations
- Regression tests for display name fallback and CSP-safe autocomplete

Also adds CSRF middleware to testServer chain for end-to-end CSRF
verification, TESTPLAN.md documenting coverage, and PLANS-v1.1.md
with implementation plans for notes+OG, PWA, editing UX, and admin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 00:18:01 +02:00

161 lines
3.7 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-or-later
package render
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"kode.naiv.no/olemd/favoritter/internal/config"
)
func testRenderer(t *testing.T) *Renderer {
t.Helper()
cfg := &config.Config{
SiteName: "Test Site",
BasePath: "/test",
}
r, err := New(cfg)
if err != nil {
t.Fatalf("create renderer: %v", err)
}
return r
}
func TestRenderPage(t *testing.T) {
r := testRenderer(t)
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
r.Page(rr, req, "login", PageData{
Title: "Logg inn",
})
if rr.Code != http.StatusOK {
t.Errorf("render page: got %d, want 200", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Logg inn") {
t.Error("page should contain title")
}
ct := rr.Header().Get("Content-Type")
if !strings.Contains(ct, "text/html") {
t.Errorf("content-type = %q, want text/html", ct)
}
}
func TestRenderPageWithData(t *testing.T) {
r := testRenderer(t)
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
r.Page(rr, req, "login", PageData{
Title: "Test Page",
SiteName: "My Site",
BasePath: "/base",
})
body := rr.Body.String()
if !strings.Contains(body, "Test Page") {
t.Error("should contain title in output")
}
}
func TestRenderMissingTemplate(t *testing.T) {
r := testRenderer(t)
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
r.Page(rr, req, "nonexistent_template_xyz", PageData{})
if rr.Code != http.StatusInternalServerError {
t.Errorf("missing template: got %d, want 500", rr.Code)
}
}
func TestRenderErrorPage(t *testing.T) {
r := testRenderer(t)
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
r.Error(rr, req, http.StatusNotFound, "Ikke funnet")
if rr.Code != http.StatusNotFound {
t.Errorf("error page: got %d, want 404", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Ikke funnet") {
t.Error("error page should contain message")
}
}
func TestRenderPopulatesCommonData(t *testing.T) {
cfg := &config.Config{
SiteName: "Favoritter",
BasePath: "/app",
ExternalURL: "https://example.com",
}
r, err := New(cfg)
if err != nil {
t.Fatalf("create renderer: %v", err)
}
req := httptest.NewRequest("GET", "/", nil)
// Add a CSRF token to the context.
type contextKey string
ctx := context.WithValue(req.Context(), contextKey("csrf_token"), "test-token")
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
r.Page(rr, req, "login", PageData{Title: "Test"})
// BasePath and SiteName should be populated from config.
body := rr.Body.String()
if !strings.Contains(body, "/app") {
t.Error("should contain basePath from config")
}
}
func TestTemplateFuncs(t *testing.T) {
cfg := &config.Config{BasePath: "/test", ExternalURL: "https://example.com"}
r, _ := New(cfg)
funcs := r.templateFuncs()
// Test truncate function.
truncate := funcs["truncate"].(func(int, string) string)
if got := truncate(5, "Hello, world!"); got != "Hello..." {
t.Errorf("truncate(5, long) = %q, want Hello...", got)
}
if got := truncate(20, "Short"); got != "Short" {
t.Errorf("truncate(20, short) = %q, want Short", got)
}
// Test with Norwegian characters (Ærlig = 5 runes: Æ r l i g).
if got := truncate(3, "Ærlig"); got != "Ærl..." {
t.Errorf("truncate(3, Ærlig) = %q, want Ærl...", got)
}
// Test add/subtract.
add := funcs["add"].(func(int, int) int)
if add(2, 3) != 5 {
t.Error("add(2,3) should be 5")
}
sub := funcs["subtract"].(func(int, int) int)
if sub(5, 3) != 2 {
t.Error("subtract(5,3) should be 2")
}
// Test basePath function.
bp := funcs["basePath"].(func() string)
if bp() != "/test" {
t.Errorf("basePath() = %q", bp())
}
}