Skip to main content
Glama
n-r-w

KnowledgeGraph MCP Server

by n-r-w
multiple-search.test.ts10.7 kB
import { Entity } from '../../core.js'; import { SearchStrategy, SearchConfig, SearchOptions } from '../../search/types.js'; import { SearchManager } from '../../search/search-manager.js'; // Skip global test setup for search strategy tests jest.mock('../setup.ts', () => ({ beforeEach: jest.fn(), afterEach: jest.fn() })); // Mock strategy for testing multiple queries class MockMultipleSearchStrategy implements SearchStrategy { constructor( private canUseDatabaseValue: boolean = false, private shouldThrowDatabaseError: boolean = false ) { } canUseDatabase(): boolean { return this.canUseDatabaseValue; } async searchDatabase(query: string | string[], threshold: number, project?: string): Promise<Entity[]> { if (this.shouldThrowDatabaseError) { throw new Error('Database search failed'); } // Handle multiple queries if (Array.isArray(query)) { let allResults: Entity[] = []; const existingEntityNames = new Set<string>(); for (const singleQuery of query) { const results = await this.searchDatabase(singleQuery, threshold, project); const newEntities = results.filter(e => !existingEntityNames.has(e.name)); allResults.push(...newEntities); newEntities.forEach(e => existingEntityNames.add(e.name)); } return allResults; } // Single query - return entities that contain the query return [ { name: `Database Result for ${query}`, entityType: 'MockType', observations: [`Found via database search for: ${query}`], tags: ['database', 'mock', query.toLowerCase()] } ]; } searchClientSide(entities: Entity[], query: string | string[]): Entity[] { // Handle multiple queries if (Array.isArray(query)) { let allResults: Entity[] = []; const existingEntityNames = new Set<string>(); for (const singleQuery of query) { const results = this.searchClientSide(entities, singleQuery); const newEntities = results.filter(e => !existingEntityNames.has(e.name)); allResults.push(...newEntities); newEntities.forEach(e => existingEntityNames.add(e.name)); } return allResults; } // Single query - simple name matching return entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase())); } async getAllEntities(project?: string): Promise<Entity[]> { // Mock implementation for testing return []; } } describe('SearchManager - Multiple Query Support', () => { let searchManager: SearchManager; let mockStrategy: MockMultipleSearchStrategy; let config: SearchConfig; let testEntities: Entity[]; beforeEach(() => { config = { useDatabaseSearch: true, threshold: 0.3, clientSideFallback: true }; mockStrategy = new MockMultipleSearchStrategy(true, false); searchManager = new SearchManager(config, mockStrategy); testEntities = [ { name: 'JavaScript', entityType: 'Language', observations: ['Popular programming language', 'Used for web development'], tags: ['programming', 'web', 'frontend'] }, { name: 'TypeScript', entityType: 'Language', observations: ['Superset of JavaScript', 'Adds static typing'], tags: ['programming', 'web', 'typed'] }, { name: 'React', entityType: 'Framework', observations: ['UI library for JavaScript', 'Component-based'], tags: ['frontend', 'ui', 'javascript'] }, { name: 'Node.js', entityType: 'Runtime', observations: ['JavaScript runtime for server-side', 'Built on V8 engine'], tags: ['backend', 'server', 'javascript'] }, { name: 'Python', entityType: 'Language', observations: ['High-level programming language', 'Great for data science'], tags: ['programming', 'data', 'backend'] } ]; }); describe('Multiple Query Array Support', () => { test('should handle array of queries with exact search', async () => { const queries = ['JavaScript', 'Python']; const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(queries, testEntities, options); expect(results.length).toBeGreaterThanOrEqual(2); expect(results.map(r => r.name)).toContain('JavaScript'); expect(results.map(r => r.name)).toContain('Python'); }); test('should handle array of queries with fuzzy search using database', async () => { const queries = ['test1', 'test2']; const options: SearchOptions = { searchMode: 'fuzzy' }; const results = await searchManager.search(queries, testEntities, options, 'test-project'); expect(results).toHaveLength(2); expect(results.map(r => r.name)).toContain('Database Result for test1'); expect(results.map(r => r.name)).toContain('Database Result for test2'); }); test('should handle array of queries with client-side search', async () => { const clientSideStrategy = new MockMultipleSearchStrategy(false, false); const clientSideManager = new SearchManager(config, clientSideStrategy); const queries = ['Script', 'Node']; const options: SearchOptions = { searchMode: 'fuzzy' }; const results = await clientSideManager.search(queries, testEntities, options); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.map(r => r.name)).toContain('JavaScript'); expect(results.map(r => r.name)).toContain('TypeScript'); expect(results.map(r => r.name)).toContain('Node.js'); }); test('should deduplicate results from multiple queries', async () => { const queries = ['Script', 'JavaScript']; // Both should match JavaScript const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(queries, testEntities, options); // Should not have duplicate JavaScript entries const jsResults = results.filter(r => r.name === 'JavaScript'); expect(jsResults).toHaveLength(1); }); test('should handle empty query array', async () => { const queries: string[] = []; const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(queries, testEntities, options); expect(results).toHaveLength(0); }); test('should handle single query in array format', async () => { const queries = ['JavaScript']; const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(queries, testEntities, options); expect(results.length).toBeGreaterThanOrEqual(1); expect(results.map(r => r.name)).toContain('JavaScript'); }); }); describe('Backward Compatibility', () => { test('should still handle single string queries', async () => { const query = 'JavaScript'; const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(query, testEntities, options); expect(results.length).toBeGreaterThanOrEqual(1); expect(results.map(r => r.name)).toContain('JavaScript'); }); test('should handle single string with fuzzy search', async () => { const query = 'test'; const options: SearchOptions = { searchMode: 'fuzzy' }; const results = await searchManager.search(query, testEntities, options, 'test-project'); expect(results).toHaveLength(1); expect(results[0].name).toBe('Database Result for test'); }); }); describe('Error Handling for Multiple Queries', () => { test('should handle database failure with multiple queries and fallback', async () => { const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); const failingStrategy = new MockMultipleSearchStrategy(true, true); const failingManager = new SearchManager(config, failingStrategy); const queries = ['Script', 'Node']; const options: SearchOptions = { searchMode: 'fuzzy' }; const results = await failingManager.search(queries, testEntities, options); expect(results.length).toBeGreaterThanOrEqual(3); expect(results.map(r => r.name)).toContain('JavaScript'); expect(results.map(r => r.name)).toContain('TypeScript'); expect(results.map(r => r.name)).toContain('Node.js'); consoleSpy.mockRestore(); }); test('should throw error with multiple queries when database fails and fallback disabled', async () => { const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); const noFallbackConfig = { ...config, clientSideFallback: false }; const failingStrategy = new MockMultipleSearchStrategy(true, true); const failingManager = new SearchManager(noFallbackConfig, failingStrategy); const queries = ['test1', 'test2']; const options: SearchOptions = { searchMode: 'fuzzy' }; await expect(failingManager.search(queries, testEntities, options)).rejects.toThrow('Database search failed'); consoleSpy.mockRestore(); }); }); describe('Performance and Edge Cases', () => { test('should handle large number of queries efficiently', async () => { const queries = Array.from({ length: 10 }, (_, i) => `query${i}`); const options: SearchOptions = { searchMode: 'exact' }; const startTime = Date.now(); const results = await searchManager.search(queries, testEntities, options); const endTime = Date.now(); // Should complete within reasonable time (less than 1 second) expect(endTime - startTime).toBeLessThan(1000); expect(results).toHaveLength(0); // No matches expected for these queries }); test('should handle queries with special characters', async () => { const queries = ['Java-Script', 'Type@Script', 'Node.js']; const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(queries, testEntities, options); // Should find Node.js at minimum expect(results.map(r => r.name)).toContain('Node.js'); }); test('should handle duplicate queries in array', async () => { const queries = ['JavaScript', 'JavaScript', 'TypeScript']; const options: SearchOptions = { searchMode: 'exact' }; const results = await searchManager.search(queries, testEntities, options); // Should not have duplicate results const jsResults = results.filter(r => r.name === 'JavaScript'); expect(jsResults).toHaveLength(1); const tsResults = results.filter(r => r.name === 'TypeScript'); expect(tsResults).toHaveLength(1); }); }); });

Latest Blog Posts

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/n-r-w/knowledgegraph-mcp'

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