Files
ApiCore/ApiCore-Backend/src/feedings/feedings.service.spec.ts
T
Nick SzaunigandClaude Opus 5.5 5cf4e87ca3 feat(automation): suggest follow-up checks after treatments and feedings
Two new rules, "Kontrolle nach Behandlung" and "Kontrolle nach
Fütterung", create a check task when a treatment or feeding is recorded.
The due date counts from the measure itself (end of treatment, else its
date; feeding date) plus the configured number of days, and the task
names the product and date. At most one open check per colony and rule.

Both rules ship switched off and without an interval: the right timing
depends on the product (package leaflet) and season, so the beekeeper
sets it. Every task-creating rule can now carry a "due after N days"
setting in the automation page.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 06:23:13 +02:00

104 lines
3.0 KiB
TypeScript

import { NotFoundException } from '@nestjs/common';
import { AutomationService } from '../automation/automation.service';
import { FeedingsService } from './feedings.service';
import { PrismaService } from '../prisma/prisma.service';
import { AuditLogService } from '../audit/audit-log.service';
import { CalendarSyncService } from '../calendar-sync/calendar-sync.service';
describe('FeedingsService', () => {
let service: FeedingsService;
let prisma: {
feeding: {
findMany: jest.Mock;
findUnique: jest.Mock;
create: jest.Mock;
update: jest.Mock;
groupBy: jest.Mock;
};
};
let auditLog: { record: jest.Mock };
const feeding = { id: 'feeding-1', colonyId: 'colony-1', date: new Date() };
beforeEach(() => {
prisma = {
feeding: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
groupBy: jest.fn(),
},
};
auditLog = { record: jest.fn() };
const calendarSync = { enqueue: jest.fn(), cancel: jest.fn() };
service = new FeedingsService(
prisma as unknown as PrismaService,
auditLog as unknown as AuditLogService,
calendarSync as unknown as CalendarSyncService,
{ evaluateFeeding: jest.fn() } as unknown as AutomationService,
);
});
it('findById throws NotFoundException when missing', async () => {
prisma.feeding.findUnique.mockResolvedValue(null);
await expect(service.findById('missing')).rejects.toBeInstanceOf(
NotFoundException,
);
});
it('create stores the acting user as the feeder', async () => {
prisma.feeding.create.mockResolvedValue(feeding);
await service.create(
{
colonyId: 'colony-1',
date: '2026-01-01',
feedType: 'SUGAR_WATER',
amount: 2,
unit: 'l',
},
'user-1',
);
const [[createArg]] = prisma.feeding.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.feeding.findUnique.mockResolvedValue(feeding);
const updated = { ...feeding, notes: 'geändert' };
prisma.feeding.update.mockResolvedValue(updated);
const result = await service.update(
'feeding-1',
{ notes: 'geändert' },
'user-1',
);
expect(result).toBe(updated);
expect(auditLog.record).toHaveBeenCalledWith(
expect.objectContaining({
entityType: 'Feeding',
entityId: 'feeding-1',
action: 'UPDATE',
beforeData: feeding,
afterData: updated,
}),
);
});
it('summaryByColony sums amounts per unit', async () => {
prisma.feeding.groupBy.mockResolvedValue([
{ unit: 'l', _sum: { amount: 5.5 } },
]);
const summary = await service.summaryByColony('colony-1');
expect(summary).toEqual([{ unit: 'l', totalAmount: 5.5 }]);
});
});