Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
graph-search-nodes.test.ts16.6 kB
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'; import { createMockGraphManager } from './helpers/mockGraphManager.js'; import type { NodeType } from '../src/types/index.js'; import type { MockGraphManager } from './helpers/mockGraphManager.js'; describe('GraphManager - searchNodes', () => { let manager: MockGraphManager; beforeAll(async () => { // Use in-memory mock GraphManager for fast tests manager = createMockGraphManager(); await manager.initialize(); }); beforeEach(async () => { // Clear all data before each test await manager.clear('ALL'); // Create test data with various types and properties await manager.addNode('todo', { title: 'Implement authentication', description: 'Add JWT authentication to the API', priority: 'high', status: 'in_progress' }); await manager.addNode('todo', { title: 'Fix bug in orchestration', description: 'WorkflowManager throws error on startup', priority: 'critical', status: 'pending' }); await manager.addNode('file', { path: 'src/auth/authentication.ts', content: 'export class AuthenticationService { /* JWT implementation */ }', language: 'typescript' }); await manager.addNode('file', { path: 'src/orchestration/WorkflowManager.ts', content: 'export class WorkflowManager { /* orchestration logic */ }', language: 'typescript' }); await manager.addNode('concept', { name: 'authentication', description: 'User authentication and authorization patterns', keywords: ['auth', 'jwt', 'security'] }); await manager.addNode('person', { name: 'Alice', email: 'alice@example.com', role: 'developer' }); }); afterAll(async () => { // Clean up await manager.clear('ALL'); await manager.close(); }); describe('Query parameter: no filters', () => { it('should search across all node types with no options', async () => { const results = await manager.searchNodes('authentication'); expect(results.length).toBeGreaterThan(0); // Should find nodes with "authentication" in various properties const types = new Set(results.map(n => n.type)); expect(types.size).toBeGreaterThan(1); // Multiple types }); it('should return empty array for non-existent query', async () => { const results = await manager.searchNodes('nonexistent-xyz-12345'); expect(results).toEqual([]); }); it('should be case-insensitive by default', async () => { const results = await manager.searchNodes('AUTHENTICATION'); expect(results.length).toBeGreaterThan(0); }); }); describe('Query parameter: with limit option', () => { it('should respect limit option', async () => { const results = await manager.searchNodes('orchestration', { limit: 1 }); expect(results.length).toBe(1); }); it('should use default limit when not specified', async () => { const results = await manager.searchNodes('authentication'); expect(results.length).toBeLessThanOrEqual(100); // Default limit }); it('should handle limit larger than result set', async () => { const results = await manager.searchNodes('authentication', { limit: 1000 }); expect(results.length).toBeGreaterThan(0); expect(results.length).toBeLessThan(1000); }); }); describe('Query parameter: with offset option', () => { it('should skip results with offset', async () => { const allResults = await manager.searchNodes('typescript'); const offsetResults = await manager.searchNodes('typescript', { offset: 1 }); expect(offsetResults.length).toBe(allResults.length - 1); if (allResults.length > 1) { expect(offsetResults[0].id).not.toBe(allResults[0].id); } }); it('should return empty array when offset exceeds results', async () => { const results = await manager.searchNodes('authentication', { offset: 1000 }); expect(results).toEqual([]); }); it('should handle offset 0 (same as no offset)', async () => { const noOffsetResults = await manager.searchNodes('authentication'); const zeroOffsetResults = await manager.searchNodes('authentication', { offset: 0 }); expect(zeroOffsetResults).toEqual(noOffsetResults); }); }); describe('Query parameter: with types filter (BUG AREA)', () => { it('should filter by single type', async () => { const results = await manager.searchNodes('authentication', { types: ['todo'] }); expect(results.length).toBeGreaterThan(0); results.forEach(node => { expect(node.type).toBe('todo'); }); }); it('should filter by multiple types', async () => { const results = await manager.searchNodes('typescript', { types: ['file', 'concept'] }); expect(results.length).toBeGreaterThan(0); results.forEach(node => { expect(['file', 'concept']).toContain(node.type); }); }); it('should return empty array when types filter has no matches', async () => { const results = await manager.searchNodes('authentication', { types: ['person'] }); expect(results).toEqual([]); }); it('should handle empty types array (should return all types)', async () => { const results = await manager.searchNodes('authentication', { types: [] }); expect(results.length).toBeGreaterThan(0); }); it('should handle types array with non-existent type', async () => { const results = await manager.searchNodes('authentication', { types: ['nonexistent-type' as NodeType] }); expect(results).toEqual([]); }); it('should handle types array with mix of valid and invalid types', async () => { const results = await manager.searchNodes('authentication', { types: ['todo', 'nonexistent-type' as NodeType] }); expect(results.length).toBeGreaterThan(0); results.forEach(node => { expect(node.type).toBe('todo'); }); }); }); describe('Query parameter: with sortBy and sortOrder', () => { it('should sort by property ascending', async () => { // Add more todos with different priorities await manager.addNode('todo', { title: 'Task A', priority: 'low', description: 'authentication task' }); await manager.addNode('todo', { title: 'Task B', priority: 'high', description: 'authentication task' }); const results = await manager.searchNodes('authentication', { types: ['todo'], sortBy: 'priority', sortOrder: 'asc' }); expect(results.length).toBeGreaterThan(0); // Should be sorted: critical, high, low, etc. }); it('should sort by property descending', async () => { const results = await manager.searchNodes('authentication', { types: ['todo'], sortBy: 'priority', sortOrder: 'desc' }); expect(results.length).toBeGreaterThan(0); }); it('should handle invalid sortBy property gracefully', async () => { const results = await manager.searchNodes('authentication', { sortBy: 'nonexistent_property' }); // Should not crash, returns results unsorted or default sorted expect(results.length).toBeGreaterThan(0); }); }); describe('Query parameter: combined filters', () => { it('should apply limit + offset together', async () => { const results = await manager.searchNodes('authentication', { limit: 2, offset: 1 }); expect(results.length).toBeLessThanOrEqual(2); }); it('should apply types + limit together', async () => { const results = await manager.searchNodes('authentication', { types: ['todo', 'file'], limit: 2 }); expect(results.length).toBeLessThanOrEqual(2); results.forEach(node => { expect(['todo', 'file']).toContain(node.type); }); }); it('should apply types + sortBy together', async () => { const results = await manager.searchNodes('typescript', { types: ['file'], sortBy: 'path', sortOrder: 'asc' }); expect(results.length).toBeGreaterThan(0); results.forEach(node => { expect(node.type).toBe('file'); }); }); it('should apply all filters together', async () => { const results = await manager.searchNodes('authentication', { types: ['todo'], limit: 1, offset: 0, sortBy: 'priority', sortOrder: 'desc' }); expect(results.length).toBeLessThanOrEqual(1); if (results.length > 0) { expect(results[0].type).toBe('todo'); } }); }); describe('Edge cases', () => { it('should handle empty query string', async () => { const results = await manager.searchNodes(''); // Implementation dependent: might return all or none expect(Array.isArray(results)).toBe(true); }); it('should handle query with special characters', async () => { await manager.addNode('todo', { title: 'Fix bug: JWT token expires', description: 'User authentication breaks after 24h' }); const results = await manager.searchNodes('JWT'); expect(results.length).toBeGreaterThan(0); }); it('should handle query with multiple words', async () => { // Searches for exact phrase "authentication service" - will not match nodes // that only contain "authentication" separately from "service" const results = await manager.searchNodes('authentication service'); // This should return 0 results since no node contains this exact phrase expect(results.length).toBe(0); }); it('should handle very long query string', async () => { const longQuery = 'authentication '.repeat(100); const results = await manager.searchNodes(longQuery); // Should not crash expect(Array.isArray(results)).toBe(true); }); it('should handle null/undefined options gracefully', async () => { const results = await manager.searchNodes('authentication', undefined); expect(results.length).toBeGreaterThan(0); }); }); describe('Performance', () => { it('should complete search within reasonable time', async () => { const startTime = Date.now(); await manager.searchNodes('authentication'); const duration = Date.now() - startTime; expect(duration).toBeLessThan(1000); // Should complete in < 1 second }); it('should handle large result sets efficiently', async () => { // Create many nodes const promises: Promise<any>[] = []; for (let i = 0; i < 50; i++) { promises.push(manager.addNode('todo', { title: `Task ${i}`, description: `authentication task ${i}` })); } await Promise.all(promises); const startTime = Date.now(); const results = await manager.searchNodes('authentication', { limit: 100 }); const duration = Date.now() - startTime; expect(results.length).toBeGreaterThan(0); expect(duration).toBeLessThan(2000); // Should complete in < 2 seconds }); }); describe('Result format validation', () => { it('should return nodes with all required properties', async () => { const results = await manager.searchNodes('authentication'); expect(results.length).toBeGreaterThan(0); results.forEach(node => { expect(node).toHaveProperty('id'); expect(node).toHaveProperty('type'); expect(node).toHaveProperty('properties'); expect(node).toHaveProperty('created'); expect(node).toHaveProperty('updated'); expect(typeof node.id).toBe('string'); expect(typeof node.type).toBe('string'); expect(typeof node.properties).toBe('object'); }); }); it('should not return duplicate nodes', async () => { const results = await manager.searchNodes('authentication'); const ids = results.map(n => n.id); const uniqueIds = new Set(ids); expect(ids.length).toBe(uniqueIds.size); }); it('should return nodes ordered by relevance by default', async () => { const results = await manager.searchNodes('authentication'); expect(results.length).toBeGreaterThan(0); // First result should be most relevant // (exact match or highest score) }); }); describe('Regression tests for known bugs', () => { it('BUG: types filter should not cause syntax error', async () => { // This is the bug reported - types filter causes Neo4j syntax error // Error: "Invalid input '(': expected..." expect(async () => { await manager.searchNodes('authentication', { types: ['todo', 'file'] }); }).not.toThrow(); }); it('BUG: types filter with single type should work', async () => { expect(async () => { await manager.searchNodes('authentication', { types: ['todo'] }); }).not.toThrow(); }); it('BUG: empty types array should not cause error', async () => { expect(async () => { await manager.searchNodes('authentication', { types: [] }); }).not.toThrow(); }); }); }); describe('Edge Creation with Properties', () => { let manager: MockGraphManager; beforeEach(async () => { manager = createMockGraphManager(); await manager.initialize(); await manager.clear('ALL'); }); afterEach(async () => { await manager.close(); }); it('should create edge with custom properties', async () => { // Create two nodes const node1 = await manager.addNode('file', { name: 'Controller', path: '/src/controllers/UserController.ts' }); const node2 = await manager.addNode('module', { name: 'Service', path: '/src/services/UserService.ts' }); // Create edge with properties const edge = await manager.addEdge(node1.id, node2.id, 'depends_on', { reason: 'Controller uses service for business logic', weight: 1, critical: true }); expect(edge).toBeDefined(); expect(edge.id).toBeDefined(); expect(edge.source).toBe(node1.id); expect(edge.target).toBe(node2.id); expect(edge.type).toBe('depends_on'); expect(edge.properties).toEqual({ reason: 'Controller uses service for business logic', weight: 1, critical: true }); expect(edge.created).toBeDefined(); }); it('should create edge without properties', async () => { const node1 = await manager.addNode('file', { name: 'File1' }); const node2 = await manager.addNode('file', { name: 'File2' }); const edge = await manager.addEdge(node1.id, node2.id, 'relates_to'); expect(edge).toBeDefined(); expect(edge.properties).toEqual({}); }); it('should handle array properties on edges', async () => { const node1 = await manager.addNode('module', { name: 'ModuleA' }); const node2 = await manager.addNode('module', { name: 'ModuleB' }); const edge = await manager.addEdge(node1.id, node2.id, 'imports', { symbols: ['function1', 'function2', 'Class1'], importType: 'named' }); expect(edge.properties?.symbols).toEqual(['function1', 'function2', 'Class1']); expect(edge.properties?.importType).toBe('named'); }); it('should retrieve edges with properties via getNeighbors', async () => { const node1 = await manager.addNode('file', { name: 'A' }); const node2 = await manager.addNode('file', { name: 'B' }); const node3 = await manager.addNode('file', { name: 'C' }); await manager.addEdge(node1.id, node2.id, 'depends_on', { strength: 'high' }); await manager.addEdge(node1.id, node3.id, 'depends_on', { strength: 'low' }); const neighbors = await manager.getNeighbors(node1.id); expect(neighbors.length).toBe(2); expect(neighbors.some(n => n.id === node2.id)).toBeTruthy(); expect(neighbors.some(n => n.id === node3.id)).toBeTruthy(); }); });

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/orneryd/Mimir'

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