diff --git a/package.json b/package.json index f03638c..f031f27 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "db:push:local": "drizzle-kit push", "dev": "bun --watch src/index.ts", "db:studio": "drizzle-kit studio --host 0.0.0.0", - "studio:remote": "bash scripts/remote-studio.sh" + "studio:remote": "bash scripts/remote-studio.sh", + "test": "bun test" }, "dependencies": { "@napi-rs/canvas": "^0.1.84", diff --git a/src/modules/user/user.service.test.ts b/src/modules/user/user.service.test.ts new file mode 100644 index 0000000..8419895 --- /dev/null +++ b/src/modules/user/user.service.test.ts @@ -0,0 +1,99 @@ + +import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"; +import { userService } from "./user.service"; + +// Define mock functions outside so we can control them in tests +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 }); + + +// Mock DrizzleClient +mock.module("@/lib/DrizzleClient", () => { + return { + DrizzleClient: { + query: { + users: { + findFirst: mockFindFirst, + }, + }, + insert: mockInsert, + update: mockUpdate, + delete: mockDelete, + transaction: async (cb: any) => { + // Pass the mock client itself as the transaction object + // This simplifies things as we use the same structure for tx and client + return cb({ + query: { + users: { + findFirst: mockFindFirst, + }, + }, + insert: mockInsert, + update: mockUpdate, + delete: mockDelete, + }); + } + }, + }; +}); + +describe("userService", () => { + beforeEach(() => { + mockFindFirst.mockReset(); + mockInsert.mockClear(); + mockValues.mockClear(); + mockReturning.mockClear(); + }); + + describe("getUserById", () => { + it("should return a user when found", async () => { + const mockUser = { id: 123n, username: "testuser", class: null }; + mockFindFirst.mockResolvedValue(mockUser); + + const result = await userService.getUserById("123"); + + expect(result).toEqual(mockUser as any); + expect(mockFindFirst).toHaveBeenCalledTimes(1); + }); + + it("should return undefined when user not found", async () => { + mockFindFirst.mockResolvedValue(undefined); + + const result = await userService.getUserById("999"); + + expect(result).toBeUndefined(); + expect(mockFindFirst).toHaveBeenCalledTimes(1); + }); + }); + + describe("createUser", () => { + it("should create and return a new user", async () => { + const newUser = { id: 456n, username: "newuser", classId: null }; + mockReturning.mockResolvedValue([newUser]); + + const result = await userService.createUser("456", "newuser"); + + expect(result).toEqual(newUser as any); + expect(mockInsert).toHaveBeenCalledTimes(1); + expect(mockValues).toHaveBeenCalledWith({ + id: 456n, + username: "newuser", + classId: undefined + }); + }); + }); +});