Skip to main content
Glama
n-r-w

KnowledgeGraph MCP Server

by n-r-w
sqlite-strategy.test.ts8.98 kB
import Database from 'better-sqlite3'; import { Entity } from '../../core.js'; import { SearchConfig } from '../../search/types.js'; import { SQLiteFuzzyStrategy } from '../../search/strategies/sqlite-strategy.js'; // Skip global test setup for search strategy tests jest.mock('../setup.ts', () => ({ beforeEach: jest.fn(), afterEach: jest.fn() })); describe('SQLiteFuzzyStrategy', () => { let strategy: SQLiteFuzzyStrategy; let mockDb: jest.Mocked<Database.Database>; let mockStatement: jest.Mocked<Database.Statement>; let config: SearchConfig; let testEntities: Entity[]; beforeEach(() => { // Setup mock statement mockStatement = { all: jest.fn(), run: jest.fn(), get: jest.fn(), finalize: jest.fn() } as any; // Setup mock database mockDb = { prepare: jest.fn().mockReturnValue(mockStatement), close: jest.fn(), exec: jest.fn() } as any; config = { useDatabaseSearch: false, // SQLite always uses client-side search threshold: 0.3, clientSideFallback: true, fuseOptions: { threshold: 0.3, distance: 100, includeScore: true, keys: ['name', 'entityType', 'observations', 'tags'] } }; strategy = new SQLiteFuzzyStrategy(config, mockDb, 'test-project'); 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'] } ]; }); afterEach(() => { jest.clearAllMocks(); }); describe('Constructor', () => { test('should initialize with provided parameters', () => { expect(strategy).toBeDefined(); expect(strategy['config']).toEqual(config); expect(strategy['db']).toBe(mockDb); expect(strategy['project']).toBe('test-project'); }); }); describe('canUseDatabase', () => { test('should always return false for SQLite', () => { expect(strategy.canUseDatabase()).toBe(false); }); test('should return false even when config enables database search', () => { const configWithDb = { ...config, useDatabaseSearch: true }; const strategyWithDb = new SQLiteFuzzyStrategy(configWithDb, mockDb, 'test-project'); expect(strategyWithDb.canUseDatabase()).toBe(false); }); }); describe('searchDatabase', () => { test('should throw error since SQLite does not support database-level fuzzy search', async () => { await expect(strategy.searchDatabase('test', 0.3)).rejects.toThrow( 'SQLite does not support database-level fuzzy search. Use client-side search instead.' ); }); }); describe('searchClientSide', () => { test('should perform fuzzy search using Fuse.js', () => { const results = strategy.searchClientSide(testEntities, 'JavaScrpt'); // Intentional typo expect(results.length).toBeGreaterThan(0); // Check that JavaScript is in the results (fuzzy search results may vary in order) expect(results.map(r => r.name)).toContain('JavaScript'); }); test('should use custom fuse options from config', () => { const customConfig = { ...config, fuseOptions: { threshold: 0.1, // Very strict matching distance: 50, includeScore: true, keys: ['name'] // Only search in name } }; const customStrategy = new SQLiteFuzzyStrategy(customConfig, mockDb, 'test-project'); const results = customStrategy.searchClientSide(testEntities, 'React'); expect(results.length).toBeGreaterThan(0); expect(results[0].name).toBe('React'); }); test('should handle empty entity list', () => { const results = strategy.searchClientSide([], 'test'); expect(results).toHaveLength(0); }); test('should handle empty query', () => { const results = strategy.searchClientSide(testEntities, ''); expect(results).toHaveLength(0); }); test('should find entities with partial matches', () => { const results = strategy.searchClientSide(testEntities, 'Script'); expect(results.length).toBeGreaterThan(0); const names = results.map(r => r.name); expect(names).toContain('JavaScript'); expect(names).toContain('TypeScript'); }); }); describe('getAllEntities', () => { test('should fetch all entities for the project', async () => { const mockRows = [ { name: 'JavaScript', entity_type: 'Language', observations: '["Popular programming language"]', tags: '["programming", "web"]' }, { name: 'TypeScript', entity_type: 'Language', observations: '["Superset of JavaScript"]', tags: '["programming", "typed"]' } ]; mockStatement.all.mockReturnValue(mockRows); const results = await strategy.getAllEntities(); expect(mockDb.prepare).toHaveBeenCalledWith( expect.stringMatching(/SELECT name, entity_type, observations, tags\s+FROM entities\s+WHERE project = \?\s+ORDER BY updated_at DESC, name\s+LIMIT \?/) ); expect(mockStatement.all).toHaveBeenCalledWith('test-project', 10000); expect(results).toHaveLength(2); expect(results[0]).toEqual({ name: 'JavaScript', entityType: 'Language', observations: ['Popular programming language'], tags: ['programming', 'web'] }); }); test('should use provided project parameter', async () => { mockStatement.all.mockReturnValue([]); await strategy.getAllEntities('custom-project'); expect(mockStatement.all).toHaveBeenCalledWith('custom-project', 10000); }); test('should handle database errors by throwing', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); mockStatement.all.mockImplementation(() => { throw new Error('Database error'); }); await expect(strategy.getAllEntities()).rejects.toThrow('Database error'); consoleSpy.mockRestore(); }); test('should handle malformed JSON in observations and tags', async () => { const mockRows = [ { name: 'Test Entity', entity_type: 'Test', observations: 'invalid json', tags: 'invalid json' } ]; mockStatement.all.mockReturnValue(mockRows); const results = await strategy.getAllEntities(); expect(results).toHaveLength(1); expect(results[0].observations).toEqual([]); expect(results[0].tags).toEqual([]); }); }); describe('searchExact', () => { test('should perform exact search at database level', async () => { const mockRows = [ { name: 'JavaScript', entity_type: 'Language', observations: '["Popular programming language"]', tags: '["programming", "web"]' } ]; mockStatement.all.mockReturnValue(mockRows); const results = await strategy.searchExact('script'); expect(mockDb.prepare).toHaveBeenCalledWith( expect.stringContaining('LOWER(name) LIKE ?') ); expect(mockStatement.all).toHaveBeenCalledWith( 'test-project', '%script%', '%script%', '%script%', '%script%', 100 ); expect(results).toHaveLength(1); expect(results[0].name).toBe('JavaScript'); }); test('should use provided project parameter', async () => { mockStatement.all.mockReturnValue([]); await strategy.searchExact('test', 'custom-project'); expect(mockStatement.all).toHaveBeenCalledWith( 'custom-project', '%test%', '%test%', '%test%', '%test%', 100 ); }); test('should handle database errors by throwing', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); mockStatement.all.mockImplementation(() => { throw new Error('Database error'); }); await expect(strategy.searchExact('test')).rejects.toThrow('Database error'); consoleSpy.mockRestore(); }); test('should handle empty query', async () => { mockStatement.all.mockReturnValue([]); const results = await strategy.searchExact(''); expect(mockStatement.all).toHaveBeenCalledWith( 'test-project', '%%', '%%', '%%', '%%', 100 ); expect(results).toHaveLength(0); }); }); });

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