import { describe, expect, it, beforeEach, mock } from 'bun:test' import { UnauthenticatedError, InternalServerError } from '~/helpers/errors' // Mock dependencies mock.module('@/user/queries/query', () => ({ default: { getUserByEmail: mock(async (email: string) => { if (email === 'test@example.com') { return { id: 'user-123', name: 'Test User', email: 'test@example.com', role: 'admin', image: null, googleId: null, password: '$2a$10$mockhashedpassword', } } if (email === 'volunteer@example.com') { return { id: 'volunteer-123', name: 'Test Volunteer', email: 'volunteer@example.com', role: 'volunteer', image: null, googleId: null, password: '$2a$10$mockhashedpassword', } } return null }), getVolunteerById: mock(async ({ id }: { id: string }) => { if (id === 'volunteer-123') { return { id: 'vol-456', partnerId: 'partner-789', name: 'Test Volunteer', phone: '+1234567890', gender: 'male', } } return null }), }, })) mock.module('~/helpers/utils', () => ({ passwordVerify: mock((password: string, _hash: string) => { return password === 'correctpassword' }), })) mock.module('~/helpers/jwt', () => ({ sign: mock(async (payload: unknown) => { return `jwt.token.${Buffer.from(JSON.stringify(payload)).toString('base64')}` }), })) mock.module('~/plugins', () => ({ logger: { debug: mock(() => undefined), info: mock(() => undefined), }, })) import CommandService from '~/modules/auth/commands/service' describe('Auth CommandService', () => { beforeEach(() => { // Reset all mocks before each test mock.restore() // Reapply main mocks mock.module('@/user/queries/query', () => ({ default: { getUserByEmail: mock(async (email: string) => { if (email === 'test@example.com') { return { id: 'user-123', name: 'Test User', email: 'test@example.com', role: 'admin', image: null, googleId: null, password: '$2a$10$mockhashedpassword', } } if (email === 'volunteer@example.com') { return { id: 'volunteer-123', name: 'Test Volunteer', email: 'volunteer@example.com', role: 'volunteer', image: null, googleId: null, password: '$2a$10$mockhashedpassword', } } return null }), getVolunteerById: mock(async ({ id }: { id: string }) => { if (id === 'volunteer-123') { return { id: 'vol-456', partnerId: 'partner-789', name: 'Test Volunteer', phone: '+1234567890', gender: 'male', } } return null }), }, })) mock.module('~/helpers/utils', () => ({ passwordVerify: mock((password: string, hash: string) => { return password === 'correctpassword' }), })) mock.module('~/helpers/jwt', () => ({ sign: mock(async (payload: any) => { return `jwt.token.${Buffer.from(JSON.stringify(payload)).toString('base64')}` }), })) mock.module('~/plugins', () => ({ logger: { debug: mock(() => {}), info: mock(() => {}), }, })) }) describe('login', () => { it('should login successfully with valid credentials', async () => { const result = await CommandService.login({ email: 'test@example.com', password: 'correctpassword', }) expect(result).toHaveProperty('accessToken') expect(typeof result.accessToken).toBe('string') expect(result.accessToken).toMatch(/^jwt\.token\./) }) it('should throw UnauthenticatedError for non-existent user', async () => { expect(async () => { await CommandService.login({ email: 'nonexistent@example.com', password: 'anypassword', }) }).toThrow(UnauthenticatedError) }) it('should throw UnauthenticatedError for incorrect password', async () => { expect(async () => { await CommandService.login({ email: 'test@example.com', password: 'wrongpassword', }) }).toThrow(UnauthenticatedError) }) it('should login volunteer user successfully', async () => { const result = await CommandService.login({ email: 'volunteer@example.com', password: 'correctpassword', }) expect(result).toHaveProperty('accessToken') expect(typeof result.accessToken).toBe('string') // Decode the JWT payload to verify volunteer data is included const payloadBase64 = result.accessToken.split('.')[2] const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString()) expect(payload.user).toHaveProperty('volunteer') expect(payload.user.volunteer.id).toBe('vol-456') }) it('should throw InternalServerError when volunteer detail not found', async () => { // Mock getUserByEmail to return a volunteer user but getVolunteerById to return null const mockUserQuery = await import('~/modules/user/queries/query') mockUserQuery.default.getUserByEmail = mock(async () => ({ id: 'volunteer-without-detail', name: 'Volunteer Without Detail', email: 'volunteer-no-detail@example.com', role: 'volunteer', image: null, googleId: null, password: '$2a$10$mockhashedpassword', })) as any mockUserQuery.default.getVolunteerById = mock(async () => null) as any expect(async () => { await CommandService.login({ email: 'volunteer-no-detail@example.com', password: 'correctpassword', }) }).toThrow(InternalServerError) }) it('should generate unique sub for each login', async () => { const result1 = await CommandService.login({ email: 'test@example.com', password: 'correctpassword', }) // Wait a millisecond to ensure different timestamp await new Promise((resolve) => setTimeout(resolve, 1)) const result2 = await CommandService.login({ email: 'test@example.com', password: 'correctpassword', }) // Decode both tokens to compare sub values const payload1Base64 = result1.accessToken.split('.')[2] const payload1 = JSON.parse(Buffer.from(payload1Base64, 'base64').toString()) const payload2Base64 = result2.accessToken.split('.')[2] const payload2 = JSON.parse(Buffer.from(payload2Base64, 'base64').toString()) expect(payload1.sub).not.toBe(payload2.sub) }) it('should include correct user data in JWT payload', async () => { const result = await CommandService.login({ email: 'test@example.com', password: 'correctpassword', }) const payloadBase64 = result.accessToken.split('.')[2] const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString()) expect(payload.user.id).toBe('user-123') expect(payload.user.name).toBe('Test User') expect(payload.user.email).toBe('test@example.com') expect(payload.user.role).toBe('admin') expect(payload.user).not.toHaveProperty('password') }) }) })