- Added generated index files and report directories to .gitignore to prevent unnecessary tracking of transient files. - Updated README links to reflect new documentation paths for better navigation. - Improved documentation organization by ensuring all links point to the correct locations, enhancing user experience and accessibility.
152 lines
4.0 KiB
TypeScript
152 lines
4.0 KiB
TypeScript
/**
|
|
* Unit tests for authentication service
|
|
*
|
|
* This file demonstrates testing patterns for service functions in the API.
|
|
* See docs/TEST_EXAMPLES.md for more examples.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import bcrypt from 'bcryptjs'
|
|
import jwt from 'jsonwebtoken'
|
|
import { login } from './auth'
|
|
import { getDb } from '../db'
|
|
import { AppErrors } from '../lib/errors'
|
|
|
|
// Mock dependencies
|
|
vi.mock('../db')
|
|
vi.mock('../lib/errors')
|
|
vi.mock('bcryptjs')
|
|
vi.mock('jsonwebtoken')
|
|
vi.mock('../lib/secret-validation', () => ({
|
|
requireJWTSecret: () => 'test-secret'
|
|
}))
|
|
|
|
describe('auth service', () => {
|
|
let mockDb: any
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
|
|
mockDb = {
|
|
query: vi.fn()
|
|
}
|
|
|
|
vi.mocked(getDb).mockReturnValue(mockDb as any)
|
|
})
|
|
|
|
describe('login', () => {
|
|
it('should authenticate valid user and return token', async () => {
|
|
// Arrange
|
|
const mockUser = {
|
|
id: '1',
|
|
email: '[email protected]',
|
|
name: 'Test User',
|
|
password_hash: '$2a$10$hashed',
|
|
role: 'USER',
|
|
created_at: new Date('2024-01-01'),
|
|
updated_at: new Date('2024-01-01'),
|
|
}
|
|
|
|
mockDb.query.mockResolvedValue({
|
|
rows: [mockUser]
|
|
})
|
|
|
|
vi.mocked(bcrypt.compare).mockResolvedValue(true as never)
|
|
vi.mocked(jwt.sign).mockReturnValue('mock-jwt-token' as any)
|
|
|
|
// Act
|
|
const result = await login('[email protected]', 'password123')
|
|
|
|
// Assert
|
|
expect(result).toHaveProperty('token')
|
|
expect(result.token).toBe('mock-jwt-token')
|
|
expect(result.user.email).toBe('[email protected]')
|
|
expect(result.user.name).toBe('Test User')
|
|
expect(result.user.role).toBe('USER')
|
|
|
|
expect(mockDb.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('SELECT'),
|
|
['[email protected]']
|
|
)
|
|
expect(bcrypt.compare).toHaveBeenCalledWith('password123', mockUser.password_hash)
|
|
expect(jwt.sign).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should throw error for invalid email', async () => {
|
|
// Arrange
|
|
mockDb.query.mockResolvedValue({
|
|
rows: []
|
|
})
|
|
|
|
// Act & Assert
|
|
await expect(login('[email protected]', 'password123')).rejects.toThrow()
|
|
|
|
expect(bcrypt.compare).not.toHaveBeenCalled()
|
|
expect(jwt.sign).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should throw error for invalid password', async () => {
|
|
// Arrange
|
|
const mockUser = {
|
|
id: '1',
|
|
email: '[email protected]',
|
|
name: 'Test User',
|
|
password_hash: '$2a$10$hashed',
|
|
role: 'USER',
|
|
created_at: new Date('2024-01-01'),
|
|
updated_at: new Date('2024-01-01'),
|
|
}
|
|
|
|
mockDb.query.mockResolvedValue({
|
|
rows: [mockUser]
|
|
})
|
|
|
|
vi.mocked(bcrypt.compare).mockResolvedValue(false as never)
|
|
|
|
// Act & Assert
|
|
await expect(login('[email protected]', 'wrongpassword')).rejects.toThrow()
|
|
|
|
expect(bcrypt.compare).toHaveBeenCalledWith('wrongpassword', mockUser.password_hash)
|
|
expect(jwt.sign).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should include user role in JWT token', async () => {
|
|
// Arrange
|
|
const mockUser = {
|
|
id: '1',
|
|
email: '[email protected]',
|
|
name: 'Admin User',
|
|
password_hash: '$2a$10$hashed',
|
|
role: 'ADMIN',
|
|
created_at: new Date('2024-01-01'),
|
|
updated_at: new Date('2024-01-01'),
|
|
}
|
|
|
|
mockDb.query.mockResolvedValue({
|
|
rows: [mockUser]
|
|
})
|
|
|
|
vi.mocked(bcrypt.compare).mockResolvedValue(true as never)
|
|
vi.mocked(jwt.sign).mockReturnValue('mock-jwt-token' as any)
|
|
|
|
// Act
|
|
await login('[email protected]', 'password123')
|
|
|
|
// Assert
|
|
expect(jwt.sign).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
id: '1',
|
|
email: '[email protected]',
|
|
name: 'Admin User',
|
|
role: 'ADMIN',
|
|
}),
|
|
'test-secret',
|
|
expect.objectContaining({
|
|
expiresIn: expect.any(String)
|
|
})
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|