package web import ( "io/fs" "net/http" "net/http/httptest" "strings" "testing" ) func serveStatic(t *testing.T, target string, hdr map[string]string) *httptest.ResponseRecorder { t.Helper() sub, err := fs.Sub(staticFS, "static") if err != nil { t.Fatal(err) } h := http.StripPrefix("/static/", staticHandler(sub)) req := httptest.NewRequest("GET", target, nil) for k, v := range hdr { req.Header.Set(k, v) } rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec } // The whole point: a page's stylesheet link is content-addressed and cached for // good, a bare URL revalidates, and revalidation costs a 304 rather than 90KB. func TestStaticAssetsAreCacheable(t *testing.T) { versioned := assetURL("/static/css/output.css") if !strings.Contains(versioned, "?v=") { t.Fatalf("assetURL did not version the stylesheet: %q", versioned) } rec := serveStatic(t, versioned, nil) if rec.Code != 200 { t.Fatalf("versioned GET = %d, want 200", rec.Code) } if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "immutable") { t.Errorf("versioned Cache-Control = %q, want immutable", cc) } etag := rec.Header().Get("ETag") if etag == "" { t.Fatal("no ETag on a static asset") } rec = serveStatic(t, "/static/css/output.css", nil) if cc := rec.Header().Get("Cache-Control"); cc != "no-cache" { t.Errorf("bare Cache-Control = %q, want no-cache", cc) } // A stale page asking for a hash that no longer exists gets today's bytes, // but must not be told to keep them for a year under the old name. rec = serveStatic(t, "/static/css/output.css?v=000000000000", nil) if cc := rec.Header().Get("Cache-Control"); cc != "no-cache" { t.Errorf("stale-hash Cache-Control = %q, want no-cache", cc) } rec = serveStatic(t, "/static/css/output.css", map[string]string{"If-None-Match": etag}) if rec.Code != http.StatusNotModified { t.Errorf("revalidation = %d, want 304", rec.Code) } } func TestAssetURLLeavesUnknownPathsAlone(t *testing.T) { if got := assetURL("/static/nope.js"); got != "/static/nope.js" { t.Errorf("assetURL(unknown) = %q, want it unchanged", got) } }