feat(backend): Koeniginnen-Modul (Modul 4)

QueensModule: CRUD + assign-colony + history, entsprechend Lastenheft
§4.5 (Koeniginnenverwaltung). assign-colony schliesst die offene
QueenColonyHistory der bisherigen Koenigin am Zielvolk (falls vorhanden)
sowie eine evtl. noch offene History dieser Koenigin an einem anderen
Volk, bevor Colony.currentQueenId gesetzt und ein neuer History-Eintrag
angelegt wird - damit bleibt jeder Koeniginnenwechsel nachvollziehbar,
wie gefordert. Rollen wie bei Apiaries/Colonies: Lesen offen, Schreiben
auf ADMIN/BEEKEEPER beschraenkt.

5 Unit-Tests + 2 e2e-Tests, Lint, Typecheck und Build gruen.
This commit is contained in:
Nick Szaunig committed 2026-08-21 18:26:55 +02:00
1 parent 6e328cdacb
commit b18d61640d
10 files changed
+486 -1

No files matched your search

+105
View File
@@ -0,0 +1,105 @@
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 QueenBody {
id: string;
code: string;
}
describe('Queens (e2e)', () => {
let app: INestApplication<App>;
let prisma: PrismaService;
const beekeeperEmail = `queens-e2e-beekeeper-${Date.now()}@example.com`;
const password = 'SuperSecret123!';
let beekeeperId: string;
let beekeeperToken: string;
let apiaryId: string;
let colonyId: string;
let queenId: 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: `Stand ${Date.now()}` },
});
apiaryId = apiary.id;
const colony = await prisma.colony.create({
data: {
code: `V-${Date.now()}`,
apiaryId,
colonyType: 'PRODUCTION_COLONY',
},
});
colonyId = colony.id;
});
afterAll(async () => {
await prisma.queenColonyHistory.deleteMany({ where: { colonyId } });
if (colonyId) {
await prisma.colony.update({
where: { id: colonyId },
data: { currentQueenId: null },
});
}
if (queenId) {
await prisma.queen.delete({ 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('lets a BEEKEEPER create a queen', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/queens')
.set('Authorization', `Bearer ${beekeeperToken}`)
.send({ code: `Q-${Date.now()}`, breed: 'Carnica' })
.expect(201);
queenId = (response.body as QueenBody).id;
expect(queenId).toEqual(expect.any(String));
});
it('assigns the queen to a colony and records history', async () => {
await request(app.getHttpServer())
.post(`/api/v1/queens/${queenId}/assign-colony`)
.set('Authorization', `Bearer ${beekeeperToken}`)
.send({ colonyId })
.expect(201);
const colony = await request(app.getHttpServer())
.get(`/api/v1/colonies/${colonyId}`)
.set('Authorization', `Bearer ${beekeeperToken}`)
.expect(200);
expect((colony.body as { currentQueenId: string }).currentQueenId).toBe(
queenId,
);
const history = await request(app.getHttpServer())
.get(`/api/v1/queens/${queenId}/history`)
.set('Authorization', `Bearer ${beekeeperToken}`)
.expect(200);
expect(history.body).toHaveLength(1);
});
});