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]>
104 lines
3.0 KiB
TypeScript
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 }]);
|
|
});
|
|
});
|