// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 package web import ( "io" "io/fs" "net/http" "net/http/httptest" "regexp" "strings" "testing" ) func TestAssetHandlerServesThePackagedFiles(t *testing.T) { cases := map[string]string{ "/admin/assets/admin.css": "text/css", "/admin/assets/fonts.css": "text/css", "/admin/assets/graphis.svg": "image/svg+xml", "/admin/assets/fonts/ubuntu-normal-400-latin.woff2": "font/woff2", } for path, wantType := range cases { rec := httptest.NewRecorder() AssetHandler(rec, httptest.NewRequest(http.MethodGet, path, nil)) response := rec.Result() if response.StatusCode != http.StatusOK { t.Fatalf("%s: status %d", path, response.StatusCode) } if got := response.Header.Get("Content-Type"); !strings.HasPrefix(got, wantType) { t.Errorf("%s: content type %q, want %q", path, got, wantType) } body, err := io.ReadAll(response.Body) if err != nil || len(body) == 0 { t.Fatalf("%s: empty body: %v", path, err) } } } func TestAssetHandlerRejectsUnknownAndTraversal(t *testing.T) { for _, path := range []string{ "/admin/assets/", "/admin/assets/nope.css", "/admin/assets/../etc/passwd", "/admin/assets/fonts/../admin.css", } { rec := httptest.NewRecorder() AssetHandler(rec, httptest.NewRequest(http.MethodGet, path, nil)) if code := rec.Result().StatusCode; code != http.StatusOK && code != http.StatusNotFound { t.Fatalf("%s: status %d", path, code) } // Only the packaged files are a 200; a re-served parent is a hit // only if the tail resolves inside the set after Clean. if path == "/admin/assets/" || path == "/admin/assets/nope.css" { if rec.Result().StatusCode != http.StatusNotFound { t.Errorf("%s: want 404, got %d", path, rec.Result().StatusCode) } } } } func TestAssetHandlerRevalidatesWithTheETag(t *testing.T) { rec := httptest.NewRecorder() AssetHandler(rec, httptest.NewRequest(http.MethodGet, "/admin/assets/admin.css", nil)) etag := rec.Result().Header.Get("ETag") if etag == "" { t.Fatal("no ETag on the asset") } if got := rec.Result().Header.Get("Cache-Control"); !strings.Contains(got, "must-revalidate") { t.Errorf("cache-control %q, want revalidation", got) } second := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/admin/assets/admin.css", nil) request.Header.Set("If-None-Match", etag) AssetHandler(second, request) if code := second.Result().StatusCode; code != http.StatusNotModified { t.Fatalf("revalidation status %d, want 304", code) } } func TestEveryTemplateGlyphReferenceResolves(t *testing.T) { sprite := assets["graphis.svg"] if sprite == nil { t.Fatal("the sprite is not packaged") } symbol := regexp.MustCompile(`