forked from phenom/healer-man
143 lines
7.1 KiB
TypeScript
143 lines
7.1 KiB
TypeScript
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> = {}): 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();
|
|
});
|
|
});
|