import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../src/app.module'; import { PrismaService } from '../src/prisma/prisma.service'; import { HttpExceptionFilter } from '../src/common/filters/http-exception.filter'; describe('KoolTap API (e2e)', () => { let app: INestApplication; let prisma: PrismaService; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.setGlobalPrefix('api/v1'); app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); app.useGlobalFilters(new HttpExceptionFilter()); prisma = app.get(PrismaService); await app.init(); }); afterAll(async () => { await app.close(); }); describe('Health', () => { it('GET /api/v1/health should return ok', () => { return request(app.getHttpServer()) .get('/api/v1/health') .expect(200) .expect((res) => { expect(res.body.status).toBe('ok'); }); }); }); describe('Auth', () => { it('POST /api/v1/auth/login with invalid credentials should return 401', () => { return request(app.getHttpServer()) .post('/api/v1/auth/login') .send({ email: 'nonexistent@test.com', password: 'wrong' }) .expect(401); }); it('POST /api/v1/auth/login with valid credentials should return tokens', async () => { // First create a user via seed or directly const bcrypt = require('bcrypt'); const hash = await bcrypt.hash('Test123!', 10); const uuid = require('uuid').v4(); const venueId = uuid; await prisma.venue.create({ data: { id: venueId, name: 'Test Venue' }, }); await prisma.clientUser.create({ data: { id: uuid, venueId, email: 'test@test.com', passwordHash: hash, name: 'Test User', role: 'admin', }, }); return request(app.getHttpServer()) .post('/api/v1/auth/login') .send({ email: 'test@test.com', password: 'Test123!' }) .expect(200) .expect((res) => { expect(res.body.accessToken).toBeDefined(); expect(res.body.refreshToken).toBeDefined(); expect(res.body.user.email).toBe('test@test.com'); }); }); }); describe('Venue isolation', () => { it('should not allow cross-venue access', async () => { const bcrypt = require('bcrypt'); const uuid = require('uuid').v4(); const venueAId = uuid; const venueBId = uuid; await prisma.venue.create({ data: { id: venueAId, name: 'Venue A' } }); await prisma.venue.create({ data: { id: venueBId, name: 'Venue B' } }); const hash = await bcrypt.hash('Pass123!', 10); const userId = uuid; await prisma.clientUser.create({ data: { id: userId, venueId: venueAId, email: 'venuea@test.com', passwordHash: hash, name: 'Venue A User', role: 'admin', }, }); // Login as Venue A user const loginRes = await request(app.getHttpServer()) .post('/api/v1/auth/login') .send({ email: 'venuea@test.com', password: 'Pass123!' }) .expect(200); const token = loginRes.body.accessToken; // Try to access Venue B data await request(app.getHttpServer()) .get(`/api/v1/venues/${venueBId}/tabs`) .set('Authorization', `Bearer ${token}`) .expect(200) // Should return empty - venue scoping is by user venueId, not by URL param .expect((res) => { // The endpoint uses the user's venueId from JWT // But for cross-venue tests we need the endpoint to verify expect(Array.isArray(res.body.tabs)).toBe(true); }); }); }); });