/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */ /* eslint-disable max-lines -- Test suites naturally have many cases */ /* eslint-disable @typescript-eslint/consistent-type-assertions -- Tests build minimal state objects */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { Hono } from "hono"; import type { GameState } from "@elysium/types"; vi.mock("../../src/db/client.js", () => ({ prisma: { gameState: { findUnique: vi.fn(), update: vi.fn() }, }, })); vi.mock("../../src/middleware/auth.js", () => ({ authMiddleware: vi.fn(async (c: { set: (key: string, value: string) => void }, next: () => Promise) => { c.set("discordId", "test_discord_id"); await next(); }), })); vi.mock("../../src/services/logger.js", () => ({ logger: { error: vi.fn().mockResolvedValue(undefined), metric: vi.fn().mockResolvedValue(undefined), }, })); const DISCORD_ID = "test_discord_id"; const makeConsecration = (overrides: Record = {}) => ({ count: 0, divinity: 0, productionMultiplier: 1, purchasedUpgradeIds: [] as string[], ...overrides, }); const makeEnlightenment = (overrides: Record = {}) => ({ count: 0, stardust: 0, purchasedUpgradeIds: [] as string[], stardustPrayersMultiplier: 1, stardustCombatMultiplier: 1, stardustConsecrationThresholdMultiplier: 1, stardustConsecrationDivinityMultiplier: 1, stardustMetaMultiplier: 1, ...overrides, }); const makeGoddessExploration = (overrides: Record = {}) => ({ areas: [] as Array<{ id: string; status: string }>, materials: [] as Array<{ materialId: string; quantity: number }>, craftedRecipeIds: [] as string[], craftedPrayersMultiplier: 1, craftedDivinityMultiplier: 1, craftedCombatMultiplier: 1, ...overrides, }); const makeGoddessBoss = (overrides: Record = {}) => ({ id: "test_goddess_boss", name: "Test Goddess Boss", description: "A test boss", status: "available", maxHp: 100, currentHp: 100, damagePerSecond: 1, prayersReward: 50, divinityReward: 2, stardustReward: 0, upgradeRewards: [] as string[], equipmentRewards: [] as string[], consecrationRequirement: 0, zoneId: "test_goddess_zone", bountyDivinity: 5, ...overrides, }); const makeDisciple = (overrides: Record = {}) => ({ id: "test_disciple", name: "Test Disciple", class: "oracle" as const, level: 10, baseCost: 100, prayersPerSecond: 1, divinityPerSecond: 0, combatPower: 10_000, count: 1, unlocked: true, ...overrides, }); const makeGoddessState = (overrides: Record = {}) => ({ zones: [] as Array<{ id: string; status: string; unlockBossId: string | null; unlockQuestId: string | null }>, bosses: [ makeGoddessBoss() ], quests: [] as Array<{ id: string; status: string }>, disciples: [ makeDisciple() ], equipment: [] as Array<{ id: string; type: string; owned: boolean; equipped: boolean; bonus: Record }>, upgrades: [] as Array<{ id: string; purchased: boolean; target: string; multiplier: number; discipleId?: string }>, achievements: [] as Array<{ id: string; completed: boolean }>, consecration: makeConsecration(), enlightenment: makeEnlightenment(), exploration: makeGoddessExploration(), totalPrayersEarned: 0, lifetimePrayersEarned: 0, lifetimeBossesDefeated: 0, lifetimeQuestsCompleted: 0, baseClickPower: 1, lastTickAt: 0, ...overrides, }); const makeState = (overrides: Partial = {}): GameState => ({ player: { discordId: DISCORD_ID, username: "u", discriminator: "0", avatar: null, totalGoldEarned: 0, totalClicks: 0, characterName: "T" }, resources: { gold: 0, essence: 0, crystals: 0, runestones: 0, prayers: 0 }, adventurers: [], upgrades: [], quests: [], bosses: [], equipment: [], achievements: [], zones: [], exploration: { areas: [], materials: [], craftedRecipeIds: [], craftedGoldMultiplier: 1, craftedEssenceMultiplier: 1, craftedClickMultiplier: 1, craftedCombatMultiplier: 1 }, companions: { unlockedCompanionIds: [], activeCompanionId: null }, prestige: { count: 0, runestones: 0, productionMultiplier: 1, purchasedUpgradeIds: [] }, baseClickPower: 1, lastTickAt: 0, schemaVersion: 1, goddess: makeGoddessState() as GameState["goddess"], ...overrides, } as GameState); describe("goddessBoss route", () => { let app: Hono; let prisma: { gameState: { findUnique: ReturnType; update: ReturnType } }; beforeEach(async () => { vi.clearAllMocks(); const { goddessBossRouter } = await import("../../src/routes/goddessBoss.js"); const { prisma: p } = await import("../../src/db/client.js"); prisma = p as typeof prisma; app = new Hono(); app.route("/goddess-boss", goddessBossRouter); }); const challenge = (body: Record) => app.fetch(new Request("http://localhost/goddess-boss/challenge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), })); it("returns 400 when bossId is missing", async () => { const res = await challenge({}); expect(res.status).toBe(400); }); it("returns 404 when no save is found", async () => { vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce(null); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(404); }); it("returns 400 when goddess realm is not unlocked", async () => { const state = makeState({ goddess: undefined }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(400); const body = await res.json() as { error: string }; expect(body.error).toBe("Goddess realm not unlocked"); }); it("returns 404 when boss is not found in goddess state", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [] }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(404); const body = await res.json() as { error: string }; expect(body.error).toBe("Boss not found"); }); it("returns 400 when boss status is defeated", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ status: "defeated" }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(400); const body = await res.json() as { error: string }; expect(body.error).toBe("Boss is not currently available"); }); it("returns 400 when boss status is locked", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ status: "locked" }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(400); }); it("accepts in_progress boss status", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ status: "in_progress" }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); }); it("returns 403 when consecration requirement is not met", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ consecrationRequirement: 3 }) ], consecration: makeConsecration({ count: 0 }), }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(403); const body = await res.json() as { error: string }; expect(body.error).toBe("Consecration requirement not met"); }); it("returns 400 when party has no combat power (all disciples have count 0)", async () => { const state = makeState({ goddess: makeGoddessState({ disciples: [ makeDisciple({ count: 0 }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(400); const body = await res.json() as { error: string }; expect(body.error).toBe("Your disciples have no combat power"); }); it("returns 400 when party has no combat power (disciples array is empty)", async () => { const state = makeState({ goddess: makeGoddessState({ disciples: [], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(400); }); it("returns 200 with rewards when party wins", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1, level: 10 }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; rewards: { prayers: number; divinity: number } }; expect(body.won).toBe(true); expect(body.rewards.prayers).toBe(50); expect(body.rewards.divinity).toBe(2); }); it("returns 200 with bountyDivinity when first kill", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ currentHp: 50, maxHp: 50, damagePerSecond: 1, id: "celestial_sprite" }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "celestial_sprite" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; rewards: { bountyDivinity: number } }; expect(body.won).toBe(true); expect(body.rewards.bountyDivinity).toBeGreaterThan(0); }); it("returns 0 bountyDivinity when already claimed", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "celestial_sprite", bountyDivinityClaimed: true }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "celestial_sprite" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; rewards: { bountyDivinity: number } }; expect(body.won).toBe(true); expect(body.rewards.bountyDivinity).toBe(0); }); it("unlocks upgrade rewards on win", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ upgradeRewards: [ "test_upgrade" ] }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], upgrades: [ { id: "test_upgrade", purchased: false, unlocked: false, target: "global", multiplier: 1 } ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; rewards: { upgradeIds: string[] } }; expect(body.won).toBe(true); expect(body.rewards.upgradeIds).toContain("test_upgrade"); }); it("unlocks next zone boss when boss is defeated", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss", zoneId: "test_goddess_zone", status: "available" }), makeGoddessBoss({ id: "next_goddess_boss", zoneId: "test_goddess_zone", status: "locked", consecrationRequirement: 0 }), ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("does not unlock next boss if consecration requirement not met", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss", zoneId: "test_goddess_zone", status: "available" }), makeGoddessBoss({ id: "next_goddess_boss", zoneId: "test_goddess_zone", status: "locked", consecrationRequirement: 5 }), ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], consecration: makeConsecration({ count: 0 }), }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("unlocks goddess zone when boss and quest conditions are both met", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss" }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], zones: [ { id: "test_goddess_zone", status: "locked", unlockBossId: "test_goddess_boss", unlockQuestId: "test_quest" }, ], quests: [ { id: "test_quest", status: "completed" } ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("does not unlock zone when quest condition is not satisfied", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss" }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], zones: [ { id: "test_goddess_zone", status: "locked", unlockBossId: "test_goddess_boss", unlockQuestId: "test_quest" }, ], quests: [ { id: "test_quest", status: "active" } ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("unlocks zone when unlockQuestId is null", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss" }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], zones: [ { id: "test_goddess_zone", status: "locked", unlockBossId: "test_goddess_boss", unlockQuestId: null }, ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("skips zone that is already unlocked", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss" }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], zones: [ { id: "test_goddess_zone", status: "unlocked", unlockBossId: "test_goddess_boss", unlockQuestId: null }, ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("skips zone whose unlockBossId does not match the defeated boss", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ id: "test_goddess_boss" }) ], disciples: [ makeDisciple({ combatPower: 100_000, count: 1 }) ], zones: [ { id: "other_zone", status: "locked", unlockBossId: "different_boss", unlockQuestId: null }, ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("applies global upgrade multiplier to party DPS", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ currentHp: 100, damagePerSecond: 1 }) ], disciples: [ makeDisciple({ combatPower: 1, count: 1, level: 10 }) ], upgrades: [ { id: "global_u", purchased: true, target: "global", multiplier: 100_000 } ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("applies disciple-specific upgrade multiplier", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ currentHp: 100, damagePerSecond: 1 }) ], disciples: [ makeDisciple({ id: "test_disciple", combatPower: 1, count: 1, level: 10 }) ], upgrades: [ { id: "disciple_u", purchased: true, target: "disciple", multiplier: 100_000, discipleId: "test_disciple" }, ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("skips unpurchased upgrades", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ currentHp: 100_000, damagePerSecond: 1 }) ], disciples: [ makeDisciple({ combatPower: 1, count: 1, level: 10 }) ], upgrades: [ { id: "not_bought", purchased: false, target: "global", multiplier: 100_000 }, ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(false); }); it("returns 200 with casualties when party loses", async () => { const state = makeState({ goddess: makeGoddessState({ bosses: [ makeGoddessBoss({ currentHp: 1_000_000, maxHp: 1_000_000, damagePerSecond: 1_000_000 }), ], disciples: [ makeDisciple({ combatPower: 1, count: 10, level: 1 }), makeDisciple({ id: "zero_disciple", combatPower: 0, count: 0, level: 1 }), ], }) as GameState["goddess"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; casualties: Array<{ discipleId: string }> }; expect(body.won).toBe(false); expect(Array.isArray(body.casualties)).toBe(true); }); it("includes HMAC signature in response when ANTI_CHEAT_SECRET is set", async () => { process.env.ANTI_CHEAT_SECRET = "test_secret"; const state = makeState(); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { signature: string | undefined }; expect(body.signature).toBeDefined(); delete process.env.ANTI_CHEAT_SECRET; }); it("omits signature when ANTI_CHEAT_SECRET is not set", async () => { delete process.env.ANTI_CHEAT_SECRET; const state = makeState(); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(200); const body = await res.json() as { signature: string | undefined }; expect(body.signature).toBeUndefined(); }); it("returns 500 when the database throws an Error", async () => { vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce(new Error("DB error")); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(500); }); it("returns 500 when the database throws a non-Error value", async () => { vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce("raw string error"); const res = await challenge({ bossId: "test_goddess_boss" }); expect(res.status).toBe(500); }); });