Files
hyperframes/scripts/generate-catalog-payloads.test.ts
Miguel Ángel a0e7c43921 fix(catalog): fall back to recorded video when directory is over budget (#4084)
* fix(catalog): fall back to recorded video when directory is over budget

hostItemDirectory() returned "" for both "not needed" and "over the
2MB host budget", so an item that needed its own hosted directory but
exceeded the cap (texture-mask-text: 5.6MB of mask PNGs referenced via
runtime JS template strings, invisible to the static asset scanner)
silently shipped a payload with no <base href> and dead relative
image references, 404ing on the docs site. It now returns a tagged
result so the caller can fall back to the recorded video on
over-budget, same as the existing unresolved-assets fallback.

* test(catalog): cover the over-budget skip path in the payload generator

* chore(catalog): allowlist the deliberate over-budget payload deletion

* chore(catalog): dedupe hosted-directory assertions, note pre-existing complexity

Fallow's new-only gate flagged two problems this branch introduced: five
near-identical hostItemDirectory assertions in the file-race test collapse
into a shared expectHosted() helper, and the over-budget branch added to
buildPayload pushed it one branch past the complexity threshold on top of
already-high pre-existing debt (CRAP 240 before this branch, unrelated to
the fix). Suppressed with the repo's existing fallow-ignore-next-line
convention rather than refactoring an unrelated 115-line function inside
a bugfix PR.
2026-09-18 13:06:09 -04:00

69 lines
2.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
vi.mock("./generate-catalog-previews.js", async (importOriginal) => ({
...(await importOriginal<typeof import("./generate-catalog-previews.js")>()),
prepareProjectDir: vi.fn(),
}));
vi.mock("./catalog-payload-assets.ts", async (importOriginal) => ({
...(await importOriginal<typeof import("./catalog-payload-assets.ts")>()),
hostItemDirectory: vi.fn(),
}));
import { prepareProjectDir } from "./generate-catalog-previews.js";
import { hostItemDirectory } from "./catalog-payload-assets.ts";
import { buildPayload, payloadRoot } from "./generate-catalog-payloads.ts";
const cleanupPaths: string[] = [];
function tmpDir(): string {
const dir = mkdtempSync(join(tmpdir(), "hf-catalog-payload-"));
cleanupPaths.push(dir);
return dir;
}
afterEach(() => {
vi.clearAllMocks();
for (const path of cleanupPaths) rmSync(path, { recursive: true, force: true });
cleanupPaths.length = 0;
});
describe("buildPayload", () => {
it("skips and drops a stale payload when the item's directory is over the host budget", async () => {
const sourceDir = tmpDir();
// Declares a local asset so `needsOwnDirectory` routes through `hostItemDirectory`.
writeFileSync(
join(sourceDir, "registry-item.json"),
JSON.stringify({ files: [{ type: "hyperframes:asset" }] }),
);
const projectDir = tmpDir();
writeFileSync(join(projectDir, "index.html"), "<html></html>");
vi.mocked(prepareProjectDir).mockResolvedValue(projectDir);
vi.mocked(hostItemDirectory).mockReturnValue({ status: "over-budget" });
const item = {
name: "test-over-budget-item",
kind: "component" as const,
sourceDir,
entryFile: "index.html",
};
const outPath = join(payloadRoot, "components", `${item.name}.json`);
cleanupPaths.push(outPath);
mkdirSync(join(payloadRoot, "components"), { recursive: true });
writeFileSync(outPath, JSON.stringify({ html: "stale" }));
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const result = await buildPayload(item);
expect(result).toBe("skipped");
expect(existsSync(outPath)).toBe(false);
expect(
logSpy.mock.calls.some(
(call) => String(call[0]).includes("host budget") && String(call[0]).includes(item.name),
),
).toBe(true);
logSpy.mockRestore();
});
});