test(planning): verify integrated planning after trace removal
CI / validate (push) Successful in 44s
CI / build (push) Successful in 40s
CI / policy (push) Successful in 6s
CI / policy (pull_request) Successful in 7s
CI / validate (pull_request) Successful in 1m19s
CI / test (push) Successful in 1m52s
CI / benchmark (push) Failing after 5m29s
CI / test (pull_request) Successful in 3m22s
CI / security (pull_request) Failing after 12m15s
CI / build (pull_request) Failing after 14m33s
CI / benchmark (pull_request) Failing after 17m21s
CI / security (push) Failing after 19m29s
CI / images (pull_request) Skipped
CI / images (push) Skipped
CI / required (push) Failing after 1s
CI / required (pull_request) Failing after 1s

This commit is contained in:
nessar committed 2026-10-15 21:45:58 +02:00
1 parent 2f4af14869
commit 56577d23b5
3 files changed
+217 -1

No files matched your search

+63 -1
View File
@@ -33,6 +33,9 @@ describe('real planning worker cancellation', () => {
let simulateLongCalculation = false;
let simulatedElapsed = 0;
const threads = new Set<NodeWorker>();
const sentTypes: string[] = [];
const receivedTypes: string[] = [];
let terminationCount = 0;
beforeAll(async () => {
directory = mkdtempSync(join(tmpdir(), 'vac-optimizer-worker-'));
@@ -65,6 +68,9 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
heartbeats = 0;
simulateLongCalculation = false;
simulatedElapsed = 0;
sentTypes.length = 0;
receivedTypes.length = 0;
terminationCount = 0;
class RealWorker {
onmessage: ((event: MessageEvent) => void) | null = null;
onerror: (() => void) | null = null;
@@ -76,6 +82,7 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
simulatedElapsed = data.elapsed;
return;
}
receivedTypes.push(data.type);
if (holdArtifacts && ['explanation', 'artifacts'].includes(data.type)) {
heldArtifactMessages.push(() => this.onmessage?.({ data } as MessageEvent));
return;
@@ -86,10 +93,12 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
});
this.thread.on('error', () => this.onerror?.());
}
postMessage(message: unknown) {
postMessage(message: { type: string }) {
sentTypes.push(message.type);
this.thread.postMessage(message);
}
terminate() {
terminationCount++;
void this.thread.terminate();
}
}
@@ -104,6 +113,59 @@ parentPort.on('message', (data) => globalThis.self.onmessage({ data }));
});
afterAll(() => rmSync(directory, { recursive: true, force: true }));
it('delivers the outcome and explanation and releases the worker without acknowledgements', async () => {
const calculation = createPlanningCalculation({
horizon: { start: '2024-01-02', end: '2024-01-04' },
balances: [{ id: 'cp', type: 'cp', quantity: 1, expiryDate: '2024-01-03' }],
rules: [presets[0]],
});
const outcome = await calculation.outcome;
expect(outcome).toMatchObject({
kind: 'Plan',
placements: [{ date: '2024-01-02', balanceId: 'cp', type: 'cp' }],
lastCompletedStage: 'dates',
});
expect(await calculation.explanation).toMatchObject({
kind: 'PlanExplanation',
placements: [{ placement: { date: '2024-01-02', balanceId: 'cp' } }],
});
expect(await calculation.explanationError).toBeUndefined();
expect(await calculation.timings).toEqual({ planningWorkMs: expect.any(Number) });
expect(sentTypes).toEqual(['plan']);
expect(receivedTypes.slice(-3)).toEqual(['result', 'explanation', 'artifacts']);
expect(new Set(receivedTypes)).toEqual(
new Set(['heartbeat', 'progress', 'checkpoint', 'result', 'explanation', 'artifacts']),
);
expect(terminationCount).toBe(1);
});
it('ignores a replaced real worker’s late explanation without losing either outcome', async () => {
const request = {
horizon: { start: '2024-01-02', end: '2024-01-04' },
balances: [{ id: 'cp', type: 'cp', quantity: 1, expiryDate: '2024-01-03' }],
rules: [presets[0]],
};
holdArtifacts = true;
const first = createPlanningCalculation(request);
const original = await first.outcome;
expect(original.kind).toBe('Plan');
await vi.waitFor(() => expect(heldArtifactMessages).toHaveLength(2));
holdArtifacts = false;
const latest = createPlanningCalculation({ ...request, rules: [] });
const outcome = await latest.outcome;
expect(outcome).toMatchObject({ kind: 'Plan', placements: [] });
const explanation = await latest.explanation;
expect(explanation).toMatchObject({ kind: 'PlanExplanation', placements: [] });
await latest.timings;
for (const deliver of heldArtifactMessages) deliver();
expect(await first.outcome).toEqual(original);
expect(await first.explanation).toBeUndefined();
expect(await latest.outcome).toEqual(outcome);
expect(await latest.explanation).toEqual(explanation);
expect(await latest.explanationError).toBeUndefined();
expect(terminationCount).toBe(2);
});
it('preserves a complete optimization configuration and active order in the real worker snapshot', async () => {
const configuration: OptimizationConfiguration = structuredClone(
OPTIMIZATION_PRESETS.unplacedAtRisk,