import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { spawnSync } from "node:child_process"; import { parseArguments, unresolvedCoaArchiveSpellIds } from "./cli.mjs"; import { publishSnapshot } from "./snapshot.mjs"; test("CLI rejects incomplete source pairs, implicit refresh and unknown options", () => { assert.throws(() => parseArguments(["build", "--client-root", "client"]), /Supply both/); assert.throws(() => parseArguments(["build", "--refresh"]), /only supported by fetch/); assert.throws(() => parseArguments(["build", "--typo"]), /Unknown argument/); assert.throws(() => parseArguments(["lookup", "--class"]), /Missing value/); assert.equal(parseArguments(["fetch", "--refresh"]).refresh, true); assert.equal(parseArguments(["help"]).command, "help"); assert.equal(parseArguments(["--help"]).command, "help"); }); test("fetch-spells selects unresolved CoA-owned API roots and August dependencies", () => { const reference = { progressionChains: [{ classId: 27, ranks: [{ spellId: 300, rank: 1 }, { spellId: 301, rank: 2 }] }], spells: [ { id: 630868, classIds: [27], name: "July API root without global DBC" }, { id: 630869, classIds: [27], name: "Owned trigger payload without global DBC" }, { id: 100, classIds: [1], name: "Classic-only unresolved spell" }, { id: 200, classIds: [27], dbc: { id: 200 }, name: "Already resolved CoA spell" }, { id: 300, classIds: [27], dbc: { id: 300 }, name: "Resolved August rank" }, ], }; const extraNodes = [{ rankDescriptions: [{ spellId: 400, rank: 1 }, { spellId: 401, rank: 2 }] }]; assert.deepEqual(unresolvedCoaArchiveSpellIds(reference, extraNodes), [301, 400, 401, 630868, 630869]); }); test("CLI lookup uses only the verified local snapshot when fetch is disabled", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "coa-reference-cli-")); try { const reference = { baseline: { kind: "local-july" }, sources: [], classes: [{ classId: 12, gameId: "barbarian", className: "Barbarian", tabs: [{ tabId: 31, tabName: "Brutality" }] }], nodes: [{ id: 1, classId: 12, tabId: 31, name: "Strike", rankDescriptions: [{ spellId: 100, rank: 1 }] }], spells: [{ id: 100, name: "Strike" }], abilities: [], }; publishSnapshot({ outputRoot: root, reference, report: {}, markdown: "# Audit" }); const blocker = path.join(root, "disable-network.mjs"); fs.writeFileSync(blocker, 'globalThis.fetch = () => { throw new Error("Network request during offline command"); };\n'); const result = spawnSync(process.execPath, ["--import", pathToFileURL(blocker).href, path.join(import.meta.dirname, "cli.mjs"), "lookup", "--output-root", root, "--class", "barbarian", "--spell", "100"], { encoding: "utf8" }); assert.equal(result.status, 0, result.stderr); assert.equal(JSON.parse(result.stdout).spells[0].id, 100); assert.equal(JSON.parse(result.stdout).baseline.kind, "local-july"); } finally { assert.ok(root.startsWith(`${path.resolve(os.tmpdir())}${path.sep}`)); fs.rmSync(root, { recursive: true }); } });