import { INestApplication } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; import request from 'supertest'; import { App } from 'supertest/types'; import { PrismaService } from '../src/prisma/prisma.service'; import { createTestApp } from './utils/create-test-app'; import { AuthTokensBody, login } from './utils/api'; describe('Auth (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const email = `auth-e2e-${Date.now()}@example.com`; const password = 'SuperSecret123!'; let userId: string; beforeAll(async () => { app = await createTestApp(); prisma = app.get(PrismaService); const passwordHash = await bcrypt.hash(password, 12); const user = await prisma.user.create({ data: { email, passwordHash, name: 'Auth E2E User', role: 'ADMIN' }, }); userId = user.id; }); afterAll(async () => { await prisma.refreshToken.deleteMany({ where: { userId } }); await prisma.auditLog.deleteMany({ where: { userId } }); await prisma.user.delete({ where: { id: userId } }); await app.close(); }); it('rejects login with wrong password', () => { return request(app.getHttpServer()) .post('/api/v1/auth/login') .send({ email, password: 'wrong-password' }) .expect(401); }); it('logs in and returns access + refresh tokens', async () => { const tokens = await login(app, email, password); expect(tokens.accessToken).toEqual(expect.any(String)); expect(tokens.refreshToken).toEqual(expect.any(String)); }); it('rejects a protected route without a token', () => { return request(app.getHttpServer()).get('/api/v1/users').expect(401); }); it('accepts a protected route with a valid access token', async () => { const tokens = await login(app, email, password); await request(app.getHttpServer()) .get('/api/v1/users') .set('Authorization', `Bearer ${tokens.accessToken}`) .expect(200); }); it('rotates the refresh token and rejects reuse of the old one', async () => { const tokens = await login(app, email, password); const refreshResponse = await request(app.getHttpServer()) .post('/api/v1/auth/refresh') .send({ refreshToken: tokens.refreshToken }) .expect(200); const refreshed = refreshResponse.body as AuthTokensBody; expect(refreshed.refreshToken).not.toEqual(tokens.refreshToken); await request(app.getHttpServer()) .post('/api/v1/auth/refresh') .send({ refreshToken: tokens.refreshToken }) .expect(401); }); it('logs out and invalidates the refresh token', async () => { const tokens = await login(app, email, password); await request(app.getHttpServer()) .post('/api/v1/auth/logout') .send({ refreshToken: tokens.refreshToken }) .expect(204); await request(app.getHttpServer()) .post('/api/v1/auth/refresh') .send({ refreshToken: tokens.refreshToken }) .expect(401); }); });