Skip to main content
Glama
graph-traversal.test.ts101 kB
/** * Graph Traversal Tests * * Tests for graph traversal functionality using real GraphTraversal class. * Tests include: * - Finding connected memories (BFS and DFS) * - Path finding between memories * - Path explanation generation * - Waypoint expansion * - Connection strength weighting * * Requirements: 2.3, 2.4, 2.5 */ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { DatabaseConnectionManager } from "../../../database/connection-manager"; import { GraphTraversal } from "../../../graph/graph-traversal"; import { LinkType, type Memory } from "../../../graph/types"; describe("GraphTraversal - getConnectedMemories", () => { let db: DatabaseConnectionManager; let graphTraversal: GraphTraversal; let testMemoryIds: string[]; beforeEach(async () => { // Create database connection db = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: process.env.DB_NAME || "thoughtmcp_test", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", poolSize: 5, }); await db.connect(); graphTraversal = new GraphTraversal(db); testMemoryIds = []; // Clean up any existing test data const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-graph-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-graph-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-graph-%'"); } finally { db.releaseConnection(client); } }); afterEach(async () => { // Clean up test data if (db) { const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-graph-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-graph-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-graph-%'"); } finally { db.releaseConnection(client); } await db.disconnect(); } }); async function createTestMemory( id: string, content: string, salience: number = 0.5 ): Promise<void> { testMemoryIds.push(id); const client = await db.getConnection(); try { await client.query( `INSERT INTO memories (id, content, created_at, last_accessed, access_count, salience, strength, user_id, session_id, primary_sector) VALUES ($1, $2, NOW(), NOW(), 0, $3, 1.0, 'test-user', 'test-session', 'semantic')`, [id, content, salience] ); await client.query( `INSERT INTO memory_metadata (memory_id, keywords, tags, category, context, importance, is_atomic) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, [], [], "test", "", 0.5, true] ); } finally { db.releaseConnection(client); } } async function createTestLink( sourceId: string, targetId: string, linkType: string, weight: number ): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memory_links (source_id, target_id, link_type, weight, created_at, traversal_count) VALUES ($1, $2, $3, $4, NOW(), 0)`, [sourceId, targetId, linkType, weight] ); } finally { db.releaseConnection(client); } } it("should find direct connections (1-hop) with BFS", async () => { // Requirement 2.3: Waypoint graph with 1-3 links per memory // Requirement 2.4: Graph traversal for memory retrieval // Given: Memory A with direct link to B await createTestMemory("test-graph-A", "Memory A"); await createTestMemory("test-graph-B", "Memory B"); await createTestLink("test-graph-A", "test-graph-B", "semantic", 0.8); // When: Finding 1-hop connections const result = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 1 }); // Then: Should return A (start) and B (1-hop) expect(result.memories).toHaveLength(2); expect(result.visitedCount).toBe(2); expect(result.memories.find((m) => m.id === "test-graph-A")).toBeDefined(); expect(result.memories.find((m) => m.id === "test-graph-B")).toBeDefined(); }); it("should find multi-hop connections (2-3 hops) with BFS", async () => { // Requirement 2.4: Graph traversal for memory retrieval // Given: Chain A→B→C→D await createTestMemory("test-graph-A", "Memory A"); await createTestMemory("test-graph-B", "Memory B"); await createTestMemory("test-graph-C", "Memory C"); await createTestMemory("test-graph-D", "Memory D"); await createTestLink("test-graph-A", "test-graph-B", "semantic", 0.8); await createTestLink("test-graph-B", "test-graph-C", "semantic", 0.7); await createTestLink("test-graph-C", "test-graph-D", "semantic", 0.9); // When: Finding 2-hop connections const result2 = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 2 }); // Then: Should return A, B, C (up to 2 hops) expect(result2.memories).toHaveLength(3); expect(result2.visitedCount).toBe(3); // When: Finding 3-hop connections const result3 = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 3 }); // Then: Should return A, B, C, D (up to 3 hops) expect(result3.memories).toHaveLength(4); expect(result3.visitedCount).toBe(4); }); it("should handle isolated nodes (no connections)", async () => { // Requirement 2.3: Waypoint graph structure // Given: Memory with no outgoing links await createTestMemory("test-graph-isolated", "Isolated Memory"); // When: Finding connections const result = await graphTraversal.getConnectedMemories("test-graph-isolated", { maxDepth: 3, }); // Then: Should return only the start node expect(result.memories).toHaveLength(1); expect(result.visitedCount).toBe(1); expect(result.memories[0].id).toBe("test-graph-isolated"); }); it("should support depth-first traversal", async () => { // Requirement 2.4: DFS traversal // Given: Tree structure A→B, A→C, B→D await createTestMemory("test-graph-A", "Memory A"); await createTestMemory("test-graph-B", "Memory B"); await createTestMemory("test-graph-C", "Memory C"); await createTestMemory("test-graph-D", "Memory D"); await createTestLink("test-graph-A", "test-graph-B", "semantic", 0.8); await createTestLink("test-graph-A", "test-graph-C", "semantic", 0.7); await createTestLink("test-graph-B", "test-graph-D", "semantic", 0.9); // When: DFS traversal with depth 2 const result = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should visit all reachable nodes expect(result.memories).toHaveLength(4); expect(result.visitedCount).toBe(4); }); it("should filter by minimum weight threshold", async () => { // Requirement 2.5: Weight threshold filtering // Given: Links with different weights await createTestMemory("test-graph-A", "Memory A"); await createTestMemory("test-graph-B", "Memory B"); await createTestMemory("test-graph-C", "Memory C"); await createTestMemory("test-graph-D", "Memory D"); await createTestLink("test-graph-A", "test-graph-B", "semantic", 0.9); await createTestLink("test-graph-A", "test-graph-C", "semantic", 0.5); await createTestLink("test-graph-A", "test-graph-D", "semantic", 0.7); // When: Filtering by minimum weight 0.6 const result = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 1, minWeight: 0.6, }); // Then: Should return only A, B (0.9), and D (0.7), not C (0.5) expect(result.memories).toHaveLength(3); const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-graph-A"); expect(ids).toContain("test-graph-B"); expect(ids).toContain("test-graph-D"); expect(ids).not.toContain("test-graph-C"); }); it("should handle non-existent memory ID", async () => { // Requirement 2.4: Edge case handling // When: Querying non-existent memory const result = await graphTraversal.getConnectedMemories("non-existent-id", { maxDepth: 1 }); // Then: Should return empty result expect(result.memories).toHaveLength(0); expect(result.visitedCount).toBe(0); }); it("should handle depth 0 boundary condition", async () => { // Requirement 2.4: Edge case handling // Given: Memory with connections await createTestMemory("test-graph-A", "Memory A"); await createTestMemory("test-graph-B", "Memory B"); await createTestLink("test-graph-A", "test-graph-B", "semantic", 0.8); // When: Traversing with depth 0 const result = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 0 }); // Then: Should return only start node expect(result.memories).toHaveLength(1); expect(result.memories[0].id).toBe("test-graph-A"); }); it("should prevent infinite loops in cyclic graphs", async () => { // Requirement 2.3: Cycle detection // Given: Cycle A→B→C→A await createTestMemory("test-graph-A", "Memory A"); await createTestMemory("test-graph-B", "Memory B"); await createTestMemory("test-graph-C", "Memory C"); await createTestLink("test-graph-A", "test-graph-B", "semantic", 0.8); await createTestLink("test-graph-B", "test-graph-C", "semantic", 0.7); await createTestLink("test-graph-C", "test-graph-A", "semantic", 0.9); // When: Traversing with large depth const result = await graphTraversal.getConnectedMemories("test-graph-A", { maxDepth: 10 }); // Then: Should visit each node only once expect(result.memories).toHaveLength(3); expect(result.visitedCount).toBe(3); }); }); describe("GraphTraversal - findPath", () => { let db: DatabaseConnectionManager; let graphTraversal: GraphTraversal; beforeEach(async () => { db = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: process.env.DB_NAME || "thoughtmcp_test", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", poolSize: 5, }); await db.connect(); graphTraversal = new GraphTraversal(db); const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-path-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-path-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-path-%'"); } finally { db.releaseConnection(client); } }); afterEach(async () => { if (db) { const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-path-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-path-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-path-%'"); } finally { db.releaseConnection(client); } await db.disconnect(); } }); async function createTestMemory(id: string, content: string): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memories (id, content, created_at, last_accessed, access_count, salience, strength, user_id, session_id, primary_sector) VALUES ($1, $2, NOW(), NOW(), 0, 0.5, 1.0, 'test-user', 'test-session', 'semantic')`, [id, content] ); await client.query( `INSERT INTO memory_metadata (memory_id, keywords, tags, category, context, importance, is_atomic) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, [], [], "test", "", 0.5, true] ); } finally { db.releaseConnection(client); } } async function createTestLink( sourceId: string, targetId: string, linkType: string, weight: number ): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memory_links (source_id, target_id, link_type, weight, created_at, traversal_count) VALUES ($1, $2, $3, $4, NOW(), 0)`, [sourceId, targetId, linkType, weight] ); } finally { db.releaseConnection(client); } } it("should find shortest path between two memories", async () => { // Requirement 2.4: Path finding // Given: Path A→B→C await createTestMemory("test-path-A", "Memory A"); await createTestMemory("test-path-B", "Memory B"); await createTestMemory("test-path-C", "Memory C"); await createTestLink("test-path-A", "test-path-B", "causal", 0.85); await createTestLink("test-path-B", "test-path-C", "temporal", 0.72); // When: Finding path from A to C const path = await graphTraversal.findPath("test-path-A", "test-path-C", 5); // Then: Should find path A→B→C expect(path).not.toBeNull(); expect(path!.memories).toHaveLength(3); expect(path!.links).toHaveLength(2); expect(path!.memories[0].id).toBe("test-path-A"); expect(path!.memories[1].id).toBe("test-path-B"); expect(path!.memories[2].id).toBe("test-path-C"); }); it("should return null when no path exists", async () => { // Requirement 2.4: Handle disconnected graphs // Given: Disconnected memories A and B await createTestMemory("test-path-A", "Memory A"); await createTestMemory("test-path-B", "Memory B"); // When: Finding path from A to B const path = await graphTraversal.findPath("test-path-A", "test-path-B", 5); // Then: Should return null expect(path).toBeNull(); }); it("should respect maxDepth limit", async () => { // Requirement 2.4: Depth-limited path finding // Given: Long chain A→B→C→D→E await createTestMemory("test-path-A", "Memory A"); await createTestMemory("test-path-B", "Memory B"); await createTestMemory("test-path-C", "Memory C"); await createTestMemory("test-path-D", "Memory D"); await createTestMemory("test-path-E", "Memory E"); await createTestLink("test-path-A", "test-path-B", "semantic", 0.8); await createTestLink("test-path-B", "test-path-C", "semantic", 0.7); await createTestLink("test-path-C", "test-path-D", "semantic", 0.9); await createTestLink("test-path-D", "test-path-E", "semantic", 0.6); // When: Finding path with maxDepth 2 const path = await graphTraversal.findPath("test-path-A", "test-path-E", 2); // Then: Should not find path (requires 4 hops) expect(path).toBeNull(); }); it("should calculate path weight correctly", async () => { // Requirement 2.5: Path weight calculation // Given: Path with known weights await createTestMemory("test-path-A", "Memory A"); await createTestMemory("test-path-B", "Memory B"); await createTestMemory("test-path-C", "Memory C"); await createTestLink("test-path-A", "test-path-B", "causal", 0.8); await createTestLink("test-path-B", "test-path-C", "temporal", 0.6); // When: Finding path const path = await graphTraversal.findPath("test-path-A", "test-path-C", 5); // Then: Should calculate average weight (0.8 + 0.6) / 2 = 0.7 expect(path).not.toBeNull(); expect(path!.totalWeight).toBeCloseTo(0.7, 2); }); it("should handle source equals target", async () => { // Requirement 2.4: Edge case handling // Given: Single memory await createTestMemory("test-path-A", "Memory A"); // When: Finding path from A to A const path = await graphTraversal.findPath("test-path-A", "test-path-A", 5); // Then: Should return path with single memory expect(path).not.toBeNull(); expect(path!.memories).toHaveLength(1); expect(path!.links).toHaveLength(0); expect(path!.memories[0].id).toBe("test-path-A"); }); it("should find shortest path when multiple paths exist", async () => { // Requirement 2.4: Shortest path selection // Given: Two paths from A to D: // Path 1: A→B→D (2 hops) // Path 2: A→C→E→D (3 hops) await createTestMemory("test-path-A", "Memory A"); await createTestMemory("test-path-B", "Memory B"); await createTestMemory("test-path-C", "Memory C"); await createTestMemory("test-path-D", "Memory D"); await createTestMemory("test-path-E", "Memory E"); // Shorter path: A→B→D await createTestLink("test-path-A", "test-path-B", "semantic", 0.8); await createTestLink("test-path-B", "test-path-D", "semantic", 0.7); // Longer path: A→C→E→D await createTestLink("test-path-A", "test-path-C", "semantic", 0.9); await createTestLink("test-path-C", "test-path-E", "semantic", 0.8); await createTestLink("test-path-E", "test-path-D", "semantic", 0.7); // When: Finding path from A to D const path = await graphTraversal.findPath("test-path-A", "test-path-D", 5); // Then: Should return shorter path (2 hops) expect(path).not.toBeNull(); expect(path!.memories).toHaveLength(3); expect(path!.links).toHaveLength(2); expect(path!.memories[0].id).toBe("test-path-A"); expect(path!.memories[1].id).toBe("test-path-B"); expect(path!.memories[2].id).toBe("test-path-D"); }); it("should handle cyclic graphs without infinite loops", async () => { // Requirement 2.4: Cycle handling // Given: Cycle A→B→C→A with target D reachable from B await createTestMemory("test-path-A", "Memory A"); await createTestMemory("test-path-B", "Memory B"); await createTestMemory("test-path-C", "Memory C"); await createTestMemory("test-path-D", "Memory D"); // Create cycle await createTestLink("test-path-A", "test-path-B", "semantic", 0.8); await createTestLink("test-path-B", "test-path-C", "semantic", 0.7); await createTestLink("test-path-C", "test-path-A", "semantic", 0.9); // Add path to target await createTestLink("test-path-B", "test-path-D", "semantic", 0.6); // When: Finding path from A to D const path = await graphTraversal.findPath("test-path-A", "test-path-D", 5); // Then: Should find path without infinite loop expect(path).not.toBeNull(); expect(path!.memories).toHaveLength(3); expect(path!.memories[0].id).toBe("test-path-A"); expect(path!.memories[1].id).toBe("test-path-B"); expect(path!.memories[2].id).toBe("test-path-D"); }); it("should return null for non-existent source memory", async () => { // Requirement 2.4: Error handling // Given: Valid target memory await createTestMemory("test-path-target", "Target Memory"); // When: Finding path from non-existent source const path = await graphTraversal.findPath("non-existent-source", "test-path-target", 5); // Then: Should return null expect(path).toBeNull(); }); it("should return null for non-existent target memory", async () => { // Requirement 2.4: Error handling // Given: Valid source memory await createTestMemory("test-path-source", "Source Memory"); // When: Finding path to non-existent target const path = await graphTraversal.findPath("test-path-source", "non-existent-target", 5); // Then: Should return null expect(path).toBeNull(); }); }); describe("GraphTraversal - explainPath", () => { let db: DatabaseConnectionManager; let graphTraversal: GraphTraversal; beforeEach(async () => { db = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: process.env.DB_NAME || "thoughtmcp_test", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", poolSize: 5, }); await db.connect(); graphTraversal = new GraphTraversal(db); }); afterEach(async () => { if (db) { await db.disconnect(); } }); function createMemory(id: string, content: string): Memory { return { id, content, createdAt: new Date(), lastAccessed: new Date(), accessCount: 0, salience: 0.5, strength: 1.0, userId: "test-user", sessionId: "test-session", primarySector: "semantic", metadata: { keywords: [], tags: [], category: "test", context: "", importance: 0.5, isAtomic: true, }, }; } it("should generate explanation for multi-step path", async () => { // Requirement 2.4: Path explanation generation // Given: Path A→B→C const path = { memories: [ createMemory("A", "Memory A"), createMemory("B", "Memory B"), createMemory("C", "Memory C"), ], links: [ { sourceId: "A", targetId: "B", linkType: LinkType.Causal, weight: 0.85, createdAt: new Date(), traversalCount: 0, }, { sourceId: "B", targetId: "C", linkType: LinkType.Temporal, weight: 0.72, createdAt: new Date(), traversalCount: 0, }, ], totalWeight: 0.785, explanation: "", }; // When: Generating explanation const explanation = graphTraversal.explainPath(path); // Then: Should include all memories, link types, and weights expect(explanation).toContain("Memory A"); expect(explanation).toContain("Memory B"); expect(explanation).toContain("Memory C"); expect(explanation).toContain("causal"); expect(explanation).toContain("temporal"); expect(explanation).toContain("0.85"); expect(explanation).toContain("0.72"); expect(explanation).toContain("0.79"); // total weight }); it("should handle empty path", async () => { // Requirement 2.4: Edge case handling // Given: Empty path const emptyPath = { memories: [], links: [], totalWeight: 0, explanation: "", }; // When: Generating explanation const explanation = graphTraversal.explainPath(emptyPath); // Then: Should return appropriate message expect(explanation).toBe("No path found"); }); it("should handle single memory path", async () => { // Requirement 2.4: Edge case handling // Given: Path with single memory const singlePath = { memories: [createMemory("A", "Memory A")], links: [], totalWeight: 0, explanation: "", }; // When: Generating explanation const explanation = graphTraversal.explainPath(singlePath); // Then: Should return just the memory content expect(explanation).toBe("Memory A"); }); it("should truncate long content", async () => { // Requirement 2.4: Content truncation // Given: Path with memory having long content (>50 chars) and a link const longContent = "A".repeat(100); const path = { memories: [createMemory("A", longContent), createMemory("B", "Short")], links: [ { sourceId: "A", targetId: "B", linkType: LinkType.Semantic, weight: 0.8, createdAt: new Date(), traversalCount: 0, }, ], totalWeight: 0.8, explanation: "", }; // When: Generating explanation const explanation = graphTraversal.explainPath(path); // Then: Should truncate long content to 50 chars (47 + "...") expect(explanation).toContain("..."); expect(explanation).toContain("Short"); // Second memory should be included }); }); describe("GraphTraversal - expandViaWaypoint", () => { let db: DatabaseConnectionManager; let graphTraversal: GraphTraversal; beforeEach(async () => { db = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: process.env.DB_NAME || "thoughtmcp_test", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", poolSize: 5, }); await db.connect(); graphTraversal = new GraphTraversal(db); const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-expand-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-expand-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-expand-%'"); } finally { db.releaseConnection(client); } }); afterEach(async () => { if (db) { const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-expand-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-expand-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-expand-%'"); } finally { db.releaseConnection(client); } await db.disconnect(); } }); async function createTestMemory(id: string, content: string): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memories (id, content, created_at, last_accessed, access_count, salience, strength, user_id, session_id, primary_sector) VALUES ($1, $2, NOW(), NOW(), 0, 0.5, 1.0, 'test-user', 'test-session', 'semantic')`, [id, content] ); await client.query( `INSERT INTO memory_metadata (memory_id, keywords, tags, category, context, importance, is_atomic) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, [], [], "test", "", 0.5, true] ); } finally { db.releaseConnection(client); } } async function createTestLink( sourceId: string, targetId: string, linkType: string, weight: number ): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memory_links (source_id, target_id, link_type, weight, created_at, traversal_count) VALUES ($1, $2, $3, $4, NOW(), 0)`, [sourceId, targetId, linkType, weight] ); } finally { db.releaseConnection(client); } } it("should expand for 1 hop", async () => { // Requirement 2.3: Waypoint expansion // Given: A→B await createTestMemory("test-expand-A", "Memory A"); await createTestMemory("test-expand-B", "Memory B"); await createTestLink("test-expand-A", "test-expand-B", "semantic", 0.8); // When: Expanding 1 hop const memories = await graphTraversal.expandViaWaypoint("test-expand-A", 1); // Then: Should return A and B expect(memories).toHaveLength(2); const ids = memories.map((m) => m.id); expect(ids).toContain("test-expand-A"); expect(ids).toContain("test-expand-B"); }); it("should expand for multiple hops", async () => { // Requirement 2.3: Multi-hop expansion // Given: A→B→C→D await createTestMemory("test-expand-A", "Memory A"); await createTestMemory("test-expand-B", "Memory B"); await createTestMemory("test-expand-C", "Memory C"); await createTestMemory("test-expand-D", "Memory D"); await createTestLink("test-expand-A", "test-expand-B", "semantic", 0.8); await createTestLink("test-expand-B", "test-expand-C", "semantic", 0.7); await createTestLink("test-expand-C", "test-expand-D", "semantic", 0.9); // When: Expanding 2 hops const memories = await graphTraversal.expandViaWaypoint("test-expand-A", 2); // Then: Should return A, B, C expect(memories).toHaveLength(3); }); it("should return empty array for negative hops", async () => { // Requirement 2.3: Edge case handling // Given: Any memory await createTestMemory("test-expand-A", "Memory A"); // When: Expanding with negative hops const memories = await graphTraversal.expandViaWaypoint("test-expand-A", -1); // Then: Should return empty array expect(memories).toHaveLength(0); }); it("should return only start node for 0 hops", async () => { // Requirement 2.3: Edge case handling // Given: Memory with connections await createTestMemory("test-expand-A", "Memory A"); await createTestMemory("test-expand-B", "Memory B"); await createTestLink("test-expand-A", "test-expand-B", "semantic", 0.8); // When: Expanding 0 hops const memories = await graphTraversal.expandViaWaypoint("test-expand-A", 0); // Then: Should return only start node expect(memories).toHaveLength(1); expect(memories[0].id).toBe("test-expand-A"); }); it("should handle non-existent start memory", async () => { // Requirement 2.3: Error handling // When: Expanding from non-existent memory const memories = await graphTraversal.expandViaWaypoint("non-existent-id", 2); // Then: Should return empty array expect(memories).toHaveLength(0); }); }); describe("GraphTraversal - Error Handling and Edge Cases", () => { let db: DatabaseConnectionManager; let graphTraversal: GraphTraversal; beforeEach(async () => { db = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: process.env.DB_NAME || "thoughtmcp_test", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", poolSize: 5, }); await db.connect(); graphTraversal = new GraphTraversal(db); const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-error-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-error-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-error-%'"); } finally { db.releaseConnection(client); } }); afterEach(async () => { if (db) { const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-error-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-error-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-error-%'"); } finally { db.releaseConnection(client); } await db.disconnect(); } }); async function createTestMemory(id: string, content: string): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memories (id, content, created_at, last_accessed, access_count, salience, strength, user_id, session_id, primary_sector) VALUES ($1, $2, NOW(), NOW(), 0, 0.5, 1.0, 'test-user', 'test-session', 'semantic')`, [id, content] ); await client.query( `INSERT INTO memory_metadata (memory_id, keywords, tags, category, context, importance, is_atomic) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, [], [], "test", "", 0.5, true] ); } finally { db.releaseConnection(client); } } async function createTestLink( sourceId: string, targetId: string, linkType: string, weight: number ): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memory_links (source_id, target_id, link_type, weight, created_at, traversal_count) VALUES ($1, $2, $3, $4, NOW(), 0)`, [sourceId, targetId, linkType, weight] ); } finally { db.releaseConnection(client); } } it("should handle memory with missing metadata gracefully", async () => { // Requirement 2.4: Error handling // Given: Memory without metadata (edge case) const client = await db.getConnection(); try { await client.query( `INSERT INTO memories (id, content, created_at, last_accessed, access_count, salience, strength, user_id, session_id, primary_sector) VALUES ($1, $2, NOW(), NOW(), 0, 0.5, 1.0, 'test-user', 'test-session', 'semantic')`, ["test-error-no-metadata", "Memory without metadata"] ); // Intentionally not inserting metadata } finally { db.releaseConnection(client); } // When: Traversing from this memory const result = await graphTraversal.getConnectedMemories("test-error-no-metadata", { maxDepth: 1, }); // Then: Should still return the memory with default metadata expect(result.memories).toHaveLength(1); expect(result.memories[0].id).toBe("test-error-no-metadata"); expect(result.memories[0].metadata).toBeDefined(); }); it("should handle broken links gracefully (target memory deleted)", async () => { // Requirement 2.4: Error handling // Given: Link pointing to non-existent memory await createTestMemory("test-error-A", "Memory A"); await createTestMemory("test-error-B", "Memory B"); await createTestLink("test-error-A", "test-error-B", "semantic", 0.8); // Delete target memory but leave link (simulates orphaned link) const client = await db.getConnection(); try { await client.query("DELETE FROM memory_metadata WHERE memory_id = $1", ["test-error-B"]); await client.query("DELETE FROM memories WHERE id = $1", ["test-error-B"]); } finally { db.releaseConnection(client); } // When: Traversing from A const result = await graphTraversal.getConnectedMemories("test-error-A", { maxDepth: 1 }); // Then: Should return only A (broken link ignored) expect(result.memories).toHaveLength(1); expect(result.memories[0].id).toBe("test-error-A"); }); it("should handle DFS with missing memory in traversal path", async () => { // Requirement 2.4: DFS error handling // Given: Chain where middle node will be deleted after link creation await createTestMemory("test-error-A", "Memory A"); await createTestMemory("test-error-B", "Memory B"); await createTestMemory("test-error-C", "Memory C"); await createTestLink("test-error-A", "test-error-B", "semantic", 0.8); await createTestLink("test-error-A", "test-error-C", "semantic", 0.7); // Delete B after links are created const client = await db.getConnection(); try { await client.query("DELETE FROM memory_metadata WHERE memory_id = $1", ["test-error-B"]); await client.query("DELETE FROM memories WHERE id = $1", ["test-error-B"]); } finally { db.releaseConnection(client); } // When: DFS traversal const result = await graphTraversal.getConnectedMemories("test-error-A", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should skip broken link and continue with valid ones expect(result.memories.length).toBeGreaterThanOrEqual(2); const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-error-A"); expect(ids).toContain("test-error-C"); }); it("should handle path finding with broken intermediate node", async () => { // Requirement 2.4: Path finding error handling // Given: Path A→B→C where B will be deleted await createTestMemory("test-error-A", "Memory A"); await createTestMemory("test-error-B", "Memory B"); await createTestMemory("test-error-C", "Memory C"); await createTestLink("test-error-A", "test-error-B", "semantic", 0.8); await createTestLink("test-error-B", "test-error-C", "semantic", 0.7); // Delete intermediate node const client = await db.getConnection(); try { await client.query("DELETE FROM memory_metadata WHERE memory_id = $1", ["test-error-B"]); await client.query("DELETE FROM memories WHERE id = $1", ["test-error-B"]); } finally { db.releaseConnection(client); } // When: Finding path from A to C const path = await graphTraversal.findPath("test-error-A", "test-error-C", 5); // Then: Should return null (path broken) expect(path).toBeNull(); }); it("should handle DFS with minWeight filtering", async () => { // Requirement 2.5: DFS with weight filtering // Given: Tree with varying weights await createTestMemory("test-error-A", "Memory A"); await createTestMemory("test-error-B", "Memory B"); await createTestMemory("test-error-C", "Memory C"); await createTestMemory("test-error-D", "Memory D"); await createTestLink("test-error-A", "test-error-B", "semantic", 0.9); await createTestLink("test-error-A", "test-error-C", "semantic", 0.4); await createTestLink("test-error-B", "test-error-D", "semantic", 0.8); // When: DFS with minWeight 0.5 const result = await graphTraversal.getConnectedMemories("test-error-A", { maxDepth: 2, traversalType: "depth-first", minWeight: 0.5, }); // Then: Should include A, B, D but not C (weight 0.4) const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-error-A"); expect(ids).toContain("test-error-B"); expect(ids).toContain("test-error-D"); expect(ids).not.toContain("test-error-C"); }); it("should handle DFS with non-existent start memory", async () => { // Requirement 2.4: DFS error handling for missing start node // When: DFS from non-existent memory const result = await graphTraversal.getConnectedMemories("non-existent-dfs-start", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should return empty result expect(result.memories).toHaveLength(0); expect(result.visitedCount).toBe(0); }); it("should handle database query errors in getOutgoingLinks gracefully", async () => { // Requirement 2.4: Database error handling // Given: Memory with invalid link data that causes query error await createTestMemory("test-error-query", "Memory A"); // Create a corrupted link by inserting invalid data directly const client = await db.getConnection(); try { // Insert a link with NULL weight which violates constraint await client.query( `INSERT INTO memory_links (source_id, target_id, link_type, weight, created_at, traversal_count) VALUES ($1, $2, $3, NULL, NOW(), 0) ON CONFLICT DO NOTHING`, ["test-error-query", "test-error-target", "semantic"] ); } catch { // Expected to fail, that's okay } finally { db.releaseConnection(client); } // When: Traversing from this memory (should handle query errors) const result = await graphTraversal.getConnectedMemories("test-error-query", { maxDepth: 1, }); // Then: Should handle error gracefully expect(result).toBeDefined(); }); it("should handle database connection errors gracefully during traversal", async () => { // Requirement 2.4: Database connection error handling // Given: Valid memory setup await createTestMemory("test-error-conn-A", "Memory A"); await createTestMemory("test-error-conn-B", "Memory B"); await createTestLink("test-error-conn-A", "test-error-conn-B", "semantic", 0.8); // When: Disconnect database mid-traversal await db.disconnect(); // Try to traverse (will fail to get connections) const result = await graphTraversal.getConnectedMemories("test-error-conn-A", { maxDepth: 1, }); // Then: Should handle error gracefully without throwing expect(result).toBeDefined(); }); it("should handle null current node in DFS traversal", async () => { // Requirement 2.4: DFS null handling // Given: Memory with connections await createTestMemory("test-error-dfs-null", "Memory A"); await createTestMemory("test-error-dfs-B", "Memory B"); await createTestLink("test-error-dfs-null", "test-error-dfs-B", "semantic", 0.8); // When: DFS traversal (internal stack.pop() could return undefined) const result = await graphTraversal.getConnectedMemories("test-error-dfs-null", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should complete successfully expect(result.memories.length).toBeGreaterThan(0); }); it("should skip null memories in DFS traversal path", async () => { // Requirement 2.4: DFS null memory handling // Given: Chain where a memory might be null during traversal await createTestMemory("test-error-dfs-chain-A", "Memory A"); await createTestMemory("test-error-dfs-chain-B", "Memory B"); await createTestMemory("test-error-dfs-chain-C", "Memory C"); await createTestLink("test-error-dfs-chain-A", "test-error-dfs-chain-B", "semantic", 0.8); await createTestLink("test-error-dfs-chain-B", "test-error-dfs-chain-C", "semantic", 0.7); // Delete middle memory after links created const client = await db.getConnection(); try { await client.query("DELETE FROM memory_metadata WHERE memory_id = $1", [ "test-error-dfs-chain-B", ]); await client.query("DELETE FROM memories WHERE id = $1", ["test-error-dfs-chain-B"]); } finally { db.releaseConnection(client); } // When: DFS traversal encounters null memory const result = await graphTraversal.getConnectedMemories("test-error-dfs-chain-A", { maxDepth: 3, traversalType: "depth-first", }); // Then: Should skip null memory and continue expect(result.memories.length).toBeGreaterThanOrEqual(1); const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-error-dfs-chain-A"); }); }); describe("GraphTraversal - Database Error Simulation", () => { it("should handle database query errors in getOutgoingLinks", async () => { // Requirement 2.4: Error handling in getOutgoingLinks // Create a mock database that throws errors const mockDb = { getConnection: async () => ({ query: async () => { throw new Error("Simulated database error"); }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: Calling getConnectedMemories (which calls getOutgoingLinks internally) const result = await graphTraversal.getConnectedMemories("any-id", { maxDepth: 1 }); // Then: Should handle error gracefully and return empty result expect(result.memories).toHaveLength(0); expect(result.visitedCount).toBe(0); }); it("should handle database query errors in getMemory", async () => { // Requirement 2.4: Error handling in getMemory // Create a mock database that throws errors const mockDb = { getConnection: async () => ({ query: async () => { throw new Error("Simulated database error in getMemory"); }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: Calling findPath (which calls getMemory internally) const path = await graphTraversal.findPath("source-id", "target-id", 5); // Then: Should handle error gracefully and return null expect(path).toBeNull(); }); it("should handle database errors in BFS traversal", async () => { // Requirement 2.4: BFS error handling let callCount = 0; const mockDb = { getConnection: async () => ({ query: async () => { callCount++; // First call succeeds (getMemory for start node), subsequent calls fail if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { // Metadata query return { rows: [] }; } else { // Subsequent queries fail (getOutgoingLinks) throw new Error("Database error"); } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS traversal with database errors const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2 }); // Then: Should return at least the start node expect(result.memories).toHaveLength(1); expect(result.memories[0].id).toBe("start-id"); }); it("should handle database errors in DFS traversal", async () => { // Requirement 2.4: DFS error handling let callCount = 0; const mockDb = { getConnection: async () => ({ query: async () => { callCount++; // First call succeeds (getMemory for start node check) if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { // Metadata query for start node return { rows: [] }; } else if (callCount === 3) { // Second getMemory call in DFS loop return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { // Metadata query for second getMemory return { rows: [] }; } else { // Subsequent queries fail (getOutgoingLinks) throw new Error("Database error"); } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS traversal with database errors const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should return at least the start node expect(result.memories).toHaveLength(1); expect(result.memories[0].id).toBe("start-id"); }); it("should skip null memories in DFS when getMemory returns null", async () => { // Requirement 2.4: DFS null memory handling (lines 410-411) // This test ensures that when getMemory returns null for a memory ID in the DFS stack, // the code skips adding it to the memories array but continues traversal let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; // First call: getMemory for start node check (before DFS loop) if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { // Metadata query for start node (before DFS loop) return { rows: [] }; } else if (callCount === 3) { // getMemory call in DFS loop for start-id - return it return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { // Metadata for start-id in DFS loop return { rows: [] }; } else if (callCount === 5) { // getOutgoingLinks for start node - return links to two memories return { rows: [ { source_id: "start-id", target_id: "deleted-memory-id", link_type: "semantic", weight: 0.8, created_at: new Date(), traversal_count: 0, }, { source_id: "start-id", target_id: "valid-memory-id", link_type: "semantic", weight: 0.7, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 6) { // getMemory for valid-memory-id (processed first due to stack LIFO) return { rows: [ { id: "valid-memory-id", content: "Valid memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 7) { // Metadata for valid-memory-id return { rows: [] }; } else if (callCount === 8) { // getOutgoingLinks for valid-memory-id return { rows: [] }; } else if (callCount === 9) { // getMemory for deleted-memory-id - return null (no rows) - THIS HITS LINE 410-411 return { rows: [] }; } else if (callCount === 10) { // Metadata query for deleted-memory-id (won't be called since rows is empty) return { rows: [] }; } else { // Any other calls return empty return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS traversal where getMemory returns null for a linked node const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should skip the null memory and not crash expect(result).toBeDefined(); expect(result.memories).toHaveLength(2); // start-id and valid-memory-id expect(result.memories[0].id).toBe("start-id"); expect(result.memories[1].id).toBe("valid-memory-id"); expect(result.visitedCount).toBe(3); // Visited start-id, valid-memory-id, and deleted-memory-id }); it("should skip null memories in BFS when getMemory returns null", async () => { // Requirement 2.4: BFS null memory handling (lines 397-398) let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; // First call: getMemory for start node check if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { // Metadata query for start node return { rows: [] }; } else if (callCount === 3) { // getOutgoingLinks for start node - return a link to a non-existent memory return { rows: [ { source_id: "start-id", target_id: "deleted-memory-id", link_type: "semantic", weight: 0.8, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 4) { // getMemory for deleted-memory-id - return null (no rows) return { rows: [] }; } else { // Any other calls return empty return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS traversal where getMemory returns null for a linked node const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2, traversalType: "breadth-first", }); // Then: Should skip the null memory and not crash expect(result).toBeDefined(); expect(result.memories).toHaveLength(1); // Only start-id expect(result.memories[0].id).toBe("start-id"); expect(result.visitedCount).toBe(2); // Visited start-id and deleted-memory-id }); it("should handle BFS without minWeight filter", async () => { // Requirement 2.4: BFS without weight filtering (line 356 else branch) let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.3, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 4) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 5) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS without minWeight (should use else branch) const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, // No minWeight specified }); // Then: Should include all links regardless of weight expect(result.memories).toHaveLength(2); }); it("should handle DFS without minWeight filter", async () => { // Requirement 2.4: DFS without weight filtering (line 430 else branch) let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { return { rows: [] }; } else if (callCount === 5) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.3, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 6) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 7) { return { rows: [] }; } else if (callCount === 8) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS without minWeight (should use else branch) const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, traversalType: "depth-first", // No minWeight specified }); // Then: Should include all links regardless of weight expect(result.memories).toHaveLength(2); }); it("should default to breadth-first traversal when traversalType is undefined", async () => { // Requirement 2.4: Default traversal type (line 44 else branch) // This test ensures that when traversalType is not specified, BFS is used by default let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.8, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 4) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 5) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: Calling getConnectedMemories without specifying traversalType const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, // traversalType is undefined - should default to BFS }); // Then: Should use BFS and return results expect(result).toBeDefined(); expect(result.memories).toHaveLength(2); expect(result.visitedCount).toBe(2); }); it("should skip already visited nodes in DFS traversal", async () => { // Requirement 2.4: DFS already-visited check (lines 410-411) // This test ensures the "if (visited.has(current.memoryId)) continue" branch is hit let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; // First call: getMemory for start node check if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { // Metadata query for start node return { rows: [] }; } else if (callCount === 3) { // getMemory for start-id in DFS loop return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { // Metadata for start-id return { rows: [] }; } else if (callCount === 5) { // getOutgoingLinks - return two links that point to same target (creates duplicate in stack) return { rows: [ { source_id: "start-id", target_id: "shared-target", link_type: "semantic", weight: 0.9, created_at: new Date(), traversal_count: 0, }, { source_id: "start-id", target_id: "shared-target", link_type: "causal", weight: 0.8, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 6) { // getMemory for shared-target (first time) return { rows: [ { id: "shared-target", content: "Shared target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 7) { // Metadata for shared-target return { rows: [] }; } else if (callCount === 8) { // getOutgoingLinks for shared-target return { rows: [] }; } else { // Any other calls return empty return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS traversal with duplicate target IDs in stack const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should visit each node only once (hits the visited check) expect(result).toBeDefined(); expect(result.memories).toHaveLength(2); // start-id and shared-target expect(result.visitedCount).toBe(2); // Only 2 unique nodes visited }); }); describe("GraphTraversal - Path Finding Edge Cases", () => { it("should find path when maxDepth exactly matches required depth", async () => { // Requirement 2.4: Test maxDepth boundary conditions in findPath let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { // getMemory for source return { rows: [ { id: "source-id", content: "Source memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { // Metadata for source return { rows: [] }; } else if (callCount === 3) { // getOutgoingLinks for source return { rows: [ { source_id: "source-id", target_id: "target-id", link_type: "semantic", weight: 0.8, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 4) { // getMemory for target return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 5) { // Metadata for target return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: Finding path with exact maxDepth needed const path = await graphTraversal.findPath("source-id", "target-id", 1); // Then: Should find the path expect(path).not.toBeNull(); expect(path!.memories).toHaveLength(2); }); it("should handle isolated nodes with no outgoing links in BFS", async () => { // Requirement 2.4: Test BFS with no outgoing links let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "isolated-id", content: "Isolated memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { // getOutgoingLinks returns empty array return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS with no outgoing links const result = await graphTraversal.getConnectedMemories("isolated-id", { maxDepth: 2, }); // Then: Should return only the start node expect(result.memories).toHaveLength(1); expect(result.visitedCount).toBe(1); }); it("should handle isolated nodes with no outgoing links in DFS", async () => { // Requirement 2.4: Test DFS with no outgoing links let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "isolated-id", content: "Isolated memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { // getMemory in DFS loop return { rows: [ { id: "isolated-id", content: "Isolated memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { return { rows: [] }; } else if (callCount === 5) { // getOutgoingLinks returns empty array return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS with no outgoing links const result = await graphTraversal.getConnectedMemories("isolated-id", { maxDepth: 2, traversalType: "depth-first", }); // Then: Should return only the start node expect(result.memories).toHaveLength(1); expect(result.visitedCount).toBe(1); }); it("should handle BFS with minWeight filtering all links", async () => { // Requirement 2.5: Test BFS when minWeight filters out all links // This helps cover the minWeight branch more thoroughly let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { // getOutgoingLinks returns links with low weights return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.1, created_at: new Date(), traversal_count: 0, }, ], }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS with minWeight that filters out all links const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2, minWeight: 0.5, }); // Then: Should return only the start node expect(result.memories).toHaveLength(1); expect(result.visitedCount).toBe(1); }); it("should handle DFS with minWeight filtering all links", async () => { // Requirement 2.5: Test DFS when minWeight filters out all links // This helps cover the minWeight branch more thoroughly let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { // getMemory in DFS loop return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { return { rows: [] }; } else if (callCount === 5) { // getOutgoingLinks returns links with low weights return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.1, created_at: new Date(), traversal_count: 0, }, ], }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS with minWeight that filters out all links const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 2, traversalType: "depth-first", minWeight: 0.5, }); // Then: Should return only the start node expect(result.memories).toHaveLength(1); expect(result.visitedCount).toBe(1); }); }); describe("GraphTraversal - Explicit Option Combinations", () => { it("should apply all traversal options in BFS", async () => { // Requirement 2.4: Test BFS with maxDepth, minWeight, and linkTypes let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.8, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 4) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 5) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS with all options explicitly set const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, minWeight: 0.5, traversalType: "breadth-first", includePaths: false, }); // Then: Should return results expect(result.memories).toHaveLength(2); }); it("should apply all traversal options in DFS", async () => { // Requirement 2.4: Test DFS with maxDepth, minWeight, and linkTypes let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { return { rows: [] }; } else if (callCount === 5) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.8, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 6) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 7) { return { rows: [] }; } else if (callCount === 8) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS with all options explicitly set const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, minWeight: 0.5, traversalType: "depth-first", includePaths: false, }); // Then: Should return results expect(result.memories).toHaveLength(2); }); it("should handle BFS with minWeight as 0", async () => { // Requirement 2.5: Test BFS with minWeight explicitly set to 0 // This tests the truthy check for minWeight let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.0, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 4) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 5) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: BFS with minWeight = 0 (falsy but valid) const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, minWeight: 0, }); // Then: Should include links with weight >= 0 expect(result.memories).toHaveLength(2); }); it("should handle DFS with minWeight as 0", async () => { // Requirement 2.5: Test DFS with minWeight explicitly set to 0 // This tests the truthy check for minWeight let callCount = 0; const mockDb = { getConnection: async () => ({ query: async (_sql: string) => { callCount++; if (callCount === 1) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 2) { return { rows: [] }; } else if (callCount === 3) { return { rows: [ { id: "start-id", content: "Start memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 4) { return { rows: [] }; } else if (callCount === 5) { return { rows: [ { source_id: "start-id", target_id: "target-id", link_type: "semantic", weight: 0.0, created_at: new Date(), traversal_count: 0, }, ], }; } else if (callCount === 6) { return { rows: [ { id: "target-id", content: "Target memory", created_at: new Date(), last_accessed: new Date(), access_count: 0, salience: 0.5, strength: 1.0, user_id: "test-user", session_id: "test-session", primary_sector: "semantic", }, ], }; } else if (callCount === 7) { return { rows: [] }; } else if (callCount === 8) { return { rows: [] }; } else { return { rows: [] }; } }, }), releaseConnection: () => {}, } as unknown as DatabaseConnectionManager; const graphTraversal = new GraphTraversal(mockDb); // When: DFS with minWeight = 0 (falsy but valid) const result = await graphTraversal.getConnectedMemories("start-id", { maxDepth: 1, traversalType: "depth-first", minWeight: 0, }); // Then: Should include links with weight >= 0 expect(result.memories).toHaveLength(2); }); }); describe("GraphTraversal - Boundary Condition Coverage", () => { let db: DatabaseConnectionManager; let graphTraversal: GraphTraversal; beforeEach(async () => { db = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: process.env.DB_NAME || "thoughtmcp_test", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", poolSize: 5, }); await db.connect(); graphTraversal = new GraphTraversal(db); const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-boundary-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-boundary-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-boundary-%'"); } finally { db.releaseConnection(client); } }); afterEach(async () => { if (db) { const client = await db.getConnection(); try { await client.query("DELETE FROM memory_links WHERE source_id LIKE 'test-boundary-%'"); await client.query("DELETE FROM memory_metadata WHERE memory_id LIKE 'test-boundary-%'"); await client.query("DELETE FROM memories WHERE id LIKE 'test-boundary-%'"); } finally { db.releaseConnection(client); } await db.disconnect(); } }); async function createTestMemory(id: string, content: string): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memories (id, content, created_at, last_accessed, access_count, salience, strength, user_id, session_id, primary_sector) VALUES ($1, $2, NOW(), NOW(), 0, 0.5, 1.0, 'test-user', 'test-session', 'semantic')`, [id, content] ); await client.query( `INSERT INTO memory_metadata (memory_id, keywords, tags, category, context, importance, is_atomic) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, [], [], "test", "", 0.5, true] ); } finally { db.releaseConnection(client); } } async function createTestLink( sourceId: string, targetId: string, linkType: string, weight: number ): Promise<void> { const client = await db.getConnection(); try { await client.query( `INSERT INTO memory_links (source_id, target_id, link_type, weight, created_at, traversal_count) VALUES ($1, $2, $3, $4, NOW(), 0)`, [sourceId, targetId, linkType, weight] ); } finally { db.releaseConnection(client); } } it("should explicitly test DFS visited check (line 406) - diamond graph pattern", async () => { // Requirement 2.4: DFS visited check optimization // This test creates a diamond pattern where node D can be reached via two paths: // A → B → D and A → C → D // The DFS visited check (line 406) should prevent D from being processed twice // Given: Diamond graph A → B → D, A → C → D await createTestMemory("test-boundary-A", "Memory A"); await createTestMemory("test-boundary-B", "Memory B"); await createTestMemory("test-boundary-C", "Memory C"); await createTestMemory("test-boundary-D", "Memory D"); // Create diamond pattern await createTestLink("test-boundary-A", "test-boundary-B", "semantic", 0.9); await createTestLink("test-boundary-A", "test-boundary-C", "semantic", 0.8); await createTestLink("test-boundary-B", "test-boundary-D", "semantic", 0.7); await createTestLink("test-boundary-C", "test-boundary-D", "semantic", 0.6); // When: DFS traversal from A const result = await graphTraversal.getConnectedMemories("test-boundary-A", { maxDepth: 3, traversalType: "depth-first", }); // Then: Should visit D only once despite two paths leading to it expect(result.memories).toHaveLength(4); // A, B, C, D expect(result.visitedCount).toBe(4); // Each node visited exactly once // Verify D appears exactly once in the result const dCount = result.memories.filter((m) => m.id === "test-boundary-D").length; expect(dCount).toBe(1); }); it("should explicitly test DFS visited check (line 406) - complex multi-path graph", async () => { // Requirement 2.4: DFS visited check with multiple convergent paths // This test creates a more complex graph where node E can be reached via multiple paths: // A → B → E, A → C → E, A → D → E // The visited check should prevent E from being added multiple times // Given: Multi-path graph converging at E await createTestMemory("test-boundary-multi-A", "Memory A"); await createTestMemory("test-boundary-multi-B", "Memory B"); await createTestMemory("test-boundary-multi-C", "Memory C"); await createTestMemory("test-boundary-multi-D", "Memory D"); await createTestMemory("test-boundary-multi-E", "Memory E"); // Create multiple paths to E await createTestLink("test-boundary-multi-A", "test-boundary-multi-B", "semantic", 0.9); await createTestLink("test-boundary-multi-A", "test-boundary-multi-C", "semantic", 0.8); await createTestLink("test-boundary-multi-A", "test-boundary-multi-D", "semantic", 0.7); await createTestLink("test-boundary-multi-B", "test-boundary-multi-E", "semantic", 0.6); await createTestLink("test-boundary-multi-C", "test-boundary-multi-E", "semantic", 0.5); await createTestLink("test-boundary-multi-D", "test-boundary-multi-E", "semantic", 0.4); // When: DFS traversal from A with sufficient depth const result = await graphTraversal.getConnectedMemories("test-boundary-multi-A", { maxDepth: 3, traversalType: "depth-first", }); // Then: Should visit E only once despite three paths leading to it expect(result.memories).toHaveLength(5); // A, B, C, D, E expect(result.visitedCount).toBe(5); // Each node visited exactly once // Verify E appears exactly once const eCount = result.memories.filter((m) => m.id === "test-boundary-multi-E").length; expect(eCount).toBe(1); }); it("should explicitly test DFS minWeight undefined branch (line 430) - no filtering", async () => { // Requirement 2.5: DFS without minWeight filtering (else branch at line 430) // When minWeight is undefined, all links should be included regardless of weight // Given: Links with varying weights including very low weights await createTestMemory("test-boundary-weight-A", "Memory A"); await createTestMemory("test-boundary-weight-B", "Memory B"); await createTestMemory("test-boundary-weight-C", "Memory C"); await createTestMemory("test-boundary-weight-D", "Memory D"); // Create links with different weights, including very low ones await createTestLink("test-boundary-weight-A", "test-boundary-weight-B", "semantic", 0.9); await createTestLink("test-boundary-weight-A", "test-boundary-weight-C", "semantic", 0.1); // Very low weight await createTestLink("test-boundary-weight-A", "test-boundary-weight-D", "semantic", 0.05); // Very low weight // When: DFS without minWeight (undefined) const result = await graphTraversal.getConnectedMemories("test-boundary-weight-A", { maxDepth: 1, traversalType: "depth-first", // minWeight is undefined - should use else branch at line 430 }); // Then: Should include ALL links regardless of weight expect(result.memories).toHaveLength(4); // A, B, C, D const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-boundary-weight-A"); expect(ids).toContain("test-boundary-weight-B"); expect(ids).toContain("test-boundary-weight-C"); // Low weight included expect(ids).toContain("test-boundary-weight-D"); // Very low weight included }); it("should explicitly test DFS minWeight defined branch (line 430) - with filtering", async () => { // Requirement 2.5: DFS with minWeight filtering (if branch at line 430) // When minWeight is defined, only links meeting the threshold should be included // Given: Links with varying weights await createTestMemory("test-boundary-filter-A", "Memory A"); await createTestMemory("test-boundary-filter-B", "Memory B"); await createTestMemory("test-boundary-filter-C", "Memory C"); await createTestMemory("test-boundary-filter-D", "Memory D"); await createTestMemory("test-boundary-filter-E", "Memory E"); // Create links with specific weights to test filtering await createTestLink("test-boundary-filter-A", "test-boundary-filter-B", "semantic", 0.9); // Above threshold await createTestLink("test-boundary-filter-A", "test-boundary-filter-C", "semantic", 0.7); // Above threshold await createTestLink("test-boundary-filter-A", "test-boundary-filter-D", "semantic", 0.5); // At threshold await createTestLink("test-boundary-filter-A", "test-boundary-filter-E", "semantic", 0.3); // Below threshold // When: DFS with minWeight = 0.5 (defined) const result = await graphTraversal.getConnectedMemories("test-boundary-filter-A", { maxDepth: 1, traversalType: "depth-first", minWeight: 0.5, // Defined - should use if branch at line 430 }); // Then: Should include only links with weight >= 0.5 expect(result.memories).toHaveLength(4); // A, B, C, D (not E) const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-boundary-filter-A"); expect(ids).toContain("test-boundary-filter-B"); // 0.9 >= 0.5 expect(ids).toContain("test-boundary-filter-C"); // 0.7 >= 0.5 expect(ids).toContain("test-boundary-filter-D"); // 0.5 >= 0.5 expect(ids).not.toContain("test-boundary-filter-E"); // 0.3 < 0.5 - filtered out }); it("should test DFS minWeight edge case - weight exactly at threshold", async () => { // Requirement 2.5: DFS minWeight boundary condition // Test that links with weight exactly equal to minWeight are included // Given: Links with weights at and around threshold await createTestMemory("test-boundary-edge-A", "Memory A"); await createTestMemory("test-boundary-edge-B", "Memory B"); await createTestMemory("test-boundary-edge-C", "Memory C"); await createTestMemory("test-boundary-edge-D", "Memory D"); await createTestLink("test-boundary-edge-A", "test-boundary-edge-B", "semantic", 0.601); // Just above await createTestLink("test-boundary-edge-A", "test-boundary-edge-C", "semantic", 0.6); // Exactly at await createTestLink("test-boundary-edge-A", "test-boundary-edge-D", "semantic", 0.599); // Just below // When: DFS with minWeight = 0.6 const result = await graphTraversal.getConnectedMemories("test-boundary-edge-A", { maxDepth: 1, traversalType: "depth-first", minWeight: 0.6, }); // Then: Should include B and C (>= 0.6) but not D (< 0.6) const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-boundary-edge-B"); // 0.601 >= 0.6 expect(ids).toContain("test-boundary-edge-C"); // 0.6 >= 0.6 (exactly at threshold) expect(ids).not.toContain("test-boundary-edge-D"); // 0.599 < 0.6 }); it("should test DFS minWeight = 0 - includes all links", async () => { // Requirement 2.5: DFS minWeight with zero threshold // When minWeight is 0, all links should be included (even those with 0 weight) // Given: Links including one with 0 weight await createTestMemory("test-boundary-zero-A", "Memory A"); await createTestMemory("test-boundary-zero-B", "Memory B"); await createTestMemory("test-boundary-zero-C", "Memory C"); await createTestLink("test-boundary-zero-A", "test-boundary-zero-B", "semantic", 0.5); await createTestLink("test-boundary-zero-A", "test-boundary-zero-C", "semantic", 0.0); // Zero weight // When: DFS with minWeight = 0 const result = await graphTraversal.getConnectedMemories("test-boundary-zero-A", { maxDepth: 1, traversalType: "depth-first", minWeight: 0, }); // Then: Should include all links including zero-weight link expect(result.memories).toHaveLength(3); // A, B, C const ids = result.memories.map((m) => m.id); expect(ids).toContain("test-boundary-zero-C"); // Zero weight link included }); });

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