import { describe, it, expect, vi } from 'vitest'; import bcrypt from 'bcryptjs'; import { AuthService } from '@/modules/identity/auth/service/auth.service'; const REAL_PASSWORD_HASH = bcrypt.hashSync('the-real-password', 10); function fakeUser(overrides: Partial> = {}) { return { id: 'u1', email: 'agent@example.com', name: 'Agent', role: 'AGENT', passwordHash: REAL_PASSWORD_HASH, active: true, createdAt: new Date(), updatedAt: new Date(), ...overrides, }; } describe('AuthService.login failure parity', () => { it('throws the identical error for a nonexistent email and a wrong password', async () => { const repoFoundUser = { findByEmail: vi.fn().mockResolvedValue(fakeUser()) } as never; const repoNoUser = { findByEmail: vi.fn().mockResolvedValue(null) } as never; const serviceWithUser = new AuthService(repoFoundUser); const serviceWithoutUser = new AuthService(repoNoUser); let errorWithUser: Error | undefined; let errorWithoutUser: Error | undefined; try { await serviceWithUser.login({ email: 'agent@example.com', password: 'definitely-wrong' }); } catch (e) { errorWithUser = e as Error; } try { await serviceWithoutUser.login({ email: 'nobody@example.com', password: 'anything' }); } catch (e) { errorWithoutUser = e as Error; } expect(errorWithUser).toBeDefined(); expect(errorWithoutUser).toBeDefined(); expect(errorWithUser?.message).toBe(errorWithoutUser?.message); expect((errorWithUser as { statusCode?: number })?.statusCode).toBe( (errorWithoutUser as { statusCode?: number })?.statusCode, ); }); it('rejects a deactivated account with the same error, never a distinguishable one', async () => { const repo = { findByEmail: vi.fn().mockResolvedValue(fakeUser({ active: false })), } as never; const service = new AuthService(repo); await expect( service.login({ email: 'agent@example.com', password: 'anything' }), ).rejects.toMatchObject({ statusCode: 401 }); }); });