import { describe, expect, it, vi } from "vitest"; import type { ToolcraftModelDocument } from "./model-document"; import { encodeToolcraftModelDocument } from "./model-document-codec"; import { createValidModelDocument } from "./model-document-test-support"; import { ToolcraftModelDocumentValidationError, validateToolcraftModelDocument, } from "./model-document-validation"; function accessor( target: object, property: PropertyKey, get: () => never, ): void { Object.defineProperty(target, property, { configurable: true, enumerable: true, get, }); } describe("canonical model hostile input handling", () => { it("rejects a throwing top-level getter without invoking it", () => { const get = vi.fn(() => { throw new Error("top-level getter escaped"); }); const document = { ...createValidModelDocument() }; accessor(document, "primitives", get); expect(validateToolcraftModelDocument(document)).toMatchObject({ code: "accessor-property", ok: false, path: "primitives", }); expect(get).not.toHaveBeenCalled(); }); it("rejects a throwing nested getter without invoking it", () => { const get = vi.fn(() => { throw new Error("nested getter escaped"); }); const primitive = { ...createValidModelDocument().primitives[0] }; accessor(primitive, "positions", get); const document = createValidModelDocument({ primitives: [primitive] }); expect(validateToolcraftModelDocument(document)).toMatchObject({ code: "accessor-property", ok: false, path: "primitives[0].positions", }); expect(get).not.toHaveBeenCalled(); }); it("rejects a throwing array entry without invoking it", () => { const get = vi.fn(() => { throw new Error("array getter escaped"); }); const nodes = [...createValidModelDocument().nodes]; accessor(nodes, 0, get); const document = createValidModelDocument({ nodes }); expect(validateToolcraftModelDocument(document)).toMatchObject({ code: "accessor-property", ok: false, path: "nodes[0]", }); expect(get).not.toHaveBeenCalled(); }); it("converts a throwing proxy trap into a typed access failure", () => { const document = new Proxy(createValidModelDocument(), { getOwnPropertyDescriptor() { throw new Error("proxy trap escaped"); }, }); expect(validateToolcraftModelDocument(document)).toMatchObject({ code: "input-access-failed", ok: false, path: "version", }); }); it("converts a proxy trap throwing a hostile value into a typed failure", () => { const hostileThrownValue = new Proxy({}, { getPrototypeOf() { throw new Error("thrown value inspection escaped"); }, }); const document = new Proxy(createValidModelDocument(), { getOwnPropertyDescriptor() { throw hostileThrownValue; }, }); expect(validateToolcraftModelDocument(document)).toMatchObject({ code: "input-access-failed", ok: false, path: "version", }); }); it("makes codec encode throw the typed validation failure", () => { const get = vi.fn(() => { throw new Error("codec getter escaped"); }); const document = { ...createValidModelDocument() }; accessor(document, "bounds", get); expect(() => encodeToolcraftModelDocument(document as ToolcraftModelDocument), ).toThrow(ToolcraftModelDocumentValidationError); try { encodeToolcraftModelDocument(document as ToolcraftModelDocument); } catch (error) { expect(error).toMatchObject({ code: "accessor-property", path: "bounds", }); } expect(get).not.toHaveBeenCalled(); }); it("does not invoke typed-array metadata accessors while encoding", () => { const get = vi.fn(() => { throw new Error("typed-array getter escaped"); }); const source = createValidModelDocument(); const positions = source.primitives[0]!.positions; accessor(positions, "byteLength", get); const document = createValidModelDocument({ primitives: [{ ...source.primitives[0]!, positions }], }); expect(() => encodeToolcraftModelDocument(document)).not.toThrow(); expect(get).not.toHaveBeenCalled(); }); });