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 PublicBatchBody { lotNumber: string; productName: string | null; honeyType: string | null; waterContent: number | null; apiaries: { name: string; location: string | null }[]; harvestDates: string[]; } describe('Public batch origin page (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const beekeeperEmail = `public-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 productId: string; let bottlingBatchId: string; const lotNumber = `WH-E2E-${Date.now()}`; const apiaryName = `Stand Wald ${Date.now()}`; 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: { connect: { key: 'BEEKEEPER' } }, }, }); beekeeperId = beekeeper.id; beekeeperToken = (await login(app, beekeeperEmail, password)).accessToken; const apiary = await prisma.apiary.create({ data: { name: apiaryName, location: 'Wald' }, }); 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('2026-08-12'), userId: beekeeperId }, }); harvestId = harvest.id; await prisma.harvestColony.create({ data: { harvestId, colonyId } }); const extraction = await prisma.extraction.create({ data: { date: new Date('2026-08-13'), 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, honeyType: 'Sommerhonig', waterContent: 16.8, }, }); honeyBatchId = honeyBatch.id; const product = await prisma.product.create({ data: { name: 'Sommerhonig 500g' }, }); productId = product.id; const bottlingBatch = await prisma.bottlingBatch.create({ data: { honeyBatchId, productId, date: new Date('2026-08-20'), jarSize: 500, jarCount: 10, totalAmount: 5, bestBeforeDate: new Date('2028-08-20'), lotNumber, userId: beekeeperId, }, }); bottlingBatchId = bottlingBatch.id; }); afterAll(async () => { await prisma.bottlingBatch.deleteMany({ where: { id: bottlingBatchId } }); await prisma.product.deleteMany({ where: { id: productId } }); 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('returns public-safe origin data for a lot without any authentication', async () => { const response = await request(app.getHttpServer()) .get(`/api/v1/public/batches/${lotNumber}`) .expect(200); const body = response.body as PublicBatchBody; expect(body.lotNumber).toBe(lotNumber); expect(body.productName).toBe('Sommerhonig 500g'); expect(body.honeyType).toBe('Sommerhonig'); expect(body.waterContent).toBe(16.8); expect(body.apiaries).toEqual([{ name: apiaryName, location: 'Wald' }]); expect(body.harvestDates).toHaveLength(1); }); it('returns 404 for an unknown lot', () => { return request(app.getHttpServer()) .get('/api/v1/public/batches/does-not-exist') .expect(404); }); it('generates a QR code for the bottling batch that deep-links to the public page', async () => { const response = await request(app.getHttpServer()) .get(`/api/v1/qr/bottling-batches/${bottlingBatchId}.svg`) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('image/svg+xml'); }); });