245 lines
7.4 KiB
TypeScript
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')
|
|
})
|
|
})
|
|
})
|