import assert from "node:assert/strict"; import { EventEmitter } from "node:events"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:http"; import os from "node:os"; import path from "node:path"; import { PassThrough } from "node:stream"; import test from "node:test"; import { createLocalBalanceLabApi } from "./local-balance-lab-api.mjs"; test("serves loopback-only local Balance Lab jobs without account authentication", async () => { const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-local-balance-gui-")); const spawnProcess = (_executable, args) => { const child = new EventEmitter(); child.stdout = new PassThrough(); child.stderr = new PassThrough(); child.kill = () => child.emit("close", null); const outputDir = args[args.indexOf("--output-dir") + 1]; setImmediate(() => { mkdirSync(outputDir, { recursive: true }); writeFileSync(path.join(outputDir, "suite-report.json"), JSON.stringify({ summary: { partyConfigurations: 1, bosses: 1, runs: 1, classSpecs: 5, classAbilities: 5 }, })); child.stdout.write("Party 1/1: wow-party-001\n"); child.stdout.end(); child.stderr.end(); child.emit("close", 0); }); return child; }; const api = createLocalBalanceLabApi({ dataDir: directory, projectRoot: directory, spawnProcess, runtimeProbe: () => ({ available: true, executable: "python-test", version: "Python 3.test", error: null }), }); const server = createServer((request, response) => { void api.handle(request, response).then((handled) => { if (!handled) response.writeHead(404).end(); }); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const origin = `http://127.0.0.1:${server.address().port}`; try { const runtime = await (await fetch(`${origin}/api/local/balance-lab/runtime`)).json(); assert.equal(runtime.runtime.available, true); const startedResponse = await fetch(`${origin}/api/local/balance-lab/jobs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source: "wow", level: 20, partyConfigurations: 1, simulationsPerParty: 1, durationSeconds: 5, partySeed: 1, bossCategory: "all", bossKeys: ["stormwind-stockade/entry-1717"], }), }); assert.equal(startedResponse.status, 202); const started = (await startedResponse.json()).job; assert.equal(started.requestedBy, "Local GUI"); await new Promise((resolve) => setImmediate(resolve)); const jobs = await (await fetch(`${origin}/api/local/balance-lab/jobs`)).json(); assert.equal(jobs.jobs[0].status, "complete"); const artifact = await fetch(`${origin}/api/local/balance-lab/jobs/${started.id}/artifacts/suite-report.json`); assert.equal(artifact.status, 200); assert.equal((await artifact.json()).summary.runs, 1); assert.equal((await fetch(`${origin}/api/not-local-balance-lab`)).status, 404); } finally { await new Promise((resolve) => server.close(resolve)); api.close(); rmSync(directory, { recursive: true, force: true }); } }); test("persists revisioned local companion readiness and recovers from a corrupt policy file", async () => { const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-local-readiness-")); const open = async () => { const api = createLocalBalanceLabApi({ dataDir: directory, projectRoot: directory, runtimeProbe: () => ({ available: true, executable: "python-test", version: "Python 3.test", error: null }), }); const server = createServer((request, response) => { void api.handle(request, response).then((handled) => { if (!handled) response.writeHead(404).end(); }); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); return { api, server, origin: `http://127.0.0.1:${server.address().port}`, close: async () => { await new Promise((resolve) => server.close(resolve)); api.close(); }, }; }; try { const first = await open(); const initial = await (await fetch(`${first.origin}/api/local/balance-lab/companion-spec-readiness`)).json(); const configured = { ...initial.config, statuses: { ...initial.config.statuses, "paladin:retribution": "beta" }, }; const savedResponse = await fetch(`${first.origin}/api/local/balance-lab/companion-spec-readiness`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ expectedRevision: 0, config: configured }), }); assert.equal(savedResponse.status, 200); assert.equal((await savedResponse.json()).revision, 1); const staleResponse = await fetch(`${first.origin}/api/local/balance-lab/companion-spec-readiness`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ expectedRevision: 0, config: configured }), }); assert.equal(staleResponse.status, 409); const invalidResponse = await fetch(`${first.origin}/api/local/balance-lab/companion-spec-readiness`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ expectedRevision: 1, config: { ...configured, statuses: { ...configured.statuses, "warrior:protection": "needs-testing" } }, }), }); assert.equal(invalidResponse.status, 400); await first.close(); const second = await open(); const restored = await (await fetch(`${second.origin}/api/local/balance-lab/companion-spec-readiness`)).json(); assert.equal(restored.revision, 1); assert.equal(restored.config.statuses["paladin:retribution"], "beta"); await second.close(); writeFileSync(path.join(directory, "companion-spec-readiness.json"), "{not-json"); const third = await open(); const fallback = await (await fetch(`${third.origin}/api/local/balance-lab/companion-spec-readiness`)).json(); assert.equal(fallback.revision, 0); assert.equal(fallback.config.statuses["warrior:protection"], "ready"); await third.close(); } finally { rmSync(directory, { recursive: true, force: true }); } });