import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { gunzipSync } from "node:zlib"; import { prepareContentObjects } from "./content-objects.mjs"; import { selectBootstrapContent } from "./prepare-bootstrap.mjs"; import { createContentManifest } from "./publish-content.mjs"; import contentPolicy from "../../shared/content-policy.json" with { type: "json" }; test("ships one lossless object for identical assets and verifies original bytes", async () => { const directory = await mkdtemp(path.join(os.tmpdir(), "healer-content-compression-")); try { const sourcePath = path.join(directory, "original.glb"); const original = Buffer.from("original geometry, textures, and animation\n".repeat(4096)); await writeFile(sourcePath, original); const common = { sourcePath, bytes: original.length, sha256: createHash("sha256").update(original).digest("hex"), extension: ".glb" }; const files = ["first", "second"].map((id) => ({ ...common, logicalPath: `assets/creatures/${id}/model.glb`, packId: `creatures:${id}` })); const result = await prepareContentObjects(files, { directory: path.join(directory, "objects"), baseUrl: "/content/bundled" }); assert.equal(result.uniqueObjects, 1); assert.equal(result.uniqueBytes, original.length); assert.equal(result.logicalBytes, original.length * 2); assert.ok(result.storedBytes < original.length / 10); assert.equal((await readdir(path.join(directory, "objects"))).length, 1); assert.deepEqual(result.files[0].download, result.files[1].download); assert.ok(result.files[0].download.url.endsWith(".gzip"), "avoid Android's automatic .gz expansion"); const stored = await readFile(path.join(directory, "objects", path.basename(result.files[0].download.url))); assert.deepEqual(gunzipSync(stored), original); assert.deepEqual(await readFile(sourcePath), original); const again = await prepareContentObjects(files, { directory: path.join(directory, "objects"), baseUrl: "/content/bundled" }); assert.equal(again.writtenObjects, 0); assert.equal(createContentManifest(result.files).version, createContentManifest(again.files).version); await writeFile(sourcePath, "changed"); await assert.rejects(prepareContentObjects(files, { directory: path.join(directory, "objects"), baseUrl: "/content/bundled" }), /changed during packaging/); } finally { await rm(directory, { recursive: true, force: true }); } }); test("bundles only starter packs and their exact dependencies", () => { const ids = [...contentPolicy.bootstrapPackIds, "dungeon:second", "shared:creatures", "manastorm:shared-map", "manastorm:optional-map", "characters:extra"]; const files = ids.map((packId, index) => ({ logicalPath: `assets/${index}.glb`, packId, sha256: String(index).repeat(64), bytes: 100, extension: ".glb" })); const result = selectBootstrapContent(files, [ { id: "dungeon:second", dependencies: [] }, ]); assert.deepEqual(result.files.map((file) => file.packId), contentPolicy.bootstrapPackIds); const packs = new Map(result.packDefinitions.map((pack) => [pack.id, pack])); assert.deepEqual([...packs.keys()].sort(), [...contentPolicy.bootstrapPackIds].sort()); assert.equal([...packs.values()].every((pack) => pack.bundledOnAndroid), true); assert.equal(packs.has("dungeon:second"), false); assert.equal(packs.has("manastorm:shared-map"), false); assert.equal(packs.has("manastorm:optional-map"), false); });