Skip to main content
Glama

documcp

by tosin2013
manager-advanced.test.ts15.3 kB
/** * Tests for uncovered branches in Memory Manager * Covers: getRelated (lines 171-202), export (lines 381-398), import (lines 409-415) */ import { promises as fs } from "fs"; import path from "path"; import os from "os"; import { MemoryManager } from "../../src/memory/manager.js"; import { MemoryEntry } from "../../src/memory/storage.js"; describe("MemoryManager - Advanced Features Coverage", () => { let manager: MemoryManager; let tempDir: string; beforeEach(async () => { tempDir = path.join( os.tmpdir(), `manager-advanced-test-${Date.now()}-${Math.random() .toString(36) .substr(2, 9)}`, ); await fs.mkdir(tempDir, { recursive: true }); manager = new MemoryManager(tempDir); await manager.initialize(); }); afterEach(async () => { try { await manager.close(); await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe("getRelated - Tag-based Relationships (lines 189-195)", () => { it("should find related memories by overlapping tags", async () => { // Create entries with overlapping tags const entry1 = await manager.remember( "analysis", { name: "Project A" }, { projectId: "proj-001", tags: ["typescript", "react", "frontend"], }, ); await manager.remember( "analysis", { name: "Project B" }, { projectId: "proj-002", tags: ["typescript", "vue", "frontend"], }, ); await manager.remember( "analysis", { name: "Project C" }, { projectId: "proj-003", tags: ["python", "backend"], }, ); // Get related memories for entry1 (should find project B via overlapping tags) const related = await manager.getRelated(entry1, 10); expect(related.length).toBeGreaterThan(0); // Should include Project B (shares typescript and frontend tags) const relatedNames = related.map((r) => r.data.name); expect(relatedNames).toContain("Project B"); // Should not include entry1 itself expect(relatedNames).not.toContain("Project A"); }); it("should find related memories by same type (lines 182-186)", async () => { const entry1 = await manager.remember( "recommendation", { ssg: "jekyll" }, { projectId: "proj-001" }, ); await manager.remember( "recommendation", { ssg: "hugo" }, { projectId: "proj-002" }, ); await manager.remember( "analysis", { type: "different" }, { projectId: "proj-003" }, ); const related = await manager.getRelated(entry1, 10); // Should find the other recommendation, not the analysis expect(related.length).toBeGreaterThan(0); const types = related.map((r) => r.type); expect(types).toContain("recommendation"); }); it("should find related memories by same project (lines 174-179)", async () => { manager.setContext({ projectId: "shared-project" }); const entry1 = await manager.remember( "analysis", { step: "step1" }, { projectId: "shared-project" }, ); await manager.remember( "analysis", { step: "step2" }, { projectId: "shared-project" }, ); await manager.remember( "analysis", { step: "step3" }, { projectId: "different-project" }, ); const related = await manager.getRelated(entry1, 10); // Should find step2 from same project expect(related.length).toBeGreaterThan(0); const projectIds = related.map((r) => r.metadata.projectId); expect(projectIds).toContain("shared-project"); }); it("should deduplicate and limit related memories (lines 198-202)", async () => { const entry1 = await manager.remember( "analysis", { name: "Entry 1" }, { projectId: "proj-001", tags: ["tag1", "tag2"], }, ); // Create many related entries for (let i = 0; i < 20; i++) { await manager.remember( "analysis", { name: `Entry ${i + 2}` }, { projectId: "proj-001", tags: i < 10 ? ["tag1"] : ["tag2"], }, ); } // Request limit of 5 const related = await manager.getRelated(entry1, 5); // Should be limited to 5 (deduplicated) expect(related.length).toBeLessThanOrEqual(5); // Should not include entry1 itself const names = related.map((r) => r.data.name); expect(names).not.toContain("Entry 1"); }); it("should handle entry without tags gracefully (line 189)", async () => { const entryNoTags = await manager.remember( "analysis", { name: "No Tags" }, { projectId: "proj-001" }, ); await manager.remember( "analysis", { name: "Also No Tags" }, { projectId: "proj-001" }, ); // Should still find related by project const related = await manager.getRelated(entryNoTags, 10); expect(related.length).toBeGreaterThan(0); }); it("should handle entry with empty tags array (line 189)", async () => { const entryEmptyTags = await manager.remember( "analysis", { name: "Empty Tags" }, { projectId: "proj-001", tags: [], }, ); await manager.remember( "analysis", { name: "Other Entry" }, { projectId: "proj-001" }, ); const related = await manager.getRelated(entryEmptyTags, 10); expect(related.length).toBeGreaterThan(0); }); }); describe("CSV Export (lines 381-398)", () => { it("should export memories as CSV format", async () => { manager.setContext({ projectId: "csv-proj-001" }); await manager.remember( "analysis", { test: "data1" }, { repository: "github.com/test/repo1", ssg: "jekyll", }, ); manager.setContext({ projectId: "csv-proj-002" }); await manager.remember( "recommendation", { test: "data2" }, { repository: "github.com/test/repo2", ssg: "hugo", }, ); // Export as CSV const csvData = await manager.export("csv"); // Verify CSV structure expect(csvData).toContain("id,timestamp,type,projectId,repository,ssg"); expect(csvData).toContain("csv-proj-001"); expect(csvData).toContain("csv-proj-002"); expect(csvData).toContain("github.com/test/repo1"); expect(csvData).toContain("github.com/test/repo2"); expect(csvData).toContain("jekyll"); expect(csvData).toContain("hugo"); // Verify rows are comma-separated const lines = csvData.split("\n").filter((l) => l.trim()); expect(lines.length).toBeGreaterThanOrEqual(3); // header + 2 rows // Each line should have the same number of commas const headerCommas = (lines[0].match(/,/g) || []).length; for (let i = 1; i < lines.length; i++) { const rowCommas = (lines[i].match(/,/g) || []).length; expect(rowCommas).toBe(headerCommas); } }); it("should export memories for specific project only", async () => { manager.setContext({ projectId: "project-a" }); await manager.remember("analysis", { project: "A" }, {}); manager.setContext({ projectId: "project-b" }); await manager.remember("analysis", { project: "B" }, {}); // Export only project-a const csvData = await manager.export("csv", "project-a"); expect(csvData).toContain("project-a"); expect(csvData).not.toContain("project-b"); }); it("should handle missing metadata fields in CSV export (lines 393-395)", async () => { // Create entry with minimal metadata await manager.remember("analysis", { test: "minimal" }, {}); const csvData = await manager.export("csv"); // Should have empty fields for missing metadata const lines = csvData.split("\n"); expect(lines.length).toBeGreaterThan(1); // Verify header expect(lines[0]).toContain("id,timestamp,type,projectId,repository,ssg"); // Data row should have appropriate number of commas (empty fields) const dataRow = lines[1]; const headerCommas = (lines[0].match(/,/g) || []).length; const dataCommas = (dataRow.match(/,/g) || []).length; expect(dataCommas).toBe(headerCommas); }); it("should export as JSON by default", async () => { await manager.remember( "analysis", { json: "test" }, { projectId: "json-proj" }, ); const jsonData = await manager.export("json"); const parsed = JSON.parse(jsonData); expect(Array.isArray(parsed)).toBe(true); expect(parsed.length).toBeGreaterThan(0); expect(parsed[0].data.json).toBe("test"); }); }); describe("CSV Import (lines 409-428)", () => { it("should import memories from CSV format", async () => { // Create CSV data const csvData = `id,timestamp,type,projectId,repository,ssg mem-001,2024-01-01T00:00:00.000Z,analysis,proj-csv-001,github.com/test/repo1,jekyll mem-002,2024-01-02T00:00:00.000Z,recommendation,proj-csv-002,github.com/test/repo2,hugo mem-003,2024-01-03T00:00:00.000Z,deployment,proj-csv-003,github.com/test/repo3,mkdocs`; const imported = await manager.import(csvData, "csv"); expect(imported).toBe(3); // Verify entries were imported const recalled1 = await manager.recall("mem-001"); expect(recalled1).not.toBeNull(); expect(recalled1?.type).toBe("analysis"); expect(recalled1?.metadata.projectId).toBe("proj-csv-001"); expect(recalled1?.metadata.ssg).toBe("jekyll"); const recalled2 = await manager.recall("mem-002"); expect(recalled2).not.toBeNull(); expect(recalled2?.type).toBe("recommendation"); }); it("should skip malformed CSV rows (line 414)", async () => { // CSV with mismatched column counts const csvData = `id,timestamp,type,projectId,repository,ssg mem-001,2024-01-01T00:00:00.000Z,analysis,proj-001,github.com/test/repo,jekyll mem-002,2024-01-02T00:00:00.000Z,recommendation mem-003,2024-01-03T00:00:00.000Z,deployment,proj-003,github.com/test/repo3,mkdocs`; const imported = await manager.import(csvData, "csv"); // Should import 2 (skipping the malformed row) expect(imported).toBe(2); // Verify valid entries were imported const recalled1 = await manager.recall("mem-001"); expect(recalled1).not.toBeNull(); // Malformed entry should not be imported const recalled2 = await manager.recall("mem-002"); expect(recalled2).toBeNull(); const recalled3 = await manager.recall("mem-003"); expect(recalled3).not.toBeNull(); }); it("should import memories from JSON format", async () => { const jsonData = JSON.stringify([ { id: "json-001", timestamp: "2024-01-01T00:00:00.000Z", type: "analysis", data: { test: "json-import" }, metadata: { projectId: "json-proj" }, }, ]); const imported = await manager.import(jsonData, "json"); expect(imported).toBe(1); const recalled = await manager.recall("json-001"); expect(recalled).not.toBeNull(); expect(recalled?.data.test).toBe("json-import"); }); it("should emit import-complete event (line 437)", async () => { let eventEmitted = false; let importedCount = 0; manager.on("import-complete", (count) => { eventEmitted = true; importedCount = count; }); const jsonData = JSON.stringify([ { id: "event-001", timestamp: "2024-01-01T00:00:00.000Z", type: "analysis", data: {}, metadata: {}, }, ]); await manager.import(jsonData, "json"); expect(eventEmitted).toBe(true); expect(importedCount).toBe(1); }); it("should handle empty CSV import gracefully", async () => { const csvData = `id,timestamp,type,projectId,repository,ssg`; const imported = await manager.import(csvData, "csv"); expect(imported).toBe(0); }); it("should handle empty JSON import gracefully", async () => { const jsonData = JSON.stringify([]); const imported = await manager.import(jsonData, "json"); expect(imported).toBe(0); }); }); describe("Export and Import Round-trip", () => { it("should maintain data integrity through CSV round-trip", async () => { // Create test data manager.setContext({ projectId: "roundtrip-proj", repository: "github.com/test/roundtrip", }); const originalEntry = await manager.remember( "analysis", { roundtrip: "test" }, { ssg: "docusaurus", }, ); // Export as CSV const csvData = await manager.export("csv"); // Create new manager and import const tempDir2 = path.join( os.tmpdir(), `manager-roundtrip-${Date.now()}`, ); await fs.mkdir(tempDir2, { recursive: true }); const manager2 = new MemoryManager(tempDir2); await manager2.initialize(); const imported = await manager2.import(csvData, "csv"); expect(imported).toBeGreaterThan(0); // Verify data matches const recalled = await manager2.recall(originalEntry.id); expect(recalled).not.toBeNull(); expect(recalled?.type).toBe(originalEntry.type); expect(recalled?.metadata.projectId).toBe( originalEntry.metadata.projectId, ); expect(recalled?.metadata.ssg).toBe(originalEntry.metadata.ssg); await manager2.close(); await fs.rm(tempDir2, { recursive: true, force: true }); }); it("should maintain data integrity through JSON round-trip", async () => { // Create test data with complex structure manager.setContext({ projectId: "json-roundtrip" }); const originalEntry = await manager.remember( "analysis", { complex: "data", nested: { value: 123 }, array: [1, 2, 3], }, { tags: ["tag1", "tag2"], }, ); // Export as JSON const jsonData = await manager.export("json"); // Create new manager and import const tempDir2 = path.join( os.tmpdir(), `manager-json-roundtrip-${Date.now()}`, ); await fs.mkdir(tempDir2, { recursive: true }); const manager2 = new MemoryManager(tempDir2); await manager2.initialize(); const imported = await manager2.import(jsonData, "json"); expect(imported).toBeGreaterThan(0); // Verify complex data maintained const recalled = await manager2.recall(originalEntry.id); expect(recalled).not.toBeNull(); expect(recalled?.data).toEqual(originalEntry.data); expect(recalled?.metadata.tags).toEqual(originalEntry.metadata.tags); await manager2.close(); await fs.rm(tempDir2, { recursive: true, force: true }); }); }); });

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