Files
AuroraBot-discord/shared/modules/class/class.service.test.ts
2026-01-08 16:09:26 +01:00

209 lines
7.4 KiB
TypeScript

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();
});
});
});