From 4f5b335792d1c8ad0df5d2bb1a7437e9567f20a3 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Tue, 14 Oct 2025 18:46:54 -0700 Subject: [PATCH] fix(tests): enable cyclic schema MCP tool test (#10912) --- .../context-compress-interactive.test.ts | 12 +++------ integration-tests/ctrl-c-exit.test.ts | 4 +-- .../file-system-interactive.test.ts | 4 +-- .../mcp_server_cyclic_schema.test.ts | 17 +++++------- integration-tests/test-helper.ts | 27 ++++++++++++++++++- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/integration-tests/context-compress-interactive.test.ts b/integration-tests/context-compress-interactive.test.ts index 51043e1265..be95bb41ac 100644 --- a/integration-tests/context-compress-interactive.test.ts +++ b/integration-tests/context-compress-interactive.test.ts @@ -27,15 +27,13 @@ describe.skip('Interactive Mode', () => { const longPrompt = 'Dont do anything except returning a 1000 token long paragraph with the at the end to indicate end of response. This is a moderately long sentence.'; - await run.type(longPrompt); - await run.type('\r'); + await run.sendKeys(longPrompt); + await run.sendKeys('\r'); await run.expectText('einstein', 25000); await run.type('/compress'); - // A small delay to allow React to re-render the command list. - await new Promise((resolve) => setTimeout(resolve, 100)); - await run.type('\r'); + await run.sendKeys('\r'); const foundEvent = await rig.waitForTelemetryEvent( 'chat_compression', @@ -53,9 +51,7 @@ describe.skip('Interactive Mode', () => { const run = await rig.runInteractive(); await run.type('/compress'); - // A small delay to allow React to re-render the command list. - await new Promise((resolve) => setTimeout(resolve, 100)); - await run.type('\r'); + await run.sendKeys('\r'); await run.expectText('compression was not beneficial', 25000); }); }); diff --git a/integration-tests/ctrl-c-exit.test.ts b/integration-tests/ctrl-c-exit.test.ts index 1a52ff16dd..bdb4d12ccf 100644 --- a/integration-tests/ctrl-c-exit.test.ts +++ b/integration-tests/ctrl-c-exit.test.ts @@ -16,7 +16,7 @@ describe.skip('Ctrl+C exit', () => { const run = await rig.runInteractive(); // Send first Ctrl+C - run.type('\x03'); + run.sendKeys('\x03'); await run.expectText('Press Ctrl+C again to exit', 5000); @@ -40,7 +40,7 @@ describe.skip('Ctrl+C exit', () => { } // Send second Ctrl+C - run.type('\x03'); + run.sendKeys('\x03'); const exitCode = await run.expectExit(); expect(exitCode, `Process exited with code ${exitCode}.`).toBe(0); diff --git a/integration-tests/file-system-interactive.test.ts b/integration-tests/file-system-interactive.test.ts index 80622dd452..79207e7a97 100644 --- a/integration-tests/file-system-interactive.test.ts +++ b/integration-tests/file-system-interactive.test.ts @@ -28,7 +28,7 @@ describe.skip('Interactive file system', () => { // Step 1: Read the file const readPrompt = `Read the version from ${fileName}`; await run.type(readPrompt); - await run.type('\r'); + await run.sendKeys('\r'); const readCall = await rig.waitForToolCall('read_file', 30000); expect(readCall, 'Expected to find a read_file tool call').toBe(true); @@ -38,7 +38,7 @@ describe.skip('Interactive file system', () => { // Step 2: Write the file const writePrompt = `now change the version to 1.0.1 in the file`; await run.type(writePrompt); - await run.type('\r'); + await run.sendKeys('\r'); await rig.expectToolCallSuccess(['write_file', 'replace'], 30000); diff --git a/integration-tests/mcp_server_cyclic_schema.test.ts b/integration-tests/mcp_server_cyclic_schema.test.ts index 8ff6d8ce96..b8178eff1f 100644 --- a/integration-tests/mcp_server_cyclic_schema.test.ts +++ b/integration-tests/mcp_server_cyclic_schema.test.ts @@ -23,7 +23,7 @@ import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; -import { beforeAll, describe, expect, it } from 'vitest'; +import { beforeAll, describe, it } from 'vitest'; import { TestRig } from './test-helper.js'; // Create a minimal MCP server that doesn't require external dependencies @@ -192,15 +192,12 @@ describe('mcp server with cyclic tool schema is detected', () => { } }); - //TODO - https://github.com/google-gemini/gemini-cli/issues/10735 - it.skip('mcp tool list should include tool with cyclic tool schema', async () => { - const tool_list_output = await rig.run('/mcp list'); - expect(tool_list_output).toContain('tool_with_cyclic_schema'); - }); + it('mcp tool list should include tool with cyclic tool schema', async () => { + const run = await rig.runInteractive(); - it('gemini api call should be successful with cyclic mcp tool schema', async () => { - // Run any command and verify that we get a non-error response from - // the Gemini API. - await rig.run('hello'); + await run.type('/mcp list'); + await run.sendKeys('\r'); + + await run.expectText('tool_with_cyclic_schema'); }); }); diff --git a/integration-tests/test-helper.ts b/integration-tests/test-helper.ts index ee8a5c6297..33bb499256 100644 --- a/integration-tests/test-helper.ts +++ b/integration-tests/test-helper.ts @@ -195,8 +195,33 @@ export class InteractiveRun { expect(found, `Did not find expected text: "${text}"`).toBe(true); } - // Simulates typing a string one character at a time to avoid paste detection. + // This types slowly to make sure command is correct, but only work for short + // commands that are not multi-line, use sendKeys to type long prompts async type(text: string) { + let typedSoFar = ''; + for (const char of text) { + this.ptyProcess.write(char); + typedSoFar += char; + + // Wait for the typed sequence so far to be echoed back. + const found = await poll( + () => stripAnsi(this.output).includes(typedSoFar), + 5000, // 5s timeout per character (generous for CI) + 10, // check frequently + ); + + if (!found) { + throw new Error( + `Timed out waiting for typed text to appear in output: "${typedSoFar}".\nStripped output:\n${stripAnsi( + this.output, + )}`, + ); + } + } + } + + // Simulates typing a string one character at a time to avoid paste detection. + async sendKeys(text: string) { const delay = 5; for (const char of text) { this.ptyProcess.write(char);