/** * @file pets.test.js * @description `/api/pets` and `server/lib/pets.js`: importing a * `.codex-pet.zip` (root or one folder deep; refused when a file is missing, * the sheet is not a 1536×1872 WebP, or an entry is too large BEFORE it is * inflated), uploading an image (raster only — SVG refused), installing a * featured pet only from codex-pets.net by its fixed id, deleting, serving the * sprite with nosniff, ids made by the server, and every write same-origin. * @author Nguyễn Ngọc Trí Vĩ */ "use strict"; const { describe, it, before, after, beforeEach } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const http = require("node:http"); const AdmZip = require("adm-zip"); const ROOT = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "pets-"))); process.env.DASHBOARD_DB_PATH = path.join(ROOT, "t.db"); process.env.CLAUDE_HOME = path.join(ROOT, "claude"); process.env.DASHBOARD_DATA_DIR = path.join(ROOT, "data"); process.env.DASHBOARD_REMOTE_SYNC_MS = "0"; process.env.DASHBOARD_LIVENESS_PROBE = "0"; const { createApp } = require("../index"); const pets = require("../lib/pets"); let server; let base; /** A WebP header (VP8X) claiming `w`×`h` — the server never decodes pixels. */ function webp(w = 1536, h = 1872, pad = 64) { const b = Buffer.alloc(30 + pad); b.write("RIFF", 0); b.writeUInt32LE(b.length - 8, 4); b.write("WEBP", 8); b.write("VP8X", 12); b.writeUInt32LE(10, 16); b.writeUIntLE(w - 1, 24, 3); b.writeUIntLE(h - 1, 27, 3); return b; } const PNG = Buffer.concat([ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), Buffer.alloc(32), ]); function bundle(files) { const zip = new AdmZip(); for (const [name, data] of Object.entries(files)) zip.addFile(name, Buffer.isBuffer(data) ? data : Buffer.from(JSON.stringify(data))); return zip.toBuffer(); } function request(method, url, { body, headers = {} } = {}) { return new Promise((resolve, reject) => { const req = http.request( `${base}${url}`, { method, headers: { ...(body ? { "content-type": "application/octet-stream" } : {}), ...headers, }, }, (res) => { const chunks = []; res.on("data", (c) => chunks.push(c)); res.on("end", () => { const buf = Buffer.concat(chunks); let json = null; try { json = JSON.parse(buf.toString("utf8")); } catch { /* a sprite */ } resolve({ status: res.statusCode, headers: res.headers, json, buf }); }); } ); req.on("error", reject); if (body) req.write(body); req.end(); }); } before(async () => { const app = createApp(); server = http.createServer(app); await new Promise((r) => server.listen(0, "127.0.0.1", r)); base = `http://127.0.0.1:${server.address().port}`; }); after(() => { server.close(); pets.__setFetch(null); fs.rmSync(ROOT, { recursive: true, force: true }); }); beforeEach(() => { fs.rmSync(path.join(ROOT, "data", "pets"), { recursive: true, force: true }); pets.__setFetch(() => { throw new Error("no network in tests"); }); }); describe("importing a .codex-pet bundle", () => { it("stores pet.json + spritesheet.webp under an id the server makes, and serves the sheet", async () => { const r = await request("POST", "/api/pets/import", { body: bundle({ "pet.json": { id: "../../evil", displayName: " Corgi\nByte ", description: "A corgi." }, "spritesheet.webp": webp(), }), }); assert.equal(r.status, 201); assert.match(r.json.pet.id, /^p-[0-9a-f]{12}$/); assert.equal(r.json.pet.displayName, "Corgi Byte"); assert.equal(r.json.pet.kind, "codex"); const list = await request("GET", "/api/pets"); assert.deepEqual( list.json.pets.map((p) => p.id), [r.json.pet.id] ); assert.deepEqual(list.json.sheet, { width: 1536, height: 1872, cellWidth: 192, cellHeight: 208, }); const sprite = await request("GET", `/api/pets/${r.json.pet.id}/sprite`); assert.equal(sprite.status, 200); assert.equal(sprite.headers["content-type"], "image/webp"); assert.equal(sprite.headers["x-content-type-options"], "nosniff"); }); it("accepts the two files one folder deep", async () => { const r = await request("POST", "/api/pets/import", { body: bundle({ "corgi/pet.json": { displayName: "Corgi" }, "corgi/spritesheet.webp": webp(), }), }); assert.equal(r.status, 201); }); it("accepts a v2 sheet (1536×2288, the 16 gaze poses in rows 9–10)", async () => { const r = await request("POST", "/api/pets/import", { body: bundle({ "pet.json": { spriteVersionNumber: 2 }, "spritesheet.webp": webp(1536, 2288), }), }); assert.equal(r.status, 201); }); it("refuses a bundle missing a file, a sheet of the wrong size, and a non-zip", async () => { const missing = await request("POST", "/api/pets/import", { body: bundle({ "pet.json": {} }) }); assert.equal(missing.status, 400); assert.equal(missing.json.error.code, "EBADPET"); const wrong = await request("POST", "/api/pets/import", { body: bundle({ "pet.json": {}, "spritesheet.webp": webp(512, 512) }), }); assert.equal(wrong.status, 400); assert.match(wrong.json.error.message, /512×512/); const notZip = await request("POST", "/api/pets/import", { body: Buffer.from("hello") }); assert.equal(notZip.json.error.code, "EBADPET"); assert.deepEqual((await request("GET", "/api/pets")).json.pets, []); }); it("refuses an oversized entry from its header, before inflating it", async () => { const r = await request("POST", "/api/pets/import", { body: bundle({ "pet.json": {}, "spritesheet.webp": Buffer.concat([webp(), Buffer.alloc(7 * 1024 * 1024)]), }), }); assert.equal(r.status, 413); assert.equal(r.json.error.code, "ETOOBIG"); }); }); describe("uploading an image", () => { it("keeps a raster image as a still pet, named after its file", async () => { const r = await request("POST", "/api/pets/upload?name=My%20cat.png", { body: PNG }); assert.equal(r.status, 201); assert.equal(r.json.pet.kind, "image"); assert.equal(r.json.pet.displayName, "My cat"); const sprite = await request("GET", `/api/pets/${r.json.pet.id}/sprite`); assert.equal(sprite.headers["content-type"], "image/png"); }); it("refuses SVG and anything that is not a known raster", async () => { const svg = await request("POST", "/api/pets/upload?name=x.svg", { body: Buffer.from(''), }); assert.equal(svg.status, 400); assert.equal(svg.json.error.code, "EBADPET"); }); }); describe("featured pets", () => { it("installs one only from codex-pets.net by its fixed id, once per id", async () => { const urls = []; pets.__setFetch(async (url) => { urls.push(url); const body = url.endsWith("/spritesheet") ? webp() : Buffer.from(JSON.stringify({ pet: { description: "A martial artist." } })); return { ok: true, status: 200, headers: { get: () => null }, arrayBuffer: async () => body }; }); const r = await request("POST", "/api/pets/featured/goku"); assert.equal(r.status, 201); assert.equal(r.json.pet.id, "cp-goku"); assert.equal(r.json.pet.source, "featured"); assert.equal(r.json.pet.description, "A martial artist."); assert.deepEqual(urls, [ "https://codex-pets.net/api/pets/goku", "https://codex-pets.net/api/pets/goku/spritesheet", ]); await request("POST", "/api/pets/featured/goku"); const list = await request("GET", "/api/pets"); assert.equal(list.json.pets.length, 1); assert.equal(list.json.featured.find((f) => f.id === "goku").installed, true); }); it("refuses an id that is not featured without any request", async () => { let called = false; pets.__setFetch(async () => { called = true; }); const r = await request("POST", "/api/pets/featured/some-other-pet"); assert.equal(r.status, 404); assert.equal(called, false); }); it("reports a failed download as EFETCH and a bad sheet as EBADPET", async () => { pets.__setFetch(async () => ({ ok: false, status: 503, headers: { get: () => null } })); const down = await request("POST", "/api/pets/featured/goku"); assert.equal(down.status, 502); assert.equal(down.json.error.code, "EFETCH"); pets.__setFetch(async () => ({ ok: true, status: 200, headers: { get: () => null }, arrayBuffer: async () => webp(100, 100), })); const bad = await request("POST", "/api/pets/featured/goku"); assert.equal(bad.json.error.code, "EBADPET"); }); }); describe("deleting and serving", () => { it("deletes a pet, and answers 404 for an unknown or path-shaped id", async () => { const r = await request("POST", "/api/pets/upload?name=a.png", { body: PNG }); const del = await request("DELETE", `/api/pets/${r.json.pet.id}`); assert.equal(del.status, 200); assert.deepEqual((await request("GET", "/api/pets")).json.pets, []); assert.equal((await request("DELETE", `/api/pets/${r.json.pet.id}`)).status, 404); assert.equal((await request("GET", "/api/pets/..%2F..%2Ft.db/sprite")).status, 404); assert.equal((await request("DELETE", "/api/pets/p-nothere00000")).status, 404); }); }); describe("same-origin", () => { it("refuses every write from another origin", async () => { const headers = { origin: "https://evil.example" }; assert.equal( (await request("POST", "/api/pets/upload?name=a.png", { body: PNG, headers })).status, 403 ); assert.equal((await request("POST", "/api/pets/import", { body: PNG, headers })).status, 403); assert.equal((await request("POST", "/api/pets/featured/goku", { headers })).status, 403); assert.equal((await request("DELETE", "/api/pets/p-000000000000", { headers })).status, 403); }); });