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 { DatabaseSync } from "node:sqlite"; import { GameDatabaseError, openGameDatabase } from "./database.mjs"; function withDatabase(run) { const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-db-test-")); const database = openGameDatabase({ dataDir: directory, sessionTtlDays: 1 }); return Promise.resolve(run(database)).finally(() => { database.close(); rmSync(directory, { recursive: true, force: true }); }); } test("registers, authenticates, and revokes an account session", () => withDatabase(async (database) => { const registered = await database.register("CaveHealer", "serpent-pass"); assert.equal(registered.account.username, "CaveHealer"); assert.equal(database.authenticate(registered.token).id, registered.account.id); const loggedIn = await database.login("cavehealer", "serpent-pass"); assert.equal(loggedIn.account.id, registered.account.id); database.logout(loggedIn.token); assert.throws(() => database.authenticate(loggedIn.token), (error) => ( error instanceof GameDatabaseError && error.status === 401 )); })); test("rejects duplicate usernames and incorrect passwords", () => withDatabase(async (database) => { await database.register("PartyLead", "eight-plus"); await assert.rejects(database.register("partylead", "another-pass"), (error) => ( error instanceof GameDatabaseError && error.status === 409 )); await assert.rejects(database.login("PartyLead", "wrong-pass"), (error) => ( error instanceof GameDatabaseError && error.status === 401 )); })); test("persists opaque cloud roster data with increasing revisions", () => withDatabase(async (database) => { const registered = await database.register("CloudPriest", "cloud-pass"); assert.equal(database.getCloudSave(registered.account.id).revision, 0); const first = database.putCloudSave(registered.account.id, { version: 1, characters: [{ id: "character-one", name: "Mendwell" }], }); const second = database.putCloudSave(registered.account.id, { version: 1, characters: [{ id: "character-one", name: "Mendbetter" }], }); assert.equal(first.revision, 1); assert.equal(second.revision, 2); assert.equal(database.getCloudSave(registered.account.id).data.characters[0].name, "Mendbetter"); })); test("derives GM authority from the hardcoded Phenom account", () => withDatabase(async (database) => { const gm = await database.register("Phenom", "storm-admin"); const player = await database.register("Ordinary", "storm-player"); assert.deepEqual(gm.account.roles, ["gm"]); assert.deepEqual((await database.login("pHeNoM", "storm-admin")).account.roles, ["gm"]); assert.equal(database.isGameMaster(database.authenticate(gm.token)), true); assert.equal(database.isGameMaster(database.authenticate(player.token)), false); assert.equal(database.isGameMaster({ ...player.account, roles: ["gm"] }), false); })); test("recovers Manastorm configuration after restart and falls back safely from corrupt data", async () => { const directory = mkdtempSync(path.join(os.tmpdir(), "healer-man-db-restart-test-")); const databasePath = path.join(directory, "game.db"); try { const first = openGameDatabase({ databasePath }); const gm = await first.register("Phenom", "storm-admin"); const current = first.getManastormConfig(); first.putManastormConfig(first.authenticate(gm.token), 0, { ...current.config, global: { ...current.config.global, enabled: false }, }); first.close(); const restarted = openGameDatabase({ databasePath }); assert.equal(restarted.getManastormConfig().revision, 1); assert.equal(restarted.getManastormConfig().config.global.enabled, false); restarted.close(); const raw = new DatabaseSync(databasePath); raw.prepare("UPDATE manastorm_admin_config SET config_json = ? WHERE id = 1").run("{corrupt"); raw.close(); const recovered = openGameDatabase({ databasePath }); assert.equal(recovered.getManastormConfig().revision, 0); assert.deepEqual(recovered.getManastormConfig().config.bosses, {}); recovered.close(); } finally { rmSync(directory, { recursive: true, force: true }); } }); test("persists revisioned Manastorm GM configuration and rejects stale or unauthorized writes", () => withDatabase(async (database) => { const gm = await database.register("Phenom", "storm-admin"); const player = await database.register("Ordinary", "storm-player"); const gmAccount = database.authenticate(gm.token); const playerAccount = database.authenticate(player.token); const initial = database.getManastormConfig(); assert.equal(initial.revision, 0); assert.deepEqual(initial.config.bosses, {}); const configured = { ...initial.config, global: { ...initial.config.global, affixesEnabled: false }, }; const saved = database.putManastormConfig(gmAccount, 0, configured); assert.equal(saved.revision, 1); assert.equal(saved.config.global.affixesEnabled, false); assert.equal(database.getManastormConfig().updatedBy, "Phenom"); assert.throws(() => database.putManastormConfig(gmAccount, 0, configured), (error) => ( error instanceof GameDatabaseError && error.status === 409 )); assert.throws(() => database.putManastormConfig(playerAccount, 1, configured), (error) => ( error instanceof GameDatabaseError && error.status === 403 )); assert.throws(() => database.putManastormConfig(gmAccount, 1, { ...configured, bosses: { "boss:incomplete": { status: "ready" } }, }), (error) => error instanceof GameDatabaseError && error.status === 400); }));