import { ConflictException, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { createHash } from 'crypto'; jest.mock('bcrypt', () => ({ hash: jest.fn(() => Promise.resolve('password-hash')), compare: jest.fn(() => Promise.resolve(true)), })); jest.mock('../../../prisma/prisma.service', () => ({ PrismaService: class PrismaService {}, })); import { AuthService } from './auth.service'; const user = { id: 'user', email: 'user@example.com', passwordHash: 'password-hash', createdAt: new Date('2026-01-01T00:00:00.000Z'), updatedAt: new Date('2026-01-01T00:00:00.000Z'), }; function hash(token: string): string { return createHash('sha256').update(token).digest('hex'); } function twoCallBarrier(value: T): () => Promise { let calls = 0; let release: () => void; const ready = new Promise((resolve) => { release = resolve; }); return async () => { calls += 1; if (calls === 2) release(); await ready; return value; }; } describe('AuthService', () => { const jwt = new JwtService({ secret: 'test-secret-that-is-long-enough' }); const prisma = { user: { findUnique: jest.fn(), create: jest.fn() }, session: { findUnique: jest.fn(), create: jest.fn(), update: jest.fn(), updateMany: jest.fn(), delete: jest.fn(), }, }; const service = new AuthService(prisma as never, jwt); beforeEach(() => { jest.clearAllMocks(); }); it('maps the database loser of a concurrent registration to EMAIL_ALREADY_REGISTERED', async () => { prisma.user.findUnique.mockImplementation(twoCallBarrier(null)); prisma.user.create .mockResolvedValueOnce(user) .mockRejectedValueOnce( Object.assign(new Error('duplicate email'), { code: 'P2002' }), ); prisma.session.create.mockResolvedValue({ id: 'session', userId: user.id, refreshTokenHash: '', expiresAt: new Date(), revokedAt: null, createdAt: new Date(), updatedAt: new Date(), }); prisma.session.update.mockResolvedValue({}); const results = await Promise.allSettled([ service.register(user.email, 'secure password'), service.register(user.email, 'secure password'), ]); const rejected = results.find( (result): result is PromiseRejectedResult => result.status === 'rejected', ); expect( results.filter((result) => result.status === 'fulfilled'), ).toHaveLength(1); expect(rejected?.reason).toEqual(expect.any(ConflictException)); expect((rejected?.reason as ConflictException).getResponse()).toEqual({ code: 'EMAIL_ALREADY_REGISTERED', }); }); it('allows one concurrent refresh winner without revoking its session', async () => { const oldToken = await jwt.signAsync( { sub: user.id, sid: 'session', type: 'refresh', jti: 'old-token' }, { expiresIn: '7d', algorithm: 'HS256' }, ); let currentHash = hash(oldToken); const session = { id: 'session', userId: user.id, refreshTokenHash: currentHash, expiresAt: new Date(Date.now() + 60_000), revokedAt: null, createdAt: new Date(), updatedAt: new Date(), user, }; prisma.session.findUnique.mockImplementation(twoCallBarrier(session)); prisma.session.updateMany.mockImplementation( ({ where, data, }: { data: { refreshTokenHash: string }; where: { id?: string; refreshTokenHash?: string }; }) => { if (where.id === session.id && where.refreshTokenHash === currentHash) { currentHash = data.refreshTokenHash; return Promise.resolve({ count: 1 }); } return Promise.resolve({ count: 0 }); }, ); const results = await Promise.allSettled([ service.refresh(oldToken), service.refresh(oldToken), ]); const rejected = results.find( (result): result is PromiseRejectedResult => result.status === 'rejected', ); expect( results.filter((result) => result.status === 'fulfilled'), ).toHaveLength(1); expect(rejected?.reason).toEqual(expect.any(UnauthorizedException)); expect(prisma.session.updateMany).toHaveBeenCalledTimes(2); expect( prisma.session.updateMany.mock.calls.map( ([input]) => (input as { where: { id?: string; refreshTokenHash?: string } }) .where, ), ).toContainEqual({ id: session.id, refreshTokenHash: hash(oldToken) }); expect(prisma.session.update).not.toHaveBeenCalled(); }); });