mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-02 13:40:46 +00:00
fix: cli input stream handling and error management, improve e2e and unit tests
This commit is contained in:
parent
6eb16c0bcf
commit
f578ff07a2
13 changed files with 741 additions and 150 deletions
|
|
@ -42,6 +42,7 @@ function createMockContext(debugMode: boolean = false): IControlContext {
|
|||
permissionMode: 'default',
|
||||
sdkMcpServers: new Set<string>(),
|
||||
mcpClients: new Map(),
|
||||
inputClosed: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -637,6 +638,130 @@ describe('ControlDispatcher', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('markInputClosed', () => {
|
||||
it('should reject all pending outgoing requests when input closes', () => {
|
||||
const requestId1 = 'reject-req-1';
|
||||
const requestId2 = 'reject-req-2';
|
||||
const resolve1 = vi.fn();
|
||||
const resolve2 = vi.fn();
|
||||
const reject1 = vi.fn();
|
||||
const reject2 = vi.fn();
|
||||
const timeoutId1 = setTimeout(() => {}, 1000);
|
||||
const timeoutId2 = setTimeout(() => {}, 1000);
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
const register = (
|
||||
dispatcher as unknown as {
|
||||
registerOutgoingRequest: (
|
||||
id: string,
|
||||
controller: string,
|
||||
resolve: (response: ControlResponse) => void,
|
||||
reject: (error: Error) => void,
|
||||
timeoutId: NodeJS.Timeout,
|
||||
) => void;
|
||||
}
|
||||
).registerOutgoingRequest.bind(dispatcher);
|
||||
|
||||
register(requestId1, 'SystemController', resolve1, reject1, timeoutId1);
|
||||
register(requestId2, 'SystemController', resolve2, reject2, timeoutId2);
|
||||
|
||||
dispatcher.markInputClosed();
|
||||
|
||||
expect(reject1).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: 'Input closed' }),
|
||||
);
|
||||
expect(reject2).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: 'Input closed' }),
|
||||
);
|
||||
expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId1);
|
||||
expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId2);
|
||||
});
|
||||
|
||||
it('should mark input as closed on context', () => {
|
||||
dispatcher.markInputClosed();
|
||||
expect(mockContext.inputClosed).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle empty pending requests gracefully', () => {
|
||||
expect(() => dispatcher.markInputClosed()).not.toThrow();
|
||||
});
|
||||
|
||||
it('should be idempotent when called multiple times', () => {
|
||||
const requestId = 'idempotent-req';
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const timeoutId = setTimeout(() => {}, 1000);
|
||||
|
||||
(
|
||||
dispatcher as unknown as {
|
||||
registerOutgoingRequest: (
|
||||
id: string,
|
||||
controller: string,
|
||||
resolve: (response: ControlResponse) => void,
|
||||
reject: (error: Error) => void,
|
||||
timeoutId: NodeJS.Timeout,
|
||||
) => void;
|
||||
}
|
||||
).registerOutgoingRequest(
|
||||
requestId,
|
||||
'SystemController',
|
||||
resolve,
|
||||
reject,
|
||||
timeoutId,
|
||||
);
|
||||
|
||||
dispatcher.markInputClosed();
|
||||
const firstRejectCount = vi.mocked(reject).mock.calls.length;
|
||||
|
||||
// Call again - should not reject again
|
||||
dispatcher.markInputClosed();
|
||||
const secondRejectCount = vi.mocked(reject).mock.calls.length;
|
||||
|
||||
expect(secondRejectCount).toBe(firstRejectCount);
|
||||
});
|
||||
|
||||
it('should log input closure in debug mode', () => {
|
||||
const context = createMockContext(true);
|
||||
const consoleSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
const dispatcherWithDebug = new ControlDispatcher(context);
|
||||
const requestId = 'reject-req-debug';
|
||||
const resolve = vi.fn();
|
||||
const reject = vi.fn();
|
||||
const timeoutId = setTimeout(() => {}, 1000);
|
||||
|
||||
(
|
||||
dispatcherWithDebug as unknown as {
|
||||
registerOutgoingRequest: (
|
||||
id: string,
|
||||
controller: string,
|
||||
resolve: (response: ControlResponse) => void,
|
||||
reject: (error: Error) => void,
|
||||
timeoutId: NodeJS.Timeout,
|
||||
) => void;
|
||||
}
|
||||
).registerOutgoingRequest(
|
||||
requestId,
|
||||
'SystemController',
|
||||
resolve,
|
||||
reject,
|
||||
timeoutId,
|
||||
);
|
||||
|
||||
dispatcherWithDebug.markInputClosed();
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'[ControlDispatcher] Input closed, rejecting 1 pending outgoing requests',
|
||||
),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('should cancel all pending incoming requests', () => {
|
||||
const requestId1 = 'shutdown-req-1';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue