Queen.hatchYear was already captured but never used anywhere - no signal existed for "this colony's queen is overdue for evaluation or replacement," despite that being one of the most common productivity and swarming risk factors in beekeeping. Adds an OLD_QUEEN dashboard alert alongside the existing overdue-task/swarm-check ones, flagging any active colony whose current queen has completed 3+ seasons. Co-Authored-By: Claude Sonnet 5 <[email protected]>
142 lines
4.2 KiB
TypeScript
142 lines
4.2 KiB
TypeScript
import { DashboardService } from './dashboard.service';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
describe('DashboardService', () => {
|
|
let service: DashboardService;
|
|
let prisma: {
|
|
apiary: { count: jest.Mock };
|
|
colony: { count: jest.Mock; findMany: jest.Mock };
|
|
queen: { count: jest.Mock };
|
|
task: { count: jest.Mock; findMany: jest.Mock };
|
|
honeyBatch: { count: jest.Mock };
|
|
stockItem: { findMany: jest.Mock };
|
|
swarmControlCheck: { findMany: jest.Mock };
|
|
};
|
|
|
|
beforeEach(() => {
|
|
prisma = {
|
|
apiary: { count: jest.fn().mockResolvedValue(2) },
|
|
colony: {
|
|
count: jest.fn().mockResolvedValue(5),
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
},
|
|
queen: { count: jest.fn().mockResolvedValue(4) },
|
|
task: {
|
|
count: jest.fn().mockResolvedValue(3),
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
},
|
|
honeyBatch: { count: jest.fn().mockResolvedValue(1) },
|
|
stockItem: { findMany: jest.fn().mockResolvedValue([]) },
|
|
swarmControlCheck: { findMany: jest.fn().mockResolvedValue([]) },
|
|
};
|
|
service = new DashboardService(prisma as unknown as PrismaService);
|
|
});
|
|
|
|
it('summary counts low-stock items below their minimum threshold', async () => {
|
|
prisma.stockItem.findMany.mockResolvedValue([
|
|
{ id: 'item-1', quantity: 2, minThreshold: 5 },
|
|
{ id: 'item-2', quantity: 10, minThreshold: 5 },
|
|
]);
|
|
|
|
const summary = await service.summary();
|
|
|
|
expect(summary.stockItemsLowCount).toBe(1);
|
|
expect(summary.apiariesActive).toBe(2);
|
|
expect(summary.coloniesActive).toBe(5);
|
|
});
|
|
|
|
it('alerts flags overdue tasks, attention colonies, and low stock', async () => {
|
|
prisma.task.findMany.mockResolvedValue([
|
|
{ id: 'task-1', title: 'Beute prüfen' },
|
|
]);
|
|
prisma.colony.findMany.mockResolvedValue([
|
|
{ id: 'colony-1', code: 'V-001', status: 'WEAK' },
|
|
]);
|
|
prisma.stockItem.findMany.mockResolvedValue([
|
|
{ id: 'item-1', name: 'Zuckerlösung', quantity: 2, minThreshold: 5 },
|
|
]);
|
|
|
|
const alerts = await service.alerts();
|
|
|
|
expect(alerts).toHaveLength(3);
|
|
expect(alerts.map((alert) => alert.type)).toEqual([
|
|
'OVERDUE_TASK',
|
|
'COLONY_ATTENTION',
|
|
'LOW_STOCK',
|
|
]);
|
|
});
|
|
|
|
it('flags a colony whose most recent swarm check is overdue', async () => {
|
|
prisma.swarmControlCheck.findMany.mockResolvedValue([
|
|
{
|
|
colonyId: 'colony-1',
|
|
nextCheckDate: new Date('2020-01-01'),
|
|
colony: { code: 'V-001' },
|
|
},
|
|
]);
|
|
|
|
const alerts = await service.alerts();
|
|
|
|
expect(alerts).toEqual([
|
|
{
|
|
type: 'OVERDUE_SWARM_CHECK',
|
|
message: 'Schwarmkontrolle für Volk V-001 überfällig',
|
|
relatedType: 'Colony',
|
|
relatedId: 'colony-1',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('ignores an overdue swarm check when a newer check already superseded it', async () => {
|
|
prisma.swarmControlCheck.findMany.mockResolvedValue([
|
|
{
|
|
colonyId: 'colony-1',
|
|
date: new Date('2026-06-10'),
|
|
nextCheckDate: null,
|
|
colony: { code: 'V-001' },
|
|
},
|
|
{
|
|
colonyId: 'colony-1',
|
|
date: new Date('2026-06-01'),
|
|
nextCheckDate: new Date('2020-01-01'),
|
|
colony: { code: 'V-001' },
|
|
},
|
|
]);
|
|
|
|
const alerts = await service.alerts();
|
|
|
|
expect(alerts).toHaveLength(0);
|
|
});
|
|
|
|
describe('OLD_QUEEN', () => {
|
|
it('flags a colony whose queen is at least 3 years old', async () => {
|
|
prisma.colony.findMany
|
|
.mockResolvedValueOnce([]) // attentionColonies
|
|
.mockResolvedValueOnce([
|
|
{ id: 'colony-1', code: 'V-001', currentQueen: { hatchYear: 2020 } },
|
|
]);
|
|
|
|
const alerts = await service.alerts();
|
|
|
|
expect(alerts).toHaveLength(1);
|
|
expect(alerts[0].type).toBe('OLD_QUEEN');
|
|
expect(alerts[0].relatedId).toBe('colony-1');
|
|
});
|
|
|
|
it('does not flag a queen younger than the threshold', async () => {
|
|
const currentYear = new Date().getFullYear();
|
|
prisma.colony.findMany.mockResolvedValueOnce([]).mockResolvedValueOnce([
|
|
{
|
|
id: 'colony-1',
|
|
code: 'V-001',
|
|
currentQueen: { hatchYear: currentYear },
|
|
},
|
|
]);
|
|
|
|
const alerts = await service.alerts();
|
|
|
|
expect(alerts).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|