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); }); });