import { describe, it, expect, mock, beforeEach } from "bun:test"; import { classService } from "@shared/modules/class/class.service"; import { classes, users } from "@db/schema"; // Define mock functions const mockFindMany = mock(); const mockFindFirst = mock(); const mockInsert = mock(); const mockUpdate = mock(); const mockDelete = mock(); const mockValues = mock(); const mockReturning = mock(); const mockSet = mock(); const mockWhere = mock(); // Chainable mock setup mockInsert.mockReturnValue({ values: mockValues }); mockValues.mockReturnValue({ returning: mockReturning }); mockUpdate.mockReturnValue({ set: mockSet }); mockSet.mockReturnValue({ where: mockWhere }); mockWhere.mockReturnValue({ returning: mockReturning }); mockDelete.mockReturnValue({ where: mockWhere }); // Fix for delete chaining if needed, usually delete(table).where(...) // Mock DrizzleClient mock.module("@shared/db/DrizzleClient", () => { return { DrizzleClient: { query: { classes: { findMany: mockFindMany, findFirst: mockFindFirst, }, }, insert: mockInsert, update: mockUpdate, delete: mockDelete, transaction: async (cb: any) => { return cb({ query: { classes: { findMany: mockFindMany, findFirst: mockFindFirst, }, }, insert: mockInsert, update: mockUpdate, delete: mockDelete, }); } }, }; }); describe("classService", () => { beforeEach(() => { mockFindMany.mockReset(); mockFindFirst.mockReset(); mockInsert.mockClear(); mockUpdate.mockClear(); mockDelete.mockClear(); mockValues.mockClear(); mockReturning.mockClear(); mockSet.mockClear(); mockWhere.mockClear(); }); describe("getAllClasses", () => { it("should return all classes", async () => { const mockClasses = [{ id: 1n, name: "Warrior" }, { id: 2n, name: "Mage" }]; mockFindMany.mockResolvedValue(mockClasses); const result = await classService.getAllClasses(); expect(result).toEqual(mockClasses as any); expect(mockFindMany).toHaveBeenCalledTimes(1); }); }); describe("assignClass", () => { it("should assign class to user if class exists", async () => { const mockClass = { id: 1n, name: "Warrior" }; const mockUser = { id: 123n, classId: 1n }; mockFindFirst.mockResolvedValue(mockClass); mockReturning.mockResolvedValue([mockUser]); const result = await classService.assignClass("123", 1n); expect(result).toEqual(mockUser as any); // Verify class check expect(mockFindFirst).toHaveBeenCalledTimes(1); // Verify update expect(mockUpdate).toHaveBeenCalledWith(users); expect(mockSet).toHaveBeenCalledWith({ classId: 1n }); }); it("should throw error if class not found", async () => { mockFindFirst.mockResolvedValue(undefined); expect(classService.assignClass("123", 99n)).rejects.toThrow("Class not found"); }); }); describe("getClassBalance", () => { it("should return class balance", async () => { const mockClass = { id: 1n, balance: 100n }; mockFindFirst.mockResolvedValue(mockClass); const result = await classService.getClassBalance(1n); expect(result).toBe(100n); }); it("should return 0n if class has no balance or not found", async () => { mockFindFirst.mockResolvedValue(null); const result = await classService.getClassBalance(1n); expect(result).toBe(0n); mockFindFirst.mockResolvedValue({ id: 1n, balance: null }); const result2 = await classService.getClassBalance(1n); expect(result2).toBe(0n); }); }); describe("modifyClassBalance", () => { it("should modify class balance successfully", async () => { const mockClass = { id: 1n, balance: 100n }; const updatedClass = { id: 1n, balance: 150n }; mockFindFirst.mockResolvedValue(mockClass); mockReturning.mockResolvedValue([updatedClass]); const result = await classService.modifyClassBalance(1n, 50n); expect(result).toEqual(updatedClass as any); expect(mockUpdate).toHaveBeenCalledWith(classes); // Note: sql template literal matching might be tricky, checking strict call might fail if not exact object ref // We verify at least mockSet was called expect(mockSet).toHaveBeenCalled(); }); it("should throw if class not found", async () => { mockFindFirst.mockResolvedValue(undefined); expect(classService.modifyClassBalance(1n, 50n)).rejects.toThrow("Class not found"); }); it("should throw if insufficient funds", async () => { const mockClass = { id: 1n, balance: 10n }; mockFindFirst.mockResolvedValue(mockClass); expect(classService.modifyClassBalance(1n, -20n)).rejects.toThrow("Insufficient class funds"); }); }); describe("updateClass", () => { it("should update class details", async () => { const updateData = { name: "Super Warrior" }; const updatedClass = { id: 1n, name: "Super Warrior" }; mockReturning.mockResolvedValue([updatedClass]); const result = await classService.updateClass(1n, updateData); expect(result).toEqual(updatedClass as any); expect(mockUpdate).toHaveBeenCalledWith(classes); expect(mockSet).toHaveBeenCalledWith(updateData); }); }); describe("createClass", () => { it("should create a new class", async () => { const newClassData = { name: "Archer", description: "Bow user" }; const createdClass = { id: 3n, ...newClassData }; mockReturning.mockResolvedValue([createdClass]); const result = await classService.createClass(newClassData as any); expect(result).toEqual(createdClass as any); expect(mockInsert).toHaveBeenCalledWith(classes); expect(mockValues).toHaveBeenCalledWith(newClassData); }); }); describe("deleteClass", () => { it("should delete a class", async () => { mockDelete.mockReturnValue({ where: mockWhere }); // The chain is delete(table).where(...) for delete // Wait, in user.service.test.ts: // mockDelete called without chain setup in the file provided? // "mockDelete = mock()" // And in mock: "delete: mockDelete" // And in usage: "await txFn.delete(users).where(...)" // So mockDelete must return an object with where. mockDelete.mockReturnValue({ where: mockWhere }); await classService.deleteClass(1n); expect(mockDelete).toHaveBeenCalledWith(classes); // We can't easily check 'where' arguments specifically without complex matcher if we don't return specific mock expect(mockWhere).toHaveBeenCalled(); }); }); });