refactor(debug): replace ConsolePatcher with debugLogger and update error reporting

- Replace ConsolePatcher with centralized debugLogger utility
- Refactor errorReporting to use debugLogger instead of file-based reporting
- Remove user-facing console message components:
  - Delete ConsolePatcher.ts, useConsoleMessages.ts/hook
  - Delete ConsoleSummaryDisplay.tsx, DetailedMessagesDisplay.tsx
- Update all tests in packages/core and packages/cli:
  - Mock debugLogger where needed
  - Remove assertions for console output on non-critical errors
  - Keep debugLogger assertions for fatal/network errors
  - Use HOME directory mocking for hermetic file system tests

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-02-02 17:37:54 +08:00
parent 135df54f27
commit 89e3c2cd7a
64 changed files with 1240 additions and 2416 deletions

View file

@ -19,6 +19,14 @@ import {
handleMaxTurnsExceededError,
} from './errors.js';
const mockWriteStderrLine = vi.hoisted(() => vi.fn());
const debugLoggerSpy = vi.hoisted(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}));
// Mock the core modules
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
const original =
@ -26,6 +34,12 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
return {
...original,
createDebugLogger: () => ({
debug: debugLoggerSpy.debug,
info: debugLoggerSpy.info,
warn: debugLoggerSpy.warn,
error: debugLoggerSpy.error,
}),
parseAndFormatApiError: vi.fn((error: unknown) => {
if (error instanceof Error) {
return `API Error: ${error.message}`;
@ -66,18 +80,25 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
};
});
vi.mock('./stdioHelpers.js', () => ({
writeStderrLine: mockWriteStderrLine,
writeStdoutLine: vi.fn(),
clearScreen: vi.fn(),
}));
describe('errors', () => {
let mockConfig: Config;
let processExitSpy: MockInstance;
let processStderrWriteSpy: MockInstance;
let consoleErrorSpy: MockInstance;
beforeEach(() => {
// Reset mocks
vi.clearAllMocks();
// Mock console.error
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
mockWriteStderrLine.mockClear();
debugLoggerSpy.debug.mockClear();
debugLoggerSpy.info.mockClear();
debugLoggerSpy.warn.mockClear();
debugLoggerSpy.error.mockClear();
// Mock process.stderr.write
processStderrWriteSpy = vi
@ -99,7 +120,6 @@ describe('errors', () => {
});
afterEach(() => {
consoleErrorSpy.mockRestore();
processStderrWriteSpy.mockRestore();
processExitSpy.mockRestore();
});
@ -163,7 +183,9 @@ describe('errors', () => {
handleError(testError, mockConfig);
}).toThrow(testError);
expect(consoleErrorSpy).toHaveBeenCalledWith('API Error: Test error');
expect(mockWriteStderrLine).toHaveBeenCalledWith(
'API Error: Test error',
);
});
it('should handle non-Error objects', () => {
@ -173,7 +195,9 @@ describe('errors', () => {
handleError(testError, mockConfig);
}).toThrow(testError);
expect(consoleErrorSpy).toHaveBeenCalledWith('API Error: String error');
expect(mockWriteStderrLine).toHaveBeenCalledWith(
'API Error: String error',
);
});
});
@ -191,7 +215,7 @@ describe('errors', () => {
handleError(testError, mockConfig);
}).toThrow('process.exit called with code: 1');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
JSON.stringify(
{
error: {
@ -213,7 +237,7 @@ describe('errors', () => {
handleError(testError, mockConfig, 42);
}).toThrow('process.exit called with code: 42');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
JSON.stringify(
{
error: {
@ -235,7 +259,7 @@ describe('errors', () => {
handleError(fatalError, mockConfig);
}).toThrow('process.exit called with code: 42');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
JSON.stringify(
{
error: {
@ -271,7 +295,7 @@ describe('errors', () => {
handleError(errorWithStatus, mockConfig);
}).toThrow('process.exit called with code: 1'); // string codes become 1
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
JSON.stringify(
{
error: {
@ -307,7 +331,7 @@ describe('errors', () => {
it('should log error message to stderr and not exit', () => {
handleToolError(toolName, toolError, mockConfig);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -322,7 +346,7 @@ describe('errors', () => {
'Custom display message',
);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Custom display message',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -340,7 +364,7 @@ describe('errors', () => {
handleToolError(toolName, toolError, mockConfig);
// In JSON mode, should not exit (just log to stderr when debug mode is on)
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -350,7 +374,7 @@ describe('errors', () => {
handleToolError(toolName, toolError, mockConfig, 'CUSTOM_TOOL_ERROR');
// In JSON mode, should not exit (just log to stderr when debug mode is on)
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -360,7 +384,7 @@ describe('errors', () => {
handleToolError(toolName, toolError, mockConfig, 500);
// In JSON mode, should not exit (just log to stderr when debug mode is on)
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -376,7 +400,7 @@ describe('errors', () => {
);
// In JSON mode, should not exit (just log to stderr when debug mode is on)
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Display message',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -394,7 +418,7 @@ describe('errors', () => {
handleToolError(toolName, toolError, mockConfig);
// Should not exit in STREAM_JSON mode (just log to stderr when debug mode is on)
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
@ -407,36 +431,42 @@ describe('errors', () => {
(mockConfig.getDebugMode as Mock).mockReturnValue(false);
});
it('should not log and not exit in text mode', () => {
it('should log error and not exit in text mode', () => {
(
mockConfig.getOutputFormat as ReturnType<typeof vi.fn>
).mockReturnValue(OutputFormat.TEXT);
handleToolError(toolName, toolError, mockConfig);
expect(consoleErrorSpy).not.toHaveBeenCalled();
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
});
it('should not log and not exit in JSON mode', () => {
it('should log error and not exit in JSON mode', () => {
(
mockConfig.getOutputFormat as ReturnType<typeof vi.fn>
).mockReturnValue(OutputFormat.JSON);
handleToolError(toolName, toolError, mockConfig);
expect(consoleErrorSpy).not.toHaveBeenCalled();
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
});
it('should not log and not exit in STREAM_JSON mode', () => {
it('should log error and not exit in STREAM_JSON mode', () => {
(
mockConfig.getOutputFormat as ReturnType<typeof vi.fn>
).mockReturnValue(OutputFormat.STREAM_JSON);
handleToolError(toolName, toolError, mockConfig);
expect(consoleErrorSpy).not.toHaveBeenCalled();
expect(debugLoggerSpy.error).toHaveBeenCalledWith(
'Error executing tool test-tool: Tool failed',
);
expect(processExitSpy).not.toHaveBeenCalled();
});
});
@ -565,7 +595,9 @@ describe('errors', () => {
handleCancellationError(mockConfig);
}).toThrow('process.exit called with code: 130');
expect(consoleErrorSpy).toHaveBeenCalledWith('Operation cancelled.');
expect(mockWriteStderrLine).toHaveBeenCalledWith(
'Operation cancelled.',
);
});
});
@ -581,7 +613,7 @@ describe('errors', () => {
handleCancellationError(mockConfig);
}).toThrow('process.exit called with code: 130');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
JSON.stringify(
{
error: {
@ -611,7 +643,7 @@ describe('errors', () => {
handleMaxTurnsExceededError(mockConfig);
}).toThrow('process.exit called with code: 53');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
'Reached max session turns for this session. Increase the number of turns by specifying maxSessionTurns in settings.json.',
);
});
@ -629,7 +661,7 @@ describe('errors', () => {
handleMaxTurnsExceededError(mockConfig);
}).toThrow('process.exit called with code: 53');
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect(mockWriteStderrLine).toHaveBeenCalledWith(
JSON.stringify(
{
error: {