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'; describe('Export (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const beekeeperEmail = `export-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; 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: `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: 20 }, }); honeyBatchId = honeyBatch.id; }); afterAll(async () => { 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('exports colonies as CSV', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/export/colonies.csv') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('text/csv'); expect(response.text.split('\n')[0]).toContain('code'); expect(response.text).toContain(colonyId); }); it('exports honey batches as CSV', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/export/honey-batches.csv') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.text).toContain(honeyBatchId); }); it('exports colonies as JSON', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/export/colonies.json') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as { id: string }[]; expect(body.some((colony) => colony.id === colonyId)).toBe(true); }); it('rejects an unsupported JSON entity with 400', () => { return request(app.getHttpServer()) .get('/api/v1/export/bogus.json') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(400); }); it('exports the honey batch traceability report as PDF', async () => { const response = await request(app.getHttpServer()) .get( `/api/v1/export/honey-batch-traceability.pdf?honeyBatchId=${honeyBatchId}`, ) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('application/pdf'); const pdfBody = response.body as Buffer; expect(pdfBody.subarray(0, 4).toString('latin1')).toBe('%PDF'); }); it('rejects the traceability report without honeyBatchId with 400', () => { return request(app.getHttpServer()) .get('/api/v1/export/honey-batch-traceability.pdf') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(400); }); it('rejects the colony annual report without a colonyId', () => { return request(app.getHttpServer()) .get('/api/v1/export/colony-annual-report.pdf') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(400); }); it('rejects the colony annual report for an unknown colony', () => { return request(app.getHttpServer()) .get('/api/v1/export/colony-annual-report.pdf?colonyId=does-not-exist') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(404); }); it('renders a colony annual report PDF', async () => { const response = await request(app.getHttpServer()) .get(`/api/v1/export/colony-annual-report.pdf?colonyId=${colonyId}`) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('application/pdf'); expect((response.body as Buffer).subarray(0, 4).toString('latin1')).toBe( '%PDF', ); }); it('renders the record card (Stockkarte) for the whole history and for one year', async () => { for (const query of [ `colonyId=${colonyId}`, `colonyId=${colonyId}&year=2026`, ]) { const response = await request(app.getHttpServer()) .get(`/api/v1/export/colony-record-card.pdf?${query}`) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('application/pdf'); expect((response.body as Buffer).subarray(0, 4).toString('latin1')).toBe( '%PDF', ); } }); it('rejects the record card without a colonyId and for an unknown colony', async () => { await request(app.getHttpServer()) .get('/api/v1/export/colony-record-card.pdf') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(400); await request(app.getHttpServer()) .get('/api/v1/export/colony-record-card.pdf?colonyId=does-not-exist') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(404); }); it('renders the Bestandsbuch for a year, also when nothing was treated', async () => { for (const query of ['year=2026', 'year=1999']) { const response = await request(app.getHttpServer()) .get(`/api/v1/export/treatment-log.pdf?${query}`) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('application/pdf'); expect((response.body as Buffer).subarray(0, 4).toString('latin1')).toBe( '%PDF', ); } }); it('rejects the apiary annual report without an apiaryId', () => { return request(app.getHttpServer()) .get('/api/v1/export/apiary-annual-report.pdf') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(400); }); it('renders an apiary annual report PDF', async () => { const response = await request(app.getHttpServer()) .get(`/api/v1/export/apiary-annual-report.pdf?apiaryId=${apiaryId}`) .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); expect(response.headers['content-type']).toContain('application/pdf'); expect((response.body as Buffer).subarray(0, 4).toString('latin1')).toBe( '%PDF', ); }); });