diff --git a/src/modules/quest/quest.service.test.ts b/src/modules/quest/quest.service.test.ts new file mode 100644 index 0000000..6460382 --- /dev/null +++ b/src/modules/quest/quest.service.test.ts @@ -0,0 +1,154 @@ +import { describe, it, expect, mock, beforeEach } from "bun:test"; +import { questService } from "./quest.service"; +import { userQuests } from "@/db/schema"; +import { eq, and } from "drizzle-orm"; + +// Mock dependencies +const mockFindFirst = mock(); +const mockFindMany = mock(); +const mockInsert = mock(); +const mockUpdate = mock(); +const mockDelete = mock(); +const mockValues = mock(); +const mockReturning = mock(); +const mockSet = mock(); +const mockWhere = mock(); +const mockOnConflictDoNothing = mock(); + +// Chain setup +mockInsert.mockReturnValue({ values: mockValues }); +mockValues.mockReturnValue({ + onConflictDoNothing: mockOnConflictDoNothing +}); +mockOnConflictDoNothing.mockReturnValue({ returning: mockReturning }); + +mockUpdate.mockReturnValue({ set: mockSet }); +mockSet.mockReturnValue({ where: mockWhere }); +mockWhere.mockReturnValue({ returning: mockReturning }); + +// Mock DrizzleClient +mock.module("@/lib/DrizzleClient", () => { + const createMockTx = () => ({ + query: { + userQuests: { findFirst: mockFindFirst, findMany: mockFindMany }, + }, + insert: mockInsert, + update: mockUpdate, + delete: mockDelete, + }); + + return { + DrizzleClient: { + ...createMockTx(), + transaction: async (cb: any) => cb(createMockTx()), + } + }; +}); + +// Mock External Services +const mockModifyUserBalance = mock(); +mock.module("@/modules/economy/economy.service", () => ({ + economyService: { + modifyUserBalance: mockModifyUserBalance + } +})); + +const mockAddXp = mock(); +mock.module("@/modules/leveling/leveling.service", () => ({ + levelingService: { + addXp: mockAddXp + } +})); + +describe("questService", () => { + beforeEach(() => { + mockFindFirst.mockReset(); + mockFindMany.mockReset(); + mockInsert.mockClear(); + mockUpdate.mockClear(); + mockValues.mockClear(); + mockReturning.mockClear(); + mockSet.mockClear(); + mockWhere.mockClear(); + mockOnConflictDoNothing.mockClear(); + mockModifyUserBalance.mockClear(); + mockAddXp.mockClear(); + }); + + describe("assignQuest", () => { + it("should assign quest", async () => { + mockReturning.mockResolvedValue([{ userId: 1n, questId: 101 }]); + + const result = await questService.assignQuest("1", 101); + + expect(result).toEqual([{ userId: 1n, questId: 101 }] as any); + expect(mockInsert).toHaveBeenCalledWith(userQuests); + expect(mockValues).toHaveBeenCalledWith({ + userId: 1n, + questId: 101, + progress: 0 + }); + }); + }); + + describe("updateProgress", () => { + it("should update progress", async () => { + mockReturning.mockResolvedValue([{ userId: 1n, questId: 101, progress: 50 }]); + + const result = await questService.updateProgress("1", 101, 50); + + expect(result).toEqual([{ userId: 1n, questId: 101, progress: 50 }] as any); + expect(mockUpdate).toHaveBeenCalledWith(userQuests); + expect(mockSet).toHaveBeenCalledWith({ progress: 50 }); + }); + }); + + describe("completeQuest", () => { + it("should complete quest and grant rewards", async () => { + const mockUserQuest = { + userId: 1n, + questId: 101, + completedAt: null, + quest: { + rewards: { balance: 100, xp: 50 } + } + }; + mockFindFirst.mockResolvedValue(mockUserQuest); + + const result = await questService.completeQuest("1", 101); + + expect(result.success).toBe(true); + expect(result.rewards.balance).toBe(100n); + expect(result.rewards.xp).toBe(50n); + + // Check updates + expect(mockUpdate).toHaveBeenCalledWith(userQuests); + expect(mockSet).toHaveBeenCalledWith({ completedAt: expect.any(Date) }); + + // Check service calls + expect(mockModifyUserBalance).toHaveBeenCalledWith("1", 100n, 'QUEST_REWARD', expect.any(String), null, expect.anything()); + expect(mockAddXp).toHaveBeenCalledWith("1", 50n, expect.anything()); + }); + + it("should throw if quest not assigned", async () => { + mockFindFirst.mockResolvedValue(null); + expect(questService.completeQuest("1", 101)).rejects.toThrow("Quest not assigned"); + }); + + it("should throw if already completed", async () => { + mockFindFirst.mockResolvedValue({ completedAt: new Date() }); + expect(questService.completeQuest("1", 101)).rejects.toThrow("Quest already completed"); + }); + }); + + describe("getUserQuests", () => { + it("should return user quests", async () => { + const mockData = [{ questId: 1 }, { questId: 2 }]; + mockFindMany.mockResolvedValue(mockData); + + const result = await questService.getUserQuests("1"); + + expect(result).toEqual(mockData as any); + }); + }); +});