125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
|
import request = require('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-password' })
|
|
.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 { v4: uuid } = require('uuid');
|
|
const venueId = uuid();
|
|
const email = `test-${venueId}@test.com`;
|
|
|
|
await prisma.venue.create({
|
|
data: { id: venueId, name: 'Test Venue' },
|
|
});
|
|
|
|
await prisma.clientUser.create({
|
|
data: {
|
|
id: uuid(),
|
|
venueId,
|
|
email,
|
|
passwordHash: hash,
|
|
name: 'Test User',
|
|
role: 'admin',
|
|
},
|
|
});
|
|
|
|
return request(app.getHttpServer())
|
|
.post('/api/v1/auth/login')
|
|
.send({ email, password: 'Test123!' })
|
|
.expect(200)
|
|
.expect((res) => {
|
|
expect(res.body.accessToken).toBeDefined();
|
|
expect(res.body.refreshToken).toBeDefined();
|
|
expect(res.body.user.email).toBe(email);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Venue isolation', () => {
|
|
it('should not allow cross-venue access', async () => {
|
|
const bcrypt = require('bcrypt');
|
|
const { v4: uuid } = require('uuid');
|
|
const venueAId = uuid();
|
|
const venueBId = uuid();
|
|
const email = `venuea-${venueAId}@test.com`;
|
|
|
|
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);
|
|
await prisma.clientUser.create({
|
|
data: {
|
|
id: uuid(),
|
|
venueId: venueAId,
|
|
email,
|
|
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, 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(403)
|
|
.expect((res) => {
|
|
expect(res.body.error.code).toBe('venue_forbidden');
|
|
});
|
|
});
|
|
});
|
|
}); |