package cef // P3.2 tests: the served shell carries content-versioned references from // its own captured bundle while the on-disk shell keeps usable relative // URLs. The shell itself stays noncacheable; an invalid manifest fails // closed with a clear noncacheable error. import ( "crypto/sha256" "encoding/hex" "net/http" "strings" "testing" "testing/fstest" cefmocks "github.com/bnema/purego-cef/cef/mocks" "github.com/stretchr/testify/require" "github.com/bnema/dumber/assets" ) const versionedShellFixture = `
`
func shellBundleFS(t *testing.T, shell string) fstest.MapFS {
t.Helper()
return fstest.MapFS{
"systemviews/index.html": {Data: []byte(shell)},
"systemviews/" + assets.ManifestName: {Data: testManifestBytes(t)},
}
}
func TestAssetP32_ShellRefsVersionedFromBundleManifest(t *testing.T) {
stubIdentityResourceHandler(t)
h := newTestDumbSchemeHandler(t)
h.setAssets(shellBundleFS(t, versionedShellFixture))
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/history")))
require.Equal(t, http.StatusOK, rh.statusCode)
require.Equal(t, "text/html; charset=utf-8", rh.contentType)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"],
"P3.3 policy: the shell itself stays noncacheable")
served := string(rh.data)
require.Contains(t, served, "./systemviews.wasm?v="+strings.Repeat("b", 64))
require.Contains(t, served, "./wasm_exec.js?v="+strings.Repeat("c", 64))
require.Contains(t, served, "./systemviews.css?v="+strings.Repeat("a", 64))
require.Contains(t, served, `"./other.png"`, "unpinned references pass through untouched")
}
func TestAssetP32_ShellVersionedAcrossRoutes(t *testing.T) {
stubIdentityResourceHandler(t)
h := newTestDumbSchemeHandler(t)
h.setAssets(shellBundleFS(t, versionedShellFixture))
for _, raw := range []string{
"https://dumber.invalid/history",
"https://dumber.invalid/history/",
"https://dumber.invalid/favorites",
"https://dumber.invalid/favorites/",
"https://dumber.invalid/config",
"https://dumber.invalid/config/",
"https://dumber.invalid/index.html",
} {
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, raw)))
require.Equal(t, http.StatusOK, rh.statusCode, raw)
require.Contains(t, string(rh.data), "?v=", "%s must serve the versioned shell", raw)
}
// The bare domain root has no page mapping: existing routing rejects
// it before the asset layer, unchanged by versioning.
bare := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/")))
require.Equal(t, http.StatusNotFound, bare.statusCode)
}
func TestAssetP32_AlreadyVersionedRefsUntouched(t *testing.T) {
out := versionShellRefs(
[]byte(``),
assets.Manifest{Version: assets.ManifestVersion, Files: map[string]assets.FileEntry{
"systemviews.css": {SHA256: "a", Size: 1},
"systemviews.wasm": {SHA256: "b", Size: 2},
"wasm_exec.js": {SHA256: "c", Size: 3},
}},
)
require.NotContains(t, string(out), "?v=c?v=", "must not double-version")
require.Contains(t, string(out), `"./wasm_exec.js?v=old"`, "pre-versioned reference wins")
}
func TestAssetP32_InvalidManifestFailsClosedNoncacheable(t *testing.T) {
stubIdentityResourceHandler(t)
h := newTestDumbSchemeHandler(t)
h.setAssets(fstest.MapFS{
"systemviews/index.html": {Data: []byte("shell")},
"systemviews/" + assets.ManifestName: {Data: []byte("not json")},
})
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/history")))
require.Equal(t, http.StatusInternalServerError, rh.statusCode)
require.Equal(t, "no-store", rh.headers["Cache-Control"], "manifest failure must be noncacheable")
require.Contains(t, string(rh.data), "Asset manifest invalid")
}
func TestAssetP32_MissingManifestFailsClosed(t *testing.T) {
stubIdentityResourceHandler(t)
h := newTestDumbSchemeHandler(t)
h.setAssets(fstest.MapFS{
"systemviews/index.html": {Data: []byte("shell")},
})
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/history")))
require.Equal(t, http.StatusInternalServerError, rh.statusCode)
require.Equal(t, "no-store", rh.headers["Cache-Control"])
}
func TestAssetP32_ReplacementSwapsShellVersions(t *testing.T) {
stubIdentityResourceHandler(t)
digestA := strings.Repeat("a", 64)
manifestA, err := assets.Manifest{Version: assets.ManifestVersion, Files: map[string]assets.FileEntry{
"systemviews.css": {SHA256: digestA, Size: 1},
"systemviews.wasm": {SHA256: digestA, Size: 2},
"wasm_exec.js": {SHA256: digestA, Size: 3},
}}.Bytes()
require.NoError(t, err)
h := newTestDumbSchemeHandler(t)
h.setAssets(fstest.MapFS{
"systemviews/index.html": {Data: []byte(versionedShellFixture)},
"systemviews/" + assets.ManifestName: {Data: manifestA},
})
u := mustParseURL(t, "https://dumber.invalid/history")
require.Contains(t, string(staticHandlerOf(t, h.handleAsset(u)).data), "?v="+digestA)
digestB := strings.Repeat("b", 64)
manifestB, err := assets.Manifest{Version: assets.ManifestVersion, Files: map[string]assets.FileEntry{
"systemviews.css": {SHA256: digestB, Size: 1},
"systemviews.wasm": {SHA256: digestB, Size: 2},
"wasm_exec.js": {SHA256: digestB, Size: 3},
}}.Bytes()
require.NoError(t, err)
h.setAssets(fstest.MapFS{
"systemviews/index.html": {Data: []byte(versionedShellFixture)},
"systemviews/" + assets.ManifestName: {Data: manifestB},
})
served := string(staticHandlerOf(t, h.handleAsset(u)).data)
require.Contains(t, served, "?v="+digestB)
require.NotContains(t, served, "?v="+digestA, "shell versions follow the captured bundle")
}
func TestAssetP32_OnDiskShellKeepsRelativeURLs(t *testing.T) {
// The committed shell must stay directly usable by alternate shells:
// concrete relative URLs, no template placeholders, no baked versions.
data, err := assets.WebUIAssets.ReadFile("systemviews/index.html")
require.NoError(t, err)
shell := string(data)
for _, ref := range []string{"./systemviews.wasm", "./wasm_exec.js", "./systemviews.css"} {
require.Contains(t, shell, ref)
require.NotContains(t, shell, ref+"?v=", "on-disk shell must not bake versions")
}
require.NotContains(t, shell, "{{", "no unresolved template placeholders")
}
// ---------------------------------------------------------------------------
// P3.3: immutable headers bound to served bytes
// ---------------------------------------------------------------------------
// trueManifestFS builds a bundle whose manifest pins the true digests of
// the given files, plus a compressed WASM agreeing with the raw bytes.
func trueManifestFS(t *testing.T, wasm, js, css []byte) fstest.MapFS {
t.Helper()
entries := make(map[string]assets.FileEntry, len(assets.ManifestFiles))
for name, data := range map[string][]byte{
"systemviews.wasm": wasm,
"wasm_exec.js": js,
"systemviews.css": css,
} {
sum := sha256.Sum256(data)
entries[name] = assets.FileEntry{SHA256: hex.EncodeToString(sum[:]), Size: int64(len(data))}
}
manifest, err := assets.Manifest{Version: assets.ManifestVersion, Files: entries}.Bytes()
require.NoError(t, err)
return fstest.MapFS{
"systemviews/systemviews.wasm": {Data: wasm},
"systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, wasm)},
"systemviews/wasm_exec.js": {Data: js},
"systemviews/systemviews.css": {Data: css},
"systemviews/" + assets.ManifestName: {Data: manifest},
}
}
func digestOf(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func TestAssetP33_VersionedCSSServesImmutable(t *testing.T) {
stubIdentityResourceHandler(t)
css := []byte("/* immutable css */")
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), css))
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/systemviews.css?v="+digestOf(css))))
require.Equal(t, http.StatusOK, rh.statusCode)
require.Equal(t, css, rh.data)
require.Equal(t, immutableCacheControl, rh.headers["Cache-Control"])
}
func TestAssetP33_UnversionedCSSServesNoStore(t *testing.T) {
stubIdentityResourceHandler(t)
css := []byte("/* plain css */")
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), css))
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/systemviews.css")))
require.Equal(t, http.StatusOK, rh.statusCode)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"])
require.NotContains(t, rh.headers["Cache-Control"], "immutable")
}
func TestAssetP33_WrongVersionRejectedNoncacheable(t *testing.T) {
stubIdentityResourceHandler(t)
css := []byte("/* real css */")
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), css))
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/systemviews.css?v="+strings.Repeat("0", 64))))
require.Equal(t, http.StatusNotFound, rh.statusCode)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"])
}
func TestAssetP33_DuplicateAndEmptyVersionsRejected(t *testing.T) {
stubIdentityResourceHandler(t)
css := []byte("/* css */")
fsys := trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), css)
h := newTestDumbSchemeHandler(t)
h.setAssets(fsys)
digest := digestOf(css)
for _, raw := range []string{
"https://dumber.invalid/systemviews.css?v=" + digest + "&v=" + digest,
"https://dumber.invalid/systemviews.css?v=",
"https://dumber.invalid/systemviews.css?v=a&v=b",
// Malformed escapes fail closed instead of degrading to
// unversioned or single-version handling.
"https://dumber.invalid/systemviews.css?v=" + digest + "&v=%ZZ",
"https://dumber.invalid/systemviews.css?v=%ZZ",
} {
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, raw)))
require.Equal(t, http.StatusNotFound, rh.statusCode, raw)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"], raw)
}
// A malformed escape on an unrelated parameter is not a version:
// the request serves unversioned and noncacheable.
plain := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/systemviews.css?x=%ZZ")))
require.Equal(t, http.StatusOK, plain.statusCode)
require.Equal(t, noStoreCacheControl, plain.headers["Cache-Control"])
}
func TestAssetP33_StaleManifestRejectedNoncacheable(t *testing.T) {
stubIdentityResourceHandler(t)
// The manifest pins dummy digests while the files carry real bytes:
// v matches the pin, but the served bytes do not. URL-to-manifest
// equality alone must never earn immutable headers.
h := newTestDumbSchemeHandler(t)
h.setAssets(fstest.MapFS{
"systemviews/systemviews.css": {Data: []byte("/* real css */")},
"systemviews/systemviews.wasm": {Data: []byte("\x00asm")},
"systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, []byte("\x00asm"))},
"systemviews/wasm_exec.js": {Data: []byte("/* js */")},
"systemviews/" + assets.ManifestName: {Data: testManifestBytes(t)},
})
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/systemviews.css?v="+strings.Repeat("a", 64))))
require.Equal(t, http.StatusNotFound, rh.statusCode, "stale pin must fail closed")
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"])
}
func TestAssetP33_MissingVersionedArtifactRejected(t *testing.T) {
stubIdentityResourceHandler(t)
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), []byte("/* css */")))
// wasm_exec.js is pinned but absent from this bundle variant.
bundle := fstest.MapFS{
"systemviews/systemviews.css": {Data: []byte("/* css */")},
}
manifest, err := assets.Manifest{Version: assets.ManifestVersion, Files: map[string]assets.FileEntry{
"systemviews.css": {SHA256: digestOf([]byte("/* css */")), Size: int64(len("/* css */"))},
"systemviews.wasm": {SHA256: strings.Repeat("b", 64), Size: 2},
"wasm_exec.js": {SHA256: strings.Repeat("c", 64), Size: 3},
}}.Bytes()
require.NoError(t, err)
bundle["systemviews/"+assets.ManifestName] = &fstest.MapFile{Data: manifest}
h.setAssets(bundle)
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/wasm_exec.js?v="+strings.Repeat("c", 64))))
require.Equal(t, http.StatusNotFound, rh.statusCode)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"])
}
func TestAssetP33_VersionedWASMServesImmutableOnWire(t *testing.T) {
stubIdentityResourceHandler(t)
wasm := []byte("\x00asm-wire-immutable")
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, wasm, []byte("/* js */"), []byte("/* css */")))
wh := awaitWASMHandler(t, h.handleAsset(mustParseURL(t, wasmURL(t)+"?v="+digestOf(wasm))))
require.Equal(t, http.StatusOK, wh.statusCode)
require.Equal(t, wasm, wh.data)
response := cefmocks.NewMockResponse(t)
response.EXPECT().SetStatus(int32(http.StatusOK)).Once()
response.EXPECT().SetStatusText(http.StatusText(http.StatusOK)).Once()
response.EXPECT().SetMimeType("application/wasm").Once()
response.EXPECT().SetHeaderByName("Cache-Control", immutableCacheControl, int32(1)).Once()
var responseLength int64
wh.GetResponseHeaders(response, &responseLength, 0)
require.Equal(t, int64(len(wasm)), responseLength)
}
func TestAssetP33_WrongVersionWASMDecodesNothing(t *testing.T) {
stubIdentityResourceHandler(t)
wasm := []byte("\x00asm-no-decode")
fsys := &countingFS{inner: fstest.MapFS{
"systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, wasm)},
}}
_ = fsys
h := newTestDumbSchemeHandler(t)
inner := fstest.MapFS{
"systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, wasm)},
}
manifest, err := assets.Manifest{Version: assets.ManifestVersion, Files: map[string]assets.FileEntry{
"systemviews.css": {SHA256: strings.Repeat("a", 64), Size: 1},
"systemviews.wasm": {SHA256: digestOf(wasm), Size: int64(len(wasm))},
"wasm_exec.js": {SHA256: strings.Repeat("c", 64), Size: 3},
}}.Bytes()
require.NoError(t, err)
inner["systemviews/"+assets.ManifestName] = &fstest.MapFile{Data: manifest}
counting := &countingFS{inner: inner}
h.setAssets(counting)
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, wasmURL(t)+"?v="+strings.Repeat("0", 64))))
require.Equal(t, http.StatusNotFound, rh.statusCode)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"])
require.Zero(t, counting.compressedReads(), "rejected version must not start a decode")
}
func TestAssetP33_StaleWASMBytesFailClosedOnWire(t *testing.T) {
stubIdentityResourceHandler(t)
wasm := []byte("\x00asm-stale-wire")
other := []byte("\x00asm-other-wire")
otherManifest, err := assets.Manifest{Version: assets.ManifestVersion, Files: map[string]assets.FileEntry{
"systemviews.css": {SHA256: strings.Repeat("a", 64), Size: 1},
"systemviews.wasm": {SHA256: digestOf(other), Size: int64(len(other))},
"wasm_exec.js": {SHA256: strings.Repeat("c", 64), Size: 3},
}}.Bytes()
require.NoError(t, err)
h := newTestDumbSchemeHandler(t)
h.setAssets(fstest.MapFS{
"systemviews/systemviews.wasm": {Data: wasm},
"systemviews/systemviews.wasm.br": {Data: brotliCompressForTest(t, wasm)},
"systemviews/" + assets.ManifestName: {Data: otherManifest},
})
// v matches the stale pin, but the decoded bytes hash differently.
wh := awaitWASMHandler(t, h.handleAsset(mustParseURL(t, wasmURL(t)+"?v="+digestOf(other))))
require.Equal(t, http.StatusNotFound, wh.statusCode)
response := cefmocks.NewMockResponse(t)
response.EXPECT().SetStatus(int32(http.StatusNotFound)).Once()
response.EXPECT().SetStatusText(http.StatusText(http.StatusNotFound)).Once()
response.EXPECT().SetMimeType("text/html").Once()
response.EXPECT().SetCharset("utf-8").Once()
response.EXPECT().SetHeaderByName("Cache-Control", noStoreCacheControl, int32(1)).Once()
var responseLength int64
wh.GetResponseHeaders(response, &responseLength, 0)
require.Equal(t, int64(len(wh.data)), responseLength)
}
func TestAssetP33_ReplacementSwapsValidatedVersions(t *testing.T) {
stubIdentityResourceHandler(t)
cssA := []byte("/* css a */")
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), cssA))
u := "https://dumber.invalid/systemviews.css?v=" + digestOf(cssA)
require.Equal(t, immutableCacheControl, staticHandlerOf(t, h.handleAsset(mustParseURL(t, u))).headers["Cache-Control"])
cssB := []byte("/* css b */")
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), cssB))
// The old version no longer validates against the captured bundle.
old := staticHandlerOf(t, h.handleAsset(mustParseURL(t, u)))
require.Equal(t, http.StatusNotFound, old.statusCode)
require.Equal(t, noStoreCacheControl, old.headers["Cache-Control"])
fresh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/systemviews.css?v="+digestOf(cssB))))
require.Equal(t, http.StatusOK, fresh.statusCode)
require.Equal(t, immutableCacheControl, fresh.headers["Cache-Control"])
}
func TestAssetP33_ManifestItselfServesNoStore(t *testing.T) {
stubIdentityResourceHandler(t)
css := []byte("/* css */")
h := newTestDumbSchemeHandler(t)
h.setAssets(trueManifestFS(t, []byte("\x00asm"), []byte("/* js */"), css))
// The manifest is servable but never allowlisted: it stays fresh and
// noncacheable even with a version query.
for _, raw := range []string{
"https://dumber.invalid/asset-manifest.json",
"https://dumber.invalid/asset-manifest.json?v=anything",
} {
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, raw)))
require.Equal(t, http.StatusOK, rh.statusCode, raw)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"], raw)
}
}
func TestAssetP33_ShellVersionQueryIgnored(t *testing.T) {
stubIdentityResourceHandler(t)
h := newTestDumbSchemeHandler(t)
h.setAssets(shellBundleFS(t, versionedShellFixture))
// The shell document is never immutable: a ?v= on the shell serves the
// normal noncacheable shell instead of erroring or pinning.
rh := staticHandlerOf(t, h.handleAsset(mustParseURL(t, "https://dumber.invalid/history?v=whatever")))
require.Equal(t, http.StatusOK, rh.statusCode)
require.Equal(t, noStoreCacheControl, rh.headers["Cache-Control"])
require.Contains(t, string(rh.data), "?v=")
}