fix architecture and write new tests

This commit is contained in:
root
2026-06-10 00:40:19 -04:00
parent 560da1cadf
commit 80a597bc10
377 changed files with 84020 additions and 1337 deletions
@@ -0,0 +1,140 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('bcryptjs', () => ({
default: {
compare: vi.fn(),
hash: vi.fn(),
},
}))
vi.mock('../../services/notificationService', () => ({ sendTransactionalEmail: vi.fn() }))
vi.mock('./auth.employee.repo', () => ({
findEmployeeWithCompanyById: vi.fn(),
findEmployeeWithCompanyByEmail: vi.fn(),
findActiveEmployeeByEmail: vi.fn(),
findEmployeeByResetToken: vi.fn(),
findEmployeeById: vi.fn(),
updatePreferredLanguage: vi.fn(),
resetPassword: vi.fn(),
}))
import bcrypt from 'bcryptjs'
import { AppError } from '../../http/errors'
import { sendTransactionalEmail } from '../../services/notificationService'
import * as repo from './auth.employee.repo'
import * as service from './auth.employee.service'
const employee = {
id: 'employee_1',
email: 'agent@example.test',
firstName: 'Aya',
lastName: 'Agent',
role: 'AGENT',
preferredLanguage: 'fr',
companyId: 'company_1',
isActive: true,
passwordHash: 'hash_old',
company: { name: 'Atlas Cars', slug: 'atlas' },
}
describe('auth.employee.service edge behavior', () => {
beforeEach(() => {
vi.clearAllMocks()
process.env.JWT_SECRET = 'test-secret'
process.env.JWT_EXPIRY = '8h'
delete process.env.DASHBOARD_URL
delete process.env.NEXT_PUBLIC_DASHBOARD_URL
vi.mocked(bcrypt.compare).mockResolvedValue(true as never)
vi.mocked(bcrypt.hash).mockResolvedValue('hash_new' as never)
})
it('rejects inactive employee sessions before presenting tenant data', async () => {
vi.mocked(repo.findEmployeeWithCompanyById).mockResolvedValue({ ...employee, isActive: false } as never)
await expect(service.getMe('employee_1')).rejects.toMatchObject({
statusCode: 401,
error: 'unauthenticated',
})
})
it('logs in active employees with a signed token and company context', async () => {
vi.mocked(repo.findEmployeeWithCompanyByEmail).mockResolvedValue(employee as never)
const result = await service.login({ email: 'agent@example.test', password: 'correct-password' })
expect(bcrypt.compare).toHaveBeenCalledWith('correct-password', 'hash_old')
expect(result).toMatchObject({
token: expect.any(String),
employee: {
id: 'employee_1',
companyId: 'company_1',
companyName: 'Atlas Cars',
companySlug: 'atlas',
},
})
})
it('distinguishes employees without passwords from wrong credentials', async () => {
vi.mocked(repo.findEmployeeWithCompanyByEmail).mockResolvedValue({ ...employee, passwordHash: null } as never)
await expect(service.login({ email: 'agent@example.test', password: 'anything' })).rejects.toMatchObject({
statusCode: 401,
error: 'password_not_set',
})
expect(bcrypt.compare).not.toHaveBeenCalled()
})
it('sends reset links to active employees using the dashboard base path exactly once', async () => {
process.env.DASHBOARD_URL = 'https://tenant.example.test/app'
vi.mocked(repo.findActiveEmployeeByEmail).mockResolvedValue(employee as never)
vi.mocked(sendTransactionalEmail).mockResolvedValue({ success: true } as never)
await expect(service.forgotPassword('agent@example.test')).resolves.toEqual({
message: 'If that email is registered, a reset link has been sent.',
})
expect(sendTransactionalEmail).toHaveBeenCalledWith(expect.objectContaining({
to: 'agent@example.test',
subject: expect.any(String),
html: expect.stringContaining('https://tenant.example.test/app/dashboard/reset-password?token='),
text: expect.stringContaining('https://tenant.example.test/app/dashboard/reset-password?token='),
}))
})
it('does not disclose whether forgot-password emails exist', async () => {
vi.mocked(repo.findActiveEmployeeByEmail).mockResolvedValue(null)
await expect(service.forgotPassword('missing@example.test')).resolves.toEqual({
message: 'If that email is registered, a reset link has been sent.',
})
expect(sendTransactionalEmail).not.toHaveBeenCalled()
})
it('updates employee password from a stored reset token and clears reset state through the repo', async () => {
vi.mocked(repo.findEmployeeByResetToken).mockResolvedValue(employee as never)
await expect(service.resetPassword('stored-token', 'new-secure-password')).resolves.toEqual({
message: 'Password updated successfully. You can now sign in.',
})
expect(bcrypt.hash).toHaveBeenCalledWith('new-secure-password', 12)
expect(repo.resetPassword).toHaveBeenCalledWith('employee_1', 'hash_new')
})
it('rejects invalid reset tokens before hashing new passwords', async () => {
vi.mocked(repo.findEmployeeByResetToken).mockResolvedValue(null)
vi.mocked(repo.findEmployeeById).mockResolvedValue(null)
await expect(service.resetPassword('bad-token', 'new-secure-password')).rejects.toMatchObject({
statusCode: 400,
error: 'invalid_token',
})
expect(bcrypt.hash).not.toHaveBeenCalled()
})
it('delegates language preference updates and returns a stable contract', async () => {
vi.mocked(repo.updatePreferredLanguage).mockResolvedValue({} as never)
await expect(service.updateLanguage('employee_1', 'ar')).resolves.toEqual({ language: 'ar' })
expect(repo.updatePreferredLanguage).toHaveBeenCalledWith('employee_1', 'ar')
})
})