Release Healer Man 0.1.39

This commit is contained in:
phenom committed 2026-09-13 11:07:56 -04:00
1 parent b417b7c3c3
commit ed0d2fea3d
51 files changed
+17811 -49

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { openGameDatabase } from "./database.mjs";
import { createGameServer } from "./server.mjs";
import catalog from "../src/game/generated/dungeonLeaderboardCatalog.generated.json" with { type: "json" };
function run(id, durationMs, fullClear = true, dungeonId = "deadmines", difficultyId) {
const dungeon = catalog.dungeons.find((entry) => entry.id === dungeonId);
return { id, dungeonId, difficultyId: difficultyId ?? dungeon.defaultDifficultyId, durationMs,
finishedAt: 1700000000000, killedBossIds: fullClear ? dungeon.bosses.map((boss) => boss.id) : [dungeon.finalBossId],
party: [{ name: "Tester", classId: "priest", level: 20, role: "healer", npc: false },
{ name: "Garrick", classId: "warrior", level: 20, role: "tank", npc: true }] };
}
async function withDatabase(callback) {
const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-leaderboard-test-"));
let database = openGameDatabase({ dataDir: directory });
try { await callback(database, () => { database.close(); database = openGameDatabase({ dataDir: directory }); return database; }); }
finally { database.close(); rmSync(directory, { recursive: true, force: true }); }
}
test("persists ranked party/boss records, derives all-boss eligibility, handles ties, and retries without duplicates", () => withDatabase((database, reopen) => {
database.submitDungeonRun(null, run("run-fastest", 1000, false));
database.submitDungeonRun(null, run("run-full-one", 2000));
database.submitDungeonRun(null, run("run-full-tie", 2000));
const last = run("run-slowest", 3000);
const result = database.submitDungeonRun(null, last);
assert.deepEqual(result.placement, { finalRank: 4, finalTotal: 4, fullRank: 3, fullTotal: 3 });
assert.deepEqual(database.submitDungeonRun(null, last), result);
assert.throws(() => database.submitDungeonRun(null, { ...last, durationMs: 5 }), { status: 409 });
const final = database.listDungeonLeaderboard({ dungeonId: "deadmines", clear: "final" });
assert.deepEqual(final.entries.map((entry) => entry.rank), [1, 2, 2, 4]);
const full = database.listDungeonLeaderboard({ dungeonId: "deadmines", clear: "full" });
assert.deepEqual(full.entries.map((entry) => entry.rank), [1, 1, 3]);
assert.deepEqual(full.entries[0].party, last.party);
assert.equal(full.entries[0].killedBossIds.length, last.killedBossIds.length);
const page = database.listDungeonLeaderboard({ dungeonId: "deadmines", clear: "final", limit: 2, offset: 2 });
assert.equal(page.total, 4); assert.deepEqual(page.entries.map((entry) => entry.rank), [2, 4]);
database = reopen();
assert.deepEqual(database.getDungeonRun(last.id), result);
}));
test("validates final boss, dungeon/difficulty, duration and party without trusting client full-clear flags", () => withDatabase((database) => {
const valid = run("valid-run-id", 1000, false);
for (const invalid of [{ ...valid, durationMs: 0 }, { ...valid, durationMs: Infinity },
{ ...valid, killedBossIds: [] }, { ...valid, killedBossIds: ["fake-boss"] },
{ ...valid, dungeonId: "unknown" }, { ...valid, difficultyId: "fake" },
{ ...valid, party: [] }, { ...valid, party: [{ ...valid.party[0], classId: "fake" }] },
{ ...valid, party: [{ ...valid.party[0], level: 81 }] }])
assert.throws(() => database.submitDungeonRun(null, invalid), { code: "invalid_dungeon_run" });
assert.equal(database.submitDungeonRun(null, { ...valid, fullClear: true }).run.fullClear, false);
const caves = run("barren-caves-test", 2000, true, "barren-caves");
caves.party[0].classId = "rom-priest";
assert.equal(database.submitDungeonRun(null, caves).placement.fullRank, 1);
assert.equal(database.listDungeonLeaderboard({ dungeonId: "deadmines" }).total, 1);
assert.throws(() => database.listDungeonLeaderboard({ dungeonId: "deadmines", clear: "bad" }), { status: 400 });
assert.throws(() => database.listDungeonLeaderboard({ dungeonId: "deadmines", offset: -1 }), { status: 400 });
}));
test("public HTTP leaderboards accept completion records and return results with rank while rejecting unfinished runs", async () => {
const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-leaderboard-http-"));
const { server } = createGameServer({ dataDir: directory, enableShelvedModes: false });
try {
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const base = `http://127.0.0.1:${server.address().port}`;
const payload = run("http-caves-clear", 15555, true, "barren-caves");
const response = await fetch(`${base}/api/dungeon-runs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
assert.equal(response.status, 201);
const result = await response.json(); assert.equal(result.placement.finalRank, 1);
const leaderboard = await fetch(`${base}/api/dungeon-leaderboards?dungeon=barren-caves&clear=full`);
assert.equal(leaderboard.status, 200); assert.equal((await leaderboard.json()).entries[0].durationMs, 15555);
assert.deepEqual(await (await fetch(`${base}/api/dungeon-runs/${payload.id}`)).json(), result);
const bad = await fetch(`${base}/api/dungeon-runs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...payload, id: "unfinished-run", killedBossIds: [] }) });
assert.equal(bad.status, 400);
const unauthorized = await fetch(`${base}/api/dungeon-runs`, { method: "POST", headers: { Authorization: "Bearer invalid", "Content-Type": "application/json" }, body: JSON.stringify(payload) });
assert.equal(unauthorized.status, 401);
} finally { await new Promise((resolve) => server.close(resolve)); rmSync(directory, { recursive: true, force: true }); }
});
test("separates normal and heroic times and counts only bosses that exist on each difficulty", () => withDatabase((database) => {
const dungeon = catalog.dungeons.find((entry) => entry.difficulties.length > 1 && entry.difficulties.every((difficulty) => difficulty.finalBossId));
for (const difficulty of dungeon.difficulties) {
const payload = run(`difficulty-${difficulty.id}-test`, 1000, true, dungeon.id, difficulty.id);
payload.killedBossIds = difficulty.bossIds;
const result = database.submitDungeonRun(null, payload);
assert.equal(result.run.fullClear, true);
assert.equal(result.placement.finalRank, 1); assert.equal(result.placement.finalTotal, 1);
assert.equal(database.listDungeonLeaderboard({ dungeonId: dungeon.id, difficultyId: difficulty.id, clear: "full" }).total, 1);
}
}));