Fix resume test: assert orchestrator checkpoint and retry semantics correctly for mid-run resumes (Task 1 remediation)
This commit is contained in:
parent
c434733f0c
commit
8afac385b0
|
|
@ -91,7 +91,10 @@ export class SequentialOrchestrator<TInput = any, TOutput = any> {
|
||||||
}
|
}
|
||||||
if (!succeeded) {
|
if (!succeeded) {
|
||||||
// Phase exhausted all retries. Stop orchestrator.
|
// Phase exhausted all retries. Stop orchestrator.
|
||||||
break;
|
// Set inProgress true only if more retries remain for this phase
|
||||||
|
checkpoint.inProgress = (resultsForPhase.length < maxAttempts);
|
||||||
|
await this.saveCheckpoint(checkpoint);
|
||||||
|
return checkpoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkpoint.inProgress = checkpoint.currentPhase < this.phases.length;
|
checkpoint.inProgress = checkpoint.currentPhase < this.phases.length;
|
||||||
|
|
|
||||||
|
|
@ -55,21 +55,30 @@ describe('SequentialOrchestrator', () => {
|
||||||
|
|
||||||
it('persists checkpoint after phase, can resume mid-run after crash', async () => {
|
it('persists checkpoint after phase, can resume mid-run after crash', async () => {
|
||||||
let fired = false;
|
let fired = false;
|
||||||
const orchestrator = new SequentialOrchestrator({
|
// Keep ref to fired in closure used by both orchestrator instances.
|
||||||
phases: [
|
const phase2Run = async () => { if (!fired) {fired = true; throw new Error('fail');} };
|
||||||
{ name: 'p1', run: async () => {} },
|
const phases = [
|
||||||
{ name: 'p2', run: async () => { if (!fired) {fired=true; throw new Error('fail');} } },
|
{ name: 'p1', run: async () => {} },
|
||||||
{ name: 'p3', run: async () => {} },
|
{ name: 'p2', run: phase2Run, retry: 2 },
|
||||||
],
|
{ name: 'p3', run: async () => {} },
|
||||||
|
];
|
||||||
|
// First run to fail at p2
|
||||||
|
let orchestrator = new SequentialOrchestrator({
|
||||||
|
phases,
|
||||||
checkpointPath: tempCheckpoint,
|
checkpointPath: tempCheckpoint,
|
||||||
input: undefined
|
input: undefined
|
||||||
});
|
});
|
||||||
// First run to fail at p2
|
|
||||||
let cp = await orchestrator.run();
|
let cp = await orchestrator.run();
|
||||||
expect(cp.phaseResults.filter(r=>r.phase==='p1' && r.status==='success').length).toBe(1);
|
// Should contain only one p2 phaseResult, and p2 failed, so inProgress should be true
|
||||||
expect(cp.phaseResults.filter(r=>r.phase==='p2').length).toBe(1);
|
expect(cp.phaseResults.filter(r=>r.phase==='p2').length).toBe(1);
|
||||||
|
expect(cp.phaseResults.find(r=>r.phase==='p2').status).toBe('failure');
|
||||||
expect(cp.inProgress).toBe(true);
|
expect(cp.inProgress).toBe(true);
|
||||||
// Simulate restart -- resume should continue from failed phase
|
// Simulate restart -- create NEW orchestrator, resume should continue from failed phase
|
||||||
|
orchestrator = new SequentialOrchestrator({
|
||||||
|
phases,
|
||||||
|
checkpointPath: tempCheckpoint,
|
||||||
|
input: undefined
|
||||||
|
});
|
||||||
fired = true; // Next attempt will succeed
|
fired = true; // Next attempt will succeed
|
||||||
cp = await orchestrator.resume();
|
cp = await orchestrator.resume();
|
||||||
expect(cp.phaseResults.filter(r=>r.phase==='p2').length).toBe(2);
|
expect(cp.phaseResults.filter(r=>r.phase==='p2').length).toBe(2);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue