import { readFileSync } from "node:fs"; import { gzipSync } from "node:zlib"; import { runInNewContext } from "node:vm"; import { describe, expect, it, vi } from "vitest"; import { BROWSER_CONTENT_CACHE_NAME } from "./contentManager"; const workerSource = readFileSync( new URL("../../public/content-cache-sw.js", import.meta.url), "utf8", ); function contentWorker(cached?: Response, bundledFiles: Record = {}, object?: Response) { const handlers = new Map void>(); const bootstrapObjects = new Map(); const fetch = vi.fn(async (url: RequestInfo | URL) => String(url) === "/content/bootstrap-manifest.json" ? Response.json({ files: bundledFiles }) : object?.clone() ?? new Response("bundled dungeon", { status: 200 })); const match = vi.fn(async (url: RequestInfo | URL) => String(url).includes("/content/cache-objects/") ? object?.clone() : cached?.clone()); const bootstrapMatch = vi.fn(async (url: RequestInfo | URL) => bootstrapObjects.get(String(url))?.clone()); const bootstrapPut = vi.fn(async (url: RequestInfo | URL, response: Response) => { bootstrapObjects.set(String(url), response.clone()); }); runInNewContext(workerSource, { self: { location: { origin: "https://healer.test" }, addEventListener: (type: string, handler: (event: unknown) => void) => handlers.set(type, handler), }, caches: { open: async (name: string) => name === "healer-man-bootstrap-v1" ? { match: bootstrapMatch, put: bootstrapPut } : { match } }, fetch, URL, Response, Headers, DecompressionStream, }); return { fetch, match, bootstrapMatch, bootstrapPut, refresh: () => handlers.get("message")!({ data: { type: "refresh-content-bootstrap" } }), request: (path: string) => { let response: Promise | undefined; handlers.get("fetch")!({ request: new Request(new URL(path, "https://healer.test")), respondWith: (value: Promise) => { response = value; }, }); return response; }, }; } describe("browser content cache worker", () => { it("loads starter-dungeon assets without an optional content install", async () => { const paths = [ "/assets/game/dungeons/wailing-caverns/visual.glb", "/assets/creatures/wailing-caverns/raptor.glb", ]; const files = Object.fromEntries(paths.map((path, index) => [ path, { url: `/content/bundled/${String(index + 1).repeat(64)}.glb` }, ])); const worker = contentWorker(undefined, files); for (const path of paths) { const response = await worker.request(`${path}?starter-runtime=1`); expect(response?.status).toBe(200); expect(await response?.text()).toBe("bundled dungeon"); } expect(worker.fetch).toHaveBeenCalledTimes(paths.length + 1); }); it("prefers installed dungeon updates over bundled files, ignoring the runtime query", async () => { const worker = contentWorker(new Response("updated dungeon")); const response = await worker.request("/assets/game/dungeons/wailing-caverns/visual.glb?starter-runtime=1"); expect(await response?.text()).toBe("updated dungeon"); expect(worker.match).toHaveBeenCalledWith(expect.any(Request), { ignoreSearch: true }); expect(worker.fetch).not.toHaveBeenCalled(); }); it("still blocks optional dungeon assets that have not been installed", async () => { const worker = contentWorker(); const response = await worker.request("/assets/game/manastorm/optional/visual.glb"); expect(response?.status).toBe(404); expect(await response?.text()).toBe("Optional content is not installed."); expect(worker.fetch).toHaveBeenCalledTimes(1); expect(worker.fetch).toHaveBeenCalledWith("/content/bootstrap-manifest.json", { cache: "no-cache" }); }); it("serves only cached same-origin asset requests from the active content cache", () => { expect(workerSource).toContain(`const CONTENT_CACHE_NAME = "${BROWSER_CONTENT_CACHE_NAME}";`); expect(workerSource).toContain('const BOOTSTRAP_CACHE_NAME = "healer-man-bootstrap-v1";'); expect(workerSource).toContain('request.method !== "GET"'); expect(workerSource).toContain("url.origin !== self.location.origin"); expect(workerSource).toContain("MANAGED_ASSET_PREFIXES.some"); expect(workerSource).toContain('"/assets/game/"'); expect(workerSource).toContain("if (!objectPath) return cached"); expect(workerSource).toContain("BUNDLED_ASSET_PATHS.has(url.pathname)"); expect(workerSource).toContain("BUNDLED_ASSET_PREFIXES.some"); expect(workerSource).not.toContain('"/assets/game/arenas/",'); expect(workerSource).not.toContain('"/assets/characters/wow335a-v1/textures/character/human/",'); expect(workerSource).toContain('status: 404'); expect(workerSource).not.toContain("cached ?? fetch(request)"); }); it("expands bundled gzip without requiring a Manastorm install", async () => { const url = "/assets/game/manastorm/shared/visual.glb"; const original = "glTF original dungeon".repeat(100); const worker = contentWorker(undefined, { [url]: { download: { url: "/content/bundled/object.glb.gz", encoding: "gzip" } } }, new Response(gzipSync(original))); const response = await worker.request(url); expect(await response?.text()).toBe(original); expect(response?.headers.get("content-type")).toBe("model/gltf-binary"); }); it("persists immutable bootstrap objects after their first use", async () => { const url = "/assets/game/dungeons/wailing-caverns/visual.glb"; const objectUrl = `/content/bundled/${"b".repeat(64)}.glb`; const worker = contentWorker(undefined, { [url]: { url: objectUrl } }, new Response("persistent dungeon")); expect(await (await worker.request(url))?.text()).toBe("persistent dungeon"); expect(await (await worker.request(url))?.text()).toBe("persistent dungeon"); expect(worker.fetch).toHaveBeenCalledTimes(2); expect(worker.bootstrapPut).toHaveBeenCalledTimes(1); expect(worker.bootstrapMatch).toHaveBeenCalledTimes(2); }); it("resolves a tiny installed alias to one compressed shared object", async () => { const original = "glTF installed original".repeat(100); const worker = contentWorker(new Response(null, { headers: { "x-healer-man-content-object": "/content/cache-objects/shared.glb" } }), {}, new Response(gzipSync(original), { headers: { "x-healer-man-content-encoding": "gzip" } })); expect(await (await worker.request("/assets/creatures/optional/model.glb"))?.text()).toBe(original); expect(worker.fetch).not.toHaveBeenCalled(); }); it("refreshes bootstrap mappings when a new app session starts", async () => { const path = "/assets/game/dungeons/new/visual.glb"; const files: Record = {}; const worker = contentWorker(undefined, files, new Response(gzipSync("new original"))); expect((await worker.request(path))?.status).toBe(404); files[path] = { download: { url: "/content/bundled/new.glb.gzip", encoding: "gzip" } }; worker.refresh(); expect(await (await worker.request(path))?.text()).toBe("new original"); }); });