From 7993ef65b1de00a5540109ed2bd0ce9c5b336597 Mon Sep 17 00:00:00 2001 From: "A.K.M. Adib" Date: Tue, 3 Mar 2026 15:23:12 -0500 Subject: [PATCH] tests pass --- .../ui/hooks/shellCommandProcessor.test.tsx | 22 +++++- .../core/src/scheduler/tool-executor.test.ts | 70 ++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx b/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx index 0b1499b77b..5456b80cbb 100644 --- a/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx +++ b/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx @@ -50,7 +50,23 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { isBinary: mockIsBinary, }; }); -vi.mock('node:fs'); +vi.mock('node:fs', async (importOriginal) => { + const actual = await importOriginal(); + const mocked = { + ...actual, + existsSync: vi.fn(), + readFileSync: vi.fn(), + createWriteStream: vi.fn(), + promises: { + ...actual.promises, + unlink: vi.fn().mockResolvedValue(undefined), + }, + }; + return { + ...mocked, + default: mocked, + }; +}); vi.mock('node:os', async (importOriginal) => { const actual = await importOriginal(); const mocked = { @@ -559,7 +575,7 @@ describe('useShellCommandProcessor', () => { }); const tmpFile = path.join(os.tmpdir(), 'shell_pwd_abcdef.tmp'); // Verify that the temporary file was cleaned up - expect(vi.mocked(fs.unlinkSync)).toHaveBeenCalledWith(tmpFile); + expect(vi.mocked(fs.promises.unlink)).toHaveBeenCalledWith(tmpFile); expect(setShellInputFocusedMock).toHaveBeenCalledWith(false); }); @@ -587,7 +603,7 @@ describe('useShellCommandProcessor', () => { expect(finalHistoryItem.tools[0].resultDisplay).toContain( "WARNING: shell mode is stateless; the directory change to '/test/dir/new' will not persist.", ); - expect(vi.mocked(fs.unlinkSync)).toHaveBeenCalledWith(tmpFile); + expect(vi.mocked(fs.promises.unlink)).toHaveBeenCalledWith(tmpFile); }); it('should NOT show a warning if the directory does not change', async () => { diff --git a/packages/core/src/scheduler/tool-executor.test.ts b/packages/core/src/scheduler/tool-executor.test.ts index 9a31ba963f..3049d13928 100644 --- a/packages/core/src/scheduler/tool-executor.test.ts +++ b/packages/core/src/scheduler/tool-executor.test.ts @@ -318,7 +318,7 @@ describe('ToolExecutor', () => { } }); - it('should truncate large MCP tool output with single text Part', async () => { + it('should truncate large output and move file when fullOutputFilePath is provided', async () => { // 1. Setup Config for Truncation vi.spyOn(config, 'getTruncateToolOutputThreshold').mockReturnValue(10); vi.spyOn(config.storage, 'getProjectTempDir').mockReturnValue('/tmp'); @@ -326,6 +326,71 @@ describe('ToolExecutor', () => { outputFile: '/tmp/moved_output.txt', }); + const mockTool = new MockTool({ name: SHELL_TOOL_NAME }); + const invocation = mockTool.build({}); + const longOutput = 'This is a very long output that should be truncated.'; + + // 2. Mock execution returning long content AND fullOutputFilePath + vi.mocked(coreToolHookTriggers.executeToolWithHooks).mockResolvedValue({ + llmContent: longOutput, + returnDisplay: longOutput, + fullOutputFilePath: '/tmp/temp_full_output.txt', + }); + + const scheduledCall: ScheduledToolCall = { + status: CoreToolCallStatus.Scheduled, + request: { + callId: 'call-trunc-full', + name: SHELL_TOOL_NAME, + args: { command: 'echo long' }, + isClientInitiated: false, + prompt_id: 'prompt-trunc-full', + }, + tool: mockTool, + invocation: invocation as unknown as AnyToolInvocation, + startTime: Date.now(), + }; + + // 3. Execute + const result = await executor.execute({ + call: scheduledCall, + signal: new AbortController().signal, + onUpdateToolCall: vi.fn(), + }); + + // 4. Verify Truncation Logic + expect(fileUtils.moveToolOutputToFile).toHaveBeenCalledWith( + '/tmp/temp_full_output.txt', + SHELL_TOOL_NAME, + 'call-trunc-full', + expect.any(String), // temp dir + 'test-session-id', // session id from makeFakeConfig + ); + + expect(fileUtils.formatTruncatedToolOutput).toHaveBeenCalledWith( + longOutput, + '/tmp/moved_output.txt', + 10, // threshold (maxChars) + ); + + expect(result.status).toBe(CoreToolCallStatus.Success); + if (result.status === CoreToolCallStatus.Success) { + const response = result.response.responseParts[0]?.functionResponse + ?.response as Record; + // The content should be the *truncated* version returned by the mock formatTruncatedToolOutput + expect(response).toEqual({ + output: 'TruncatedContent...', + outputFile: '/tmp/moved_output.txt', + }); + expect(result.response.outputFile).toBe('/tmp/moved_output.txt'); + } + }); + + it('should truncate large MCP tool output with single text Part', async () => { + // 1. Setup Config for Truncation + vi.spyOn(config, 'getTruncateToolOutputThreshold').mockReturnValue(10); + vi.spyOn(config.storage, 'getProjectTempDir').mockReturnValue('/tmp'); + const mcpToolName = 'get_big_text'; const messageBus = createMockMessageBus(); const mcpTool = new DiscoveredMCPTool( @@ -340,10 +405,11 @@ describe('ToolExecutor', () => { const longText = 'This is a very long MCP output that should be truncated.'; // 2. Mock execution returning Part[] with single text Part + // We do NOT provide fullOutputFilePath here because we want to test the path + // that uses saveTruncatedToolOutput for MCP tools. vi.mocked(coreToolHookTriggers.executeToolWithHooks).mockResolvedValue({ llmContent: [{ text: longText }], returnDisplay: longText, - fullOutputFilePath: '/tmp/temp_full_output.txt', }); const scheduledCall: ScheduledToolCall = {