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 ColonyStatsBody { byStatus: { status: string; count: number }[]; lossesByYear: Record; } interface QueenStatsBody { totalActive: number; evaluationAverages: { queenId: string; honeyYield: number }[]; } interface ApiaryStatsBody { apiaryId: string; totalHoneyKg: number; } interface VarroaStatsBody { measurements: { colonyId: string; miteCount: number }[]; } interface HoneyStatsBody { totalHarvestKg: number; yieldPerApiary: Record; } interface FeedingStatsBody { byColony: Record; } describe('Statistics (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const beekeeperEmail = `statistics-e2e-beekeeper-${Date.now()}@example.com`; const password = 'SuperSecret123!'; let beekeeperId: string; let beekeeperToken: string; let apiaryId: string; let colonyId: string; let queenId: string; let evaluationId: string; let varroaId: string; let treatmentId: string; let feedingId: string; let harvestId: 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: `Statistik-Stand ${Date.now()}` }, }); apiaryId = apiary.id; const colony = await prisma.colony.create({ data: { code: `V-STAT-${Date.now()}`, apiaryId, colonyType: 'PRODUCTION_COLONY', }, }); colonyId = colony.id; const queen = await prisma.queen.create({ data: { code: `K-STAT-${Date.now()}` }, }); queenId = queen.id; const evaluation = await prisma.queenEvaluation.create({ data: { queenId, colonyId, date: new Date('2026-05-01'), colonyStrength: 4, gentleness: 4, combBuild: 4, swarmingTendency: 2, broodPattern: 4, honeyYield: 5, userId: beekeeperId, }, }); evaluationId = evaluation.id; const varroa = await prisma.varroaMeasurement.create({ data: { colonyId, userId: beekeeperId, date: new Date('2026-06-01'), method: 'POWDERED_SUGAR', miteCount: 3, }, }); varroaId = varroa.id; const treatment = await prisma.treatment.create({ data: { colonyId, userId: beekeeperId, date: new Date('2026-06-05'), product: 'Ameisensaeure', }, }); treatmentId = treatment.id; const feeding = await prisma.feeding.create({ data: { colonyId, userId: beekeeperId, date: new Date('2026-09-01'), feedType: 'SUGAR_WATER', amount: 7, unit: 'l', }, }); feedingId = feeding.id; const harvest = await prisma.harvest.create({ data: { apiaryId, userId: beekeeperId, date: new Date('2026-07-01'), netWeight: 12, }, }); harvestId = harvest.id; }); afterAll(async () => { await prisma.harvest.deleteMany({ where: { id: harvestId } }); await prisma.feeding.deleteMany({ where: { id: feedingId } }); await prisma.treatment.deleteMany({ where: { id: treatmentId } }); await prisma.varroaMeasurement.deleteMany({ where: { id: varroaId } }); await prisma.queenEvaluation.deleteMany({ where: { id: evaluationId } }); await prisma.queen.deleteMany({ where: { id: queenId } }); 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 colony status breakdown', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/statistics/colonies') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as ColonyStatsBody; expect( body.byStatus.some((row) => row.status === 'ACTIVE' && row.count >= 1), ).toBe(true); }); it('returns queen evaluation averages including the fixture queen', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/statistics/queens') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as QueenStatsBody; const row = body.evaluationAverages.find( (entry) => entry.queenId === queenId, ); expect(row?.honeyYield).toBe(5); }); it('returns per-apiary honey totals including the fixture harvest', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/statistics/apiaries') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as ApiaryStatsBody[]; const row = body.find((entry) => entry.apiaryId === apiaryId); expect(row?.totalHoneyKg).toBe(12); }); it('returns varroa measurements including the fixture value', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/statistics/varroa') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as VarroaStatsBody; expect( body.measurements.some( (entry) => entry.colonyId === colonyId && entry.miteCount === 3, ), ).toBe(true); }); it('returns honey yield aggregates including the fixture apiary', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/statistics/honey') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as HoneyStatsBody; expect(body.yieldPerApiary[apiaryId]).toBe(12); }); it('returns feeding totals per colony including the fixture value', async () => { const response = await request(app.getHttpServer()) .get('/api/v1/statistics/feeding') .set('Authorization', `Bearer ${beekeeperToken}`) .expect(200); const body = response.body as FeedingStatsBody; expect(body.byColony[colonyId]).toBe(7); }); });