test: add tests for class service
This commit is contained in:
209
src/modules/class/class.service.test.ts
Normal file
209
src/modules/class/class.service.test.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { describe, it, expect, mock, beforeEach } from "bun:test";
|
||||||
|
import { classService } from "./class.service";
|
||||||
|
import { classes, users } from "@/db/schema";
|
||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
// 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("@/lib/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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user