Skip to main content
Glama

Claude Desktop Commander MCP

test-literal-search.js14.6 kB
/** * Test for literal search functionality - testing regex vs literal string matching */ import path from 'path'; import fs from 'fs/promises'; import { fileURLToPath } from 'url'; import { handleStartSearch, handleGetMoreSearchResults, handleStopSearch } from '../dist/handlers/search-handlers.js'; import { configManager } from '../dist/config-manager.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Test directory for literal search tests const LITERAL_SEARCH_TEST_DIR = path.join(__dirname, 'literal-search-test-files'); // Colors for console output const colors = { reset: '\x1b[0m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[34m' }; /** * Helper function to wait for search completion and get all results */ async function searchAndWaitForCompletion(searchArgs, timeout = 10000) { const result = await handleStartSearch(searchArgs); // Extract session ID from result const sessionIdMatch = result.content[0].text.match(/Started .+ session: (.+)/); if (!sessionIdMatch) { throw new Error('Could not extract session ID from search result'); } const sessionId = sessionIdMatch[1]; try { // Wait for completion by polling const startTime = Date.now(); while (Date.now() - startTime < timeout) { const moreResults = await handleGetMoreSearchResults({ sessionId }); if (moreResults.content[0].text.includes('✅ Search completed')) { return { initialResult: result, finalResult: moreResults, sessionId }; } if (moreResults.content[0].text.includes('❌ ERROR')) { throw new Error(`Search failed: ${moreResults.content[0].text}`); } // Wait a bit before polling again await new Promise(resolve => setTimeout(resolve, 100)); } throw new Error('Search timed out'); } finally { // Always stop the search session to prevent hanging try { await handleStopSearch({ sessionId }); } catch (e) { // Ignore errors when stopping - session might already be completed } } } /** * Setup function to prepare literal search test environment */ async function setup() { console.log(`${colors.blue}Setting up literal search tests...${colors.reset}`); // Save original config const originalConfig = await configManager.getConfig(); // Set allowed directories to include test directory await configManager.setValue('allowedDirectories', [LITERAL_SEARCH_TEST_DIR]); // Create test directory await fs.mkdir(LITERAL_SEARCH_TEST_DIR, { recursive: true }); // Create test file with patterns that contain regex special characters await fs.writeFile(path.join(LITERAL_SEARCH_TEST_DIR, 'code-patterns.js'), `// JavaScript file with common code patterns // These patterns contain regex special characters that should be matched literally // Function calls with parentheses toast.error("test"); console.log("hello world"); alert("message"); // Array access with brackets array[0] data[index] items[key] // Object method calls with dots obj.method() user.getName() config.getValue() // Template literals with backticks const msg = \`Hello \${name}\`; const query = \`SELECT * FROM users WHERE id = \${id}\`; // Regex special characters const pattern = ".*"; const wildcard = "test*"; const question = "value?"; const plus = "count++"; const caret = "^start"; const dollar = "end$"; const pipe = "a|b"; const backslash = "path\\\\to\\\\file"; // Complex patterns if (condition && obj.method()) { toast.error("Error occurred"); } function validateEmail(email) { return email.includes("@") && email.includes("."); } `); // Create another file with similar but different patterns await fs.writeFile(path.join(LITERAL_SEARCH_TEST_DIR, 'similar-patterns.ts'), `// TypeScript file with similar patterns interface Config { getValue(): string; } class Logger { static error(message: string): void { console.error(\`[ERROR] \${message}\`); } static log(message: string): void { console.log(message); } } // These should NOT match when searching for exact patterns toast.errorHandler("test"); // Similar but different console.logout("hello world"); // Similar but different array.slice(0, 1); // Similar but different `); console.log(`${colors.green}✓ Setup complete: Literal search test files created${colors.reset}`); return originalConfig; } /** * Teardown function to clean up after tests */ async function teardown(originalConfig) { console.log(`${colors.blue}Cleaning up literal search tests...${colors.reset}`); // Clean up test directory await fs.rm(LITERAL_SEARCH_TEST_DIR, { force: true, recursive: true }); // Restore original config await configManager.updateConfig(originalConfig); console.log(`${colors.green}✓ Teardown complete: Test files removed and config restored${colors.reset}`); } /** * Assert function for test validation */ function assert(condition, message) { if (!condition) { throw new Error(`Assertion failed: ${message}`); } } /** * Count occurrences of a pattern in text */ function countOccurrences(text, pattern) { return (text.match(new RegExp(pattern, 'g')) || []).length; } /** * Test that literal search finds exact matches for patterns with special characters * This test should FAIL initially since literalSearch parameter doesn't exist yet */ async function testLiteralSearchExactMatches() { console.log(`${colors.yellow}Testing literal search for exact matches...${colors.reset}`); // Test 1: Search for exact function call with quotes and parentheses const { finalResult: result1 } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'toast.error("test")', searchType: 'content', literalSearch: true // This parameter should be added }); const text1 = result1.content[0].text; // Should find exactly 2 occurrences (one in each file: the exact match and in comment) const exactMatches = countOccurrences(text1, 'toast\\.error\\("test"\\)'); assert(exactMatches >= 1, `Should find exact matches for 'toast.error("test")', found: ${exactMatches}`); // Should NOT find the similar but different pattern assert(!text1.includes('toast.errorHandler'), 'Should not match similar but different patterns'); console.log(`${colors.green}✓ Literal search exact matches test passed${colors.reset}`); } /** * Test that regex search works differently than literal search */ async function testRegexVsLiteralDifference() { console.log(`${colors.yellow}Testing difference between regex and literal search...${colors.reset}`); // Test with regex (default behavior) - should interpret dots as wildcard const { finalResult: regexResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'console.log', // Dot should match any character in regex mode searchType: 'content', literalSearch: false // Explicit regex mode }); // Test with literal search - should match exact dots const { finalResult: literalResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'console.log', // Dot should match literal dot only searchType: 'content', literalSearch: true }); const regexText = regexResult.content[0].text; const literalText = literalResult.content[0].text; // Both should find console.log, but regex might find more due to dot wildcard behavior assert(regexText.includes('console.log'), 'Regex search should find console.log'); assert(literalText.includes('console.log'), 'Literal search should find console.log'); console.log(`${colors.green}✓ Regex vs literal difference test passed${colors.reset}`); } /** * Test literal search with various special characters */ async function testSpecialCharactersLiteralSearch() { console.log(`${colors.yellow}Testing literal search with various special characters...${colors.reset}`); const testPatterns = [ 'array[0]', // Brackets 'obj.method()', // Dots and parentheses 'count++', // Plus signs 'value?', // Question mark 'pattern.*', // Dot and asterisk '^start', // Caret 'end$', // Dollar sign 'a|b', // Pipe 'path\\\\to\\\\file' // Backslashes ]; for (const pattern of testPatterns) { console.log(` Testing pattern: ${pattern}`); const { finalResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: pattern, searchType: 'content', literalSearch: true }); const text = finalResult.content[0].text; // Should find the pattern or indicate no matches (both are valid for literal search) const hasResults = text.includes(pattern) || text.includes('No matches found') || text.includes('Total results found: 0'); assert(hasResults, `Should handle literal search for pattern '${pattern}' (found results or no matches message)`); } console.log(`${colors.green}✓ Special characters literal search test passed${colors.reset}`); } /** * Test that literalSearch parameter defaults to false (maintains backward compatibility) */ async function testLiteralSearchDefault() { console.log(`${colors.yellow}Testing that literalSearch defaults to false...${colors.reset}`); // Search without specifying literalSearch - should default to regex behavior const { finalResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'console.log', searchType: 'content' // literalSearch not specified - should default to false }); const text = finalResult.content[0].text; // Should work (either find matches or no matches, but not error) const isValidResult = text.includes('console.log') || text.includes('No matches found') || text.includes('Total results found'); assert(isValidResult, 'Should handle search with default literalSearch behavior'); console.log(`${colors.green}✓ Literal search default behavior test passed${colors.reset}`); } /** * Test the specific failing case that motivated this fix */ async function testOriginalFailingCase() { console.log(`${colors.yellow}Testing the original failing case that motivated this fix...${colors.reset}`); // This was the original failing search: toast.error("test") const { finalResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'toast.error("test")', searchType: 'content', literalSearch: true }); const text = finalResult.content[0].text; // Should find the exact match assert(text.includes('toast.error("test")') || text.includes('code-patterns.js'), 'Should find the exact pattern that was originally failing'); // Verify it contains the file where we know the pattern exists assert(text.includes('code-patterns.js'), 'Should find matches in code-patterns.js file'); console.log(`${colors.green}✓ Original failing case test passed${colors.reset}`); } /** * Test that demonstrates regex mode fails while literal mode succeeds * This is the key test that shows the problem we solved */ async function testRegexFailureLiteralSuccess() { console.log(`${colors.yellow}Testing that regex mode fails where literal mode succeeds...${colors.reset}`); // Test with regex mode (default) - should fail to find exact pattern due to regex interpretation const { finalResult: regexResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'toast.error("test")', // This pattern has regex special chars searchType: 'content', literalSearch: false // Use regex mode (default) }); // Test with literal mode - should succeed in finding exact pattern const { finalResult: literalResult } = await searchAndWaitForCompletion({ path: LITERAL_SEARCH_TEST_DIR, pattern: 'toast.error("test")', // Same pattern searchType: 'content', literalSearch: true // Use literal mode }); const regexText = regexResult.content[0].text; const literalText = literalResult.content[0].text; // Regex mode should find few/no matches due to special character interpretation const regexMatches = (regexText.match(/toast\.error\("test"\)/g) || []).length; // Literal mode should find the exact matches const literalMatches = (literalText.match(/toast\.error\("test"\)/g) || []).length; console.log(` Regex mode found: ${regexMatches} matches`); console.log(` Literal mode found: ${literalMatches} matches`); // The key assertion: literal should find more matches than regex assert(literalMatches > regexMatches, `Literal search should find more matches (${literalMatches}) than regex search (${regexMatches}) for patterns with special characters`); // Literal should find at least one match assert(literalMatches >= 1, 'Literal search should find at least one exact match'); console.log(`${colors.green}✓ Regex failure vs literal success test passed${colors.reset}`); } /** * Main test runner function for literal search tests */ export async function testLiteralSearch() { console.log(`${colors.blue}Starting literal search functionality tests...${colors.reset}`); let originalConfig; try { // Setup originalConfig = await setup(); // Run all literal search tests await testLiteralSearchExactMatches(); await testRegexVsLiteralDifference(); await testSpecialCharactersLiteralSearch(); await testLiteralSearchDefault(); await testOriginalFailingCase(); await testRegexFailureLiteralSuccess(); // NEW: Critical test showing the problem we solved console.log(`${colors.green}✅ All literal search tests passed!${colors.reset}`); return true; } catch (error) { console.error(`${colors.red}❌ Literal search test failed: ${error.message}${colors.reset}`); console.error(error.stack); throw error; } finally { // Cleanup if (originalConfig) { await teardown(originalConfig); } } } // Export for use in run-all-tests.js export default testLiteralSearch; // Run tests if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { testLiteralSearch().then(() => { console.log('Literal search tests completed successfully.'); process.exit(0); }).catch(error => { console.error('Literal search test execution failed:', error); process.exit(1); }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wonderwhy-er/DesktopCommanderMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server