/**
* Integration tests for all local explorer MCP tools
* Tests all 4 tools on actual node_modules to verify they return smart research data
*/
import { describe, it, expect } from 'vitest';
import { searchContentRipgrep } from '../../src/tools/local_ripgrep.js';
import { viewStructure } from '../../src/tools/local_view_structure.js';
import { findFiles } from '../../src/tools/local_find_files.js';
import { fetchContent } from '../../src/tools/local_fetch_content.js';
import type {
SearchContentResult,
ViewStructureResult,
FindFilesResult,
FetchContentResult,
} from '../../src/types.js';
import { RipgrepQuerySchema } from '../../src/scheme/local_ripgrep.js';
import path from 'path';
const NODE_MODULES_PATH = path.resolve(process.cwd(), 'node_modules');
const runRipgrep = (
query: Parameters<typeof RipgrepQuerySchema.parse>[0]
) => searchContentRipgrep(RipgrepQuerySchema.parse(query));
type ToolResult =
| SearchContentResult
| ViewStructureResult
| FindFilesResult
| FetchContentResult;
function verifySmartData<T extends ToolResult>(
result: T,
toolName: string
): T {
expect(result, `${toolName} should return a result object`).toBeDefined();
expect(result.status, `${toolName} should have status field`).toBeDefined();
expect(['hasResults', 'empty', 'error']).toContain(result.status);
if (result.status === 'hasResults') {
const hasFiles =
'files' in result && Array.isArray(result.files) && result.files.length > 0;
const hasContent =
'content' in result && typeof result.content === 'string' && result.content.length > 0;
const hasStructuredOutput =
'structuredOutput' in result &&
typeof result.structuredOutput === 'string' &&
result.structuredOutput.length > 0;
const hasPagination = Boolean(result.pagination);
expect(
hasFiles || hasContent || hasStructuredOutput || hasPagination,
`${toolName} should have data when status is hasResults`
).toBe(true);
}
if (result.hints) {
expect(Array.isArray(result.hints), `${toolName} hints should be an array`).toBe(true);
}
return result;
}
describe('Integration Tests: All Tools on node_modules', () => {
describe('local_ripgrep - Pattern Search', () => {
it('should find patterns in JavaScript files', async () => {
const result = await runRipgrep({
pattern: 'export',
path: NODE_MODULES_PATH,
include: ['*.js'],
matchesPerPage: 5,
researchGoal: 'Find exported functions in JavaScript files',
reasoning: 'Testing pattern search on node_modules',
});
verifySmartData(result, 'local_ripgrep');
if (result.status === 'hasResults') {
expect(result.files).toBeDefined();
expect(Array.isArray(result.files)).toBe(true);
}
});
it('should find files only mode', async () => {
const result = await runRipgrep({
pattern: 'package.json',
path: NODE_MODULES_PATH,
filesOnly: true,
maxFiles: 10,
researchGoal: 'Find package.json files',
reasoning: 'Testing filesOnly mode',
});
verifySmartData(result, 'local_ripgrep');
if (result.status === 'hasResults') {
expect(result.files).toBeDefined();
expect(Array.isArray(result.files)).toBe(true);
}
});
});
describe('local_view_structure - Directory Listing', () => {
it('should list directory contents', async () => {
const result = await viewStructure({
path: NODE_MODULES_PATH,
details: false,
entriesPerPage: 20,
researchGoal: 'List top-level node_modules contents',
reasoning: 'Testing basic directory listing',
});
verifySmartData(result, 'local_view_structure');
if (result.status === 'hasResults') {
expect(result.structuredOutput).toBeDefined();
}
});
it('should provide detailed file information', async () => {
const result = await viewStructure({
path: NODE_MODULES_PATH,
details: true,
entriesPerPage: 10,
sortBy: 'size',
researchGoal: 'Get detailed file information sorted by size',
reasoning: 'Testing detailed listing with sorting',
});
verifySmartData(result, 'local_view_structure');
if (result.status === 'hasResults') {
expect(result.structuredOutput).toBeDefined();
}
});
it('should generate tree view', async () => {
const result = await viewStructure({
path: NODE_MODULES_PATH,
depth: 2,
researchGoal: 'Get tree structure view',
reasoning: 'Testing tree view mode',
});
verifySmartData(result, 'local_view_structure');
if (result.status === 'hasResults') {
expect(result.structuredOutput).toBeDefined();
}
});
});
describe('local_find_files - File Discovery', () => {
it('should find files by name', async () => {
const result = await findFiles({
path: NODE_MODULES_PATH,
name: 'package.json',
maxDepth: 2,
filesPerPage: 20,
researchGoal: 'Find package.json files',
reasoning: 'Testing name-based file discovery',
});
verifySmartData(result, 'local_find_files');
if (result.status === 'hasResults') {
expect(result.files).toBeDefined();
expect(Array.isArray(result.files)).toBe(true);
}
});
it('should find files by extension', async () => {
const result = await findFiles({
path: NODE_MODULES_PATH,
type: 'f',
names: ['*.md'],
filesPerPage: 10,
researchGoal: 'Find markdown documentation files',
reasoning: 'Testing extension-based discovery',
});
verifySmartData(result, 'local_find_files');
if (result.status === 'hasResults') {
expect(result.files).toBeDefined();
}
});
it('should find directories', async () => {
const result = await findFiles({
path: NODE_MODULES_PATH,
type: 'd',
maxDepth: 1,
filesPerPage: 15,
researchGoal: 'Find top-level directories',
reasoning: 'Testing directory discovery',
});
verifySmartData(result, 'local_find_files');
if (result.status === 'hasResults') {
expect(result.files).toBeDefined();
}
});
});
describe('local_fetch_content - File Content Reading', () => {
let testFile: string | null = null;
it('should find a test file first', async () => {
const findResult = await findFiles({
path: NODE_MODULES_PATH,
name: 'package.json',
maxDepth: 2,
filesPerPage: 5,
researchGoal: 'Find package.json files',
reasoning: 'Testing file discovery for fetch_content tests',
});
if (findResult.status === 'hasResults' && findResult.files && findResult.files.length > 0) {
testFile = typeof findResult.files[0] === 'string' ? findResult.files[0] : findResult.files[0].path;
} else {
// Fallback: try to find any JavaScript file
const jsFileResult = await findFiles({
path: NODE_MODULES_PATH,
names: ['*.js'],
filesPerPage: 1,
});
if (jsFileResult.status === 'hasResults' && jsFileResult.files && jsFileResult.files.length > 0) {
testFile = typeof jsFileResult.files[0] === 'string' ? jsFileResult.files[0] : jsFileResult.files[0].path;
}
}
// Don't fail if no file found - subsequent tests will skip
if (!testFile) {
console.warn('Could not find a test file for fetch_content tests');
}
});
it('should read full file content', async () => {
if (!testFile) {
// Skip if no test file found
return;
}
const result = await fetchContent({
path: testFile,
fullContent: true,
minified: false,
researchGoal: 'Read full package.json content',
reasoning: 'Testing full content fetch',
});
verifySmartData(result, 'local_fetch_content');
if (result.status === 'hasResults') {
expect(result.content).toBeDefined();
expect(typeof result.content).toBe('string');
}
});
it('should read line range', async () => {
if (!testFile) {
return;
}
const result = await fetchContent({
path: testFile,
charOffset: 0,
charLength: 2000,
researchGoal: 'Read first 20 lines',
reasoning: 'Testing line range fetch',
});
verifySmartData(result, 'local_fetch_content');
if (result.status === 'hasResults') {
expect(result.content).toBeDefined();
}
});
it('should extract pattern-based content', async () => {
if (!testFile) {
return;
}
const result = await fetchContent({
path: testFile,
matchString: 'dependencies',
matchStringContextLines: 5,
researchGoal: 'Extract dependencies section',
reasoning: 'Testing pattern-based extraction',
});
verifySmartData(result, 'local_fetch_content');
if (result.status === 'hasResults') {
expect(result.content).toBeDefined();
}
});
});
});