mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
test(core): cover unfinished invocation guard before turn reclamation (#13257)
* test(core): cover unfinished invocation guard before turn reclamation * test(core): also pin the prepared sub-case of the beginTurn unfinished guard * test(core): pin the cancel_requested sub-case and reflow for Prettier * test(core): harden the cancel_requested test per review round 3 - release the held onConfirm gate in a finally block so a failing assertion cannot stall afterEach dispose() behind the gate - assert the confirmation rejection reason instead of a bare await, matching the sibling cancellation tests * chore(ci): retrigger validation after review --------- Co-authored-by: Jay Feng <[email protected]>
This commit is contained in:
1 parent
fb01aa6adc
commit
da19e5c283
1 file changed
+62
@@ -718,6 +718,68 @@ describe('ManagedToolRuntime', () => {
|
||||
expect(tool.invocations).toHaveLength(1025);
|
||||
});
|
||||
|
||||
it('refuses a new turn while an invocation is prepared but not yet executed', async () => {
|
||||
const ref = await prepare();
|
||||
await runtime.preflight(ref);
|
||||
expect(() =>
|
||||
runtime.beginTurn({ ...identity, promptId: 'prompt-2' }),
|
||||
).toThrow('unfinished');
|
||||
expect(runtime.status(ref).state).toBe('prepared');
|
||||
expect(runtime.hasActiveWork()).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses a new turn while an invocation is still executing and preserves access to it', async () => {
|
||||
const gate = deferred<ToolResult>();
|
||||
tool.setup = (invocation) =>
|
||||
invocation.execute.mockReturnValue(gate.promise);
|
||||
const ref = await prepare();
|
||||
await runtime.preflight(ref);
|
||||
const executing = runtime.execute(ref);
|
||||
const next = { ...identity, promptId: 'prompt-2' };
|
||||
try {
|
||||
expect(() => runtime.beginTurn(next)).toThrow('unfinished');
|
||||
expect(runtime.status(ref).state).toBe('executing');
|
||||
expect(runtime.hasActiveWork()).toBe(true);
|
||||
} finally {
|
||||
gate.resolve(rawResult);
|
||||
await executing;
|
||||
}
|
||||
expect((await executing).executionStatus).toBe('success');
|
||||
expect(runtime.status(ref).state).toBe('settled');
|
||||
await runtime.beginTurn(next);
|
||||
expect(() => runtime.status(ref)).toThrow('identity does not match');
|
||||
});
|
||||
|
||||
it('refuses a new turn while a cancellation is confirming', async () => {
|
||||
const gate = deferred<void>();
|
||||
tool.setup = (invocation) =>
|
||||
invocation.onConfirm.mockReturnValue(gate.promise);
|
||||
const ref = await prepare();
|
||||
const confirming = runtime.confirm(
|
||||
ref,
|
||||
ToolConfirmationOutcome.ProceedOnce,
|
||||
);
|
||||
const rejection = confirming.catch((error: unknown) => error);
|
||||
try {
|
||||
await vi.waitFor(() =>
|
||||
expect(tool.invocations[0].onConfirm).toHaveBeenCalledTimes(1),
|
||||
);
|
||||
expect(runtime.cancel(ref).state).toBe('cancel_requested');
|
||||
expect(() =>
|
||||
runtime.beginTurn({ ...identity, promptId: 'prompt-2' }),
|
||||
).toThrow('unfinished');
|
||||
expect(runtime.status(ref).state).toBe('cancel_requested');
|
||||
expect(runtime.hasActiveWork()).toBe(true);
|
||||
} finally {
|
||||
gate.resolve();
|
||||
}
|
||||
expect(await rejection).toMatchObject({
|
||||
message: expect.stringContaining('cancelled'),
|
||||
});
|
||||
await vi.waitFor(() => expect(runtime.status(ref).state).toBe('settled'));
|
||||
expect(runtime.status(ref).result?.executionStatus).toBe('not_started');
|
||||
});
|
||||
|
||||
it.each([
|
||||
['a replacement', { shouldProceed: true }, { value: 'rewritten' }],
|
||||
['an empty replacement', { shouldProceed: true }, {}],
|
||||
|
||||
Reference in new issue
Block a user