Skip to main content
Glama
embedding-storage.test.ts18.8 kB
/** * Embedding Storage Integration Tests (Mocked) * * Tests the interaction between EmbeddingStorage and EmbeddingEngine * using mocks for external dependencies (database, embedding model). * * This is an integration test that verifies internal module interactions work correctly, * NOT a test of external service integration. * * Requirements: 12.2, 12.3, 12.4 */ import type { PoolClient } from "pg"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { MemorySector, SectorEmbeddings } from "../../embeddings/types"; // Create mock database client function createMockClient(): PoolClient { const mockClient = { query: vi.fn(), release: vi.fn(), } as unknown as PoolClient; return mockClient; } // Create mock database manager function createMockDbManager() { const mockClient = createMockClient(); return { client: mockClient, pool: { query: vi.fn(), }, manager: { pool: { query: vi.fn(), }, getConnection: vi.fn().mockResolvedValue(mockClient), releaseConnection: vi.fn(), beginTransaction: vi.fn().mockResolvedValue(mockClient), commitTransaction: vi.fn().mockResolvedValue(undefined), rollbackTransaction: vi.fn().mockResolvedValue(undefined), connect: vi.fn().mockResolvedValue(undefined), disconnect: vi.fn().mockResolvedValue(undefined), }, }; } // Create deterministic test embedding function createTestEmbedding(dimension: number, seed: number): number[] { const embedding = new Array(dimension); for (let i = 0; i < dimension; i++) { embedding[i] = seed + i * 0.001; } // Normalize to unit vector const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); return embedding.map((val) => val / magnitude); } // Create mock embedding engine function createMockEmbeddingEngine() { const mockEmbedding = createTestEmbedding(768, 0.1); return { generateAllSectorEmbeddings: vi.fn().mockResolvedValue({ episodic: createTestEmbedding(768, 0.1), semantic: createTestEmbedding(768, 0.2), procedural: createTestEmbedding(768, 0.3), emotional: createTestEmbedding(768, 0.4), reflective: createTestEmbedding(768, 0.5), }), generateSemanticEmbedding: vi.fn().mockResolvedValue(mockEmbedding), generateEpisodicEmbedding: vi.fn().mockResolvedValue(mockEmbedding), generateProceduralEmbedding: vi.fn().mockResolvedValue(mockEmbedding), generateEmotionalEmbedding: vi.fn().mockResolvedValue(mockEmbedding), generateReflectiveEmbedding: vi.fn().mockResolvedValue(mockEmbedding), getModelDimension: vi.fn().mockReturnValue(768), }; } // Create mock embedding storage function createMockEmbeddingStorage() { return { storeEmbeddings: vi.fn().mockResolvedValue(undefined), retrieveEmbeddings: vi.fn().mockResolvedValue({ episodic: createTestEmbedding(768, 0.1), semantic: createTestEmbedding(768, 0.2), procedural: createTestEmbedding(768, 0.3), emotional: createTestEmbedding(768, 0.4), reflective: createTestEmbedding(768, 0.5), }), updateEmbeddings: vi.fn().mockResolvedValue(undefined), deleteEmbeddings: vi.fn().mockResolvedValue(undefined), vectorSimilaritySearch: vi.fn().mockResolvedValue([]), multiSectorSearch: vi.fn().mockResolvedValue([]), }; } describe("EmbeddingStorage + EmbeddingEngine Integration (Mocked)", () => { let mockDb: ReturnType<typeof createMockDbManager>; let mockEmbeddingEngine: ReturnType<typeof createMockEmbeddingEngine>; let mockEmbeddingStorage: ReturnType<typeof createMockEmbeddingStorage>; beforeEach(() => { vi.clearAllMocks(); mockDb = createMockDbManager(); mockEmbeddingEngine = createMockEmbeddingEngine(); mockEmbeddingStorage = createMockEmbeddingStorage(); }); afterEach(() => { vi.restoreAllMocks(); }); describe("Embedding Generation and Storage Flow", () => { it("should generate embeddings with engine and store them via storage", async () => { const memoryId = "test-memory-001"; const content = "Test memory content for embedding generation"; // Step 1: Generate embeddings using the engine const embeddings = await mockEmbeddingEngine.generateAllSectorEmbeddings({ text: content, sector: "semantic", }); expect(mockEmbeddingEngine.generateAllSectorEmbeddings).toHaveBeenCalledWith({ text: content, sector: "semantic", }); expect(embeddings).toHaveProperty("episodic"); expect(embeddings).toHaveProperty("semantic"); expect(embeddings).toHaveProperty("procedural"); expect(embeddings).toHaveProperty("emotional"); expect(embeddings).toHaveProperty("reflective"); // Step 2: Store embeddings using the storage await mockEmbeddingStorage.storeEmbeddings(memoryId, embeddings, "nomic-embed-text"); expect(mockEmbeddingStorage.storeEmbeddings).toHaveBeenCalledWith( memoryId, embeddings, "nomic-embed-text" ); }); it("should store embeddings within a transaction", async () => { const memoryId = "test-memory-002"; const content = "Transactional embedding storage test"; // Begin transaction const client = await mockDb.manager.beginTransaction(); expect(mockDb.manager.beginTransaction).toHaveBeenCalled(); // Generate embeddings const embeddings = await mockEmbeddingEngine.generateAllSectorEmbeddings({ text: content, sector: "semantic", }); // Store embeddings with transaction client await mockEmbeddingStorage.storeEmbeddings(memoryId, embeddings, "nomic-embed-text", client); expect(mockEmbeddingStorage.storeEmbeddings).toHaveBeenCalledWith( memoryId, embeddings, "nomic-embed-text", client ); // Commit transaction await mockDb.manager.commitTransaction(client); expect(mockDb.manager.commitTransaction).toHaveBeenCalledWith(client); }); it("should rollback transaction on embedding generation failure", async () => { mockEmbeddingEngine.generateAllSectorEmbeddings.mockRejectedValue( new Error("Embedding generation failed") ); const client = await mockDb.manager.beginTransaction(); await expect( mockEmbeddingEngine.generateAllSectorEmbeddings({ text: "Test content", sector: "semantic", }) ).rejects.toThrow("Embedding generation failed"); // Rollback on failure await mockDb.manager.rollbackTransaction(client); expect(mockDb.manager.rollbackTransaction).toHaveBeenCalledWith(client); }); it("should rollback transaction on storage failure", async () => { mockEmbeddingStorage.storeEmbeddings.mockRejectedValue(new Error("Storage failed")); const client = await mockDb.manager.beginTransaction(); const embeddings = await mockEmbeddingEngine.generateAllSectorEmbeddings({ text: "Test content", sector: "semantic", }); await expect( mockEmbeddingStorage.storeEmbeddings("test-id", embeddings, "nomic-embed-text", client) ).rejects.toThrow("Storage failed"); // Rollback on failure await mockDb.manager.rollbackTransaction(client); expect(mockDb.manager.rollbackTransaction).toHaveBeenCalledWith(client); }); }); describe("Embedding Retrieval Flow", () => { it("should retrieve stored embeddings for a memory", async () => { const memoryId = "test-memory-003"; const embeddings = await mockEmbeddingStorage.retrieveEmbeddings(memoryId); expect(mockEmbeddingStorage.retrieveEmbeddings).toHaveBeenCalledWith(memoryId); expect(embeddings).toHaveProperty("episodic"); expect(embeddings).toHaveProperty("semantic"); expect(embeddings).toHaveProperty("procedural"); expect(embeddings).toHaveProperty("emotional"); expect(embeddings).toHaveProperty("reflective"); expect(embeddings.semantic.length).toBe(768); }); it("should retrieve specific sectors when requested", async () => { const memoryId = "test-memory-004"; const sectors: MemorySector[] = ["semantic", "episodic"] as MemorySector[]; mockEmbeddingStorage.retrieveEmbeddings.mockResolvedValue({ episodic: createTestEmbedding(768, 0.1), semantic: createTestEmbedding(768, 0.2), procedural: [], emotional: [], reflective: [], }); const embeddings = await mockEmbeddingStorage.retrieveEmbeddings(memoryId, sectors); expect(mockEmbeddingStorage.retrieveEmbeddings).toHaveBeenCalledWith(memoryId, sectors); expect(embeddings.semantic.length).toBe(768); expect(embeddings.episodic.length).toBe(768); }); it("should return empty arrays for non-existent memory", async () => { const memoryId = "non-existent-memory"; mockEmbeddingStorage.retrieveEmbeddings.mockResolvedValue({ episodic: [], semantic: [], procedural: [], emotional: [], reflective: [], }); const embeddings = await mockEmbeddingStorage.retrieveEmbeddings(memoryId); expect(embeddings.semantic).toEqual([]); expect(embeddings.episodic).toEqual([]); }); }); describe("Embedding Update Flow", () => { it("should regenerate and update embeddings when content changes", async () => { const memoryId = "test-memory-005"; const newContent = "Updated memory content"; // Generate new embeddings const newEmbeddings = await mockEmbeddingEngine.generateAllSectorEmbeddings({ text: newContent, sector: "semantic", }); expect(mockEmbeddingEngine.generateAllSectorEmbeddings).toHaveBeenCalled(); // Update embeddings in storage await mockEmbeddingStorage.updateEmbeddings(memoryId, newEmbeddings, "nomic-embed-text"); expect(mockEmbeddingStorage.updateEmbeddings).toHaveBeenCalledWith( memoryId, newEmbeddings, "nomic-embed-text" ); }); it("should update only specific sectors when partial update is needed", async () => { const memoryId = "test-memory-006"; const newContent = "Partially updated content"; // Generate only semantic embedding const semanticEmbedding = await mockEmbeddingEngine.generateSemanticEmbedding(newContent); const partialUpdate: Partial<SectorEmbeddings> = { semantic: semanticEmbedding, }; await mockEmbeddingStorage.updateEmbeddings(memoryId, partialUpdate, "nomic-embed-text"); expect(mockEmbeddingStorage.updateEmbeddings).toHaveBeenCalledWith( memoryId, partialUpdate, "nomic-embed-text" ); }); }); describe("Embedding Deletion Flow", () => { it("should delete all embeddings for a memory", async () => { const memoryId = "test-memory-007"; await mockEmbeddingStorage.deleteEmbeddings(memoryId); expect(mockEmbeddingStorage.deleteEmbeddings).toHaveBeenCalledWith(memoryId); }); it("should handle deletion of non-existent memory gracefully", async () => { const memoryId = "non-existent-memory"; // Should not throw await mockEmbeddingStorage.deleteEmbeddings(memoryId); expect(mockEmbeddingStorage.deleteEmbeddings).toHaveBeenCalledWith(memoryId); }); }); describe("Vector Similarity Search Flow", () => { it("should generate query embedding and perform similarity search", async () => { const searchText = "search query text"; // Generate query embedding const queryEmbedding = await mockEmbeddingEngine.generateSemanticEmbedding(searchText); expect(mockEmbeddingEngine.generateSemanticEmbedding).toHaveBeenCalledWith(searchText); // Perform similarity search mockEmbeddingStorage.vectorSimilaritySearch.mockResolvedValue([ { memoryId: "memory-1", sector: "semantic", similarity: 0.95 }, { memoryId: "memory-2", sector: "semantic", similarity: 0.85 }, ]); const results = await mockEmbeddingStorage.vectorSimilaritySearch( queryEmbedding, "semantic" as MemorySector, 10, 0.5 ); expect(mockEmbeddingStorage.vectorSimilaritySearch).toHaveBeenCalledWith( queryEmbedding, "semantic", 10, 0.5 ); expect(results.length).toBe(2); expect(results[0].similarity).toBeGreaterThan(results[1].similarity); }); it("should return empty results when no matches found", async () => { const searchText = "no matching content"; const queryEmbedding = await mockEmbeddingEngine.generateSemanticEmbedding(searchText); mockEmbeddingStorage.vectorSimilaritySearch.mockResolvedValue([]); const results = await mockEmbeddingStorage.vectorSimilaritySearch( queryEmbedding, "semantic" as MemorySector, 10, 0.9 ); expect(results).toEqual([]); }); it("should filter results by similarity threshold", async () => { const searchText = "threshold test"; const queryEmbedding = await mockEmbeddingEngine.generateSemanticEmbedding(searchText); // Only return results above threshold mockEmbeddingStorage.vectorSimilaritySearch.mockResolvedValue([ { memoryId: "memory-1", sector: "semantic", similarity: 0.95 }, ]); const results = await mockEmbeddingStorage.vectorSimilaritySearch( queryEmbedding, "semantic" as MemorySector, 10, 0.8 ); expect(results.length).toBe(1); expect(results[0].similarity).toBeGreaterThanOrEqual(0.8); }); it("should limit results to specified count", async () => { const searchText = "limit test"; const queryEmbedding = await mockEmbeddingEngine.generateSemanticEmbedding(searchText); mockEmbeddingStorage.vectorSimilaritySearch.mockResolvedValue([ { memoryId: "memory-1", sector: "semantic", similarity: 0.95 }, { memoryId: "memory-2", sector: "semantic", similarity: 0.9 }, { memoryId: "memory-3", sector: "semantic", similarity: 0.85 }, ]); const results = await mockEmbeddingStorage.vectorSimilaritySearch( queryEmbedding, "semantic" as MemorySector, 3, 0.5 ); expect(results.length).toBeLessThanOrEqual(3); }); }); describe("Multi-Sector Search Flow", () => { it("should perform multi-sector search with weighted scoring", async () => { const searchText = "multi-sector search"; // Generate embeddings for multiple sectors const semanticEmbedding = await mockEmbeddingEngine.generateSemanticEmbedding(searchText); const episodicEmbedding = await mockEmbeddingEngine.generateEpisodicEmbedding(searchText); const queryEmbeddings: Partial<SectorEmbeddings> = { semantic: semanticEmbedding, episodic: episodicEmbedding, }; const weights = { semantic: 0.6, episodic: 0.4, }; mockEmbeddingStorage.multiSectorSearch.mockResolvedValue([ { memoryId: "memory-1", sector: "composite", similarity: 0.9 }, { memoryId: "memory-2", sector: "composite", similarity: 0.75 }, ]); const results = await mockEmbeddingStorage.multiSectorSearch(queryEmbeddings, weights, 10); expect(mockEmbeddingStorage.multiSectorSearch).toHaveBeenCalledWith( queryEmbeddings, weights, 10 ); expect(results.length).toBe(2); expect(results[0].similarity).toBeGreaterThan(results[1].similarity); }); it("should return empty results when no query embeddings provided", async () => { const results = await mockEmbeddingStorage.multiSectorSearch({}, {}, 10); expect(results).toEqual([]); }); }); describe("Error Handling", () => { it("should handle embedding engine failure gracefully", async () => { mockEmbeddingEngine.generateAllSectorEmbeddings.mockRejectedValue( new Error("Model unavailable") ); await expect( mockEmbeddingEngine.generateAllSectorEmbeddings({ text: "Test content", sector: "semantic", }) ).rejects.toThrow("Model unavailable"); }); it("should handle storage retrieval failure gracefully", async () => { mockEmbeddingStorage.retrieveEmbeddings.mockRejectedValue( new Error("Database connection lost") ); await expect(mockEmbeddingStorage.retrieveEmbeddings("test-id")).rejects.toThrow( "Database connection lost" ); }); it("should handle vector search failure gracefully", async () => { mockEmbeddingStorage.vectorSimilaritySearch.mockRejectedValue(new Error("Search failed")); await expect( mockEmbeddingStorage.vectorSimilaritySearch( createTestEmbedding(768, 0.1), "semantic" as MemorySector, 10, 0.5 ) ).rejects.toThrow("Search failed"); }); }); describe("Dimension Consistency", () => { it("should ensure embedding dimensions match between engine and storage", async () => { const dimension = mockEmbeddingEngine.getModelDimension(); expect(dimension).toBe(768); const embeddings = await mockEmbeddingEngine.generateAllSectorEmbeddings({ text: "Test content", sector: "semantic", }); // All sector embeddings should have the same dimension expect(embeddings.episodic.length).toBe(dimension); expect(embeddings.semantic.length).toBe(dimension); expect(embeddings.procedural.length).toBe(dimension); expect(embeddings.emotional.length).toBe(dimension); expect(embeddings.reflective.length).toBe(dimension); }); }); describe("Batch Operations", () => { it("should handle batch embedding generation and storage", async () => { const memories = [ { text: "Memory 1", sector: "semantic" }, { text: "Memory 2", sector: "semantic" }, { text: "Memory 3", sector: "semantic" }, ]; // Generate embeddings for all memories const allEmbeddings = await Promise.all( memories.map((m) => mockEmbeddingEngine.generateAllSectorEmbeddings(m)) ); expect(allEmbeddings.length).toBe(3); expect(mockEmbeddingEngine.generateAllSectorEmbeddings).toHaveBeenCalledTimes(3); // Store all embeddings const memoryIds = ["mem-1", "mem-2", "mem-3"]; await Promise.all( memoryIds.map((id, i) => mockEmbeddingStorage.storeEmbeddings(id, allEmbeddings[i], "nomic-embed-text") ) ); expect(mockEmbeddingStorage.storeEmbeddings).toHaveBeenCalledTimes(3); }); }); });

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/keyurgolani/ThoughtMcp'

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