/* 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(); }), })); const DISCORD_ID = "test_discord_id"; const makeBoss = (overrides: Record = {}) => ({ id: "test_boss", zoneId: "test_zone", status: "available", prestigeRequirement: 0, currentHp: 100, maxHp: 100, damagePerSecond: 1, goldReward: 50, essenceReward: 10, crystalReward: 0, upgradeRewards: [] as string[], equipmentRewards: [] as string[], ...overrides, }); const makeAdventurer = (overrides: Record = {}) => ({ id: "test_adventurer", count: 1, combatPower: 10000, // Very high DPS to guarantee win level: 10, unlocked: true, goldPerSecond: 1, essencePerSecond: 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 }, 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, ...overrides, } as GameState); describe("boss route", () => { let app: Hono; let prisma: { gameState: { findUnique: ReturnType; update: ReturnType } }; beforeEach(async () => { vi.clearAllMocks(); const { bossRouter } = await import("../../src/routes/boss.js"); const { prisma: p } = await import("../../src/db/client.js"); prisma = p as typeof prisma; app = new Hono(); app.route("/boss", bossRouter); }); const challenge = (body: Record) => app.fetch(new Request("http://localhost/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_boss" }); expect(res.status).toBe(404); }); it("returns 404 when boss is not in state", async () => { const state = makeState({ bosses: [] }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(404); }); it("returns 400 when boss is already defeated", async () => { const state = makeState({ bosses: [makeBoss({ status: "defeated" })] as GameState["bosses"] }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(400); }); it("returns 403 when prestige requirement is not met", async () => { const state = makeState({ bosses: [makeBoss({ prestigeRequirement: 5 })] as GameState["bosses"], prestige: { count: 0, runestones: 0, productionMultiplier: 1, purchasedUpgradeIds: [] }, }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(403); }); it("returns 400 when party has no adventurers", async () => { const state = makeState({ bosses: [makeBoss()] as GameState["bosses"], adventurers: [] }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(400); }); it("returns won=true when party defeats boss", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"], adventurers: [makeAdventurer({ combatPower: 10000, count: 1, level: 10 })] as GameState["adventurers"], zones: [{ id: "test_zone", status: "locked" }] as GameState["zones"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; rewards: { gold: number } }; expect(body.won).toBe(true); expect(body.rewards.gold).toBe(50); }); it("returns won=false when party is defeated", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 1_000_000, maxHp: 1_000_000, damagePerSecond: 1_000_000 })] as GameState["bosses"], // Include an adventurer with count=0 to cover the casualty-loop skip branch adventurers: [ makeAdventurer({ combatPower: 1, count: 10, level: 1 }), makeAdventurer({ id: "zero_count_adventurer", combatPower: 0, count: 0, level: 1 }), ] as GameState["adventurers"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; casualties: Array<{ adventurerId: string }> }; expect(body.won).toBe(false); expect(Array.isArray(body.casualties)).toBe(true); }); it("skips zone unlock when zone is already unlocked and bossId matches", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"], adventurers: [makeAdventurer()] as GameState["adventurers"], // Zone is already unlocked — the loop should skip it via the status==="unlocked" continue zones: [{ id: "test_zone", status: "unlocked", unlockBossId: "test_boss", unlockQuestId: null }] as GameState["zones"], quests: [], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("skips zone unlock when quest condition is not satisfied", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"], adventurers: [makeAdventurer()] as GameState["adventurers"], // Zone has unlockBossId matching but the required quest is not completed zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: "required_quest" }] as GameState["zones"], quests: [{ id: "required_quest", status: "active" }] as GameState["quests"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("unlocks next zone boss when boss is defeated and zone condition is met", async () => { const nextBoss = makeBoss({ id: "next_boss", status: "locked", prestigeRequirement: 0 }); const state = makeState({ bosses: [ makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 }), nextBoss, ] as GameState["bosses"], adventurers: [makeAdventurer()] as GameState["adventurers"], zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: null }] as GameState["zones"], quests: [], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("handles boss with upgrade and equipment rewards on win", async () => { const state = makeState({ bosses: [makeBoss({ upgradeRewards: ["some_upgrade"], equipmentRewards: ["some_equipment"], })] as GameState["bosses"], adventurers: [makeAdventurer()] as GameState["adventurers"], upgrades: [{ id: "some_upgrade", purchased: false, unlocked: false, target: "global", multiplier: 1 }] as GameState["upgrades"], equipment: [{ id: "some_equipment", owned: false, equipped: false, type: "weapon", bonus: {} }] as GameState["equipment"], zones: [], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean; rewards: { upgradeIds: string[]; equipmentIds: string[] } }; expect(body.won).toBe(true); expect(body.rewards.upgradeIds).toContain("some_upgrade"); expect(body.rewards.equipmentIds).toContain("some_equipment"); }); it("updates daily challenge progress on boss defeat", async () => { const state = makeState({ bosses: [makeBoss()] as GameState["bosses"], adventurers: [makeAdventurer()] as GameState["adventurers"], zones: [], dailyChallenges: { date: "2024-01-01", challenges: [{ id: "boss_challenge", type: "bossesDefeated", target: 3, progress: 0, completed: false, crystalReward: 5 }], } as GameState["dailyChallenges"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); }); it("applies adventurer-specific upgrade to party DPS", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"], adventurers: [makeAdventurer({ id: "test_adventurer" })] as GameState["adventurers"], upgrades: [{ id: "adv_upgrade", purchased: true, unlocked: true, target: "adventurer", adventurerId: "test_adventurer", multiplier: 2 }] as GameState["upgrades"], zones: [], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_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 when global upgrade is purchased", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"], adventurers: [makeAdventurer({ combatPower: 10000, count: 1 })] as GameState["adventurers"], upgrades: [{ id: "global_1", purchased: true, unlocked: true, target: "global", multiplier: 2 }] as GameState["upgrades"], zones: [], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); it("unlocks zone when boss defeated and quest condition is also satisfied", async () => { const state = makeState({ bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"], adventurers: [makeAdventurer()] as GameState["adventurers"], zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: "test_quest" }] as GameState["zones"], quests: [{ id: "test_quest", status: "completed" }] as GameState["quests"], }); vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never); vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never); const res = await challenge({ bossId: "test_boss" }); expect(res.status).toBe(200); const body = await res.json() as { won: boolean }; expect(body.won).toBe(true); }); });