fix(backend): Status-Guard fuer Move/Split/Merge auf inaktiven Voelkern
Beim Live-Test im Browser gefunden: move/split/merge liessen sich auch auf einem bereits DISSOLVED/MERGED-Volk aufrufen und wuerden dort klaglos durchlaufen - ein Datenintegritaetsproblem, kein reines UI-Detail. assertActive() wirft jetzt ConflictException (409), wenn das betroffene Volk (bzw. bei merge: Ziel- und alle zu vereinigenden Voelker) nicht ACTIVE ist. Neuer Unit-Test + neuer e2e-Test (dissolve, danach move -> 409).
This commit is contained in:
1 parent
d3f321f1b5
commit
a71b31df38
3 files changed
+34
-2
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { NotFoundException } from '@nestjs/common';
|
||||
import { ConflictException, NotFoundException } from '@nestjs/common';
|
||||
import { ColoniesService } from './colonies.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditLogService } from '../audit/audit-log.service';
|
||||
@@ -143,6 +143,16 @@ describe('ColoniesService', () => {
|
||||
expect(lineageArg.data.toColonyId).toBe('colony-1');
|
||||
});
|
||||
|
||||
it('move rejects a non-active colony', async () => {
|
||||
prisma.colony.findUnique.mockResolvedValue({
|
||||
...parentColony,
|
||||
status: 'DISSOLVED',
|
||||
});
|
||||
await expect(
|
||||
service.move('colony-1', { apiaryId: 'apiary-2' }, 'user-1'),
|
||||
).rejects.toBeInstanceOf(ConflictException);
|
||||
});
|
||||
|
||||
it('dissolve sets status DISSOLVED and records DEACTIVATE', async () => {
|
||||
prisma.colony.findUnique.mockResolvedValue(parentColony);
|
||||
prisma.colony.update.mockResolvedValue({
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditLogService } from '../audit/audit-log.service';
|
||||
import { Colony } from '../../generated/prisma';
|
||||
@@ -27,6 +31,14 @@ export class ColoniesService {
|
||||
return colony;
|
||||
}
|
||||
|
||||
private assertActive(colony: Colony): void {
|
||||
if (colony.status !== 'ACTIVE') {
|
||||
throw new ConflictException(
|
||||
`Colony ${colony.id} is not active (status: ${colony.status})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async create(dto: CreateColonyDto, actingUserId: string): Promise<Colony> {
|
||||
const colony = await this.prisma.colony.create({ data: { ...dto } });
|
||||
await this.auditLog.record({
|
||||
@@ -66,6 +78,7 @@ export class ColoniesService {
|
||||
actingUserId: string,
|
||||
): Promise<Colony> {
|
||||
const before = await this.findById(id);
|
||||
this.assertActive(before);
|
||||
const colony = await this.prisma.$transaction(async (tx) => {
|
||||
const updated = await tx.colony.update({
|
||||
where: { id },
|
||||
@@ -98,6 +111,7 @@ export class ColoniesService {
|
||||
actingUserId: string,
|
||||
): Promise<Colony> {
|
||||
const parent = await this.findById(id);
|
||||
this.assertActive(parent);
|
||||
const child = await this.prisma.$transaction(async (tx) => {
|
||||
const created = await tx.colony.create({
|
||||
data: {
|
||||
@@ -134,6 +148,7 @@ export class ColoniesService {
|
||||
actingUserId: string,
|
||||
): Promise<Colony> {
|
||||
const survivor = await this.findById(id);
|
||||
this.assertActive(survivor);
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
for (const mergedColonyId of dto.mergedColonyIds) {
|
||||
const merged = await tx.colony.findUnique({
|
||||
@@ -142,6 +157,7 @@ export class ColoniesService {
|
||||
if (!merged) {
|
||||
throw new NotFoundException(`Colony ${mergedColonyId} not found`);
|
||||
}
|
||||
this.assertActive(merged);
|
||||
await tx.colony.update({
|
||||
where: { id: mergedColonyId },
|
||||
data: { status: 'MERGED', deactivatedAt: new Date() },
|
||||
|
||||
@@ -188,5 +188,11 @@ describe('Colonies (e2e)', () => {
|
||||
.get(`/api/v1/colonies/${colonyId}`)
|
||||
.set('Authorization', `Bearer ${staffToken}`)
|
||||
.expect(200);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post(`/api/v1/colonies/${colonyId}/move`)
|
||||
.set('Authorization', `Bearer ${beekeeperToken}`)
|
||||
.send({ apiaryId: apiaryAId })
|
||||
.expect(409);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user