import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { brotliCompressSync } from "node:zlib"; import { createGameServer } from "./server.mjs"; test("serves the game and authenticated cloud-save API", async () => { const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-server-test-")); const staticDir = path.join(directory, "dist"); const dataDir = path.join(directory, "data"); const contentDir = path.join(directory, "content"); mkdirSync(staticDir, { recursive: true }); mkdirSync(path.join(staticDir, "assets"), { recursive: true }); mkdirSync(path.join(staticDir, "content", "bundled"), { recursive: true }); mkdirSync(path.join(contentDir, "objects"), { recursive: true }); writeFileSync(path.join(staticDir, "index.html"), "Healer Man"); const hashedScript = "console.log('compressed delivery');"; writeFileSync(path.join(staticDir, "assets", "app-12345678.js"), hashedScript); writeFileSync(path.join(staticDir, "assets", "app-12345678.js.br"), brotliCompressSync(hashedScript)); writeFileSync(path.join(staticDir, "content", "bootstrap-manifest.json"), JSON.stringify({ schemaVersion: 1, files: {} })); writeFileSync(path.join(staticDir, "content", "bundled", `${"a".repeat(64)}.json`), "{}"); writeFileSync(path.join(staticDir, "sample.glb"), Buffer.from("0123456789")); writeFileSync(path.join(contentDir, "manifest.json"), JSON.stringify({ schemaVersion: 1 })); writeFileSync(path.join(contentDir, "objects", "asset.glb"), Buffer.from("abcdefghij")); const runtime = createGameServer({ staticDir, contentDir, dataDir, corsOrigins: "https://iwanttoheal.phenomrom.com", enableShelvedModes: true, }); await new Promise((resolve) => runtime.server.listen(0, "127.0.0.1", resolve)); const address = runtime.server.address(); const origin = `http://127.0.0.1:${address.port}`; try { assert.equal((await fetch(`${origin}/`)).status, 200); const compressedScript = await fetch(`${origin}/assets/app-12345678.js`, { headers: { "Accept-Encoding": "br" }, }); assert.equal(compressedScript.headers.get("content-encoding"), "br"); assert.equal(compressedScript.headers.get("cache-control"), "public, max-age=31536000, immutable"); assert.equal(await compressedScript.text(), hashedScript); const bundledObject = await fetch(`${origin}/content/bundled/${"a".repeat(64)}.json`); assert.equal(bundledObject.headers.get("cache-control"), "public, max-age=31536000, immutable"); assert.equal((await fetch(`${origin}/content/bootstrap-manifest.json`)).headers.get("cache-control"), "no-cache"); const range = await fetch(`${origin}/sample.glb`, { headers: { Range: "bytes=2-5" } }); assert.equal(range.status, 206); assert.equal(await range.text(), "2345"); const manifest = await fetch(`${origin}/content/manifest.json`); assert.equal(manifest.status, 200); assert.equal(manifest.headers.get("cache-control"), "no-cache"); assert.equal((await manifest.json()).schemaVersion, 1); const contentRange = await fetch(`${origin}/content/objects/asset.glb`, { headers: { Range: "bytes=3-6" } }); assert.equal(contentRange.status, 206); assert.equal(contentRange.headers.get("cache-control"), "public, max-age=31536000, immutable"); assert.equal(await contentRange.text(), "defg"); assert.equal((await fetch(`${origin}/content/missing`)).status, 404); const registration = await fetch(`${origin}/api/auth/register`, { method: "POST", headers: { "Content-Type": "application/json", Origin: "https://iwanttoheal.phenomrom.com" }, body: JSON.stringify({ username: "WebHealer", password: "healing-pass" }), }); assert.equal(registration.status, 201); assert.equal(registration.headers.get("access-control-allow-origin"), "https://iwanttoheal.phenomrom.com"); const auth = await registration.json(); const saved = await fetch(`${origin}/api/cloud-save`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ data: { version: 1, characters: [] } }), }); assert.equal(saved.status, 200); assert.equal((await saved.json()).revision, 1); const publicConfig = await fetch(`${origin}/api/manastorm-config`); assert.equal(publicConfig.status, 200); assert.equal((await publicConfig.json()).revision, 0); const denied = await fetch(`${origin}/api/gm/manastorm-config`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ expectedRevision: 0, config: { schemaVersion: 1 } }), }); assert.equal(denied.status, 403); const deniedRead = await fetch(`${origin}/api/gm/manastorm-config`, { headers: { Authorization: `Bearer ${auth.token}` }, }); assert.equal(deniedRead.status, 403); const publicCooldownPolicy = await (await fetch(`${origin}/api/companion-cooldown-policy`)).json(); assert.equal(publicCooldownPolicy.revision, 0); assert.equal(publicCooldownPolicy.config.generic.defensiveHealthPercent, 55); const deniedCooldownPolicy = await fetch(`${origin}/api/gm/companion-cooldown-policy`, { headers: { Authorization: `Bearer ${auth.token}` }, }); assert.equal(deniedCooldownPolicy.status, 403); const publicPartyBuffPolicy = await (await fetch(`${origin}/api/party-buff-policy`)).json(); assert.deepEqual(publicPartyBuffPolicy.config.rules, []); const deniedPartyBuffPolicy = await fetch(`${origin}/api/gm/party-buff-policy`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ expectedRevision: 0, config: { schemaVersion: 1, rules: [] } }), }); assert.equal(deniedPartyBuffPolicy.status, 403); const publicReadiness = await (await fetch(`${origin}/api/companion-spec-readiness`)).json(); assert.equal(publicReadiness.revision, 0); assert.equal(publicReadiness.config.statuses["warrior:protection"], "ready"); const deniedReadiness = await fetch(`${origin}/api/gm/companion-spec-readiness`, { headers: { Authorization: `Bearer ${auth.token}` }, }); assert.equal(deniedReadiness.status, 403); const memberRegistration = await fetch(`${origin}/api/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "WebTank", password: "tanking-pass" }), }); const memberAuth = await memberRegistration.json(); const invited = await fetch(`${origin}/api/online-group/invite`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ username: "WebTank" }), }); assert.equal(invited.status, 201); const memberGroupState = await (await fetch(`${origin}/api/online-group`, { headers: { Authorization: `Bearer ${memberAuth.token}` }, })).json(); assert.equal(memberGroupState.invitations.length, 1); const accepted = await fetch(`${origin}/api/online-group/invite/respond`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${memberAuth.token}` }, body: JSON.stringify({ inviteId: memberGroupState.invitations[0].id, accept: true }), }); assert.equal(accepted.status, 200); assert.equal((await accepted.json()).group.members.length, 2); for (const [token, character, role] of [ [auth.token, { id: "web-healer", name: "Mendara", classId: "priest", raceId: "human", gender: "female", level: 20 }, "healer"], [memberAuth.token, { id: "web-tank", name: "Bulwarka", classId: "warrior", raceId: "human", gender: "male", level: 20 }, "tank"], ]) { const ready = await fetch(`${origin}/api/online-group/member`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ character, role }), }); assert.equal(ready.status, 200); } const activityResponse = await fetch(`${origin}/api/online-group/activity`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ type: "dungeon", contentId: "wailing-caverns", fillWithAi: false, partySize: 2, }), }); assert.equal(activityResponse.status, 200); const leaderSync = await fetch(`${origin}/api/online-session/sync`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ afterEventId: 0, presence: { position: [1, 0, 2], health: 900, maxHealth: 1_000 }, world: { mobs: { boss: { health: 500 } }, mobTransforms: {} }, }), }); assert.equal(leaderSync.status, 200); assert.equal((await leaderSync.json()).authority, true); const memberSync = await fetch(`${origin}/api/online-session/sync`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${memberAuth.token}` }, body: JSON.stringify({ afterEventId: 0, presence: { position: [3, 0, 4], health: 800, maxHealth: 1_000 }, events: [{ clientEventId: "web-tank-hit-1", kind: "damage", sourceActorId: `online-player:${memberAuth.account.id}`, targetActorId: "boss", rawAmount: 50, effectiveAmount: 45, }], }), }); assert.equal(memberSync.status, 200); const memberSession = await memberSync.json(); assert.equal(memberSession.players.length, 2); assert.equal(memberSession.events[0].targetActorId, "boss"); const leftGroup = await fetch(`${origin}/api/online-group/leave`, { method: "POST", headers: { Authorization: `Bearer ${auth.token}` }, }); assert.equal(leftGroup.status, 200); const arenaCharacter = { id: "web-healer", name: "Mendara", classId: "priest", categoryId: "wow", specializationId: "priest:discipline", raceId: "human", gender: "female", level: 80, talentRanks: {}, actionBindings: {}, inventory: [], equipment: {}, }; const arenaSave = await fetch(`${origin}/api/cloud-save`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ data: { version: 1, characters: [arenaCharacter] } }), }); assert.equal(arenaSave.status, 200); const loadout = await fetch(`${origin}/api/arena/loadouts/1`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ characterId: arenaCharacter.id, name: "Discipline PvP", classId: "priest", specializationId: "priest:discipline", role: "healer", ruleset: "wow", talents: {}, actions: {}, selected: true, }), }); assert.equal(loadout.status, 200); assert.equal((await loadout.json()).level, 80); const arenaState = await (await fetch(`${origin}/api/arena/state?characterId=${arenaCharacter.id}`, { headers: { Authorization: `Bearer ${auth.token}` }, })).json(); assert.equal(arenaState.stages.length, 8); assert.equal(arenaState.stages.every((stage) => stage.validation.status === "validated"), true); const socketTicket = await (await fetch(`${origin}/api/arena/socket-ticket`, { method: "POST", headers: { Authorization: `Bearer ${auth.token}` }, })).json(); const socket = new WebSocket(`${origin.replace("http:", "ws:")}${socketTicket.path}?ticket=${socketTicket.ticket}`); const messages = []; socket.addEventListener("message", (event) => messages.push(JSON.parse(String(event.data)))); await new Promise((resolve, reject) => { socket.addEventListener("open", resolve, { once: true }); socket.addEventListener("error", reject, { once: true }); }); const arenaQueue = await fetch(`${origin}/api/arena/queue`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ characterId: arenaCharacter.id, loadoutSlot: 1, bracket: "2v2", mode: "rated-ai", ruleset: "wow" }), }); assert.equal(arenaQueue.status, 201); assert.equal((await arenaQueue.json()).status, "ready-check"); const arenaReady = await fetch(`${origin}/api/arena/ready`, { method: "POST", headers: { Authorization: `Bearer ${auth.token}` }, }); assert.equal(arenaReady.status, 200); assert.equal((await arenaReady.json()).status, "matched"); await new Promise((resolve) => setTimeout(resolve, 25)); assert.equal(messages.some((message) => message.type === "connected"), true); assert.equal(messages.some((message) => message.type === "ready-check"), true); assert.equal(messages.some((message) => message.type === "match-start"), true); socket.close(); const gmRegistration = await fetch(`${origin}/api/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "Phenom", password: "storm-admin" }), }); assert.equal(gmRegistration.status, 201); const gmAuth = await gmRegistration.json(); assert.deepEqual(gmAuth.account.roles, ["gm"]); const publishedCooldownPolicy = await fetch(`${origin}/api/gm/companion-cooldown-policy`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ expectedRevision: publicCooldownPolicy.revision, config: { ...publicCooldownPolicy.config, generic: { ...publicCooldownPolicy.config.generic, defensiveHealthPercent: 61 }, }, }), }); assert.equal(publishedCooldownPolicy.status, 200); const publishedCooldownPolicyPayload = await publishedCooldownPolicy.json(); assert.equal(publishedCooldownPolicyPayload.revision, 1); assert.equal(publishedCooldownPolicyPayload.config.generic.defensiveHealthPercent, 61); const playerCooldownPolicy = await (await fetch(`${origin}/api/companion-cooldown-policy`)).json(); assert.equal(playerCooldownPolicy.config.generic.defensiveHealthPercent, 61); const publishedPartyBuffPolicy = await fetch(`${origin}/api/gm/party-buff-policy`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ expectedRevision: publicPartyBuffPolicy.revision, config: { schemaVersion: 1, rules: [{ abilityId: "wow335-priest-power-word-fortitude", scope: "tank" }] }, }), }); assert.equal(publishedPartyBuffPolicy.status, 200); assert.equal((await publishedPartyBuffPolicy.json()).revision, 1); const playerPartyBuffPolicy = await (await fetch(`${origin}/api/party-buff-policy`)).json(); assert.deepEqual(playerPartyBuffPolicy.config.rules, [{ abilityId: "wow335-priest-power-word-fortitude", scope: "tank" }]); const publishedReadiness = await fetch(`${origin}/api/gm/companion-spec-readiness`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ expectedRevision: publicReadiness.revision, config: { ...publicReadiness.config, statuses: { ...publicReadiness.config.statuses, "paladin:retribution": "beta" }, }, }), }); assert.equal(publishedReadiness.status, 200); assert.equal((await publishedReadiness.json()).config.statuses["paladin:retribution"], "beta"); const staleReadiness = await fetch(`${origin}/api/gm/companion-spec-readiness`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ expectedRevision: 0, config: publicReadiness.config }), }); assert.equal(staleReadiness.status, 409); const deniedBalanceLab = await fetch(`${origin}/api/gm/balance-lab/jobs`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ source: "wow", level: 40 }), }); assert.equal(deniedBalanceLab.status, 403); const balanceLabJobs = await fetch(`${origin}/api/gm/balance-lab/jobs`, { headers: { Authorization: `Bearer ${gmAuth.token}` }, }); assert.equal(balanceLabJobs.status, 200); assert.deepEqual((await balanceLabJobs.json()).jobs, []); const balanceLabRuntime = await fetch(`${origin}/api/gm/balance-lab/runtime`, { headers: { Authorization: `Bearer ${gmAuth.token}` }, }); assert.equal(balanceLabRuntime.status, 200); const balanceLabRuntimePayload = await balanceLabRuntime.json(); assert.equal(balanceLabRuntimePayload.runtime.available, true); assert.equal(balanceLabRuntimePayload.runtime.maxConcurrent, 4); const invalidBalanceLab = await fetch(`${origin}/api/gm/balance-lab/jobs`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ source: "wow", level: 81 }), }); assert.equal(invalidBalanceLab.status, 400); assert.equal((await invalidBalanceLab.json()).code, "invalid_balance_lab_job"); const invalidBalanceLabBatch = await fetch(`${origin}/api/gm/balance-lab/jobs/batch`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ specs: [{ source: "wow", level: 10 }, { source: "wow", level: 81 }] }), }); assert.equal(invalidBalanceLabBatch.status, 400); assert.equal((await invalidBalanceLabBatch.json()).code, "invalid_balance_lab_job"); const current = await (await fetch(`${origin}/api/manastorm-config`)).json(); const published = await fetch(`${origin}/api/gm/manastorm-config`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gmAuth.token}` }, body: JSON.stringify({ expectedRevision: current.revision, config: { ...current.config, global: { ...current.config.global, chaoticLinkEnabled: false }, dungeons: { deadmines: { status: "not-ready", note: "The final door trigger needs fixing." }, }, }, }), }); assert.equal(published.status, 200); const publishedPayload = await published.json(); assert.equal(publishedPayload.config.global.chaoticLinkEnabled, false); assert.equal(publishedPayload.config.dungeons.deadmines.reviewedBy, "Phenom"); const playerConfig = await (await fetch(`${origin}/api/manastorm-config`)).json(); assert.deepEqual(playerConfig.config.dungeons.deadmines, { status: "not-ready" }); const gmConfig = await (await fetch(`${origin}/api/gm/manastorm-config`, { headers: { Authorization: `Bearer ${gmAuth.token}` }, })).json(); assert.equal(gmConfig.config.dungeons.deadmines.note, "The final door trigger needs fixing."); assert.equal(gmConfig.config.dungeons.deadmines.reviewedBy, "Phenom"); } finally { await new Promise((resolve) => runtime.server.close(resolve)); rmSync(directory, { recursive: true, force: true }); } }); test("disables shelved APIs and Manastorm group creation by default", async () => { const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-shelved-server-test-")); const staticDir = path.join(directory, "dist"); const dataDir = path.join(directory, "data"); const contentDir = path.join(directory, "content"); mkdirSync(staticDir, { recursive: true }); mkdirSync(contentDir, { recursive: true }); writeFileSync(path.join(staticDir, "index.html"), "Healer Man"); const runtime = createGameServer({ staticDir, contentDir, dataDir, enableShelvedModes: false }); await new Promise((resolve) => runtime.server.listen(0, "127.0.0.1", resolve)); const address = runtime.server.address(); const origin = `http://127.0.0.1:${address.port}`; try { assert.equal(runtime.enableShelvedModes, false); for (const route of ["/api/manastorm-config", "/api/gm/manastorm-config", "/api/arena/state"]) { const response = await fetch(`${origin}${route}`); assert.equal(response.status, 503); assert.equal((await response.json()).code, "feature_disabled"); } const balanceLab = await fetch(`${origin}/api/gm/balance-lab/jobs`); assert.equal(balanceLab.status, 401); assert.equal((await balanceLab.json()).code, "authentication_required"); const registration = await fetch(`${origin}/api/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "ShelvedHealer", password: "healing-pass" }), }); const auth = await registration.json(); const activity = await fetch(`${origin}/api/online-group/activity`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` }, body: JSON.stringify({ type: "manastorm", contentId: "deadmines", partySize: 1, fillWithAi: true }), }); assert.equal(activity.status, 503); assert.equal((await activity.json()).code, "feature_disabled"); assert.equal(runtime.arenaService.inspect().tickets.size, 0); } finally { await new Promise((resolve) => runtime.server.close(resolve)); rmSync(directory, { recursive: true, force: true }); } });