Ein zweiter, unabhaengiger Audit-Durchlauf (frischer Blick, keine Vorkenntnis meiner eigenen Fixes) bestaetigte alle bisherigen Fixes als korrekt verdrahtet und fand 3 LOW-severity Robustheitsluecken - keine davon war ausnutzbar, aber alle sind jetzt behoben: - HarvestsService.create/ExtractionsService.create pruefen jetzt, dass referenzierte colonyIds/harvestIds tatsaechlich existieren, bevor die Transaktion laeuft - vorher fuehrte eine unbekannte Id zu einem ungefangenen 500 mit rohem Prisma-Fehler statt einer sauberen 400. - Etiketten-Bildelemente durchlaufen jetzt denselben Magic-Byte-Check wie Avatar-/Dokument-Uploads, statt pdfkits Bildparser ungepruefte Bytes zu uebergeben. - Webhook-Secret-Redaktion im Audit-Log nutzt jetzt explizites delete statt sich auf Prismas JSON-Serialisierung zu verlassen, die undefined-Werte verwirft (fragil, falls sich die Serialisierung mal aendert). Co-Authored-By: Claude Sonnet 5 <[email protected]>
116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
|
import { HarvestsService } from './harvests.service';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { AuditLogService } from '../audit/audit-log.service';
|
|
import { WebhooksService } from '../webhooks/webhooks.service';
|
|
|
|
describe('HarvestsService', () => {
|
|
let service: HarvestsService;
|
|
let prisma: {
|
|
harvest: { findUnique: jest.Mock; update: jest.Mock };
|
|
colony: { findMany: jest.Mock };
|
|
$transaction: jest.Mock;
|
|
};
|
|
let auditLog: { record: jest.Mock };
|
|
let tx: {
|
|
harvest: { create: jest.Mock };
|
|
harvestColony: { createMany: jest.Mock };
|
|
};
|
|
|
|
const harvest = { id: 'harvest-1', apiaryId: 'apiary-1', date: new Date() };
|
|
|
|
beforeEach(() => {
|
|
tx = {
|
|
harvest: { create: jest.fn().mockResolvedValue(harvest) },
|
|
harvestColony: { createMany: jest.fn() },
|
|
};
|
|
prisma = {
|
|
harvest: { findUnique: jest.fn(), update: jest.fn() },
|
|
colony: {
|
|
findMany: jest
|
|
.fn()
|
|
.mockResolvedValue([{ id: 'colony-1' }, { id: 'colony-2' }]),
|
|
},
|
|
$transaction: jest.fn((callback: (txArg: typeof tx) => unknown) =>
|
|
callback(tx),
|
|
),
|
|
};
|
|
auditLog = { record: jest.fn() };
|
|
const webhooks = { dispatch: jest.fn().mockResolvedValue(undefined) };
|
|
service = new HarvestsService(
|
|
prisma as unknown as PrismaService,
|
|
auditLog as unknown as AuditLogService,
|
|
webhooks as unknown as WebhooksService,
|
|
);
|
|
});
|
|
|
|
it('findById throws NotFoundException when missing', async () => {
|
|
prisma.harvest.findUnique.mockResolvedValue(null);
|
|
await expect(service.findById('missing')).rejects.toBeInstanceOf(
|
|
NotFoundException,
|
|
);
|
|
});
|
|
|
|
it('create links every colony to the harvest inside a transaction', async () => {
|
|
prisma.harvest.findUnique.mockResolvedValue(harvest);
|
|
|
|
await service.create(
|
|
{
|
|
apiaryId: 'apiary-1',
|
|
date: '2026-07-01',
|
|
colonyIds: ['colony-1', 'colony-2'],
|
|
},
|
|
'user-1',
|
|
);
|
|
|
|
const [[createArg]] = tx.harvestColony.createMany.mock.calls as [
|
|
[{ data: { harvestId: string; colonyId: string }[] }],
|
|
];
|
|
expect(createArg.data).toEqual([
|
|
{ harvestId: harvest.id, colonyId: 'colony-1' },
|
|
{ harvestId: harvest.id, colonyId: 'colony-2' },
|
|
]);
|
|
});
|
|
|
|
it('create rejects an unknown colony id instead of failing on the DB foreign-key constraint', async () => {
|
|
prisma.colony.findMany.mockResolvedValue([{ id: 'colony-1' }]);
|
|
|
|
await expect(
|
|
service.create(
|
|
{
|
|
apiaryId: 'apiary-1',
|
|
date: '2026-07-01',
|
|
colonyIds: ['colony-1', 'missing-colony'],
|
|
},
|
|
'user-1',
|
|
),
|
|
).rejects.toBeInstanceOf(BadRequestException);
|
|
expect(prisma.$transaction).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('update writes an audit log entry with before and after data', async () => {
|
|
const updated = { ...harvest, notes: 'geändert' };
|
|
prisma.harvest.findUnique
|
|
.mockResolvedValueOnce(harvest)
|
|
.mockResolvedValueOnce(updated);
|
|
prisma.harvest.update.mockResolvedValue(updated);
|
|
|
|
const result = await service.update(
|
|
'harvest-1',
|
|
{ notes: 'geändert' },
|
|
'user-1',
|
|
);
|
|
|
|
expect(result).toEqual(updated);
|
|
expect(auditLog.record).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
entityType: 'Harvest',
|
|
entityId: 'harvest-1',
|
|
action: 'UPDATE',
|
|
beforeData: harvest,
|
|
afterData: updated,
|
|
}),
|
|
);
|
|
});
|
|
});
|