Files
healer-man/server/server.test.mjs
T
2026-09-05 20:42:38 -04:00

253 lines
12 KiB
JavaScript

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 { 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(contentDir, "objects"), { recursive: true });
writeFileSync(path.join(staticDir, "index.html"), "<!doctype html><title>Healer Man</title>");
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" });
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 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 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 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 });
}
});