tree-ast-grep-mcp
Version:
Simple, direct ast-grep wrapper for AI coding agents. Zero abstractions, maximum performance.
509 lines (447 loc) • 16.7 kB
JavaScript
/**
* Complex Use Case Integration Tests
* Tests for advanced scenarios, edge cases, and error conditions
*/
import { SearchTool } from '../../build/tools/search.js';
import { ReplaceTool } from '../../build/tools/replace.js';
import { ScanTool } from '../../build/tools/scan.js';
import { AstGrepBinaryManager } from '../../build/core/binary-manager.js';
import { WorkspaceManager } from '../../build/core/workspace-manager.js';
import { ValidationError, ExecutionError, BinaryError } from '../../build/types/errors.js';
import { TestSuite, TestAssert, withTimeout } from '../utils/test-helpers.js';
import fs from 'fs/promises';
import path from 'path';
import { tmpdir } from 'os';
export default async function runComplexScenariosTests() {
const suite = new TestSuite('Complex Scenarios Integration Tests');
let binaryManager;
let workspaceManager;
let searchTool;
let replaceTool;
let scanTool;
let tempDir;
suite.beforeAll(async () => {
binaryManager = new AstGrepBinaryManager({ useSystem: true });
workspaceManager = new WorkspaceManager();
searchTool = new SearchTool(binaryManager, workspaceManager);
replaceTool = new ReplaceTool(binaryManager, workspaceManager);
scanTool = new ScanTool(binaryManager, workspaceManager);
try {
await binaryManager.initialize();
} catch (error) {
console.log(' ⚠️ Binary manager initialization failed:', error.message);
}
tempDir = await fs.mkdtemp(path.join(tmpdir(), 'ast-grep-complex-test-'));
});
suite.afterAll(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
console.log(' ⚠️ Failed to clean up temp directory');
}
});
// =============================================================================
// COMPLEX PATTERN MATCHING TESTS
// =============================================================================
suite.test('should handle deeply nested patterns', async () => {
const complexCode = `
function processUser(user) {
if (user && user.profile) {
if (user.profile.settings) {
if (user.profile.settings.theme) {
console.log("Theme: " + user.profile.settings.theme);
return user.profile.settings.theme;
}
}
}
return "default";
}
`;
const params = {
pattern: 'if ($COND) { if ($INNER) { $$$BODY } }',
language: 'javascript',
code: complexCode
};
try {
const result = await withTimeout(searchTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
// Should find nested if statements
if (result.matches.length > 0) {
TestAssert.assertTrue(result.matches[0].text.includes('if'));
}
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping test - ast-grep not available');
return;
}
throw error;
}
});
suite.test('should handle complex metavariable patterns', async () => {
const complexCode = `
const users = data.filter(user => user.active && user.verified);
const products = items.filter(item => item.available && item.inStock);
const orders = records.filter(record => record.completed);
`;
const params = {
pattern: '$ARRAY.filter($PARAM => $CONDITION)',
language: 'javascript',
code: complexCode
};
try {
const result = await withTimeout(searchTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
// Should find all filter patterns
TestAssert.assertTrue(result.matches.length >= 2);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping test - ast-grep not available');
return;
}
throw error;
}
});
suite.test('should handle multiline patterns with complex syntax', async () => {
const complexCode = `
class UserService {
async getUser(id) {
try {
const user = await this.database.findById(id);
return user;
} catch (error) {
console.error("Failed to get user:", error);
throw error;
}
}
}
`;
const params = {
pattern: 'try { $$$TRY } catch ($ERROR) { $$$CATCH }',
language: 'javascript',
code: complexCode
};
try {
const result = await withTimeout(searchTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping test - ast-grep not available');
return;
}
throw error;
}
});
// =============================================================================
// ERROR HANDLING AND EDGE CASES
// =============================================================================
suite.test('should handle malformed patterns gracefully', async () => {
const params = {
pattern: 'console.log($ARG', // Missing closing parenthesis
language: 'javascript',
code: 'console.log("test");'
};
try {
await searchTool.execute(params);
// If it doesn't throw, that's also acceptable
TestAssert.assertTrue(true, 'Malformed pattern handled gracefully');
} catch (error) {
// Should be a proper error type
TestAssert.assertTrue(error instanceof Error);
TestAssert.assertTrue(
error instanceof ValidationError ||
error instanceof ExecutionError ||
error.message.includes('ast-grep not found')
);
}
});
suite.test('should handle empty and whitespace patterns', async () => {
const testCases = [
{ pattern: '', expectedError: 'Pattern is required' },
{ pattern: ' ', expectedError: 'Pattern is required' },
{ pattern: '\n\t', expectedError: 'Pattern is required' }
];
for (const testCase of testCases) {
try {
await searchTool.execute({
pattern: testCase.pattern,
language: 'javascript',
code: 'console.log("test");'
});
TestAssert.assertTrue(false, `Should have thrown error for pattern: "${testCase.pattern}"`);
} catch (error) {
TestAssert.assertContains(error.message, 'Pattern is required');
}
}
});
suite.test('should handle extremely large code inputs', async () => {
// Generate large code file
const largeCode = Array(1000).fill().map((_, i) =>
`function func${i}() { console.log("Function ${i}"); return ${i}; }`
).join('\n');
const params = {
pattern: 'function $NAME() { $$$BODY }',
language: 'javascript',
code: largeCode,
maxMatches: 10 // Limit results
};
try {
const result = await withTimeout(searchTool.execute(params), 15000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
TestAssert.assertTrue(result.matches.length <= 10);
} catch (error) {
if (error.message.includes('ast-grep not found') ||
error.message.includes('timeout')) {
console.log(' ⚠️ Skipping large input test - ' + error.message);
return;
}
throw error;
}
});
suite.test('should handle special characters and unicode', async () => {
const unicodeCode = `
const message = "Hello 世界! 🌍";
const emoji = "🚀 Rocket launched! 🎉";
const special = "String with \\n\\t\\r and quotes \\"test\\"";
`;
const params = {
pattern: 'const $NAME = $VALUE;',
language: 'javascript',
code: unicodeCode
};
try {
const result = await withTimeout(searchTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping unicode test - ast-grep not available');
return;
}
throw error;
}
});
// =============================================================================
// TIMEOUT AND PERFORMANCE TESTS
// =============================================================================
suite.test('should respect timeout limits', async () => {
const params = {
pattern: 'console.log($ARG)',
language: 'javascript',
code: 'console.log("test");',
timeoutMs: 1 // Very short timeout
};
try {
await searchTool.execute(params);
// If it completes quickly, that's fine
TestAssert.assertTrue(true, 'Fast execution within timeout');
} catch (error) {
// Should handle timeout gracefully
TestAssert.assertTrue(error instanceof Error);
TestAssert.assertTrue(
error.message.includes('timeout') ||
error.message.includes('ast-grep not found')
);
}
});
suite.test('should handle concurrent operations', async () => {
const operations = Array(5).fill().map((_, i) =>
searchTool.execute({
pattern: `console.log($ARG)`,
language: 'javascript',
code: `console.log("Test ${i}");`
})
);
try {
const results = await Promise.allSettled(operations);
// At least some should succeed or fail gracefully
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
TestAssert.assertTrue(successful + failed === 5);
// Check that failures are proper errors
results.filter(r => r.status === 'rejected').forEach(result => {
TestAssert.assertTrue(result.reason instanceof Error);
});
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping concurrent test - ast-grep not available');
return;
}
throw error;
}
});
// =============================================================================
// VALIDATION AND SECURITY TESTS
// =============================================================================
suite.test('should validate language parameter thoroughly', async () => {
const invalidLanguages = ['', ' ', 'invalid-lang', '123', null, undefined];
for (const lang of invalidLanguages) {
try {
await searchTool.execute({
pattern: 'console.log($ARG)',
language: lang,
code: 'console.log("test");'
});
// Some invalid languages might be handled gracefully by ast-grep
TestAssert.assertTrue(true, `Language "${lang}" handled gracefully`);
} catch (error) {
// Should be proper validation or execution error
TestAssert.assertTrue(error instanceof Error);
}
}
});
suite.test('should handle path traversal attempts safely', async () => {
const dangerousPaths = [
'../../../etc/passwd',
'..\\..\\..\\windows\\system32',
'/etc/shadow',
'C:\\Windows\\System32\\config\\SAM'
];
for (const dangerousPath of dangerousPaths) {
try {
const result = await searchTool.execute({
pattern: 'console.log($ARG)',
language: 'javascript',
paths: [dangerousPath]
});
// Should either handle safely or throw appropriate error
TestAssert.assertTrue(typeof result === 'object');
} catch (error) {
// Security errors or file not found are acceptable
TestAssert.assertTrue(error instanceof Error);
TestAssert.assertTrue(
error.message.includes('not found') ||
error.message.includes('access') ||
error.message.includes('permission') ||
error.message.includes('ast-grep not found')
);
}
}
});
// =============================================================================
// COMPLEX REPLACEMENT SCENARIOS
// =============================================================================
suite.test('should handle complex nested replacements', async () => {
const complexCode = `
if (user.isAdmin()) {
if (user.hasPermission("write")) {
console.log("Admin has write permission");
database.updateUser(user.id, { lastAccess: new Date() });
}
}
`;
const params = {
pattern: 'if ($OUTER) { if ($INNER) { $$$BODY } }',
replacement: 'if ($OUTER && $INNER) { $$$BODY }',
language: 'javascript',
code: complexCode,
dryRun: true
};
try {
const result = await withTimeout(replaceTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('changes' in result || 'summary' in result);
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping complex replacement test - ast-grep not available');
return;
}
throw error;
}
});
suite.test('should handle replacement with special characters', async () => {
const code = `console.log("Hello");`;
const params = {
pattern: 'console.log($MSG)',
replacement: 'logger.info(`🚀 ${$MSG} 🎉`)',
language: 'javascript',
code: code,
dryRun: true
};
try {
const result = await withTimeout(replaceTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping special character test - ast-grep not available');
return;
}
throw error;
}
});
// =============================================================================
// FILE SYSTEM STRESS TESTS
// =============================================================================
suite.test('should handle operations on large directory structures', async () => {
// Create nested directory structure
const deepDir = path.join(tempDir, 'level1', 'level2', 'level3', 'level4');
await fs.mkdir(deepDir, { recursive: true });
// Create multiple files
for (let i = 0; i < 10; i++) {
await fs.writeFile(
path.join(deepDir, `file${i}.js`),
`console.log("File ${i}"); function test${i}() { return ${i}; }`
);
}
const params = {
pattern: 'console.log($MSG)',
language: 'javascript',
paths: [tempDir]
};
try {
const result = await withTimeout(searchTool.execute(params), 15000);
TestAssert.assertTrue(typeof result === 'object');
TestAssert.assertTrue('matches' in result);
} catch (error) {
if (error.message.includes('ast-grep not found') ||
error.message.includes('timeout')) {
console.log(' ⚠️ Skipping directory structure test - ' + error.message);
return;
}
throw error;
}
});
// =============================================================================
// BOUNDARY AND LIMIT TESTS
// =============================================================================
suite.test('should handle maximum context values', async () => {
const params = {
pattern: 'console.log($ARG)',
language: 'javascript',
code: Array(100).fill('console.log("test");').join('\n'),
context: 50 // Large context
};
try {
const result = await withTimeout(searchTool.execute(params), 10000);
TestAssert.assertTrue(typeof result === 'object');
} catch (error) {
if (error.message.includes('ast-grep not found')) {
console.log(' ⚠️ Skipping context test - ast-grep not available');
return;
}
throw error;
}
});
suite.test('should handle zero and negative numeric parameters', async () => {
const params = {
pattern: 'console.log($ARG)',
language: 'javascript',
code: 'console.log("test");',
context: -1,
maxMatches: 0,
timeoutMs: -1000
};
try {
const result = await searchTool.execute(params);
// Should handle gracefully or use defaults
TestAssert.assertTrue(typeof result === 'object');
} catch (error) {
// Should be proper validation error
TestAssert.assertTrue(error instanceof Error);
}
});
return suite.run();
}