package main import ( "bytes" "compress/gzip" "io" "io/fs" "net/http" "net/http/httptest" "regexp" "strings" "testing" ) // Every {{asset "…"}} in the templates must name an embedded file, or the page // fails to render. func TestTemplateAssetsExist(t *testing.T) { ref := regexp.MustCompile(`\{\{asset "([^"]+)"\}\}`) _ = fs.WalkDir(assets, "assets", func(p string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } b, _ := assets.ReadFile(p) for _, m := range ref.FindAllSubmatch(b, -1) { if _, err := assetURL(string(m[1])); err != nil { t.Errorf("%s: %v", p, err) } } return nil }) } func TestServeStaticCaching(t *testing.T) { h := serveStatic() get := func(url, ifNoneMatch string) *httptest.ResponseRecorder { r := httptest.NewRequest(http.MethodGet, url, nil) if ifNoneMatch != "" { r.Header.Set("If-None-Match", ifNoneMatch) } w := httptest.NewRecorder() h.ServeHTTP(w, r) return w } v := assetVersions["style.css"] versioned, _ := assetURL("style.css") if w := get(versioned, ""); w.Code != 200 || w.Header().Get("Cache-Control") != "public, max-age=31536000, immutable" { t.Errorf("versioned: %d %q", w.Code, w.Header().Get("Cache-Control")) } for _, u := range []string{"/_/style.css", "/_/style.css?v=stale"} { w := get(u, "") if w.Code != 200 || w.Header().Get("Cache-Control") != "no-cache" || w.Header().Get("ETag") != `"`+v+`"` { t.Errorf("%s: %d %q %q", u, w.Code, w.Header().Get("Cache-Control"), w.Header().Get("ETag")) } } if w := get("/_/style.css", `"`+v+`"`); w.Code != http.StatusNotModified { t.Errorf("revalidate: got %d, want 304", w.Code) } if w := get("/_/fonts/IosevkaSlab.woff2", ""); w.Header().Get("Cache-Control") != "public, max-age=31536000, immutable" { t.Errorf("font: %q", w.Header().Get("Cache-Control")) } } func TestServeStaticGzip(t *testing.T) { h := serveStatic() get := func(url, acceptEncoding, ifNoneMatch string) *httptest.ResponseRecorder { r := httptest.NewRequest(http.MethodGet, url, nil) if acceptEncoding != "" { r.Header.Set("Accept-Encoding", acceptEncoding) } if ifNoneMatch != "" { r.Header.Set("If-None-Match", ifNoneMatch) } w := httptest.NewRecorder() h.ServeHTTP(w, r) return w } want, _ := assets.ReadFile("assets/style.css") w := get("/_/style.css", "gzip, deflate, br", "") if w.Code != 200 || w.Header().Get("Content-Encoding") != "gzip" || w.Header().Get("Vary") != "Accept-Encoding" { t.Fatalf("gzip: %d %q %q", w.Code, w.Header().Get("Content-Encoding"), w.Header().Get("Vary")) } if ct := w.Header().Get("Content-Type"); !strings.HasPrefix(ct, "text/css") { t.Errorf("content-type: %q", ct) } zr, err := gzip.NewReader(w.Body) if err != nil { t.Fatal(err) } got, _ := io.ReadAll(zr) if !bytes.Equal(got, want) { t.Errorf("gzip body does not decompress to style.css") } etag := w.Header().Get("ETag") if w := get("/_/style.css", "gzip", etag); w.Code != http.StatusNotModified { t.Errorf("gzip revalidate: got %d, want 304", w.Code) } for _, ae := range []string{"", "identity", "gzip;q=0"} { w := get("/_/style.css", ae, "") if w.Header().Get("Content-Encoding") != "" || !bytes.Equal(w.Body.Bytes(), want) { t.Errorf("Accept-Encoding %q: got encoding %q", ae, w.Header().Get("Content-Encoding")) } } if w := get("/_/fonts/IosevkaSlab.woff2", "gzip", ""); w.Header().Get("Content-Encoding") != "" { t.Errorf("font should not be re-compressed") } }