UNPKG

mcp-server-debug-thinking

Version:

Graph-based MCP server for systematic debugging using Problem-Solution Trees and Hypothesis-Experiment-Learning cycles

660 lines 29 kB
import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { GraphService } from "../../services/GraphService.js"; import { ActionType } from "../../types/graphActions.js"; // Test suite for edge cases and boundary conditions describe("GraphService - Edge Cases and Boundary Conditions", () => { let graphService; beforeEach(async () => { process.env.DEBUG_DATA_DIR = `.test-edge-cases-${Date.now()}-${Math.random().toString(36).substring(7)}`; graphService = new GraphService(); await graphService.initialize(); }); afterEach(async () => { if (process.env.DEBUG_DATA_DIR) { const fs = await import("fs/promises"); try { await fs.rm(process.env.DEBUG_DATA_DIR, { recursive: true, force: true }); } catch (_error) { // Directory might not exist } delete process.env.DEBUG_DATA_DIR; } }); describe("Empty and null value handling", () => { it("should handle empty content in problems", async () => { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.nodeId).toBeDefined(); }); it("should handle whitespace-only content", async () => { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: " \n\t ", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); it("should handle empty pattern in similarity search", async () => { // Create some problems first await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Test problem", }); const result = await graphService.query({ action: ActionType.QUERY, type: "similar-problems", parameters: { pattern: "", limit: 5, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.results).toBeDefined(); }); it("should handle undefined metadata gracefully", async () => { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "Test hypothesis", metadata: undefined, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Check that default metadata is applied const graph = graphService.getGraph(); const node = graph.nodes.get(response.nodeId); expect(node?.metadata).toBeDefined(); expect((node?.metadata).confidence).toBe(50); // Default for hypothesis }); it("should handle null values in metadata", async () => { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "solution", content: "Test solution", metadata: { verified: null, customField: null, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); }); describe("Extreme length handling", () => { it("should handle very long content", async () => { const longContent = `${"A".repeat(10000)} error message ${"B".repeat(10000)}`; const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: longContent, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Verify the content was stored correctly const graph = graphService.getGraph(); const node = graph.nodes.get(response.nodeId); expect(node?.content).toBe(longContent); }); it("should handle similarity search with very long patterns", async () => { // Create a problem with long content const longProblem = `Error: ${"x".repeat(5000)} in module`; await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: longProblem, }); // Search with a long pattern const longPattern = `Error: ${"x".repeat(4999)} in module`; const result = await graphService.query({ action: ActionType.QUERY, type: "similar-problems", parameters: { pattern: longPattern, limit: 5, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); if (response.results.problems.length > 0) { // 長いテキストの場合、類似度は最適化のため低くなることがある expect(response.results.problems[0].similarity).toBeGreaterThan(0.3); } }); it("should handle very short content", async () => { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "E", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); }); describe("Special characters and encoding", () => { it("should handle unicode characters", async () => { const unicodeContent = "Error: 🚨 Failed to process émojis and ñoñ-ASCII çharacters λ→∞"; const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: unicodeContent, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Verify content is preserved const graph = graphService.getGraph(); const node = graph.nodes.get(response.nodeId); expect(node?.content).toBe(unicodeContent); }); it("should handle special regex characters in patterns", async () => { await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Error: Invalid regex pattern [a-z]+ found", }); // Search with regex special characters const result = await graphService.query({ action: ActionType.QUERY, type: "similar-problems", parameters: { pattern: "Error: Invalid regex pattern [a-z]+", limit: 5, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Should not crash due to regex special characters }); it("should handle HTML/XML content", async () => { const htmlContent = 'Error: Cannot parse <div class="test">content</div>'; const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: htmlContent, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); it("should handle JSON strings", async () => { const jsonContent = 'Error parsing JSON: {"key": "value", "nested": {"array": [1,2,3]}}'; const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: jsonContent, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); it("should handle escaped characters", async () => { const escapedContent = "Error: Path not found \"C:\\Users\\Test\\file.txt\" or 'data\\n\\tvalue'"; const result = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: escapedContent, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); }); describe("Numerical edge cases", () => { it("should handle edge strength at boundaries", async () => { // Create nodes const node1 = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "Hypothesis 1", }); const nodeId1 = JSON.parse(node1.content[0].text).nodeId; const node2 = await graphService.create({ action: ActionType.CREATE, nodeType: "experiment", content: "Experiment 1", }); const nodeId2 = JSON.parse(node2.content[0].text).nodeId; // Test minimum strength (0) const minResult = await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId2, type: "tests", strength: 0, }); const minResponse = JSON.parse(minResult.content[0].text); expect(minResponse.success).toBe(true); // Test maximum strength (1) const node3 = await graphService.create({ action: ActionType.CREATE, nodeType: "experiment", content: "Experiment 2", }); const nodeId3 = JSON.parse(node3.content[0].text).nodeId; const maxResult = await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId3, type: "tests", strength: 1, }); const maxResponse = JSON.parse(maxResult.content[0].text); expect(maxResponse.success).toBe(true); }); it("should clamp edge strength values", async () => { // Create nodes const node1 = await graphService.create({ action: ActionType.CREATE, nodeType: "observation", content: "Observation 1", }); const nodeId1 = JSON.parse(node1.content[0].text).nodeId; const node2 = await graphService.create({ action: ActionType.CREATE, nodeType: "learning", content: "Learning 1", }); const nodeId2 = JSON.parse(node2.content[0].text).nodeId; // Test over maximum strength const overResult = await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId2, type: "learns", strength: 999, }); const overResponse = JSON.parse(overResult.content[0].text); expect(overResponse.success).toBe(true); // Check that strength was clamped to 1 const graph = graphService.getGraph(); const edge = Array.from(graph.edges.values()).find((e) => e.from === nodeId1 && e.to === nodeId2); expect(edge?.strength).toBe(1); // Test negative strength const node3 = await graphService.create({ action: ActionType.CREATE, nodeType: "learning", content: "Learning 2", }); const nodeId3 = JSON.parse(node3.content[0].text).nodeId; const negResult = await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId3, type: "learns", strength: -5, }); const negResponse = JSON.parse(negResult.content[0].text); expect(negResponse.success).toBe(true); // Check that strength was clamped to 0 const negEdge = Array.from(graph.edges.values()).find((e) => e.from === nodeId1 && e.to === nodeId3); expect(negEdge?.strength).toBe(0); }); it("should handle confidence values correctly", async () => { // Test hypothesis with extreme confidence values const highConfResult = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "High confidence hypothesis", metadata: { confidence: 150, // Over 100 }, }); const highConfResponse = JSON.parse(highConfResult.content[0].text); expect(highConfResponse.success).toBe(true); const lowConfResult = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "Negative confidence hypothesis", metadata: { confidence: -50, // Negative }, }); const lowConfResponse = JSON.parse(lowConfResult.content[0].text); expect(lowConfResponse.success).toBe(true); }); }); describe("Concurrent operations", () => { it("should handle rapid node creation", async () => { const promises = []; const nodeCount = 20; // Create many nodes concurrently for (let i = 0; i < nodeCount; i++) { promises.push(graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: `Concurrent problem ${i}`, })); } const results = await Promise.all(promises); // All should succeed results.forEach((result) => { const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.nodeId).toBeDefined(); }); // Verify all nodes were created const graph = graphService.getGraph(); expect(graph.nodes.size).toBeGreaterThanOrEqual(nodeCount); }); it("should handle concurrent connections", async () => { // Create base nodes const node1 = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Central problem", }); const centralId = JSON.parse(node1.content[0].text).nodeId; // Create multiple hypotheses const hypothesisIds = []; for (let i = 0; i < 10; i++) { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: `Hypothesis ${i}`, }); hypothesisIds.push(JSON.parse(result.content[0].text).nodeId); } // Connect all hypotheses to central problem concurrently const connectPromises = hypothesisIds.map((hypId) => graphService.connect({ action: ActionType.CONNECT, from: centralId, to: hypId, type: "hypothesizes", })); const connectResults = await Promise.all(connectPromises); // All connections should succeed connectResults.forEach((result) => { const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); }); // Verify all edges were created const graph = graphService.getGraph(); const edges = Array.from(graph.edges.values()).filter((e) => e.from === centralId); expect(edges.length).toBe(10); }); }); describe("Invalid input handling", () => { it("should handle invalid node types", async () => { const result = await graphService.create({ action: ActionType.CREATE, nodeType: "invalid-type", content: "Test content", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Should still create, type system prevents this in practice }); it("should handle invalid edge types", async () => { // Create nodes const node1 = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Problem 1", }); const nodeId1 = JSON.parse(node1.content[0].text).nodeId; const node2 = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "Hypothesis 1", }); const nodeId2 = JSON.parse(node2.content[0].text).nodeId; const result = await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId2, type: "invalid-edge-type", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Should still connect, type system prevents this in practice }); it("should handle circular parent relationships", async () => { // Create first node const node1 = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Node 1", }); const nodeId1 = JSON.parse(node1.content[0].text).nodeId; // Create second node with first as parent const node2 = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Node 2", parentId: nodeId1, }); const nodeId2 = JSON.parse(node2.content[0].text).nodeId; // Try to create third node that would create a circle const node3 = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Node 3", parentId: nodeId2, }); const nodeId3 = JSON.parse(node3.content[0].text).nodeId; // The system should handle this gracefully expect(nodeId3).toBeDefined(); }); }); describe("Query edge cases", () => { it("should handle queries with no results", async () => { // Create at least one problem for other tests await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Test problem for threshold test", }); // Query for something that doesn't exist const result = await graphService.query({ action: ActionType.QUERY, type: "similar-problems", parameters: { pattern: "This exact string will never match anything 12345!@#$%", limit: 10, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.results.problems).toEqual([]); }); it("should handle queries with limit 0", async () => { await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Test problem", }); const result = await graphService.query({ action: ActionType.QUERY, type: "recent-activity", parameters: { limit: 0, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.results.nodes.length).toBe(0); }); it("should handle queries with very high limits", async () => { // Create just a few nodes for (let i = 0; i < 5; i++) { await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: `Problem ${i}`, }); } const result = await graphService.query({ action: ActionType.QUERY, type: "recent-activity", parameters: { limit: 999999, }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.results.nodes.length).toBe(5); // Should return only what exists }); it("should handle similarity threshold edge cases", async () => { await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Test problem", }); // Test with similarity threshold of 1.0 (exact match only) const exactResult = await graphService.query({ action: ActionType.QUERY, type: "similar-problems", parameters: { pattern: "Different problem", minSimilarity: 1.0, }, }); const exactResponse = JSON.parse(exactResult.content[0].text); expect(exactResponse.success).toBe(true); expect(exactResponse.results.problems).toEqual([]); // Test with similarity threshold of 0 (return everything) const allResult = await graphService.query({ action: ActionType.QUERY, type: "similar-problems", parameters: { pattern: "Any pattern", minSimilarity: 0, }, }); const allResponse = JSON.parse(allResult.content[0].text); expect(allResponse.success).toBe(true); expect(allResponse.results.problems.length).toBeGreaterThan(0); }); }); describe("Memory and performance boundaries", () => { it("should handle graphs with many edges from single node", async () => { // Create a hub node const hub = await graphService.create({ action: ActionType.CREATE, nodeType: "problem", content: "Hub problem", }); const hubId = JSON.parse(hub.content[0].text).nodeId; // Create many connected nodes const connectedCount = 50; for (let i = 0; i < connectedCount; i++) { const _node = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: `Connected hypothesis ${i}`, parentId: hubId, }); } // Query to verify performance const startTime = Date.now(); const result = await graphService.query({ action: ActionType.QUERY, type: "recent-activity", parameters: { limit: 10 }, }); const queryTime = Date.now() - startTime; const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(queryTime).toBeLessThan(100); // Should still be fast }); it("should handle deep hierarchies", async () => { let currentParentId; const depth = 20; // Create a deep chain for (let i = 0; i < depth; i++) { const nodeType = i === 0 ? "problem" : i === depth - 1 ? "solution" : "hypothesis"; const result = await graphService.create({ action: ActionType.CREATE, nodeType, content: `Level ${i} node`, parentId: currentParentId, }); currentParentId = JSON.parse(result.content[0].text).nodeId; } // Should handle deep hierarchies without stack overflow const result = await graphService.query({ action: ActionType.QUERY, type: "recent-activity", parameters: { limit: 25 }, }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.results.nodes.length).toBe(depth); }); }); describe("Data consistency edge cases", () => { it("should maintain consistency when nodes are referenced before creation", async () => { // This shouldn't happen in normal usage, but test defensive programming const nonExistentId = "non-existent-node-id"; const result = await graphService.connect({ action: ActionType.CONNECT, from: nonExistentId, to: nonExistentId, type: "supports", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(false); expect(response.message).toContain("not found"); }); it("should handle duplicate edge creation", async () => { // Create nodes const node1 = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "Hypothesis 1", }); const nodeId1 = JSON.parse(node1.content[0].text).nodeId; const node2 = await graphService.create({ action: ActionType.CREATE, nodeType: "experiment", content: "Experiment 1", }); const nodeId2 = JSON.parse(node2.content[0].text).nodeId; // Create first edge await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId2, type: "tests", }); // Create duplicate edge const dupResult = await graphService.connect({ action: ActionType.CONNECT, from: nodeId1, to: nodeId2, type: "tests", }); const dupResponse = JSON.parse(dupResult.content[0].text); expect(dupResponse.success).toBe(true); // Should allow duplicates but with different IDs // Verify both edges exist const graph = graphService.getGraph(); const edges = Array.from(graph.edges.values()).filter((e) => e.from === nodeId1 && e.to === nodeId2 && e.type === "tests"); expect(edges.length).toBe(2); }); it("should handle self-referencing edges", async () => { const node = await graphService.create({ action: ActionType.CREATE, nodeType: "hypothesis", content: "Self-referencing hypothesis", }); const nodeId = JSON.parse(node.content[0].text).nodeId; // Create self-referencing edge const result = await graphService.connect({ action: ActionType.CONNECT, from: nodeId, to: nodeId, type: "supports", }); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); // Should allow self-references // Verify edge was created const graph = graphService.getGraph(); const selfEdge = Array.from(graph.edges.values()).find((e) => e.from === nodeId && e.to === nodeId); expect(selfEdge).toBeDefined(); }); }); }); //# sourceMappingURL=GraphServiceEdgeCases.test.js.map