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 { UserBody, login } from './utils/api'; describe('Users (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const adminEmail = `users-e2e-admin-${Date.now()}@example.com`; const staffEmail = `users-e2e-staff-${Date.now()}@example.com`; const password = 'SuperSecret123!'; let adminId: string; let staffId: string; let adminAccessToken: string; let staffAccessToken: string; let createdUserId: string; beforeAll(async () => { app = await createTestApp(); prisma = app.get(PrismaService); const passwordHash = await bcrypt.hash(password, 12); const admin = await prisma.user.create({ data: { email: adminEmail, passwordHash, name: 'Users E2E Admin', legacyRole: 'ADMIN', role: { connect: { key: 'ADMIN' } }, }, }); adminId = admin.id; const staff = await prisma.user.create({ data: { email: staffEmail, passwordHash, name: 'Users E2E Staff', legacyRole: 'STAFF', role: { connect: { key: 'STAFF' } }, }, }); staffId = staff.id; adminAccessToken = (await login(app, adminEmail, password)).accessToken; staffAccessToken = (await login(app, staffEmail, password)).accessToken; }); afterAll(async () => { const userIds = [adminId, staffId, createdUserId].filter(Boolean); await prisma.refreshToken.deleteMany({ where: { userId: { in: userIds } }, }); await prisma.auditLog.deleteMany({ where: { userId: { in: userIds } } }); if (createdUserId) { await prisma.user.delete({ where: { id: createdUserId } }); } await prisma.user.deleteMany({ where: { id: { in: [adminId, staffId] } } }); await app.close(); }); it('rejects non-admin roles from listing users', () => { return request(app.getHttpServer()) .get('/api/v1/users') .set('Authorization', `Bearer ${staffAccessToken}`) .expect(403); }); it('lets an admin create a user and writes an audit log entry', async () => { const response = await request(app.getHttpServer()) .post('/api/v1/users') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ email: `users-e2e-created-${Date.now()}@example.com`, password: 'AnotherSecret123!', name: 'Created via e2e', role: 'BEEKEEPER', }) .expect(201); const created = response.body as UserBody & { passwordHash?: string }; expect(created.id).toEqual(expect.any(String)); expect(created.passwordHash).toBeUndefined(); createdUserId = created.id; const auditEntry = await prisma.auditLog.findFirst({ where: { entityType: 'User', entityId: createdUserId, action: 'CREATE' }, }); expect(auditEntry).not.toBeNull(); }); it('lets an admin deactivate a user', async () => { const response = await request(app.getHttpServer()) .post(`/api/v1/users/${createdUserId}/deactivate`) .set('Authorization', `Bearer ${adminAccessToken}`) .expect(201); const deactivated = response.body as UserBody; expect(deactivated.isActive).toBe(false); expect(deactivated.deactivatedAt).not.toBeNull(); }); it('lets an admin reactivate a deactivated user, and rejects reactivating an already-active one', async () => { const activated = await request(app.getHttpServer()) .post(`/api/v1/users/${createdUserId}/activate`) .set('Authorization', `Bearer ${adminAccessToken}`) .expect(201); const body = activated.body as UserBody; expect(body.isActive).toBe(true); expect(body.deactivatedAt).toBeNull(); await request(app.getHttpServer()) .post(`/api/v1/users/${createdUserId}/activate`) .set('Authorization', `Bearer ${adminAccessToken}`) .expect(409); }); });