150 lines
6.3 KiB
TypeScript
150 lines
6.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { runtimeStages } from "./manastorm";
|
|
import {
|
|
DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
UNIFIED_MANASTORM_BOSS_CATALOG,
|
|
authoredStageConfigForBoss,
|
|
bossAdminRecordForStatus,
|
|
derivePartySpawnSlots,
|
|
effectiveManastormCatalog,
|
|
isGameMasterSession,
|
|
normalizeManastormAdminConfig,
|
|
publicManastormAdminConfig,
|
|
resolveBossAdminEntry,
|
|
resolvedBossAdminCatalog,
|
|
type ManastormAdminConfig,
|
|
} from "./manastormAdminConfig";
|
|
|
|
function configWithBoss(key: string, record: ManastormAdminConfig["bosses"][string]): ManastormAdminConfig {
|
|
return {
|
|
...DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
bosses: { [key]: record },
|
|
};
|
|
}
|
|
|
|
describe("Manastorm GM catalog", () => {
|
|
it("migrates schema-v1 records to schema v2 without losing authored content", () => {
|
|
const candidate = UNIFIED_MANASTORM_BOSS_CATALOG.find((entry) => entry.baseStage);
|
|
expect(candidate).toBeTruthy();
|
|
const boss = bossAdminRecordForStatus(candidate!, DEFAULT_MANASTORM_ADMIN_CONFIG, "ready");
|
|
const legacy = {
|
|
...DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
schemaVersion: 1,
|
|
dungeonSpawns: { "wailing-caverns": { footPosition: [1, 2, 3], yaw: 0.5 } },
|
|
bosses: { [candidate!.key]: boss },
|
|
};
|
|
delete (legacy as { dungeons?: unknown }).dungeons;
|
|
|
|
const migrated = normalizeManastormAdminConfig(legacy);
|
|
|
|
expect(migrated.schemaVersion).toBe(2);
|
|
expect(migrated.dungeons).toEqual({});
|
|
expect(migrated.dungeonSpawns["wailing-caverns"]).toEqual({ footPosition: [1, 2, 3], yaw: 0.5 });
|
|
expect(migrated.bosses[candidate!.key]).toEqual(boss);
|
|
});
|
|
|
|
it("normalizes dungeon readiness independently from player availability", () => {
|
|
const normalized = normalizeManastormAdminConfig({
|
|
...DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
dungeons: {
|
|
"wailing-caverns": { status: "not-ready" },
|
|
deadmines: { status: "unknown" },
|
|
},
|
|
});
|
|
expect(normalized.dungeons["wailing-caverns"]?.status).toBe("not-ready");
|
|
expect(normalized.dungeons.deadmines?.status).toBe("needs-tested");
|
|
});
|
|
|
|
it("preserves normalized review notes and server-owned review metadata", () => {
|
|
const normalized = normalizeManastormAdminConfig({
|
|
...DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
dungeons: {
|
|
deadmines: {
|
|
status: "not-ready",
|
|
note: "Door trigger needs another pass.\r\nRetest with a full party.",
|
|
reviewedAt: 1_700_000_000_000,
|
|
reviewedBy: " Phenom ",
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(normalized.dungeons.deadmines).toEqual({
|
|
status: "not-ready",
|
|
note: "Door trigger needs another pass.\nRetest with a full party.",
|
|
reviewedAt: 1_700_000_000_000,
|
|
reviewedBy: "Phenom",
|
|
});
|
|
expect(publicManastormAdminConfig(normalized).dungeons.deadmines).toEqual({ status: "not-ready" });
|
|
});
|
|
|
|
it("deduplicates map/name records and defaults every boss to Needs tested", () => {
|
|
const identities = UNIFIED_MANASTORM_BOSS_CATALOG.map((entry) => `${entry.mapId}:${entry.name.toLowerCase()}`);
|
|
expect(new Set(identities).size).toBe(identities.length);
|
|
expect(UNIFIED_MANASTORM_BOSS_CATALOG.length).toBeGreaterThan(0);
|
|
expect(resolvedBossAdminCatalog(DEFAULT_MANASTORM_ADMIN_CONFIG).every((entry) => (
|
|
entry.status === "needs-tested" && !entry.inPublicPool
|
|
))).toBe(true);
|
|
const crossMode = UNIFIED_MANASTORM_BOSS_CATALOG.find((entry) => entry.name === "Burning Felguard");
|
|
expect(crossMode?.baseStage?.modeIds).toEqual(expect.arrayContaining([0, 2]));
|
|
});
|
|
|
|
it("publishes only runnable Ready bosses and never restores the legacy pool", () => {
|
|
const candidate = UNIFIED_MANASTORM_BOSS_CATALOG.find((entry) => (
|
|
resolveBossAdminEntry(entry, configWithBoss(entry.key, { status: "ready" })).runnable
|
|
));
|
|
expect(candidate).toBeTruthy();
|
|
const config = configWithBoss(candidate!.key, { status: "ready" });
|
|
const effective = effectiveManastormCatalog(config);
|
|
expect(effective.fallbackPolicy).toBe("none");
|
|
expect(runtimeStages(effective)).toHaveLength(1);
|
|
expect(runtimeStages(effective)[0].bossName).toBe(candidate!.name);
|
|
|
|
const empty = effectiveManastormCatalog(DEFAULT_MANASTORM_ADMIN_CONFIG);
|
|
expect(empty.fallbackPolicy).toBe("none");
|
|
expect(runtimeStages(empty)).toEqual([]);
|
|
});
|
|
|
|
it("materializes an inherited imported stage when a runnable boss becomes Ready", () => {
|
|
const candidate = UNIFIED_MANASTORM_BOSS_CATALOG.find((entry) => entry.baseStage);
|
|
expect(candidate).toBeTruthy();
|
|
const record = bossAdminRecordForStatus(candidate!, DEFAULT_MANASTORM_ADMIN_CONFIG, "ready");
|
|
expect(record.status).toBe("ready");
|
|
expect(record.stage).toMatchObject({
|
|
dungeonId: candidate!.baseStage!.dungeonId,
|
|
bossRuntimeId: candidate!.baseStage!.bossRuntimeId,
|
|
bossPosition: candidate!.baseStage!.bossPosition,
|
|
});
|
|
});
|
|
|
|
it("rejects stale exact-mob IDs and bounds required kills", () => {
|
|
const candidate = UNIFIED_MANASTORM_BOSS_CATALOG.find((entry) => authoredStageConfigForBoss(entry, DEFAULT_MANASTORM_ADMIN_CONFIG));
|
|
expect(candidate).toBeTruthy();
|
|
const stage = authoredStageConfigForBoss(candidate!, DEFAULT_MANASTORM_ADMIN_CONFIG)!;
|
|
const stale = configWithBoss(candidate!.key, {
|
|
status: "ready",
|
|
stage: {
|
|
...stage,
|
|
linkedMobRuntimeIds: ["missing:runtime-id"],
|
|
requiredKillCount: 1,
|
|
},
|
|
});
|
|
const resolved = resolveBossAdminEntry(candidate!, stale);
|
|
expect(resolved.runnable).toBe(false);
|
|
expect(resolved.validationIssues.join(" ")).toContain("missing:runtime-id");
|
|
});
|
|
|
|
it("derives a stable five-person formation from one anchor and yaw", () => {
|
|
const slots = derivePartySpawnSlots({ footPosition: [10, 2, 20], yaw: Math.PI / 2 }, 5);
|
|
expect(slots).toHaveLength(5);
|
|
expect(slots[0]).toEqual({ footPosition: [10, 2, 20], yaw: Math.PI / 2 });
|
|
expect(new Set(slots.map((slot) => slot.footPosition.join(","))).size).toBe(5);
|
|
expect(slots.every((slot) => slot.yaw === Math.PI / 2)).toBe(true);
|
|
});
|
|
|
|
it("never grants GM UI authority to offline or local-only sessions", () => {
|
|
expect(isGameMasterSession({ kind: "offline", roles: ["gm"] })).toBe(false);
|
|
expect(isGameMasterSession({ kind: "account", roles: ["gm"] })).toBe(false);
|
|
expect(isGameMasterSession({ kind: "account", accessToken: "server-token", roles: ["gm"] })).toBe(true);
|
|
});
|
|
});
|