package web
import (
"bytes"
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/ops"
)
// Every script quarry writes is served smaller than its source and
// parses cleanly: the minifier reporting an error here is the same
// error that would have served the source as written in production.
func TestQuarrysScriptsAreCompacted(t *testing.T) {
if assetsRaw {
t.Fatal("FAIL: QUARRY_ASSETS_RAW is set in a test run; the served assets would be the sources")
}
scripts := []struct {
name string
src, out []byte
}{
{"composer.js", composerJS, composerOut}, {"wiki.js", wikiJS, wikiOut},
{"copy.js", copyJS, copyOut}, {"onerror.js", onerrorJS, onerrorOut},
{"activity.js", activityJS, activityJSOut}, {"chat.js", chatJS, chatOut},
{"settings.js", settingsJS, settingsOut},
}
for _, a := range scripts {
if _, err := minifier.Bytes("text/javascript", a.src); err != nil {
t.Errorf("FAIL: %s does not minify: %v", a.name, err)
}
if len(a.out) == 0 || len(a.out) >= len(a.src) {
t.Errorf("FAIL: %s served at %d bytes from a %d-byte source", a.name, len(a.out), len(a.src))
}
}
// datastar is vendored minified and passed through untouched.
if !bytes.Equal(datastarOut, datastarJS) {
t.Error("FAIL: the vendored datastar bundle was re-minified")
}
t.Logf("ok: %d scripts are served compact", len(scripts))
}
// The wire carries the compact bytes under the hashed name: the name is
// the hash of what is served, and what is served is not the source.
func TestAssetsAreServedCompactUnderTheirHash(t *testing.T) {
r := ops.NewRegistry()
if err := api.RegisterAll(r, api.Deps{Version: "0.1.0-test", Repos: api.NewRepos(map[string]*api.Repo{})}); err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(HandlerWith(r, nil, nil, nil,
Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })))
t.Cleanup(srv.Close)
for _, a := range []struct {
url string
out []byte
name string
}{{onerrorURL, onerrorOut, "onerror"}, {activityJSURL, activityJSOut, "activity"}} {
if hashedAsset(a.name, a.out) != a.url {
t.Errorf("FAIL: %s's URL is not the hash of its served bytes", a.name)
}
req, _ := http.NewRequestWithContext(context.Background(), "GET", srv.URL+a.url, nil)
resp, err := srv.Client().Do(req)
if err != nil {
t.Fatal(err)
}
got, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 || !bytes.Equal(got, a.out) {
t.Errorf("FAIL: %s served %d bytes with status %d, want the %d compact bytes", a.name, len(got), resp.StatusCode, len(a.out))
}
if !strings.Contains(resp.Header.Get("Cache-Control"), "immutable") {
t.Errorf("FAIL: %s is not immutable: %q", a.name, resp.Header.Get("Cache-Control"))
}
}
t.Log("ok: hashed URLs serve the compact bytes, immutably")
}