Files
2025-10-08 11:37:54 +07:00

245 lines
7.4 KiB
TypeScript

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')
})
})
})