import { describe, expect, it, vi } from "vitest"; import { createDefaultAppearance } from "./characterCatalog"; import type { CharacterProfile, PlayerSession } from "./types"; import type { OnlineGroup } from "./onlineGroupTypes"; import { dungeonById, dungeonsByCategory, randomRegularDungeon } from "../game/dungeonCatalog"; import { DEFAULT_DUNGEON_QUEUE_POLICY } from "../game/dungeonQueuePolicy"; import { dungeonContentPackId } from "../game/dungeonContentSource"; import { queueRandomDungeon, type RandomDungeonQueueOptions } from "./randomDungeonQueue"; const character: CharacterProfile = { id: "character-1", ownerId: "player-1", name: "Remedy", raceId: "human", classId: "priest", specializationId: "priest:holy", categoryId: "wow", gender: "female", appearance: createDefaultAppearance(), level: 1, experience: 0, talentRanks: {}, actionBindings: {}, inventory: [], equipment: {}, location: "", createdAt: 1, lastPlayedAt: 1, }; const offlineSession: PlayerSession = { kind: "offline", ownerId: "player-1", displayName: "Offline" }; const accountSession: PlayerSession = { kind: "account", ownerId: "player-1", displayName: "Remedy", accessToken: "test-token" }; const group: OnlineGroup = { id: "group-1", leaderAccountId: "player-1", members: [], activity: null, activityRevision: 0, createdAt: 1, updatedAt: 1, }; function queueOptions(overrides: Partial = {}): RandomDungeonQueueOptions { return { mode: "regular", role: "healer", character, session: offlineSession, group: null, includeBetaSpecs: false, fillWithAi: true, onProgress: vi.fn(), enterDungeon: vi.fn(() => true), updateOnlineMember: vi.fn(async () => true), startOnlineActivity: vi.fn(async () => true), failureNotice: () => "", ...overrides, }; } describe("random dungeon queue", () => { it("downloads a rolled full dungeon before entering a fresh local run", async () => { let installed = false; const installPack = vi.fn(async () => { installed = true; }); const installDungeonRuntime = vi.fn(async () => undefined); const options = queueOptions(); const result = await queueRandomDungeon(options, { random: () => 0, loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY, contentIsUsable: () => installed, installPack, installDungeonRuntime, }); expect(result.status).toBe("entered"); const dungeonId = vi.mocked(options.enterDungeon).mock.calls[0][0]; expect(["wow", "rom"]).toContain(dungeonById(dungeonId)?.category); expect(installPack).toHaveBeenCalledWith(dungeonContentPackId(dungeonId)); expect(installPack.mock.invocationCallOrder[0]).toBeLessThan(installDungeonRuntime.mock.invocationCallOrder[0]); expect(installDungeonRuntime.mock.invocationCallOrder[0]) .toBeLessThan(vi.mocked(options.enterDungeon).mock.invocationCallOrder[0]); expect(options.enterDungeon).toHaveBeenCalledWith(dungeonId, "healer", expect.objectContaining({ freshRun: true })); expect(options.onProgress).toHaveBeenCalledWith("downloading", expect.objectContaining({ id: dungeonId })); }); it("keeps regular Bite-Sized separate from the creature-randomized mode", async () => { const installPack = vi.fn(async () => undefined); const options = queueOptions({ mode: "bite-sized" }); await expect(queueRandomDungeon(options, { random: () => 0, loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY, contentIsUsable: () => true, installPack, installDungeonRuntime: vi.fn(async () => undefined), })).resolves.toMatchObject({ status: "entered" }); const selectedId = vi.mocked(options.enterDungeon).mock.calls[0][0]; expect(dungeonById(selectedId)?.category).toBe("bite-sized"); expect(options.enterDungeon).toHaveBeenCalledWith(selectedId, "healer", expect.any(Object)); expect(installPack).not.toHaveBeenCalled(); }); it("skips a disabled full dungeon and rejects a queue with no enabled Bite-Sized locations", async () => { const first = randomRegularDungeon(undefined, () => 0)!; const options = queueOptions(); await expect(queueRandomDungeon(options, { random: () => 0, loadPolicy: async () => ({ schemaVersion: 1, disabled: { regular: [first.id], biteSized: [], randomizedBiteSized: [] } }), contentIsUsable: () => true, installPack: vi.fn(), installDungeonRuntime: vi.fn(), })).resolves.toMatchObject({ status: "entered" }); expect(options.enterDungeon).not.toHaveBeenCalledWith(first.id, expect.anything(), expect.anything()); const biteOptions = queueOptions({ mode: "bite-sized" }); await expect(queueRandomDungeon(biteOptions, { random: () => 0, loadPolicy: async () => ({ schemaVersion: 1, disabled: { regular: [], biteSized: dungeonsByCategory("bite-sized").map((dungeon) => dungeon.id), randomizedBiteSized: [] } }), contentIsUsable: () => true, installPack: vi.fn(), installDungeonRuntime: vi.fn(), })).resolves.toMatchObject({ status: "error", message: expect.stringMatching(/no dungeon/i) }); expect(biteOptions.enterDungeon).not.toHaveBeenCalled(); }); it("does not enter when the downloaded pack cannot be verified", async () => { const options = queueOptions(); const result = await queueRandomDungeon(options, { random: () => 0, loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY, contentIsUsable: () => false, installPack: vi.fn(async () => undefined), installDungeonRuntime: vi.fn(async () => undefined), }); expect(result).toMatchObject({ status: "error", message: expect.stringMatching(/verified/i) }); expect(options.enterDungeon).not.toHaveBeenCalled(); }); it("publishes the rolled dungeon for the online leader after installation", async () => { let installed = false; const options = queueOptions({ session: accountSession, group }); const result = await queueRandomDungeon(options, { random: () => 0, loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY, contentIsUsable: () => installed, installPack: vi.fn(async () => { installed = true; }), installDungeonRuntime: vi.fn(async () => undefined), }); expect(result.status).toBe("started"); expect(options.updateOnlineMember).toHaveBeenCalledWith(accountSession, character, "healer"); expect(options.startOnlineActivity).toHaveBeenCalledWith(accountSession, expect.objectContaining({ type: "dungeon", partySize: 5, fillWithAi: true, })); expect(options.enterDungeon).not.toHaveBeenCalled(); }); it("marks a group member ready and waits for the leader's roll", async () => { const options = queueOptions({ session: accountSession, group: { ...group, leaderAccountId: "player-2" } }); const installPack = vi.fn(async () => undefined); await expect(queueRandomDungeon(options, { random: () => 0, loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY, contentIsUsable: () => false, installPack, installDungeonRuntime: vi.fn(async () => undefined), })).resolves.toMatchObject({ status: "waiting" }); expect(installPack).not.toHaveBeenCalled(); expect(options.startOnlineActivity).not.toHaveBeenCalled(); }); });