Files
gemini-cli/integration-tests/shell-parity.test.ts
Christian Gunderman 6621bd88c8 Format.
2026-04-13 19:44:05 -07:00

124 lines
3.6 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
describe('shell-parity', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => await rig.cleanup());
it('should use run_shell_command for replace when sandboxing is enabled', async () => {
await rig.setup(
'should use run_shell_command for replace when sandboxing is enabled',
{
settings: {
security: { toolSandboxing: true },
},
},
);
rig.createFile('test.ts', 'const foo = "bar";');
// We expect the model to use run_shell_command because edit/replace/write_file are filtered out.
await rig.run({
args: `Replace "bar" with "baz" in test.ts`,
});
// Verify forbidden tools were NOT used
const forbiddenTools = [
'grep_search',
'replace',
'write_file',
'edit',
'read_file',
];
const toolLogs = rig.readToolLogs();
const usedForbidden = toolLogs.filter((t) =>
forbiddenTools.includes(t.toolRequest.name),
);
expect(usedForbidden).toHaveLength(0);
// Verify run_shell_command was used
const shellCall = await rig.waitForToolCall('run_shell_command');
expect(shellCall).toBeTruthy();
// Verify the command looks like a replace operation
const command = shellCall!.args.command as string;
// It should contain some form of replacement (sed, perl, or powershell -replace)
expect(command).toMatch(/sed|replace|Set-Content|perl/i);
// Verify file content changed
const content = rig.readFile('test.ts');
expect(content).toContain('baz');
expect(content).not.toContain('"bar"');
});
it('should use run_shell_command for search when sandboxing is enabled', async () => {
await rig.setup(
'should use run_shell_command for search when sandboxing is enabled',
{
settings: {
security: { toolSandboxing: true },
},
},
);
rig.createFile('search-me.txt', 'target-string');
await rig.run({
args: `Search for "target-string" in search-me.txt`,
});
// Verify grep_search was NOT used
const toolLogs = rig.readToolLogs();
const usedGrep = toolLogs.filter(
(t) => t.toolRequest.name === 'grep_search',
);
expect(usedGrep).toHaveLength(0);
// Verify run_shell_command was used
const shellCall = await rig.waitForToolCall('run_shell_command');
expect(shellCall).toBeTruthy();
const command = shellCall!.args.command as string;
expect(command).toMatch(/grep|rg|ripgrep|Select-String|findstr/i);
});
it('should use run_shell_command for read when sandboxing is enabled', async () => {
await rig.setup(
'should use run_shell_command for read when sandboxing is enabled',
{
settings: {
security: { toolSandboxing: true },
},
},
);
rig.createFile('read-me.txt', 'hello world');
const result = await rig.run({
args: `Read the file read-me.txt and tell me what it says`,
});
// Verify read_file was NOT used
const toolLogs = rig.readToolLogs();
const usedRead = toolLogs.filter((t) => t.toolRequest.name === 'read_file');
expect(usedRead).toHaveLength(0);
// Verify run_shell_command was used
const shellCall = await rig.waitForToolCall('run_shell_command');
expect(shellCall).toBeTruthy();
const command = shellCall!.args.command as string;
expect(command).toMatch(/cat|head|tail|less|more|Get-Content|type/i);
expect(result).toContain('hello world');
});
});