Skip to main content
Glama

documcp

by tosin2013
kg-storage.test.ts12.1 kB
/** * Tests for Knowledge Graph Storage * Phase 1: Core Knowledge Graph Integration */ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; import { promises as fs } from "fs"; import { join } from "path"; import { KGStorage } from "../../src/memory/kg-storage.js"; import { GraphNode, GraphEdge } from "../../src/memory/knowledge-graph.js"; import { tmpdir } from "os"; describe("KGStorage", () => { let storage: KGStorage; let testDir: string; beforeEach(async () => { // Create temporary test directory testDir = join(tmpdir(), `kg-storage-test-${Date.now()}`); await fs.mkdir(testDir, { recursive: true }); storage = new KGStorage({ storageDir: testDir, backupOnWrite: true, validateOnRead: true, }); await storage.initialize(); }); afterEach(async () => { // Clean up test directory try { await fs.rm(testDir, { recursive: true, force: true }); } catch (error) { console.warn("Failed to clean up test directory:", error); } }); describe("Initialization", () => { it("should create storage directory", async () => { const stats = await fs.stat(testDir); expect(stats.isDirectory()).toBe(true); }); it("should create entity and relationship files", async () => { const entityFile = join(testDir, "knowledge-graph-entities.jsonl"); const relationshipFile = join( testDir, "knowledge-graph-relationships.jsonl", ); await fs.access(entityFile); await fs.access(relationshipFile); // Files should exist (no error thrown) expect(true).toBe(true); }); it("should write file markers", async () => { const entityFile = join(testDir, "knowledge-graph-entities.jsonl"); const content = await fs.readFile(entityFile, "utf-8"); expect(content).toContain("# DOCUMCP_KNOWLEDGE_GRAPH_ENTITIES"); }); it("should reject non-DocuMCP files", async () => { // Create a non-DocuMCP file const fakeFile = join(testDir, "knowledge-graph-entities.jsonl"); await fs.writeFile(fakeFile, "not a documcp file\n", "utf-8"); const newStorage = new KGStorage({ storageDir: testDir }); await expect(newStorage.initialize()).rejects.toThrow( "is not a DocuMCP knowledge graph file", ); }); }); describe("Entity Storage", () => { it("should save and load entities", async () => { const entities: GraphNode[] = [ { id: "project:test", type: "project", label: "Test Project", properties: { name: "Test" }, weight: 1.0, lastUpdated: new Date().toISOString(), }, { id: "tech:typescript", type: "technology", label: "TypeScript", properties: { name: "TypeScript" }, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; await storage.saveEntities(entities); const loaded = await storage.loadEntities(); expect(loaded).toHaveLength(2); expect(loaded[0].id).toBe("project:test"); expect(loaded[1].id).toBe("tech:typescript"); }); it("should handle empty entity list", async () => { await storage.saveEntities([]); const loaded = await storage.loadEntities(); expect(loaded).toHaveLength(0); }); it("should preserve entity properties", async () => { const entity: GraphNode = { id: "project:complex", type: "project", label: "Complex Project", properties: { name: "Complex", technologies: ["typescript", "react"], metadata: { nested: { value: 123 } }, }, weight: 0.85, lastUpdated: new Date().toISOString(), }; await storage.saveEntities([entity]); const loaded = await storage.loadEntities(); expect(loaded[0].properties.technologies).toEqual([ "typescript", "react", ]); expect(loaded[0].properties.metadata.nested.value).toBe(123); }); }); describe("Relationship Storage", () => { it("should save and load relationships", async () => { const relationships: GraphEdge[] = [ { id: "project:test-uses-tech:typescript", source: "project:test", target: "tech:typescript", type: "uses", weight: 1.0, confidence: 0.9, properties: {}, lastUpdated: new Date().toISOString(), }, ]; await storage.saveRelationships(relationships); const loaded = await storage.loadRelationships(); expect(loaded).toHaveLength(1); expect(loaded[0].source).toBe("project:test"); expect(loaded[0].target).toBe("tech:typescript"); }); it("should handle empty relationship list", async () => { await storage.saveRelationships([]); const loaded = await storage.loadRelationships(); expect(loaded).toHaveLength(0); }); it("should preserve relationship properties", async () => { const relationship: GraphEdge = { id: "test-edge", source: "node1", target: "node2", type: "similar_to", weight: 0.75, confidence: 0.8, properties: { similarityScore: 0.75, sharedTechnologies: ["typescript"], }, lastUpdated: new Date().toISOString(), }; await storage.saveRelationships([relationship]); const loaded = await storage.loadRelationships(); expect(loaded[0].properties.similarityScore).toBe(0.75); expect(loaded[0].properties.sharedTechnologies).toEqual(["typescript"]); }); }); describe("Complete Graph Storage", () => { it("should save and load complete graph", async () => { const entities: GraphNode[] = [ { id: "project:test", type: "project", label: "Test", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; const relationships: GraphEdge[] = [ { id: "test-edge", source: "project:test", target: "tech:ts", type: "uses", weight: 1.0, confidence: 1.0, properties: {}, lastUpdated: new Date().toISOString(), }, ]; await storage.saveGraph(entities, relationships); const loaded = await storage.loadGraph(); expect(loaded.entities).toHaveLength(1); expect(loaded.relationships).toHaveLength(1); }); }); describe("Backup System", () => { it("should create backups on write", async () => { const entities: GraphNode[] = [ { id: "test", type: "project", label: "Test", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; await storage.saveEntities(entities); await storage.saveEntities(entities); // Second save should create backup const backupDir = join(testDir, "backups"); const files = await fs.readdir(backupDir); const backupFiles = files.filter((f) => f.startsWith("entities-")); expect(backupFiles.length).toBeGreaterThan(0); }); it("should restore from backup", async () => { const entities1: GraphNode[] = [ { id: "version1", type: "project", label: "V1", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; const entities2: GraphNode[] = [ { id: "version2", type: "project", label: "V2", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; // Save first version await storage.saveEntities(entities1); // Small delay to ensure different timestamps await new Promise((resolve) => setTimeout(resolve, 10)); // Save second version (creates backup of first) await storage.saveEntities(entities2); // Verify we have second version let loaded = await storage.loadEntities(); expect(loaded).toHaveLength(1); expect(loaded[0].id).toBe("version2"); // Restore from backup await storage.restoreFromBackup("entities"); // Verify we have first version back loaded = await storage.loadEntities(); expect(loaded).toHaveLength(1); expect(loaded[0].id).toBe("version1"); }); }); describe("Statistics", () => { it("should return accurate statistics", async () => { const entities: GraphNode[] = [ { id: "e1", type: "project", label: "E1", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, { id: "e2", type: "technology", label: "E2", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; const relationships: GraphEdge[] = [ { id: "r1", source: "e1", target: "e2", type: "uses", weight: 1.0, confidence: 1.0, properties: {}, lastUpdated: new Date().toISOString(), }, ]; await storage.saveGraph(entities, relationships); const stats = await storage.getStatistics(); expect(stats.entityCount).toBe(2); expect(stats.relationshipCount).toBe(1); expect(stats.schemaVersion).toBe("1.0.0"); expect(stats.fileSize.entities).toBeGreaterThan(0); }); }); describe("Integrity Verification", () => { it("should detect orphaned relationships", async () => { const entities: GraphNode[] = [ { id: "e1", type: "project", label: "E1", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; const relationships: GraphEdge[] = [ { id: "r1", source: "e1", target: "missing", // References non-existent entity type: "uses", weight: 1.0, confidence: 1.0, properties: {}, lastUpdated: new Date().toISOString(), }, ]; await storage.saveGraph(entities, relationships); const result = await storage.verifyIntegrity(); expect(result.valid).toBe(true); // No errors, just warnings expect(result.warnings.length).toBeGreaterThan(0); expect(result.warnings[0]).toContain("missing"); }); it("should detect duplicate entities", async () => { const entities: GraphNode[] = [ { id: "duplicate", type: "project", label: "E1", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, { id: "duplicate", type: "project", label: "E2", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; await storage.saveEntities(entities); const result = await storage.verifyIntegrity(); expect(result.valid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain("Duplicate entity ID"); }); }); describe("Export", () => { it("should export graph as JSON", async () => { const entities: GraphNode[] = [ { id: "test", type: "project", label: "Test", properties: {}, weight: 1.0, lastUpdated: new Date().toISOString(), }, ]; await storage.saveEntities(entities); const json = await storage.exportAsJSON(); const parsed = JSON.parse(json); expect(parsed.metadata).toBeDefined(); expect(parsed.metadata.version).toBe("1.0.0"); expect(parsed.entities).toHaveLength(1); expect(parsed.relationships).toHaveLength(0); }); }); });

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/tosin2013/documcp'

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