import assert from "node:assert/strict"; import { mkdtempSync, rmSync } from "node:fs"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { after, before, test } from "node:test"; import { createGameApiHandler } from "./game-api.mjs"; const dataDirectory = mkdtempSync(join(tmpdir(), "iwt-heal-api-")); const api = createGameApiHandler({ dataDirectory }); const server = createServer((request, response) => { void api.handle(request, response, () => { response.statusCode = 404; response.end(); }); }); let baseUrl; before(async () => { await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); baseUrl = `http://127.0.0.1:${address.port}`; }); after(async () => { await new Promise((resolve) => server.close(resolve)); api.close(); rmSync(dataDirectory, { recursive: true, force: true }); }); async function json(path, init = {}) { const response = await fetch(`${baseUrl}${path}`, init); const body = await response.json(); return { response, body }; } function save(slotId, hunterName, bulldromeKills, highestRoguelikeRound, highestRogueTrialsEndlessKills, highestHockeyHealingReturns, longestHockeyHealingSecondsAtBest, hockeyHealingPvpWins = 0, hockeyHealingPvpLosses = 0, hockeyHealingPvpBossKills = 0, highestBlockbreakerBricks = 0, longestBlockbreakerSeconds = 0, highestBlockbreakerScore = 0, highestAetherAssaultScore = 0, highestAetherAssaultWaveAtBest = 0, longestAetherAssaultSecondsAtBest = 0) { return { schemaVersion: 6, slotId, hunterName, stats: { bossKills: { bulldrome: bulldromeKills }, highestRoguelikeRound, highestRogueTrialsEndlessKills, highestHockeyHealingReturns, longestHockeyHealingSecondsAtBest, hockeyHealingPvpWins, hockeyHealingPvpLosses, hockeyHealingPvpBossKills, highestBlockbreakerBricks, longestBlockbreakerSeconds, highestBlockbreakerScore, highestAetherAssaultScore, highestAetherAssaultWaveAtBest, longestAetherAssaultSecondsAtBest }, }; } test("health endpoint reports persistent database readiness", async () => { const { response, body } = await json("/api/health"); assert.equal(response.status, 200); assert.deepEqual(body, { ok: true, database: "ready" }); }); test("accounts, server saves, and top-five plus current rankings work end to end", async () => { const players = []; for (let index = 0; index < 6; index += 1) { const username = `hunter_${index}`; const registration = await json("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password: `long-password-${index}` }), }); assert.equal(registration.response.status, 201); const token = registration.body.token; const kills = 60 - index * 10; const highestRound = 30 - index * 4; const highestEndlessKills = 24 - index * 3; const highestHockeyReturns = 30 - index * 4; const hockeyDuration = 180 - index * 10; const pvpWins = 30 - index * 4; const pvpLosses = index + 1; const pvpBossKills = 120 - index * 12; const blockbreakerBricks = index < 2 ? 600 : 700 - index * 100; const blockbreakerSeconds = 360 - index * 30; const blockbreakerScore = 20_000 - index * 2_000; const aetherScore = index < 2 ? 50_000 : 54_000 - index * 5_000; const aetherWave = index < 2 ? 12 : 10 - index; const aetherDuration = 300 - index * 20; const upload = await json("/api/saves/1", { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify({ save: save(1, `Hero ${index}`, kills, highestRound, highestEndlessKills, highestHockeyReturns, hockeyDuration, pvpWins, pvpLosses, pvpBossKills, blockbreakerBricks, blockbreakerSeconds, blockbreakerScore, aetherScore, aetherWave, aetherDuration) }), }); assert.equal(upload.response.status, 200); players.push({ token, kills, highestRound, highestEndlessKills, highestHockeyReturns, hockeyDuration, pvpWins, pvpLosses, pvpBossKills, blockbreakerBricks, blockbreakerSeconds, blockbreakerScore, aetherScore, aetherWave, aetherDuration }); } const current = players[5]; const bossBoard = await json("/api/leaderboards/boss/bulldrome?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(bossBoard.body.top.length, 5); assert.equal(bossBoard.body.top[0].value, 60); assert.equal(bossBoard.body.current.rank, 6); assert.equal(bossBoard.body.current.value, current.kills); const rogueBoard = await json("/api/leaderboards/roguelike?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(rogueBoard.body.top.length, 5); assert.equal(rogueBoard.body.current.rank, 6); assert.equal(rogueBoard.body.current.value, current.highestRound); const endlessBoard = await json("/api/leaderboards/rogue-trials-endless?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(endlessBoard.body.kind, "rogue-trials-endless"); assert.equal(endlessBoard.body.top.length, 5); assert.equal(endlessBoard.body.top[0].value, 24); assert.equal(endlessBoard.body.current.rank, 6); assert.equal(endlessBoard.body.current.value, current.highestEndlessKills); const hockeyBoard = await json("/api/leaderboards/hockey-healing?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(hockeyBoard.body.kind, "hockey-healing"); assert.equal(hockeyBoard.body.top.length, 5); assert.equal(hockeyBoard.body.top[0].value, 30); assert.equal(hockeyBoard.body.current.rank, 6); assert.equal(hockeyBoard.body.current.value, current.highestHockeyReturns); assert.equal(hockeyBoard.body.current.secondaryValue, current.hockeyDuration); const pvpWinsBoard = await json("/api/leaderboards/hockey-pvp-wins?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(pvpWinsBoard.body.kind, "hockey-pvp-wins"); assert.equal(pvpWinsBoard.body.top[0].value, 30); assert.equal(pvpWinsBoard.body.current.value, current.pvpWins); assert.equal(pvpWinsBoard.body.current.secondaryValue, current.pvpLosses); const pvpKillsBoard = await json("/api/leaderboards/hockey-pvp-boss-kills?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(pvpKillsBoard.body.kind, "hockey-pvp-boss-kills"); assert.equal(pvpKillsBoard.body.top[0].value, 120); assert.equal(pvpKillsBoard.body.current.value, current.pvpBossKills); const blockbreakerBricksBoard = await json("/api/leaderboards/blockbreaker-bricks?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(blockbreakerBricksBoard.body.kind, "blockbreaker-bricks"); assert.equal(blockbreakerBricksBoard.body.top.length, 5); assert.equal(blockbreakerBricksBoard.body.top[0].value, 600); assert.equal(blockbreakerBricksBoard.body.top[0].rank, 1); assert.equal(blockbreakerBricksBoard.body.top[1].rank, 1); assert.equal(blockbreakerBricksBoard.body.current.rank, 6); assert.equal(blockbreakerBricksBoard.body.current.value, current.blockbreakerBricks); const blockbreakerTimeBoard = await json("/api/leaderboards/blockbreaker-time?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(blockbreakerTimeBoard.body.kind, "blockbreaker-time"); assert.equal(blockbreakerTimeBoard.body.top[0].value, 360); assert.equal(blockbreakerTimeBoard.body.current.value, current.blockbreakerSeconds); const blockbreakerScoreBoard = await json("/api/leaderboards/blockbreaker-score?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(blockbreakerScoreBoard.body.kind, "blockbreaker-score"); assert.equal(blockbreakerScoreBoard.body.top[0].value, 20_000); assert.equal(blockbreakerScoreBoard.body.current.value, current.blockbreakerScore); const aetherBoard = await json("/api/leaderboards/aether-assault?slot=1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(aetherBoard.body.kind, "aether-assault"); assert.equal(aetherBoard.body.top.length, 5); assert.equal(aetherBoard.body.top[0].value, 50_000); assert.equal(aetherBoard.body.top[0].secondaryValue, 12); assert.equal(aetherBoard.body.top[0].username, "hunter_0"); assert.equal(aetherBoard.body.top[0].rank, 1); assert.equal(aetherBoard.body.top[1].value, 50_000); assert.equal(aetherBoard.body.top[1].secondaryValue, 12); assert.equal(aetherBoard.body.top[1].username, "hunter_1"); assert.equal(aetherBoard.body.top[1].rank, 2); assert.equal(aetherBoard.body.current.rank, 6); assert.equal(aetherBoard.body.current.value, current.aetherScore); assert.equal(aetherBoard.body.current.secondaryValue, current.aetherWave); const legacySnapshot = save(1, "Hero 5", current.kills, current.highestRound, current.highestEndlessKills, current.highestHockeyReturns, current.hockeyDuration, current.pvpWins, current.pvpLosses, current.pvpBossKills); delete legacySnapshot.stats.highestBlockbreakerBricks; delete legacySnapshot.stats.longestBlockbreakerSeconds; delete legacySnapshot.stats.highestBlockbreakerScore; delete legacySnapshot.stats.highestAetherAssaultScore; delete legacySnapshot.stats.highestAetherAssaultWaveAtBest; delete legacySnapshot.stats.longestAetherAssaultSecondsAtBest; legacySnapshot.schemaVersion = 5; const legacyUpload = await json("/api/saves/1", { method: "PUT", headers: { Authorization: `Bearer ${current.token}`, "Content-Type": "application/json" }, body: JSON.stringify({ save: legacySnapshot }), }); assert.equal(legacyUpload.body.save.stats.highestBlockbreakerBricks, current.blockbreakerBricks); assert.equal(legacyUpload.body.save.stats.longestBlockbreakerSeconds, current.blockbreakerSeconds); assert.equal(legacyUpload.body.save.stats.highestBlockbreakerScore, current.blockbreakerScore); assert.equal(legacyUpload.body.save.schemaVersion, 6); assert.equal(legacyUpload.body.save.stats.highestAetherAssaultScore, current.aetherScore); assert.equal(legacyUpload.body.save.stats.highestAetherAssaultWaveAtBest, current.aetherWave); assert.equal(legacyUpload.body.save.stats.longestAetherAssaultSecondsAtBest, current.aetherDuration); const download = await json("/api/saves/1", { headers: { Authorization: `Bearer ${current.token}` }, }); assert.equal(download.body.save.hunterName, "Hero 5"); }); test("Healing Hockey PVP queue pairs players and relays match snapshots", async () => { const registerPlayer = async (username) => { const registration = await json("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password: `long-password-${username}` }), }); return registration.body.token; }; const alphaToken = await registerPlayer("pvp_alpha"); const betaToken = await registerPlayer("pvp_beta"); const alphaQueue = await json("/api/hockey-pvp/queue", { method: "POST", headers: { Authorization: `Bearer ${alphaToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ slotId: 1, hunterName: "Alpha" }), }); assert.equal(alphaQueue.body.status, "waiting"); const betaQueue = await json("/api/hockey-pvp/queue", { method: "POST", headers: { Authorization: `Bearer ${betaToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ slotId: 1, hunterName: "Beta" }), }); assert.equal(betaQueue.body.status, "matched"); assert.equal(betaQueue.body.match.role, "guest"); assert.equal(betaQueue.body.match.opponentName, "Alpha"); const alphaMatched = await json(`/api/hockey-pvp/queue/${alphaQueue.body.ticketId}`, { headers: { Authorization: `Bearer ${alphaToken}` }, }); assert.equal(alphaMatched.body.status, "matched"); assert.equal(alphaMatched.body.match.role, "host"); assert.equal(alphaMatched.body.match.id, betaQueue.body.match.id); assert.equal(alphaMatched.body.match.seed, betaQueue.body.match.seed); const hostSnapshot = { sequence: 1, party: [], puck: { goalSequence: 0 } }; await json(`/api/hockey-pvp/matches/${alphaMatched.body.match.id}/state`, { method: "PUT", headers: { Authorization: `Bearer ${alphaToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ snapshot: hostSnapshot }), }); const guestExchange = await json(`/api/hockey-pvp/matches/${alphaMatched.body.match.id}/state`, { method: "PUT", headers: { Authorization: `Bearer ${betaToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ snapshot: { sequence: 1, party: [] } }), }); assert.deepEqual(guestExchange.body.opponentSnapshot, hostSnapshot); assert.deepEqual(guestExchange.body.hostSnapshot, hostSnapshot); }); test("invalid credentials cannot access server saves", async () => { const login = await json("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "hunter_0", password: "incorrect-password" }), }); assert.equal(login.response.status, 401); const saves = await json("/api/saves"); assert.equal(saves.response.status, 401); });