mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-09 01:27:41 -07:00
Refactored 3 files of tools package (#13231)
Co-authored-by: riddhi <duttariddhi@google.com>
This commit is contained in:
@@ -39,6 +39,10 @@ const downloadRipGrepMock = vi.mocked(downloadRipGrep);
|
||||
const originalGetGlobalBinDir = Storage.getGlobalBinDir.bind(Storage);
|
||||
const storageSpy = vi.spyOn(Storage, 'getGlobalBinDir');
|
||||
|
||||
function getRipgrepBinaryName() {
|
||||
return process.platform === 'win32' ? 'rg.exe' : 'rg';
|
||||
}
|
||||
|
||||
describe('canUseRipgrep', () => {
|
||||
let tempRootDir: string;
|
||||
let binDir: string;
|
||||
@@ -58,9 +62,7 @@ describe('canUseRipgrep', () => {
|
||||
});
|
||||
|
||||
it('should return true if ripgrep already exists', async () => {
|
||||
const candidateNames =
|
||||
process.platform === 'win32' ? ['rg.exe', 'rg'] : ['rg'];
|
||||
const existingPath = path.join(binDir, candidateNames[0]);
|
||||
const existingPath = path.join(binDir, getRipgrepBinaryName());
|
||||
await fs.writeFile(existingPath, '');
|
||||
|
||||
const result = await canUseRipgrep();
|
||||
@@ -69,9 +71,7 @@ describe('canUseRipgrep', () => {
|
||||
});
|
||||
|
||||
it('should download ripgrep and return true if it does not exist initially', async () => {
|
||||
const candidateNames =
|
||||
process.platform === 'win32' ? ['rg.exe', 'rg'] : ['rg'];
|
||||
const expectedPath = path.join(binDir, candidateNames[0]);
|
||||
const expectedPath = path.join(binDir, getRipgrepBinaryName());
|
||||
|
||||
downloadRipGrepMock.mockImplementation(async () => {
|
||||
await fs.writeFile(expectedPath, '');
|
||||
@@ -100,9 +100,7 @@ describe('canUseRipgrep', () => {
|
||||
});
|
||||
|
||||
it('should only download once when called concurrently', async () => {
|
||||
const candidateNames =
|
||||
process.platform === 'win32' ? ['rg.exe', 'rg'] : ['rg'];
|
||||
const expectedPath = path.join(binDir, candidateNames[0]);
|
||||
const expectedPath = path.join(binDir, getRipgrepBinaryName());
|
||||
|
||||
downloadRipGrepMock.mockImplementation(
|
||||
() =>
|
||||
@@ -146,9 +144,7 @@ describe('ensureRgPath', () => {
|
||||
});
|
||||
|
||||
it('should return rg path if ripgrep already exists', async () => {
|
||||
const candidateNames =
|
||||
process.platform === 'win32' ? ['rg.exe', 'rg'] : ['rg'];
|
||||
const existingPath = path.join(binDir, candidateNames[0]);
|
||||
const existingPath = path.join(binDir, getRipgrepBinaryName());
|
||||
await fs.writeFile(existingPath, '');
|
||||
|
||||
const rgPath = await ensureRgPath();
|
||||
@@ -157,9 +153,7 @@ describe('ensureRgPath', () => {
|
||||
});
|
||||
|
||||
it('should return rg path if ripgrep is downloaded successfully', async () => {
|
||||
const candidateNames =
|
||||
process.platform === 'win32' ? ['rg.exe', 'rg'] : ['rg'];
|
||||
const expectedPath = path.join(binDir, candidateNames[0]);
|
||||
const expectedPath = path.join(binDir, getRipgrepBinaryName());
|
||||
|
||||
downloadRipGrepMock.mockImplementation(async () => {
|
||||
await fs.writeFile(expectedPath, '');
|
||||
@@ -301,24 +295,33 @@ describe('RipGrepTool', () => {
|
||||
});
|
||||
|
||||
describe('validateToolParams', () => {
|
||||
it('should return null for valid params (pattern only)', () => {
|
||||
const params: RipGrepToolParams = { pattern: 'hello' };
|
||||
expect(grepTool.validateToolParams(params)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for valid params (pattern and path)', () => {
|
||||
const params: RipGrepToolParams = { pattern: 'hello', dir_path: '.' };
|
||||
expect(grepTool.validateToolParams(params)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for valid params (pattern, path, and include)', () => {
|
||||
const params: RipGrepToolParams = {
|
||||
pattern: 'hello',
|
||||
dir_path: '.',
|
||||
include: '*.txt',
|
||||
};
|
||||
expect(grepTool.validateToolParams(params)).toBeNull();
|
||||
});
|
||||
it.each([
|
||||
{
|
||||
name: 'pattern only',
|
||||
params: { pattern: 'hello' },
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'pattern and path',
|
||||
params: { pattern: 'hello', dir_path: '.' },
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'pattern, path, and include',
|
||||
params: { pattern: 'hello', dir_path: '.', include: '*.txt' },
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'invalid regex pattern',
|
||||
params: { pattern: '[[' },
|
||||
expected: null,
|
||||
},
|
||||
])(
|
||||
'should return null for valid params ($name)',
|
||||
({ params, expected }) => {
|
||||
expect(grepTool.validateToolParams(params)).toBe(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('should return error if pattern is missing', () => {
|
||||
const params = { dir_path: '.' } as unknown as RipGrepToolParams;
|
||||
@@ -327,11 +330,6 @@ describe('RipGrepTool', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null for what would be an invalid regex pattern', () => {
|
||||
const params: RipGrepToolParams = { pattern: '[[' };
|
||||
expect(grepTool.validateToolParams(params)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return error if path does not exist', () => {
|
||||
const params: RipGrepToolParams = {
|
||||
pattern: 'hello',
|
||||
@@ -1018,80 +1016,26 @@ describe('RipGrepTool', () => {
|
||||
expect(() => grepTool.build(params)).toThrow(/Path validation failed/);
|
||||
});
|
||||
|
||||
it('should handle empty directories gracefully', async () => {
|
||||
const emptyDir = path.join(tempRootDir, 'empty');
|
||||
await fs.mkdir(emptyDir);
|
||||
it.each([
|
||||
{
|
||||
name: 'empty directories',
|
||||
setup: async () => {
|
||||
const emptyDir = path.join(tempRootDir, 'empty');
|
||||
await fs.mkdir(emptyDir);
|
||||
return { pattern: 'test', dir_path: 'empty' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'empty files',
|
||||
setup: async () => {
|
||||
await fs.writeFile(path.join(tempRootDir, 'empty.txt'), '');
|
||||
return { pattern: 'anything' };
|
||||
},
|
||||
},
|
||||
])('should handle $name gracefully', async ({ setup }) => {
|
||||
mockSpawn.mockImplementationOnce(createMockSpawn({ exitCode: 1 }));
|
||||
|
||||
// Setup specific mock for this test - searching in empty directory should return no matches
|
||||
mockSpawn.mockImplementationOnce(() => {
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
},
|
||||
stderr: {
|
||||
on: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
},
|
||||
on: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
kill: vi.fn(),
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
const onClose = mockProcess.on.mock.calls.find(
|
||||
(call) => call[0] === 'close',
|
||||
)?.[1];
|
||||
|
||||
if (onClose) {
|
||||
onClose(1);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return mockProcess as unknown as ChildProcess;
|
||||
});
|
||||
|
||||
const params: RipGrepToolParams = { pattern: 'test', dir_path: 'empty' };
|
||||
const invocation = grepTool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(result.llmContent).toContain('No matches found');
|
||||
expect(result.returnDisplay).toBe('No matches found');
|
||||
});
|
||||
|
||||
it('should handle empty files correctly', async () => {
|
||||
await fs.writeFile(path.join(tempRootDir, 'empty.txt'), '');
|
||||
|
||||
// Setup specific mock for this test - searching for anything in empty files should return no matches
|
||||
mockSpawn.mockImplementationOnce(() => {
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
},
|
||||
stderr: {
|
||||
on: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
},
|
||||
on: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
kill: vi.fn(),
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
const onClose = mockProcess.on.mock.calls.find(
|
||||
(call) => call[0] === 'close',
|
||||
)?.[1];
|
||||
|
||||
if (onClose) {
|
||||
onClose(1);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return mockProcess as unknown as ChildProcess;
|
||||
});
|
||||
|
||||
const params: RipGrepToolParams = { pattern: 'anything' };
|
||||
const params = await setup();
|
||||
const invocation = grepTool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
@@ -1627,38 +1571,42 @@ describe('RipGrepTool', () => {
|
||||
expect(result.llmContent).toContain('L1: HELLO world');
|
||||
});
|
||||
|
||||
it('should handle fixed_strings parameter', async () => {
|
||||
mockSpawn.mockImplementationOnce(
|
||||
createMockSpawn({
|
||||
outputData:
|
||||
JSON.stringify({
|
||||
type: 'match',
|
||||
data: {
|
||||
path: { text: 'fileA.txt' },
|
||||
line_number: 1,
|
||||
lines: { text: 'hello.world\n' },
|
||||
},
|
||||
}) + '\n',
|
||||
exitCode: 0,
|
||||
}),
|
||||
);
|
||||
it.each([
|
||||
{
|
||||
name: 'fixed_strings parameter',
|
||||
params: { pattern: 'hello.world', fixed_strings: true },
|
||||
mockOutput: {
|
||||
path: { text: 'fileA.txt' },
|
||||
line_number: 1,
|
||||
lines: { text: 'hello.world\n' },
|
||||
},
|
||||
expectedArgs: ['--fixed-strings'],
|
||||
expectedPattern: 'hello.world',
|
||||
},
|
||||
])(
|
||||
'should handle $name',
|
||||
async ({ params, mockOutput, expectedArgs, expectedPattern }) => {
|
||||
mockSpawn.mockImplementationOnce(
|
||||
createMockSpawn({
|
||||
outputData:
|
||||
JSON.stringify({ type: 'match', data: mockOutput }) + '\n',
|
||||
exitCode: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
const params: RipGrepToolParams = {
|
||||
pattern: 'hello.world',
|
||||
fixed_strings: true,
|
||||
};
|
||||
const invocation = grepTool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
expect(mockSpawn).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining(['--fixed-strings']),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(result.llmContent).toContain(
|
||||
'Found 1 match for pattern "hello.world"',
|
||||
);
|
||||
expect(result.llmContent).toContain('L1: hello.world');
|
||||
});
|
||||
const invocation = grepTool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(mockSpawn).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining(expectedArgs),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(result.llmContent).toContain(
|
||||
`Found 1 match for pattern "${expectedPattern}"`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('should handle no_ignore parameter', async () => {
|
||||
mockSpawn.mockImplementationOnce(
|
||||
@@ -1680,14 +1628,12 @@ describe('RipGrepTool', () => {
|
||||
const invocation = grepTool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
// Should have --no-ignore
|
||||
expect(mockSpawn).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.arrayContaining(['--no-ignore']),
|
||||
expect.anything(),
|
||||
);
|
||||
|
||||
// Should NOT have default excludes when no_ignore is true
|
||||
expect(mockSpawn).toHaveBeenLastCalledWith(
|
||||
expect.anything(),
|
||||
expect.not.arrayContaining(['--glob', '!node_modules']),
|
||||
@@ -1749,22 +1695,29 @@ describe('RipGrepTool', () => {
|
||||
});
|
||||
|
||||
describe('getDescription', () => {
|
||||
it('should generate correct description with pattern only', () => {
|
||||
const params: RipGrepToolParams = { pattern: 'testPattern' };
|
||||
const invocation = grepTool.build(params);
|
||||
expect(invocation.getDescription()).toBe("'testPattern' within ./");
|
||||
});
|
||||
|
||||
it('should generate correct description with pattern and include', () => {
|
||||
const params: RipGrepToolParams = {
|
||||
pattern: 'testPattern',
|
||||
include: '*.ts',
|
||||
};
|
||||
const invocation = grepTool.build(params);
|
||||
expect(invocation.getDescription()).toBe(
|
||||
"'testPattern' in *.ts within ./",
|
||||
);
|
||||
});
|
||||
it.each([
|
||||
{
|
||||
name: 'pattern only',
|
||||
params: { pattern: 'testPattern' },
|
||||
expected: "'testPattern' within ./",
|
||||
},
|
||||
{
|
||||
name: 'pattern and include',
|
||||
params: { pattern: 'testPattern', include: '*.ts' },
|
||||
expected: "'testPattern' in *.ts within ./",
|
||||
},
|
||||
{
|
||||
name: 'root path in description',
|
||||
params: { pattern: 'testPattern', dir_path: '.' },
|
||||
expected: "'testPattern' within ./",
|
||||
},
|
||||
])(
|
||||
'should generate correct description with $name',
|
||||
({ params, expected }) => {
|
||||
const invocation = grepTool.build(params);
|
||||
expect(invocation.getDescription()).toBe(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('should generate correct description with pattern and path', async () => {
|
||||
const dirPath = path.join(tempRootDir, 'src', 'app');
|
||||
@@ -1774,13 +1727,11 @@ describe('RipGrepTool', () => {
|
||||
dir_path: path.join('src', 'app'),
|
||||
};
|
||||
const invocation = grepTool.build(params);
|
||||
// The path will be relative to the tempRootDir, so we check for containment.
|
||||
expect(invocation.getDescription()).toContain("'testPattern' within");
|
||||
expect(invocation.getDescription()).toContain(path.join('src', 'app'));
|
||||
});
|
||||
|
||||
it('should use ./ when no path is specified (defaults to CWD)', () => {
|
||||
// Create a mock config with multiple directories
|
||||
const multiDirConfig = {
|
||||
getTargetDir: () => tempRootDir,
|
||||
getWorkspaceContext: () =>
|
||||
@@ -1808,15 +1759,6 @@ describe('RipGrepTool', () => {
|
||||
);
|
||||
expect(invocation.getDescription()).toContain(path.join('src', 'app'));
|
||||
});
|
||||
|
||||
it('should use ./ for root path in description', () => {
|
||||
const params: RipGrepToolParams = {
|
||||
pattern: 'testPattern',
|
||||
dir_path: '.',
|
||||
};
|
||||
const invocation = grepTool.build(params);
|
||||
expect(invocation.getDescription()).toBe("'testPattern' within ./");
|
||||
});
|
||||
});
|
||||
});
|
||||
afterAll(() => {
|
||||
|
||||
Reference in New Issue
Block a user