import test from "node:test"; import assert from "node:assert/strict"; import { OpsInputError, optNonNegativeInt, optPositiveInt, optSubset, reqVideoId, reqVideoItems, } from "./_lib"; // Run with: // pnpm -C editor exec tsx --test "app/**/*.test.ts" const BUCKET = ["a1", "b2", "c3"]; test("optSubset: absent is undefined, so the caller falls back to the whole list", () => { assert.equal(optSubset({}, "ids", BUCKET, "the bucket"), undefined); }); test("optSubset: a subset comes back in the caller's order, duplicates collapsed", () => { assert.deepEqual( optSubset({ ids: ["c3", "a1", "c3"] }, "ids", BUCKET, "the bucket"), ["c3", "a1"], ); }); test("optSubset: a stray id is refused and every stray is named", () => { assert.throws( () => optSubset({ ids: ["a1", "zz", "yy"] }, "ids", BUCKET, "the bucket"), (e: unknown) => e instanceof OpsInputError && /2 of "ids" not in the bucket: zz, yy/.test(e.message), ); }); test("optSubset: an empty or non-string list is refused, never read as 'all'", () => { for (const ids of [[], "a1", [1], [""]]) { assert.throws( () => optSubset({ ids }, "ids", BUCKET, "the bucket"), OpsInputError, JSON.stringify(ids), ); } }); test("optPositiveInt: absent is undefined; a whole number above zero comes back", () => { assert.equal(optPositiveInt({}, "limit"), undefined); assert.equal(optPositiveInt({ limit: 200 }, "limit"), 200); }); test("optPositiveInt: zero, negatives, fractions and strings are refused", () => { for (const limit of [0, -5, 2.5, "10", null, Number.NaN]) { assert.throws( () => optPositiveInt({ limit }, "limit"), (e: unknown) => e instanceof OpsInputError && /"limit" must be a whole number above zero/.test(e.message), JSON.stringify(limit), ); } }); test("optNonNegativeInt: absent is undefined; zero and whole numbers come back", () => { assert.equal(optNonNegativeInt({}, "gapMs"), undefined); assert.equal(optNonNegativeInt({ gapMs: 0 }, "gapMs"), 0); assert.equal(optNonNegativeInt({ gapMs: 30000 }, "gapMs"), 30000); }); test("optNonNegativeInt: negatives, fractions and strings are refused", () => { for (const gapMs of [-1, 1.5, "10", null, Number.NaN]) { assert.throws( () => optNonNegativeInt({ gapMs }, "gapMs"), (e: unknown) => e instanceof OpsInputError && /"gapMs" must be a whole number, zero or above/.test(e.message), JSON.stringify(gapMs), ); } }); test("reqVideoItems: a list of { slug, id } comes back trimmed, in order", () => { assert.deepEqual( reqVideoItems( { items: [{ slug: "demo-channel", id: " abc123 " }, { slug: "other", id: "x" }] }, "items", ), [ { slug: "demo-channel", id: "abc123" }, { slug: "other", id: "x" }, ], ); }); test("reqVideoItems: an empty list, a non-object, an extra key or a missing id is refused", () => { for (const items of [ undefined, [], "demo-channel/abc123", ["abc123"], [{ slug: "demo-channel" }], [{ slug: "demo-channel", id: "abc123", height: 720 }], ]) { assert.throws(() => reqVideoItems({ items }, "items"), OpsInputError, JSON.stringify(items)); } }); test("reqVideoItems: a traversing slug or id is refused at the door", () => { for (const item of [ { slug: "../../escape", id: "abc123" }, { slug: "demo-channel", id: "../abc123" }, { slug: "demo-channel", id: ".." }, { slug: "demo-channel", id: "a\\b" }, ]) { assert.throws(() => reqVideoItems({ items: [item] }, "items"), OpsInputError, JSON.stringify(item)); } }); test("reqVideoId: one path segment comes back trimmed; a traversing or empty one is refused", () => { assert.equal(reqVideoId({ id: " H64QQZuw-aA " }, "id"), "H64QQZuw-aA"); for (const id of ["..", ".", "a/b", "a\\b", "", 7]) { assert.throws( () => reqVideoId({ id }, "id"), OpsInputError, JSON.stringify(id), ); } });