This commit is contained in:
nessar committed 2026-10-07 00:28:29 +02:00
1 parent 03841c6e3d
commit 036a8c0e1e
32 files changed
+1805 -89

No files matched your search

+83 -1
View File
@@ -78,6 +78,84 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
});
afterAll(() => rmSync(directory, { recursive: true, force: true }));
it.each([false, true])(
'keeps declarations fixed in direct and worker planning and date comparisons (cancelled: %s)',
async (cancelled) => {
const submitted = {
horizon: { start: '2024-01-02', end: '2024-02-01' },
balances: [
{ id: 'cp', type: 'cp' as const, quantity: 4, expiryDate: '2024-01-05' },
],
rules: [],
declaredDays: [
{ date: '2024-01-02', type: 'cp' as const },
{ date: '2024-01-03', type: 'rtt' as const },
{ date: '2024-01-05', type: 'other' as const },
],
};
const { calculatePlanningInternal, calculatePlanningAsyncInternal } =
await import('./leave-planner.core');
let interruption = false;
const direct = cancelled
? await calculatePlanningAsyncInternal(
{
...submitted,
progress: (stage: string) => {
if (stage === 'non-working-day-gain') interruption = true;
},
},
undefined,
undefined,
undefined,
undefined,
() => interruption,
)
: calculatePlanningInternal(submitted);
const calculation = createPlanningCalculation({
...submitted,
progress: (stage) => {
if (cancelled && stage === 'non-working-day-gain') calculation.cancel();
},
});
const outcome = await calculation.outcome;
expect(outcome.kind).toBe(cancelled ? 'PlanningIncomplete' : 'Plan');
expect(direct.kind).toBe(outcome.kind);
const plan =
outcome.kind === 'Plan'
? outcome
: outcome.kind === 'PlanningIncomplete'
? outcome.incumbent
: undefined;
const directPlan =
direct.kind === 'Plan'
? direct
: direct.kind === 'PlanningIncomplete'
? direct.incumbent
: undefined;
expect(plan).toBeDefined();
expect(plan?.declaredDays).toEqual(submitted.declaredDays);
expect(plan?.placements.map(({ date }) => date)).toEqual(['2024-01-04']);
expect(directPlan?.placements).toEqual(plan?.placements);
expect(plan?.remainingBalances).toMatchObject([{ id: 'cp', quantity: 3 }]);
if (!cancelled) expect(outcome).toEqual(direct);
expect(await calculation.explanation).toMatchObject({
kind: 'PlanExplanation',
declaredDays: submitted.declaredDays,
});
expect((await calculation.trace)?.initialState.declaredDays).toEqual(
submitted.declaredDays,
);
const original = structuredClone(outcome);
const comparison = await calculation.analyzeCounterfactual(plan!.placements[0]);
for (const { date } of submitted.declaredDays)
expect(comparison.results.find((result) => result.date === date)).toMatchObject({
status: 'ineligible',
reason: { code: 'declared-day' },
});
expect(await calculation.outcome).toEqual(original);
},
);
it('stops during calendar preparation without a plan or an obligation conflict', async () => {
const stages: string[] = [];
let preparationUpdates = 0;
@@ -85,6 +163,7 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
horizon: { start: '2024-01-01', end: '2027-01-01' },
balances: [],
rules: [],
declaredDays: [{ date: '2024-01-02', type: 'other' }],
progress: (stage) => {
stages.push(stage);
if (stage === 'preparation' && ++preparationUpdates === 2) calculation.cancel();
@@ -99,7 +178,10 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
expect(outcome.incumbent).toBeUndefined();
expect(preparationUpdates).toBeGreaterThanOrEqual(2);
expect(stages).not.toContain('obligations');
expect((await calculation.trace)?.status).toBe('incomplete');
expect(await calculation.trace).toMatchObject({
status: 'incomplete',
initialState: { declaredDays: [{ date: '2024-01-02', type: 'other' }] },
});
expect(await calculation.explanationError).toBeUndefined();
expect(heartbeats).toBeGreaterThan(0);
});