mirror of https://github.com/buster-so/buster.git
465 lines
15 KiB
TypeScript
465 lines
15 KiB
TypeScript
import { createSandbox } from '@buster/sandbox';
|
|
import { RuntimeContext } from '@mastra/core/runtime-context';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import { DocsAgentContextKeys } from '../../../agents/docs-agent/docs-agent-context';
|
|
import { grepSearch } from './grep-search-tool';
|
|
|
|
describe.sequential('grep-search-tool integration test', () => {
|
|
const hasApiKey = !!process.env.DAYTONA_API_KEY;
|
|
let sharedSandbox: any;
|
|
|
|
beforeAll(async () => {
|
|
if (hasApiKey) {
|
|
sharedSandbox = await createSandbox({
|
|
language: 'typescript',
|
|
});
|
|
}
|
|
}, 120000);
|
|
|
|
afterAll(async () => {
|
|
if (sharedSandbox) {
|
|
await sharedSandbox.delete();
|
|
}
|
|
}, 65000);
|
|
|
|
function getTestDir() {
|
|
return `test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should execute ripgrep commands in sandbox environment',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// First, create test files with searchable content
|
|
const createFilesCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('test1.txt', 'Hello world\\nThis is a test file\\nGoodbye world');
|
|
fs.writeFileSync('test2.txt', 'Another test file\\nHello again\\nMore content here');
|
|
fs.mkdirSync('subdir', { recursive: true });
|
|
fs.writeFileSync('subdir/test3.txt', 'Nested file\\nHello from subdirectory\\nEnd of file');
|
|
|
|
console.log('Files created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFilesCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -n "test" test1.txt`, `cd ${testDir} && rg -n "Hello" .`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(2);
|
|
|
|
// Check first command results
|
|
const firstResult = result.results[0];
|
|
expect(firstResult?.success).toBe(true);
|
|
expect(firstResult?.command).toBe(`cd ${testDir} && rg -n "test" test1.txt`);
|
|
expect(firstResult?.stdout).toContain('2:This is a test file');
|
|
|
|
// Check second command results (searches all files)
|
|
const secondResult = result.results[1];
|
|
expect(secondResult?.success).toBe(true);
|
|
expect(secondResult?.command).toBe(`cd ${testDir} && rg -n "Hello" .`);
|
|
expect(secondResult?.stdout).toContain('test1.txt:1:Hello world');
|
|
expect(secondResult?.stdout).toContain('test2.txt:2:Hello again');
|
|
expect(secondResult?.stdout).toContain('test3.txt:2:Hello from subdirectory');
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle case-insensitive searches',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file with mixed case content
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('case-test.txt', 'HELLO World\\nhello world\\nHeLLo WoRLd');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -i -n "hello" case-test.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
expect(search?.stdout).toContain('1:HELLO World');
|
|
expect(search?.stdout).toContain('2:hello world');
|
|
expect(search?.stdout).toContain('3:HeLLo WoRLd');
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle whole word matches',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('word-test.txt', 'test testing\\ntested tester\\ntest');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -w -n "test" word-test.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
expect(search?.stdout).toContain('1:test testing');
|
|
expect(search?.stdout).toContain('3:test');
|
|
expect(search?.stdout).not.toContain('tester'); // Should not match partial words
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle fixed string searches (literal)',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file with regex special characters
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('regex-test.txt', 'Price: $10.99\\nPattern: test.*\\nArray: [1,2,3]');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -F -n "$10.99" regex-test.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
expect(search?.stdout).toContain('1:Price: $10.99');
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle inverted matches',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('invert-test.txt', 'line with test\\nline without\\nanother test line\\nno match here');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -v -n "test" invert-test.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
expect(search?.stdout).toContain('2:line without');
|
|
expect(search?.stdout).toContain('4:no match here');
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle max count option',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file with multiple matches
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('many-matches.txt', 'test 1\\ntest 2\\ntest 3\\ntest 4\\ntest 5');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -m 3 -n "test" many-matches.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
const lines = search?.stdout?.trim().split('\n') || [];
|
|
expect(lines).toHaveLength(3); // Should only return 3 matches
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle no matches found',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file with no matching content
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('no-match.txt', 'This file has no matches\\nNothing to find here');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg -n "nonexistent" no-match.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true); // Exit code 1 is treated as success for rg
|
|
expect(search?.stdout).toBe('');
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle multiple commands',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create test files
|
|
const createFilesCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('file1.txt', 'First file with test');
|
|
fs.writeFileSync('file2.txt', 'Second file with test');
|
|
console.log('Files created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFilesCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [
|
|
`cd ${testDir} && rg -n "test" file1.txt`,
|
|
`cd ${testDir} && rg -n "test" file2.txt`,
|
|
`cd ${testDir} && rg -n "nonexistent" file1.txt`,
|
|
],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(3);
|
|
expect(result.results[0]?.stdout).toContain('First file with test');
|
|
expect(result.results[1]?.stdout).toContain('Second file with test');
|
|
expect(result.results[2]?.stdout).toBe(''); // No match
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle file not found error',
|
|
async () => {
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: ['rg "test" /nonexistent/path/file.txt'],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(false);
|
|
expect(search?.error).toBeDefined();
|
|
expect(search?.stderr).toBeDefined();
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle complex rg commands with multiple flags',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create test files
|
|
const createFilesCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.mkdirSync('src', { recursive: true });
|
|
fs.writeFileSync('src/main.ts', 'TODO: implement feature\\nconsole.log("test");');
|
|
fs.writeFileSync('src/utils.ts', 'TODO: fix bug\\nexport function test() {}');
|
|
fs.writeFileSync('src/readme.md', 'TODO: update docs');
|
|
console.log('Files created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFilesCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg --type ts --color never -n "TODO" src/`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
expect(search?.stdout).toContain('src/main.ts:1:TODO: implement feature');
|
|
expect(search?.stdout).toContain('src/utils.ts:1:TODO: fix bug');
|
|
expect(search?.stdout).not.toContain('readme.md'); // Should not match .md files
|
|
},
|
|
65000
|
|
);
|
|
|
|
(hasApiKey ? it : it.skip)(
|
|
'should handle JSON output from rg',
|
|
async () => {
|
|
const testDir = getTestDir();
|
|
|
|
// Create a test file
|
|
const createFileCode = `
|
|
const fs = require('fs');
|
|
|
|
// Create and enter test directory
|
|
fs.mkdirSync('${testDir}', { recursive: true });
|
|
process.chdir('${testDir}');
|
|
|
|
fs.writeFileSync('json-test.txt', 'Line with test\\nAnother line');
|
|
console.log('File created in ' + process.cwd());
|
|
`;
|
|
|
|
await sharedSandbox.process.codeRun(createFileCode);
|
|
|
|
const runtimeContext = new RuntimeContext();
|
|
runtimeContext.set(DocsAgentContextKeys.Sandbox, sharedSandbox);
|
|
|
|
const result = await grepSearch.execute({
|
|
context: {
|
|
commands: [`cd ${testDir} && rg --json "test" json-test.txt`],
|
|
},
|
|
runtimeContext,
|
|
});
|
|
|
|
expect(result.results).toHaveLength(1);
|
|
const search = result.results[0];
|
|
expect(search?.success).toBe(true);
|
|
|
|
// Parse JSON output
|
|
const jsonLines = search?.stdout?.trim().split('\n') || [];
|
|
expect(jsonLines.length).toBeGreaterThan(0);
|
|
|
|
const firstLine = JSON.parse(jsonLines[0] || '{}');
|
|
expect(firstLine.type).toBe('begin');
|
|
|
|
// Find the match line
|
|
const matchLine = jsonLines.find((line) => {
|
|
const parsed = JSON.parse(line);
|
|
return parsed.type === 'match';
|
|
});
|
|
expect(matchLine).toBeDefined();
|
|
},
|
|
65000
|
|
);
|
|
});
|