feat(backend): Statistik- und Reporting-Endpunkte (Modul 27)
Aggregations-Endpunkte fuer Voelker, Koeniginnen, Bienenstaende, Varroa, Honig und Fuetterung nach Lastenheft Abschnitt 4.26.
This commit is contained in:
1 parent
8080a7f5ec
commit
e8177432b0
6 files changed
+706
No files matched your search
@@ -0,0 +1,219 @@
|
||||
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<string, number>;
|
||||
}
|
||||
|
||||
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<string, number>;
|
||||
}
|
||||
|
||||
interface FeedingStatsBody {
|
||||
byColony: Record<string, number>;
|
||||
}
|
||||
|
||||
describe('Statistics (e2e)', () => {
|
||||
let app: INestApplication<App>;
|
||||
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: '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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user