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 ImportReportBody { created: number; } describe('Colony strength in occupied frames (e2e)', () => { let app: INestApplication; let prisma: PrismaService; const suffix = `${Date.now()}`; const password = 'SuperSecret123!'; let adminToken: string; let adminId: string; let apiaryId: string; let apiaryName: string; let colonyId: string; const auth = (token: string) => ({ Authorization: `Bearer ${token}` }); beforeAll(async () => { app = await createTestApp(); prisma = app.get(PrismaService); const admin = await prisma.user.create({ data: { email: `strength-admin-${suffix}@example.com`, passwordHash: await bcrypt.hash(password, 12), name: 'Admin', role: { connect: { key: 'ADMIN' } }, }, }); adminId = admin.id; adminToken = (await login(app, admin.email, password)).accessToken; apiaryName = `Strength ${suffix}`; apiaryId = (await prisma.apiary.create({ data: { name: apiaryName } })).id; colonyId = ( await prisma.colony.create({ data: { code: `ST-${suffix}`, apiaryId, colonyType: 'PRODUCTION_COLONY', strength: 7, }, }) ).id; }); afterAll(async () => { const colonies = await prisma.colony.findMany({ where: { apiaryId }, select: { id: true }, }); const ids = colonies.map((row) => row.id); const inspections = await prisma.inspection.findMany({ where: { colonyId: { in: ids } }, select: { id: true }, }); const inspectionIds = inspections.map((row) => row.id); await prisma.domainEvent.deleteMany({ where: { OR: [ { aggregateType: 'Inspection', aggregateId: { in: inspectionIds } }, { aggregateType: 'Colony', aggregateId: { in: ids } }, ], }, }); await prisma.calendarSyncOutboxEntry.deleteMany({ where: { sourceType: 'INSPECTION', sourceId: { in: inspectionIds } }, }); await prisma.inspection.deleteMany({ where: { colonyId: { in: ids } } }); await prisma.colony.deleteMany({ where: { id: { in: ids } } }); await prisma.apiary.deleteMany({ where: { id: apiaryId } }); await prisma.refreshToken.deleteMany({ where: { userId: adminId } }); await prisma.auditLog.deleteMany({ where: { userId: adminId } }); await prisma.user.deleteMany({ where: { id: adminId } }); await app.close(); }); it('PATCH occupiedFrames keeps the legacy strength', async () => { await request(app.getHttpServer()) .patch(`/api/v1/colonies/${colonyId}`) .set(auth(adminToken)) .send({ occupiedFrames: 16 }) .expect(200); expect( await prisma.colony.findUniqueOrThrow({ where: { id: colonyId } }), ).toMatchObject({ occupiedFrames: 16, strength: 7 }); await request(app.getHttpServer()) .patch(`/api/v1/colonies/${colonyId}`) .set(auth(adminToken)) .send({ occupiedFrames: null }) .expect(200); expect( (await prisma.colony.findUniqueOrThrow({ where: { id: colonyId } })) .occupiedFrames, ).toBeNull(); await request(app.getHttpServer()) .patch(`/api/v1/colonies/${colonyId}`) .set(auth(adminToken)) .send({ occupiedFrames: 41 }) .expect(400); }); it('a takeover from an inspection writes only occupiedFrames', async () => { await request(app.getHttpServer()) .post('/api/v1/inspections') .set(auth(adminToken)) .send({ colonyId, date: '2026-06-01T10:00:00.000Z', colonyStrength: 12, applyToColony: ['occupiedFrames'], }) .expect(201); expect( await prisma.colony.findUniqueOrThrow({ where: { id: colonyId } }), ).toMatchObject({ occupiedFrames: 12, strength: 7, broodStatus: null, feedStatus: null, }); }); it('colonyStrength 41 answers 400 "Höchstens 40 besetzte Waben."', async () => { const response = await request(app.getHttpServer()) .post('/api/v1/inspections') .set(auth(adminToken)) .send({ colonyId, date: '2026-06-02T10:00:00.000Z', colonyStrength: 41 }) .expect(400); expect(JSON.stringify(response.body)).toContain( 'Höchstens 40 besetzte Waben.', ); }); it('CSV export and import carry strength and occupiedFrames', async () => { const exported = await request(app.getHttpServer()) .get('/api/v1/export/colonies.csv') .set(auth(adminToken)) .expect(200); const lines = exported.text.split('\n'); expect(lines[0].trim().endsWith(',occupiedFrames')).toBe(true); expect(lines[0]).toContain('strength,healthStatus'); const row = lines.find((line) => line.includes(`ST-${suffix}`)) ?? ''; expect(row).toContain(',7,'); expect(row.trim().endsWith(',12')).toBe(true); const code = `ST-IMP-${suffix}`; const csv = `code,apiaryName,colonyType,strength,occupiedFrames\n${code},${apiaryName},PRODUCTION_COLONY,5,20`; const response = await request(app.getHttpServer()) .post('/api/v1/import/colonies') .set(auth(adminToken)) .attach('file', Buffer.from(csv), { filename: 'colonies.csv', contentType: 'text/csv', }) .expect(201); expect((response.body as ImportReportBody).created).toBe(1); expect( await prisma.colony.findUniqueOrThrow({ where: { code } }), ).toMatchObject({ strength: 5, occupiedFrames: 20 }); }); });