- Rule configs with typed parsers and registry; Berlin day helpers - Schema: T4 enums, Suggestion table, feed vocabulary (feedStatus as ColonyCondition with legacy text mapping), split queen source, varroa sample size; migration harness for data migrations - SuggestionsService: propose/refresh/resolve/obsolete/expire, accept/dismiss/reopen incl. bulk, lock order, concurrency e2e - SeasonService: phenology-based segments, inspection/varroa due dates, queen age; TimeEventsStep with JobPartialFailure - Frontend: per-rule settings forms with visible value sources Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
import { Logger } from '@nestjs/common';
|
|
import { JobPartialFailure } from '../system/job-run.service';
|
|
import { DailyEvaluationJob } from './daily-evaluation.job';
|
|
import type { TimeEventsStep } from './steps/time-events.step';
|
|
|
|
describe('DailyEvaluationJob', () => {
|
|
const COUNTS = {
|
|
WithdrawalEnded: 1,
|
|
TaskDue: 1,
|
|
QueenAged: 0,
|
|
VarroaMeasurementDue: 0,
|
|
InspectionOverdue: 0,
|
|
};
|
|
let timeEvents: { name: 'time-events'; run: jest.Mock };
|
|
let job: DailyEvaluationJob;
|
|
|
|
beforeEach(() => {
|
|
jest.useFakeTimers({ now: new Date('2026-06-10T02:30:00.000Z') });
|
|
timeEvents = { name: 'time-events', run: jest.fn() };
|
|
timeEvents.run.mockResolvedValue(COUNTS);
|
|
job = new DailyEvaluationJob(timeEvents as unknown as TimeEventsStep);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('is the daily-evaluation job', () => {
|
|
expect(job.name).toBe('daily-evaluation');
|
|
});
|
|
|
|
it('runs the steps for the Berlin day of the run and reports their counters', async () => {
|
|
expect(await job.run()).toEqual(COUNTS);
|
|
expect(timeEvents.run).toHaveBeenCalledWith('2026-06-10');
|
|
});
|
|
|
|
it('takes the Berlin day, not the UTC day, around midnight', async () => {
|
|
jest.setSystemTime(new Date('2026-06-09T22:30:00.000Z'));
|
|
|
|
await job.run();
|
|
|
|
expect(timeEvents.run).toHaveBeenCalledWith('2026-06-10');
|
|
});
|
|
|
|
it('a failing step fails the run with the counters of all steps', async () => {
|
|
jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
|
|
timeEvents.run.mockRejectedValue(
|
|
new JobPartialFailure({ WithdrawalEnded: 1 }, ['TaskDue']),
|
|
);
|
|
|
|
const failure = await job.run().then(
|
|
() => null,
|
|
(error: unknown) => error,
|
|
);
|
|
|
|
expect(failure).toBeInstanceOf(JobPartialFailure);
|
|
expect(failure).toMatchObject({
|
|
failedSteps: ['time-events'],
|
|
result: { WithdrawalEnded: 1 },
|
|
message: 'Daily evaluation failed for: time-events: TaskDue',
|
|
});
|
|
});
|
|
});
|