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'; import { CalendarSyncService } from '../calendar-sync/calendar-sync.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; update: jest.Mock }; harvestColony: { createMany: jest.Mock; deleteMany: jest.Mock }; }; const harvest = { id: 'harvest-1', apiaryId: 'apiary-1', date: new Date() }; beforeEach(() => { tx = { harvest: { create: jest.fn().mockResolvedValue(harvest), update: jest.fn(), }, harvestColony: { createMany: jest.fn(), deleteMany: 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) }; const calendarSync = { enqueue: jest.fn(), cancel: jest.fn() }; service = new HarvestsService( prisma as unknown as PrismaService, auditLog as unknown as AuditLogService, webhooks as unknown as WebhooksService, calendarSync as unknown as CalendarSyncService, ); }); 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); const result = await service.update( 'harvest-1', { notes: 'geändert' }, 'user-1', ); expect(result).toEqual(updated); expect(tx.harvestColony.deleteMany).not.toHaveBeenCalled(); expect(auditLog.record).toHaveBeenCalledWith( expect.objectContaining({ entityType: 'Harvest', entityId: 'harvest-1', action: 'UPDATE', beforeData: harvest, afterData: updated, }), ); }); it('update replaces the whole colony set when colonyIds is given', async () => { prisma.harvest.findUnique.mockResolvedValue(harvest); await service.update( 'harvest-1', { colonyIds: ['colony-1', 'colony-2'] }, 'user-1', ); expect(tx.harvestColony.deleteMany).toHaveBeenCalledWith({ where: { harvestId: 'harvest-1' }, }); const [[createArg]] = tx.harvestColony.createMany.mock.calls as [ [{ data: { harvestId: string; colonyId: string }[] }], ]; expect(createArg.data).toEqual([ { harvestId: 'harvest-1', colonyId: 'colony-1' }, { harvestId: 'harvest-1', colonyId: 'colony-2' }, ]); }); it('update rejects an unknown colony id before touching the database', async () => { prisma.harvest.findUnique.mockResolvedValue(harvest); prisma.colony.findMany.mockResolvedValue([{ id: 'colony-1' }]); await expect( service.update( 'harvest-1', { colonyIds: ['colony-1', 'missing-colony'] }, 'user-1', ), ).rejects.toBeInstanceOf(BadRequestException); expect(prisma.$transaction).not.toHaveBeenCalled(); }); });