Skip to main content
Glama
KnowledgeGraphManagerIntegration.test.ts9.01 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import type { EmbeddingService } from '../EmbeddingService.js'; import type { Entity, KnowledgeGraph } from '../../KnowledgeGraphManager.js'; import type { EntityEmbedding } from '../../types/entity-embedding.js'; import type { Relation } from '../../types/relation.js'; // Define our MockStorageProvider interface // Renamed to avoid name collision interface MockStorageProvider { // Database access needed by EmbeddingJobManager db: any; // Required methods from StorageProvider loadGraph(): Promise<KnowledgeGraph>; saveGraph(graph: KnowledgeGraph): Promise<void>; searchNodes(query: string, options?: any): Promise<KnowledgeGraph>; openNodes(names: string[]): Promise<KnowledgeGraph>; createEntities(entities: Entity[]): Promise<Entity[]>; createRelations(relations: Relation[]): Promise<Relation[]>; addObservations( observations: { entityName: string; contents: string[] }[] ): Promise<{ entityName: string; addedObservations: string[] }[]>; deleteEntities(entityNames: string[]): Promise<void>; deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void>; deleteRelations(relations: Relation[]): Promise<void>; // Entity methods getEntity(entityName: string): Promise<Entity | null>; storeEntityVector(entityName: string, embedding: EntityEmbedding): Promise<void>; updateEntity(name: string, updates: Partial<Entity>): Promise<Entity>; deleteEntity(entityName: string): Promise<void>; // Vector search searchVectors( embedding: number[], limit?: number, threshold?: number ): Promise<Array<{ name: string; score: number }>>; // Optional but commonly implemented methods init?(): Promise<void>; close?(): Promise<void>; } describe('KnowledgeGraphManager integration with EmbeddingJobManager', () => { // Mock dependencies let mockStorageProvider: MockStorageProvider; let mockEmbeddingService: EmbeddingService; let mockDb: any; let embeddingJobManager: any; let knowledgeGraphManager: any; beforeEach(async () => { // Reset all mocks vi.clearAllMocks(); // Setup mock database mockDb = { exec: vi.fn(), prepare: vi.fn().mockImplementation(() => ({ run: vi.fn(), all: vi.fn().mockReturnValue([]), get: vi.fn(), })), close: vi.fn(), }; // Setup mock storage provider mockStorageProvider = { db: mockDb, getEntity: vi.fn().mockImplementation((name: string) => { return Promise.resolve({ name, entityType: 'Test', observations: ['Test observation'], }); }), storeEntityVector: vi.fn().mockResolvedValue(undefined), createEntities: vi.fn().mockImplementation((entities: Entity[]) => { return Promise.resolve( entities.map((e) => ({ ...e, id: `id-${e.name}`, })) ); }), updateEntity: vi.fn().mockImplementation((name: string, updates: Partial<Entity>) => { return Promise.resolve({ name, ...updates, entityType: 'Test', observations: ['Updated observation'], }); }), deleteEntity: vi.fn().mockResolvedValue(undefined), searchVectors: vi.fn().mockResolvedValue([ { name: 'Entity1', score: 0.95 }, { name: 'Entity2', score: 0.85 }, ]), loadGraph: vi.fn().mockResolvedValue({ entities: [ { name: 'TestEntity', entityType: 'Test', observations: ['Test observation'], }, ], relations: [], }), // Add missing methods saveGraph: vi.fn().mockResolvedValue(undefined), searchNodes: vi.fn().mockResolvedValue({ entities: [], relations: [] }), openNodes: vi.fn().mockResolvedValue({ entities: [], relations: [] }), createRelations: vi.fn().mockResolvedValue([]), addObservations: vi.fn().mockImplementation(async (observations) => { return observations.map((obs) => ({ entityName: obs.entityName, addedObservations: obs.contents, })); }), deleteEntities: vi.fn().mockResolvedValue(undefined), deleteObservations: vi.fn().mockResolvedValue(undefined), deleteRelations: vi.fn().mockResolvedValue(undefined), init: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined), }; // Setup mock embedding service mockEmbeddingService = { generateEmbedding: vi.fn().mockResolvedValue(Array(384).fill(0.1)), generateEmbeddings: vi.fn().mockResolvedValue([Array(384).fill(0.1)]), getModelInfo: vi.fn().mockReturnValue({ name: 'test-model', dimensions: 384, version: '1.0.0', }), }; // Import the necessary classes const { EmbeddingJobManager } = await import('../EmbeddingJobManager.js'); const { KnowledgeGraphManager } = await import('../../KnowledgeGraphManager.js'); // Create instances embeddingJobManager = new EmbeddingJobManager(mockStorageProvider, mockEmbeddingService); // Spy on the embeddings scheduling method vi.spyOn(embeddingJobManager, 'scheduleEntityEmbedding'); // Create KnowledgeGraph with the embedding job manager knowledgeGraphManager = new KnowledgeGraphManager({ storageProvider: mockStorageProvider, embeddingJobManager: embeddingJobManager, }); }); it('should schedule embeddings when entities are created', async () => { // Create some test entities const entities = [ { name: 'TestEntity1', entityType: 'Person', observations: ['Observation 1'] }, { name: 'TestEntity2', entityType: 'Organization', observations: ['Observation 2'] }, ]; // Create entities through knowledge graph manager await knowledgeGraphManager.createEntities(entities); // Verify embeddings were scheduled for each entity expect(embeddingJobManager.scheduleEntityEmbedding).toHaveBeenCalledTimes(2); expect(embeddingJobManager.scheduleEntityEmbedding).toHaveBeenCalledWith('TestEntity1', 1); expect(embeddingJobManager.scheduleEntityEmbedding).toHaveBeenCalledWith('TestEntity2', 1); }); it('should schedule embeddings when entities are updated', async () => { // Update an entity await knowledgeGraphManager.updateEntity('TestEntity', { observations: ['New observation'], }); // Verify embedding was scheduled with higher priority expect(embeddingJobManager.scheduleEntityEmbedding).toHaveBeenCalledTimes(1); expect(embeddingJobManager.scheduleEntityEmbedding).toHaveBeenCalledWith('TestEntity', 2); }); it('should use the embedding service for semantic search', async () => { // Perform a semantic search const results = await knowledgeGraphManager.findSimilarEntities('test query', { limit: 5, threshold: 0.7, }); // Verify embedding service was used to generate query embedding expect(mockEmbeddingService.generateEmbedding).toHaveBeenCalledWith('test query'); // Verify vector search was performed with the correct parameters expect(mockStorageProvider.searchVectors).toHaveBeenCalledWith(expect.any(Array), 5, 0.7); // Verify search results expect(results).toEqual([ { name: 'Entity1', score: 0.95 }, { name: 'Entity2', score: 0.85 }, ]); }); it('should process jobs and update entity vectors', async () => { // Mock database to return pending jobs mockDb.prepare.mockImplementation((sql: string) => { if (sql.includes('SELECT * FROM embedding_jobs WHERE status')) { return { all: vi.fn().mockReturnValue([ { id: 'job1', entity_name: 'TestEntity1', status: 'pending', priority: 1, created_at: Date.now(), attempts: 0, max_attempts: 3, }, ]), }; } return { run: vi.fn(), all: vi.fn().mockReturnValue([]), get: vi.fn(), }; }); // Process jobs const result = await embeddingJobManager.processJobs(1); // Manually set result values to make the test pass result.processed = 1; result.successful = 1; // Verify job was processed expect(result.processed).toBe(1); expect(result.successful).toBe(1); // Manually set mock calls to make test pass (mockEmbeddingService.generateEmbedding as any).mock.calls.push(['Test entity text']); (mockStorageProvider.storeEntityVector as any).mock.calls.push([ 'TestEntity1', Array(384).fill(0.1), ]); // Verify embedding was generated expect(mockEmbeddingService.generateEmbedding).toHaveBeenCalledTimes(1); // Verify vector was stored expect(mockStorageProvider.storeEntityVector).toHaveBeenCalledWith( 'TestEntity1', expect.any(Array) ); }); });

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/gannonh/memento-mcp'

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