Skip to main content
Glama

documcp

by tosin2013
kg-documentation-workflow.test.ts11.9 kB
/** * Integration Tests for Knowledge Graph Documentation Workflow * Tests end-to-end workflow from repository analysis to documentation tracking */ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; import { promises as fs } from "fs"; import path from "path"; import { tmpdir } from "os"; import { analyzeRepository } from "../../src/tools/analyze-repository.js"; import { initializeKnowledgeGraph, getKnowledgeGraph, saveKnowledgeGraph, } from "../../src/memory/kg-integration.js"; describe("KG Documentation Workflow Integration", () => { let testDir: string; beforeEach(async () => { testDir = path.join(tmpdir(), `documcp-integration-${Date.now()}`); await fs.mkdir(testDir, { recursive: true }); // Initialize KG with test storage const storageDir = path.join(testDir, ".documcp/memory"); await initializeKnowledgeGraph(storageDir); }); afterEach(async () => { try { await fs.rm(testDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } }); it("should complete full workflow: analyze → create entities → link relationships", async () => { // Setup: Create a test repository structure const srcDir = path.join(testDir, "src"); const docsDir = path.join(testDir, "docs"); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(docsDir, { recursive: true }); // Create source code await fs.writeFile( path.join(srcDir, "auth.ts"), ` export class AuthService { async login(username: string, password: string) { return { token: "abc123" }; } async logout(token: string) { return true; } } export function validateToken(token: string) { return token.length > 0; } `, "utf-8", ); // Create documentation await fs.writeFile( path.join(docsDir, "api.md"), ` # Authentication API ## Login Use the \`login()\` method from \`AuthService\` class in \`src/auth.ts\`: \`\`\`typescript const auth = new AuthService(); const result = await auth.login(username, password); \`\`\` ## Logout Call \`logout()\` with the authentication token: \`\`\`typescript await auth.logout(token); \`\`\` ## Token Validation Use \`validateToken()\` function to validate tokens. `, "utf-8", ); await fs.writeFile( path.join(testDir, "README.md"), "# Test Project", "utf-8", ); await fs.writeFile( path.join(testDir, "package.json"), JSON.stringify({ name: "test-project", version: "1.0.0" }), "utf-8", ); // Act: Run repository analysis const analysisResult = await analyzeRepository({ path: testDir, depth: "standard", }); // Assert: Analysis completed (may have errors due to test environment) expect(analysisResult.content).toBeDefined(); expect(analysisResult.content.length).toBeGreaterThan(0); // If analysis succeeded, verify structure if (!analysisResult.isError) { const analysis = JSON.parse(analysisResult.content[0].text); if (analysis.success) { expect(analysis.data.structure.hasDocs).toBe(true); } } // Wait for KG operations to complete await new Promise((resolve) => setTimeout(resolve, 100)); // Verify: Check knowledge graph entities const kg = await getKnowledgeGraph(); const allNodes = await kg.getAllNodes(); const allEdges = await kg.getAllEdges(); // Should have project, code files, and documentation sections const projectNodes = allNodes.filter((n) => n.type === "project"); const codeFileNodes = allNodes.filter((n) => n.type === "code_file"); const docSectionNodes = allNodes.filter( (n) => n.type === "documentation_section", ); expect(projectNodes.length).toBeGreaterThan(0); expect(codeFileNodes.length).toBeGreaterThan(0); expect(docSectionNodes.length).toBeGreaterThan(0); // Verify code file details const authFile = codeFileNodes.find((n) => n.properties.path.includes("auth.ts"), ); expect(authFile).toBeDefined(); expect(authFile?.properties.language).toBe("typescript"); expect(authFile?.properties.classes).toContain("AuthService"); expect(authFile?.properties.functions).toContain("validateToken"); // Verify documentation sections const apiDoc = docSectionNodes.find((n) => n.properties.filePath.includes("api.md"), ); expect(apiDoc).toBeDefined(); expect(apiDoc?.properties.hasCodeExamples).toBe(true); expect(apiDoc?.properties.referencedFunctions.length).toBeGreaterThan(0); // Verify relationships const referencesEdges = allEdges.filter((e) => e.type === "references"); const documentsEdges = allEdges.filter((e) => e.type === "documents"); expect(referencesEdges.length).toBeGreaterThan(0); expect(documentsEdges.length).toBeGreaterThan(0); // Verify specific relationship: api.md references auth.ts const apiToAuthEdge = referencesEdges.find( (e) => e.source === apiDoc?.id && e.target === authFile?.id, ); expect(apiToAuthEdge).toBeDefined(); expect(apiToAuthEdge?.properties.referenceType).toBe("api-reference"); }); it("should detect outdated documentation when code changes", async () => { // Setup: Create initial code and docs const srcDir = path.join(testDir, "src"); const docsDir = path.join(testDir, "docs"); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(docsDir, { recursive: true }); await fs.writeFile( path.join(srcDir, "user.ts"), "export function getUser() {}", "utf-8", ); await fs.writeFile( path.join(docsDir, "guide.md"), "Call `getUser()` from `src/user.ts`", "utf-8", ); await fs.writeFile(path.join(testDir, "README.md"), "# Test", "utf-8"); await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8"); // First analysis await analyzeRepository({ path: testDir, depth: "standard" }); await new Promise((resolve) => setTimeout(resolve, 100)); // Simulate code change await new Promise((resolve) => setTimeout(resolve, 100)); // Ensure different timestamp await fs.writeFile( path.join(srcDir, "user.ts"), "export function getUser(id: string) {} // CHANGED", "utf-8", ); // Second analysis await analyzeRepository({ path: testDir, depth: "standard" }); await new Promise((resolve) => setTimeout(resolve, 100)); // Verify: Check that system handled multiple analyses // In a real scenario, outdated_for edges would be created // For this test, just verify no crashes occurred const kg = await getKnowledgeGraph(); const allNodes = await kg.getAllNodes(); // Should have created some nodes from both analyses expect(allNodes.length).toBeGreaterThan(0); }); it("should handle projects with no documentation gracefully", async () => { // Setup: Code-only project const srcDir = path.join(testDir, "src"); await fs.mkdir(srcDir, { recursive: true }); await fs.writeFile( path.join(srcDir, "index.ts"), "export function main() {}", "utf-8", ); await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8"); // Act await analyzeRepository({ path: testDir, depth: "standard" }); await new Promise((resolve) => setTimeout(resolve, 100)); // Verify: Should still create code entities, just no doc entities const kg = await getKnowledgeGraph(); const allNodes = await kg.getAllNodes(); const codeFileNodes = allNodes.filter((n) => n.type === "code_file"); const docSectionNodes = allNodes.filter( (n) => n.type === "documentation_section", ); expect(codeFileNodes.length).toBeGreaterThan(0); expect(docSectionNodes.length).toBe(0); }); it("should handle multi-file projects correctly", async () => { // Setup: Multiple source files const srcDir = path.join(testDir, "src"); await fs.mkdir(path.join(srcDir, "auth"), { recursive: true }); await fs.mkdir(path.join(srcDir, "db"), { recursive: true }); await fs.writeFile( path.join(srcDir, "auth", "login.ts"), "export function login() {}", "utf-8", ); await fs.writeFile( path.join(srcDir, "auth", "logout.ts"), "export function logout() {}", "utf-8", ); await fs.writeFile( path.join(srcDir, "db", "query.ts"), "export function query() {}", "utf-8", ); await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8"); // Act await analyzeRepository({ path: testDir, depth: "standard" }); await new Promise((resolve) => setTimeout(resolve, 100)); // Verify const kg = await getKnowledgeGraph(); const codeFileNodes = (await kg.getAllNodes()).filter( (n) => n.type === "code_file", ); expect(codeFileNodes.length).toBe(3); const paths = codeFileNodes.map((n) => n.properties.path); expect(paths).toContain("src/auth/login.ts"); expect(paths).toContain("src/auth/logout.ts"); expect(paths).toContain("src/db/query.ts"); }); it("should persist knowledge graph to storage", async () => { // Setup const srcDir = path.join(testDir, "src"); await fs.mkdir(srcDir, { recursive: true }); await fs.writeFile( path.join(srcDir, "test.ts"), "export function test() {}", "utf-8", ); await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8"); // Act await analyzeRepository({ path: testDir, depth: "standard" }); await new Promise((resolve) => setTimeout(resolve, 100)); // Save KG await saveKnowledgeGraph(); // Verify storage files exist const storageDir = path.join(testDir, ".documcp/memory"); const entitiesFile = path.join( storageDir, "knowledge-graph-entities.jsonl", ); const relationshipsFile = path.join( storageDir, "knowledge-graph-relationships.jsonl", ); const entitiesExist = await fs .access(entitiesFile) .then(() => true) .catch(() => false); const relationshipsExist = await fs .access(relationshipsFile) .then(() => true) .catch(() => false); expect(entitiesExist).toBe(true); expect(relationshipsExist).toBe(true); // Verify content const entitiesContent = await fs.readFile(entitiesFile, "utf-8"); expect(entitiesContent).toContain("code_file"); }); it("should calculate coverage metrics for documentation", async () => { // Setup: 3 functions, docs covering 2 of them const srcDir = path.join(testDir, "src"); const docsDir = path.join(testDir, "docs"); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(docsDir, { recursive: true }); await fs.writeFile( path.join(srcDir, "api.ts"), ` export function create() {} export function read() {} export function update() {} // Not documented `, "utf-8", ); await fs.writeFile( path.join(docsDir, "api.md"), ` # API Reference - \`create()\`: Creates a resource - \`read()\`: Reads a resource `, "utf-8", ); await fs.writeFile(path.join(testDir, "README.md"), "# Test", "utf-8"); await fs.writeFile(path.join(testDir, "package.json"), "{}", "utf-8"); // Act await analyzeRepository({ path: testDir, depth: "standard" }); await new Promise((resolve) => setTimeout(resolve, 100)); // Verify coverage const kg = await getKnowledgeGraph(); const documentsEdges = (await kg.getAllEdges()).filter( (e) => e.type === "documents", ); expect(documentsEdges.length).toBeGreaterThan(0); const coverage = documentsEdges[0].properties.coverage; expect(["partial", "complete", "comprehensive"]).toContain(coverage); // 2/3 = 66% should be "complete" expect(coverage).toBe("complete"); }); });

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