112 lines
3.4 KiB
Go
112 lines
3.4 KiB
Go
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")
|
|
}
|
|
}
|