// IMP-52 u3/u4 — vitest coverage for the vite `/api/user-overrides/:key` // GET and PUT endpoints and their supporting helpers. // // Scope: // u3 (read path): // 1) isValidUserOverridesKey: accept MDX-stem keys (03, 03__DX_BIM, // a-b.c), reject empty / leading-dot / `..` / `/` / `\` / // disallowed chars. Mirrors src/user_overrides_io.validate_key so // backend (u2) and frontend endpoint (u3) agree on every key. // 2) userOverridesPath: returns /data/user_overrides/.json. // 3) handleGetUserOverrides: method != GET → false (next chained for // PUT); invalid key → 400; missing file → 200 {}; corrupt JSON / // non-object root → 200 {} (graceful degrade per u1 load contract); // valid object JSON → 200 with parsed payload echoed back. // // u4 (write path): // 4) mergeUserOverrides: only KNOWN_USER_OVERRIDES_AXES mutated; // foreign top-level keys preserved; null clears axis; non-axis // partial keys dropped (allowlist). // 5) atomicWriteUserOverrides: tmp + rename; parent dir auto-created. // 6) handlePutUserOverrides: method != PUT → false (next chained); // invalid key → 400; invalid JSON → 400; non-object body → 400; // success → 200 with merged result; partial-merge preserves axes // not in payload; foreign-key preserve on disk; allowlist drops // unknown payload keys; explicit null clears; corrupt existing → // recover to clean state. // // Tests exercise the pure handlers with mock req/res — no real vite server. import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { EventEmitter } from "node:events"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { KNOWN_USER_OVERRIDES_AXES, USER_OVERRIDES_KEY_RE, atomicWriteUserOverrides, handleGetUserOverrides, handlePutUserOverrides, isValidUserOverridesKey, mergeUserOverrides, userOverridesPath, } from "../../vite.config"; // --------------------------------------------------------------------------- // mock res helper — captures writeHead(status, headers) + end(body) so the // handler can be invoked synchronously without spawning a TCP socket. // --------------------------------------------------------------------------- function makeMockRes() { const state = { statusCode: 0, headers: {} as Record, body: "", ended: false, }; return { state, res: { writeHead(status: number, headers?: Record) { state.statusCode = status; if (headers) state.headers = headers; }, end(body?: string) { state.body = body ?? ""; state.ended = true; }, }, }; } describe("USER_OVERRIDES_KEY_RE (IMP-52 u3)", () => { it("matches Python validate_key regex literally", () => { // The pattern locked in src/user_overrides_io.py:_KEY_RE — any drift here // means backend pipeline fallback (u2) and the vite endpoint disagree on // which keys are routable, which is the single failure mode that would // silently lose persisted overrides. expect(USER_OVERRIDES_KEY_RE.source).toBe( "^[A-Za-z0-9_][A-Za-z0-9_.\\-]*$", ); }); }); describe("isValidUserOverridesKey (IMP-52 u3)", () => { it("accepts MDX-stem-style keys actually used in samples/mdx/", () => { // 03 / 04 / 05 are the wired sample MDXs (vite.config.ts:SAMPLE_MDX_MAP). expect(isValidUserOverridesKey("03")).toBe(true); expect(isValidUserOverridesKey("04")).toBe(true); expect(isValidUserOverridesKey("05")).toBe(true); // Stage 1 EVIDENCE references 03__DX_BIM... — must round-trip. expect(isValidUserOverridesKey("03__DX_BIM")).toBe(true); expect(isValidUserOverridesKey("a-b.c")).toBe(true); expect(isValidUserOverridesKey("a")).toBe(true); expect(isValidUserOverridesKey("_leading_underscore")).toBe(true); expect(isValidUserOverridesKey("9starts_with_digit")).toBe(true); }); it("rejects empty and whitespace-only keys", () => { expect(isValidUserOverridesKey("")).toBe(false); }); it("rejects path-traversal substrings", () => { // `..` rejected explicitly even if the rest of the regex would allow it // — `a..b` would otherwise pass the char class. expect(isValidUserOverridesKey("..")).toBe(false); expect(isValidUserOverridesKey("a..b")).toBe(false); expect(isValidUserOverridesKey("../escape")).toBe(false); }); it("rejects path separators", () => { expect(isValidUserOverridesKey("a/b")).toBe(false); expect(isValidUserOverridesKey("a\\b")).toBe(false); expect(isValidUserOverridesKey("/")).toBe(false); expect(isValidUserOverridesKey("\\")).toBe(false); }); it("rejects keys starting with a non-word character", () => { expect(isValidUserOverridesKey(".hidden")).toBe(false); expect(isValidUserOverridesKey("-leading-dash")).toBe(false); }); it("rejects characters outside [A-Za-z0-9_.-]", () => { expect(isValidUserOverridesKey("a b")).toBe(false); expect(isValidUserOverridesKey("a:b")).toBe(false); expect(isValidUserOverridesKey("a*b")).toBe(false); expect(isValidUserOverridesKey("a%2Fb")).toBe(false); }); }); describe("userOverridesPath (IMP-52 u3)", () => { it("resolves /data/user_overrides/.json regardless of OS sep", () => { const root = path.join("X:", "design_agent"); const got = userOverridesPath(root, "03"); expect(got).toBe(path.join(root, "data", "user_overrides", "03.json")); }); }); describe("handleGetUserOverrides (IMP-52 u3)", () => { let tmpRoot: string; let overridesDir: string; beforeEach(() => { tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "imp52-u3-")); overridesDir = path.join(tmpRoot, "data", "user_overrides"); fs.mkdirSync(overridesDir, { recursive: true }); }); afterEach(() => { fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("returns false (next chained) when method != GET", () => { const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "PUT", url: "/03" }, res, tmpRoot, ); expect(handled).toBe(false); // Crucial for u4: PUT must reach its own middleware unobstructed. expect(state.ended).toBe(false); expect(state.statusCode).toBe(0); }); it("returns 400 on invalid key (path traversal)", () => { const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "GET", url: "/../escape" }, res, tmpRoot, ); expect(handled).toBe(true); expect(state.statusCode).toBe(400); expect(JSON.parse(state.body)).toEqual({ error: "invalid key" }); }); it("returns 400 on invalid key (missing key segment)", () => { const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "GET", url: "/" }, res, tmpRoot, ); expect(handled).toBe(true); expect(state.statusCode).toBe(400); }); it("returns 200 {} on missing file (graceful degrade)", () => { const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "GET", url: "/03" }, res, tmpRoot, ); expect(handled).toBe(true); expect(state.statusCode).toBe(200); expect(state.body).toBe("{}"); }); it("returns 200 {} on corrupt JSON (graceful degrade)", () => { fs.writeFileSync(path.join(overridesDir, "03.json"), "{not json", "utf-8"); const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "GET", url: "/03" }, res, tmpRoot, ); expect(handled).toBe(true); expect(state.statusCode).toBe(200); expect(state.body).toBe("{}"); }); it("returns 200 {} when JSON root is not an object", () => { // Mirrors u1 load() which treats non-object roots as corrupt — covers // both arrays and primitives so the frontend never receives a shape // the typed service (u5) can't deserialize. fs.writeFileSync( path.join(overridesDir, "arr.json"), JSON.stringify([1, 2, 3]), "utf-8", ); const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "GET", url: "/arr" }, res, tmpRoot, ); expect(handled).toBe(true); expect(state.statusCode).toBe(200); expect(state.body).toBe("{}"); fs.writeFileSync(path.join(overridesDir, "num.json"), "42", "utf-8"); const { res: res2, state: state2 } = makeMockRes(); handleGetUserOverrides({ method: "GET", url: "/num" }, res2, tmpRoot); expect(state2.statusCode).toBe(200); expect(state2.body).toBe("{}"); }); it("returns 200 with parsed JSON object on hit", () => { const payload = { layout: "two_zone_split", frames: { "03-1+03-2": "frame_07" }, zone_geometries: { top: { x: 0.05, y: 0.1, w: 0.9, h: 0.3 }, }, zone_sections: { top: ["03-1", "03-2"] }, }; fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify(payload), "utf-8", ); const { res, state } = makeMockRes(); const handled = handleGetUserOverrides( { method: "GET", url: "/03" }, res, tmpRoot, ); expect(handled).toBe(true); expect(state.statusCode).toBe(200); expect(state.headers["Content-Type"]).toBe( "application/json; charset=utf-8", ); expect(JSON.parse(state.body)).toEqual(payload); }); it("preserves foreign top-level keys in the response", () => { // Forward-compat with future axes (e.g., zone_sizes, image_overrides). // u1 save() preserves them on the disk side; u3 GET must surface them // so the frontend service (u5) can decide whether to act on them. const payload = { layout: "single_zone", zone_sizes: { top: 0.42 }, // not part of KNOWN_AXES yet custom_extension: { foo: "bar" }, }; fs.writeFileSync( path.join(overridesDir, "future.json"), JSON.stringify(payload), "utf-8", ); const { res, state } = makeMockRes(); handleGetUserOverrides({ method: "GET", url: "/future" }, res, tmpRoot); expect(state.statusCode).toBe(200); expect(JSON.parse(state.body)).toEqual(payload); }); it("strips the leading slash and ignores query string when keying", () => { fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify({ layout: "x" }), "utf-8", ); const { res, state } = makeMockRes(); handleGetUserOverrides( { method: "GET", url: "/03?ts=1747884800" }, res, tmpRoot, ); expect(state.statusCode).toBe(200); expect(JSON.parse(state.body)).toEqual({ layout: "x" }); }); }); // --------------------------------------------------------------------------- // IMP-52 u4 — PUT endpoint coverage // --------------------------------------------------------------------------- describe("KNOWN_USER_OVERRIDES_AXES (IMP-52 u4)", () => { it("matches the Python KNOWN_AXES tuple in src/user_overrides_io.py", () => { // The on-disk schema is shared with backend pipeline fallback (u2). // Any drift here means a PUT could write an axis that the Python // load() ignores, or vice-versa, silently losing user overrides. // Mirror is Python-minus-`slide_css` (known IMP-45 #74 gap — the // frontend never writes slide_css). IMP-55 #93 u1 adds the bool // `manual_section_assignment` axis as a first-class allowlist entry. expect(KNOWN_USER_OVERRIDES_AXES).toEqual([ "layout", "zone_geometries", "zone_sections", "frames", "image_overrides", "manual_section_assignment", ]); }); }); describe("mergeUserOverrides (IMP-55 #93 u1) — manual_section_assignment bool axis", () => { it("merges bool true / false literally and clears on null", () => { // The PUT handler must treat the bool axis like any other allowlisted // axis: replace on write, preserve when absent, delete on null. Tests // both true→false flip and explicit null-clear so the backend (u9) // sees the exact frontend intent. let merged = mergeUserOverrides({}, { manual_section_assignment: true }); expect(merged.manual_section_assignment).toBe(true); merged = mergeUserOverrides(merged, { manual_section_assignment: false }); expect(merged.manual_section_assignment).toBe(false); merged = mergeUserOverrides(merged, { manual_section_assignment: null }); expect("manual_section_assignment" in merged).toBe(false); }); it("preserves bool axis when partial touches only a sibling axis", () => { const existing = { manual_section_assignment: true, layout: "old" }; const merged = mergeUserOverrides(existing, { layout: "new" }); expect(merged.manual_section_assignment).toBe(true); expect(merged.layout).toBe("new"); }); }); describe("mergeUserOverrides (IMP-52 u4)", () => { it("only mutates KNOWN_AXES present in partial", () => { const existing = { layout: "old", frames: { "03-1": "frame_01" }, zone_geometries: { top: { x: 0, y: 0, w: 1, h: 0.5 } }, zone_sections: { top: ["03-1"] }, }; const merged = mergeUserOverrides(existing, { layout: "new" }); expect(merged.layout).toBe("new"); // axes not in partial are preserved expect(merged.frames).toEqual({ "03-1": "frame_01" }); expect(merged.zone_geometries).toEqual({ top: { x: 0, y: 0, w: 1, h: 0.5 }, }); expect(merged.zone_sections).toEqual({ top: ["03-1"] }); }); it("preserves foreign top-level keys in existing", () => { // Forward-compat: future axes (zone_sizes, schema_version, etc.) on // disk must survive PUT writes that only touch the 5 in-scope axes. // `image_overrides` is no longer a foreign key after IMP-51 #79 u2 — // it joined KNOWN_USER_OVERRIDES_AXES — so we probe with axes that // are still NOT in the allowlist. const existing = { layout: "old", zone_sizes: { top: 0.42 }, schema_version: 2, }; const merged = mergeUserOverrides(existing, { layout: "new" }); expect(merged.zone_sizes).toEqual({ top: 0.42 }); expect(merged.schema_version).toBe(2); }); it("clears axis when partial value is null (explicit clear)", () => { const existing = { layout: "x", frames: { "03-1": "f01" } }; const merged = mergeUserOverrides(existing, { layout: null }); expect("layout" in merged).toBe(false); expect(merged.frames).toEqual({ "03-1": "f01" }); }); it("drops non-axis keys in partial (allowlist)", () => { // PUT payload may carry junk fields (typo, malicious key); allowlist // ensures only the 5 axes can be written to disk. const merged = mergeUserOverrides( {}, { layout: "x", random_key: "evil", __proto__: "x" } as Record< string, unknown >, ); expect(merged.layout).toBe("x"); expect("random_key" in merged).toBe(false); }); it("merges all 5 axes when present in partial", () => { const merged = mergeUserOverrides( {}, { layout: "two_zone_split", frames: { "03-1+03-2": "frame_07" }, zone_geometries: { top: { x: 0, y: 0, w: 1, h: 0.5 } }, zone_sections: { top: ["03-1", "03-2"] }, image_overrides: { "img-1": { x: 0.1, y: 0.2, w: 0.3, h: 0.25 } }, }, ); expect(Object.keys(merged).sort()).toEqual([ "frames", "image_overrides", "layout", "zone_geometries", "zone_sections", ]); }); it("preserves image_overrides when absent from partial (5th axis IMP-51 #79 u2)", () => { // Sibling axis of layout/frames/zone_geometries/zone_sections: a PUT // that touches only layout must NOT erase the image_overrides map // already on disk. Mirrors the partial-merge invariant for the 4 // pre-existing axes. const existing = { layout: "old", image_overrides: { "img-1": { x: 0.1, y: 0.2, w: 0.3, h: 0.25 } }, }; const merged = mergeUserOverrides(existing, { layout: "new" }); expect(merged.image_overrides).toEqual({ "img-1": { x: 0.1, y: 0.2, w: 0.3, h: 0.25 }, }); expect(merged.layout).toBe("new"); }); it("clears image_overrides when partial value is null (explicit clear)", () => { // Same null-sentinel contract as the 4 sibling axes — `null` removes // the axis from disk so the next render reverts to baseline (no // user image position/size override). const existing = { layout: "x", image_overrides: { "img-1": { x: 0.1, y: 0.2, w: 0.3, h: 0.25 } }, }; const merged = mergeUserOverrides(existing, { image_overrides: null }); expect("image_overrides" in merged).toBe(false); expect(merged.layout).toBe("x"); }); it("does not mutate the existing input", () => { const existing = { layout: "old", frames: { a: "b" } }; const snapshot = JSON.parse(JSON.stringify(existing)); mergeUserOverrides(existing, { layout: "new", layout_evil: "x" } as Record< string, unknown >); expect(existing).toEqual(snapshot); }); }); describe("atomicWriteUserOverrides (IMP-52 u4)", () => { let tmpRoot: string; beforeEach(() => { tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "imp52-u4-aw-")); }); afterEach(() => { fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("creates parent dir if missing and writes JSON content", () => { const filePath = path.join(tmpRoot, "data", "user_overrides", "03.json"); expect(fs.existsSync(path.dirname(filePath))).toBe(false); atomicWriteUserOverrides(filePath, { layout: "x" }); expect(fs.existsSync(filePath)).toBe(true); expect(JSON.parse(fs.readFileSync(filePath, "utf-8"))).toEqual({ layout: "x", }); }); it("leaves no .tmp residue after a successful write", () => { const filePath = path.join(tmpRoot, "data", "user_overrides", "03.json"); atomicWriteUserOverrides(filePath, { layout: "x" }); const dirContents = fs.readdirSync(path.dirname(filePath)); expect(dirContents).toEqual(["03.json"]); }); it("overwrites an existing file atomically", () => { const filePath = path.join(tmpRoot, "data", "user_overrides", "03.json"); atomicWriteUserOverrides(filePath, { layout: "v1" }); atomicWriteUserOverrides(filePath, { layout: "v2" }); expect(JSON.parse(fs.readFileSync(filePath, "utf-8"))).toEqual({ layout: "v2", }); }); }); // req mock — EventEmitter with method/url + a `send(body)` helper that // emits the data chunk and then `end`, mirroring the node IncomingMessage // flow used by vite's dev middlewares. function makeMockReq(opts: { method?: string; url?: string; }): EventEmitter & { method?: string; url?: string; send: (body: string) => void } { const ee = new EventEmitter() as EventEmitter & { method?: string; url?: string; send: (body: string) => void; }; ee.method = opts.method; ee.url = opts.url; ee.send = (body: string) => { if (body.length > 0) ee.emit("data", Buffer.from(body, "utf-8")); ee.emit("end"); }; return ee; } describe("handlePutUserOverrides (IMP-52 u4)", () => { let tmpRoot: string; let overridesDir: string; beforeEach(() => { tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "imp52-u4-")); overridesDir = path.join(tmpRoot, "data", "user_overrides"); }); afterEach(() => { fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("returns false (next chained) when method != PUT", () => { const req = makeMockReq({ method: "GET", url: "/03" }); const { res, state } = makeMockRes(); const handled = handlePutUserOverrides(req, res, tmpRoot); expect(handled).toBe(false); expect(state.ended).toBe(false); }); it("returns 400 on invalid key", () => { const req = makeMockReq({ method: "PUT", url: "/../escape" }); const { res, state } = makeMockRes(); const handled = handlePutUserOverrides(req, res, tmpRoot); expect(handled).toBe(true); expect(state.statusCode).toBe(400); expect(JSON.parse(state.body)).toEqual({ error: "invalid key" }); }); it("returns 400 on invalid JSON body", () => { const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send("{not json"); expect(state.statusCode).toBe(400); expect(JSON.parse(state.body)).toEqual({ error: "invalid JSON" }); // file MUST NOT have been created on parse failure expect(fs.existsSync(path.join(overridesDir, "03.json"))).toBe(false); }); it("returns 400 when JSON body is an array", () => { const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify([1, 2, 3])); expect(state.statusCode).toBe(400); expect(JSON.parse(state.body)).toEqual({ error: "body must be a JSON object", }); }); it("returns 400 when JSON body is a primitive", () => { const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send("42"); expect(state.statusCode).toBe(400); expect(JSON.parse(state.body)).toEqual({ error: "body must be a JSON object", }); }); it("creates the override file on first PUT and returns merged body", () => { const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); const payload = { layout: "two_zone_split" }; req.send(JSON.stringify(payload)); expect(state.statusCode).toBe(200); expect(state.headers["Content-Type"]).toBe( "application/json; charset=utf-8", ); expect(JSON.parse(state.body)).toEqual({ layout: "two_zone_split" }); const filePath = path.join(overridesDir, "03.json"); expect(fs.existsSync(filePath)).toBe(true); expect(JSON.parse(fs.readFileSync(filePath, "utf-8"))).toEqual({ layout: "two_zone_split", }); }); it("partial-merges: axes absent from payload are preserved on disk", () => { fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify({ layout: "old", frames: { "03-1": "frame_01" }, zone_sections: { top: ["03-1"] }, }), "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify({ layout: "new" })); expect(state.statusCode).toBe(200); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect(onDisk).toEqual({ layout: "new", frames: { "03-1": "frame_01" }, zone_sections: { top: ["03-1"] }, }); }); it("preserves foreign top-level keys on disk (forward-compat)", () => { // `image_overrides` is no longer a foreign key after IMP-51 #79 u2; // probe with axes that are still NOT in KNOWN_USER_OVERRIDES_AXES. fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "future.json"), JSON.stringify({ layout: "old", zone_sizes: { top: 0.42 }, schema_version: 2, }), "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/future" }); const { res } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify({ layout: "new" })); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "future.json"), "utf-8"), ); expect(onDisk.zone_sizes).toEqual({ top: 0.42 }); expect(onDisk.schema_version).toBe(2); expect(onDisk.layout).toBe("new"); }); it("persists image_overrides partial-merge and preserves sibling axes (IMP-51 #79 u2)", () => { // 5th axis end-to-end PUT round-trip: writing only image_overrides // must NOT touch the 4 sibling axes already on disk. Mirrors the // existing partial-merge test for layout above. fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify({ layout: "two_zone_split", frames: { "03-1": "frame_01" }, zone_geometries: { top: { x: 0, y: 0, w: 1, h: 0.5 } }, zone_sections: { top: ["03-1"] }, }), "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send( JSON.stringify({ image_overrides: { "img-1": { x: 0.1, y: 0.2, w: 0.3, h: 0.25 } }, }), ); expect(state.statusCode).toBe(200); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect(onDisk).toEqual({ layout: "two_zone_split", frames: { "03-1": "frame_01" }, zone_geometries: { top: { x: 0, y: 0, w: 1, h: 0.5 } }, zone_sections: { top: ["03-1"] }, image_overrides: { "img-1": { x: 0.1, y: 0.2, w: 0.3, h: 0.25 } }, }); }); it("drops non-axis payload keys (allowlist enforced at write)", () => { fs.mkdirSync(overridesDir, { recursive: true }); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send( JSON.stringify({ layout: "two_zone_split", random_evil_key: "should not persist", }), ); expect(state.statusCode).toBe(200); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect(onDisk).toEqual({ layout: "two_zone_split" }); expect("random_evil_key" in onDisk).toBe(false); }); it("clears an axis when payload sets it to null", () => { fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify({ layout: "old", frames: { "03-1": "f01" } }), "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify({ layout: null })); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect("layout" in onDisk).toBe(false); expect(onDisk.frames).toEqual({ "03-1": "f01" }); }); it("recovers from corrupt existing file (graceful degrade)", () => { fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "03.json"), "{this is not JSON", "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify({ layout: "recovered" })); expect(state.statusCode).toBe(200); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect(onDisk).toEqual({ layout: "recovered" }); }); it("treats array-rooted existing file as empty (graceful degrade)", () => { fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify(["not", "an", "object"]), "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify({ layout: "recovered" })); expect(state.statusCode).toBe(200); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect(onDisk).toEqual({ layout: "recovered" }); }); it("strips the leading slash and ignores query string when keying", () => { const req = makeMockReq({ method: "PUT", url: "/03?ts=1747884800", }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(JSON.stringify({ layout: "x" })); expect(state.statusCode).toBe(200); expect(fs.existsSync(path.join(overridesDir, "03.json"))).toBe(true); }); it("accepts an empty body as a no-op partial (no axes mutated)", () => { fs.mkdirSync(overridesDir, { recursive: true }); fs.writeFileSync( path.join(overridesDir, "03.json"), JSON.stringify({ layout: "kept" }), "utf-8", ); const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); req.send(""); expect(state.statusCode).toBe(200); const onDisk = JSON.parse( fs.readFileSync(path.join(overridesDir, "03.json"), "utf-8"), ); expect(onDisk).toEqual({ layout: "kept" }); }); it("accepts a chunked PUT body (concatenates data events)", () => { const req = makeMockReq({ method: "PUT", url: "/03" }); const { res, state } = makeMockRes(); expect(handlePutUserOverrides(req, res, tmpRoot)).toBe(true); const body = JSON.stringify({ layout: "two_zone_split", frames: { "03-1": "frame_01" }, }); // Emit in two halves to simulate a fragmented HTTP body. const half = Math.floor(body.length / 2); req.emit("data", Buffer.from(body.slice(0, half), "utf-8")); req.emit("data", Buffer.from(body.slice(half), "utf-8")); req.emit("end"); expect(state.statusCode).toBe(200); expect(JSON.parse(state.body)).toEqual({ layout: "two_zone_split", frames: { "03-1": "frame_01" }, }); }); });