Bisher liessen sich diese acht Objekttypen nur anlegen, nie nachträglich korrigieren – Aufgaben hatten zwar schon einen PATCH-Endpunkt, aber keine Bearbeiten-UI, die übrigen sieben hatten weder Backend- noch Frontend-Unterstützung. Jetzt bekommt jeder Typ einen PATCH :id Endpunkt (UpdateXDto per PartialType(PickType(...)) nach demselben Muster wie UpdateColonyDto/UpdateApiaryDto, mit Audit-Log-Eintrag inkl. Vorher-/Nachher-Stand) sowie eine Bearbeiten-UI: - Aufgaben: Dialog auf der Aufgabenliste - Durchsichten/Behandlungen/Fütterungen/Varroa-Messungen: Dialog pro Listeneintrag auf der Volk-Detailseite - Ernten: Dialog auf der eigenen Ernte-Detailseite - Schleuderungen: Dialog direkt in der Rückverfolgbarkeits-Karte der Honigcharge (keine eigene Detailseite nötig); Erntezeilen dort verlinken jetzt zur Ernte-Detailseite statt sie zu duplizieren - Abfüllungen: Dialog auf der Abfüllungs-Detailseite, bewusst nur Datum/MHD/Losnummer editierbar – Glasgröße/-anzahl bleiben unveränderlich, weil sie bereits unumkehrbar die Honigcharge-Menge reduziert haben Relationsstiftende Fremdschlüssel (colonyId, apiaryId/colonyIds, harvestIds, honeyBatchId) bleiben überall bewusst nicht editierbar, konsistent mit dem bestehenden apiaryId-Ausschluss bei UpdateColonyDto.
121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
import { NotFoundException } from '@nestjs/common';
|
|
import { InspectionsService } from './inspections.service';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { WebhooksService } from '../webhooks/webhooks.service';
|
|
import { AutomationService } from '../automation/automation.service';
|
|
import { AuditLogService } from '../audit/audit-log.service';
|
|
|
|
describe('InspectionsService', () => {
|
|
let service: InspectionsService;
|
|
let prisma: {
|
|
inspection: {
|
|
findMany: jest.Mock;
|
|
findUnique: jest.Mock;
|
|
create: jest.Mock;
|
|
update: jest.Mock;
|
|
};
|
|
task: { create: jest.Mock };
|
|
};
|
|
let auditLog: { record: jest.Mock };
|
|
|
|
const inspection = {
|
|
id: 'inspection-1',
|
|
colonyId: 'colony-1',
|
|
date: new Date(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
prisma = {
|
|
inspection: {
|
|
findMany: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
},
|
|
task: { create: jest.fn() },
|
|
};
|
|
const webhooks = { dispatch: jest.fn().mockResolvedValue(undefined) };
|
|
const automation = {
|
|
evaluateInspection: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
auditLog = { record: jest.fn().mockResolvedValue(undefined) };
|
|
service = new InspectionsService(
|
|
prisma as unknown as PrismaService,
|
|
webhooks as unknown as WebhooksService,
|
|
automation as unknown as AutomationService,
|
|
auditLog as unknown as AuditLogService,
|
|
);
|
|
});
|
|
|
|
it('findById throws NotFoundException when missing', async () => {
|
|
prisma.inspection.findUnique.mockResolvedValue(null);
|
|
await expect(service.findById('missing')).rejects.toBeInstanceOf(
|
|
NotFoundException,
|
|
);
|
|
});
|
|
|
|
it('create stores the acting user as the inspector', async () => {
|
|
prisma.inspection.create.mockResolvedValue(inspection);
|
|
|
|
await service.create(
|
|
{ colonyId: 'colony-1', date: '2026-01-01' },
|
|
'user-1',
|
|
);
|
|
|
|
const [[createArg]] = prisma.inspection.create.mock.calls as [
|
|
[{ data: { colonyId: string; userId: string } }],
|
|
];
|
|
expect(createArg.data.colonyId).toBe('colony-1');
|
|
expect(createArg.data.userId).toBe('user-1');
|
|
});
|
|
|
|
it('update writes an audit log entry with before and after data', async () => {
|
|
prisma.inspection.findUnique.mockResolvedValue(inspection);
|
|
const updated = { ...inspection, notes: 'geändert' };
|
|
prisma.inspection.update.mockResolvedValue(updated);
|
|
|
|
const result = await service.update(
|
|
'inspection-1',
|
|
{ notes: 'geändert' },
|
|
'user-1',
|
|
);
|
|
|
|
expect(result).toBe(updated);
|
|
expect(auditLog.record).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
entityType: 'Inspection',
|
|
entityId: 'inspection-1',
|
|
action: 'UPDATE',
|
|
beforeData: inspection,
|
|
afterData: updated,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('createTaskFromInspection links the task to the inspection and its colony', async () => {
|
|
prisma.inspection.findUnique.mockResolvedValue(inspection);
|
|
prisma.task.create.mockResolvedValue({ id: 'task-1' });
|
|
|
|
await service.createTaskFromInspection(
|
|
'inspection-1',
|
|
{ title: 'Nachschauen' },
|
|
'user-1',
|
|
);
|
|
|
|
const [[taskArg]] = prisma.task.create.mock.calls as [
|
|
[
|
|
{
|
|
data: {
|
|
sourceInspectionId: string;
|
|
relatedType: string;
|
|
relatedId: string;
|
|
};
|
|
},
|
|
],
|
|
];
|
|
expect(taskArg.data.sourceInspectionId).toBe('inspection-1');
|
|
expect(taskArg.data.relatedType).toBe('COLONY');
|
|
expect(taskArg.data.relatedId).toBe('colony-1');
|
|
});
|
|
});
|