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 { login } from './utils/api'; interface HoneyBatchBody { amount: number | null; status: string; } interface BottlingBatchBody { id: string; totalAmount: number; lotNumber: string; } describe('Bottling (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const beekeeperEmail = `bottling-e2e-beekeeper-${Date.now()}@example.com`; const password = 'SuperSecret123!'; let beekeeperId: string; let beekeeperToken: string; let apiaryId: string; let colonyId: string; let harvestId: string; let extractionId: string; let honeyBatchId: string; let bottlingBatchId: string; beforeAll(async () => { app = await createTestApp(); prisma = app.get(PrismaService); const passwordHash = await bcrypt.hash(password, 12); const beekeeper = await prisma.user.create({ data: { email: beekeeperEmail, passwordHash, name: 'Beekeeper', role: 'BEEKEEPER', }, }); beekeeperId = beekeeper.id; beekeeperToken = (await login(app, beekeeperEmail, password)).accessToken; const apiary = await prisma.apiary.create({ data: { name: `Stand ${Date.now()}` }, }); apiaryId = apiary.id; const colony = await prisma.colony.create({ data: { code: `V-${Date.now()}`, apiaryId, colonyType: 'PRODUCTION_COLONY', }, }); colonyId = colony.id; const harvest = await prisma.harvest.create({ data: { apiaryId, date: new Date(), userId: beekeeperId }, }); harvestId = harvest.id; await prisma.harvestColony.create({ data: { harvestId, colonyId } }); const extraction = await prisma.extraction.create({ data: { date: new Date(), userId: beekeeperId }, }); extractionId = extraction.id; await prisma.extractionHarvest.create({ data: { extractionId, harvestId }, }); const honeyBatch = await prisma.honeyBatch.create({ data: { batchNumber: `HC-${Date.now()}`, extractionId, amount: 25 }, }); honeyBatchId = honeyBatch.id; }); afterAll(async () => { if (bottlingBatchId) { await prisma.bottlingBatch.deleteMany({ where: { id: bottlingBatchId } }); } await prisma.honeyBatch.deleteMany({ where: { id: honeyBatchId } }); await prisma.extractionHarvest.deleteMany({ where: { extractionId } }); await prisma.extraction.deleteMany({ where: { id: extractionId } }); await prisma.harvestColony.deleteMany({ where: { harvestId } }); await prisma.harvest.deleteMany({ where: { id: harvestId } }); await prisma.colony.deleteMany({ where: { id: colonyId } }); await prisma.apiary.deleteMany({ where: { id: apiaryId } }); await prisma.refreshToken.deleteMany({ where: { userId: beekeeperId } }); await prisma.auditLog.deleteMany({ where: { userId: beekeeperId } }); await prisma.user.deleteMany({ where: { id: beekeeperId } }); await app.close(); }); it('rejects a bottling amount exceeding the available honey batch amount', () => { return request(app.getHttpServer()) .post('/api/v1/bottling-batches') .set('Authorization', `Bearer ${beekeeperToken}`) .send({ honeyBatchId, date: new Date().toISOString(), jarSize: 500, jarCount: 1000, bestBeforeDate: new Date().toISOString(), lotNumber: `L-${Date.now()}`, }) .expect(400); }); it('bottles a honey batch and reduces its remaining amount', async () => { const response = await request(app.getHttpServer()) .post('/api/v1/bottling-batches') .set('Authorization', `Bearer ${beekeeperToken}`) .send({ honeyBatchId, date: new Date().toISOString(), jarSize: 500, jarCount: 50, bestBeforeDate: new Date().toISOString(), lotNumber: `L-${Date.now()}`, }) .expect(201); const bottlingBatch = response.body as BottlingBatchBody; bottlingBatchId = bottlingBatch.id; expect(bottlingBatch.totalAmount).toBe(25); const honeyBatchResponse = await request(app.getHttpServer()) .get(`/api/v1/honey-batches/${honeyBatchId}`) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const honeyBatch = honeyBatchResponse.body as HoneyBatchBody; expect(honeyBatch.amount).toBe(0); expect(honeyBatch.status).toBe('BOTTLED'); }); });