Skip to main content
Glama
diagram-service.test.ts11.8 kB
/** * Unit tests for diagram-service.ts * Tests business logic for diagram data access and management */ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { listDiagrams, getDiagramInfo, searchDiagrams, diagramExists, getDiagramCount, deleteDiagram, } from "../src/diagram-service.js"; import * as fileUtils from "../src/file-utils.js"; import { readdir, stat, mkdir, writeFile, rmdir, unlink, utimes } from "fs/promises"; import { join } from "path"; import { tmpdir } from "os"; describe("Diagram Service", () => { let testHomeDir: string; let testLiveDir: string; let originalHome: string | undefined; let originalXdgConfigHome: string | undefined; beforeEach(async () => { // Save original environment originalHome = process.env.HOME; originalXdgConfigHome = process.env.XDG_CONFIG_HOME; // Clear XDG_CONFIG_HOME to ensure HOME is used delete process.env.XDG_CONFIG_HOME; // Create temporary HOME directory to isolate tests testHomeDir = join(tmpdir(), `diagram-service-test-home-${Date.now()}`); process.env.HOME = testHomeDir; // Live dir will be: testHomeDir/.config/claude-mermaid/live testLiveDir = join(testHomeDir, ".config", "claude-mermaid", "live"); await mkdir(testLiveDir, { recursive: true }); }); afterEach(async () => { // Restore original environment if (originalHome) { process.env.HOME = originalHome; } else { delete process.env.HOME; } if (originalXdgConfigHome) { process.env.XDG_CONFIG_HOME = originalXdgConfigHome; } else { delete process.env.XDG_CONFIG_HOME; } // Cleanup test directory try { const entries = await readdir(testLiveDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(testLiveDir, entry.name); if (entry.isDirectory()) { const files = await readdir(fullPath); for (const file of files) { await unlink(join(fullPath, file)); } await rmdir(fullPath); } else { await unlink(fullPath); } } await rmdir(testLiveDir); await rmdir(join(testHomeDir, ".config", "claude-mermaid")); await rmdir(join(testHomeDir, ".config")); await rmdir(testHomeDir); } catch (error) { // Ignore cleanup errors } }); describe("listDiagrams", () => { it("should return empty array when no diagrams exist", async () => { const diagrams = await listDiagrams(); expect(diagrams).toEqual([]); }); it("should list all valid diagrams", async () => { const now = new Date(); const earlier = new Date(now.getTime() - 1000); await createTestDiagram("test-diagram-1", "svg", earlier); await createTestDiagram("test-diagram-2", "png", now); const diagrams = await listDiagrams(); expect(diagrams).toHaveLength(2); expect(diagrams[0].id).toBe("test-diagram-2"); expect(diagrams[0].format).toBe("png"); expect(diagrams[1].id).toBe("test-diagram-1"); expect(diagrams[1].format).toBe("svg"); }); it("should sort diagrams by modification time (newest first)", async () => { const oldTime = new Date("2024-01-01T10:00:00Z"); const newTime = new Date("2024-01-02T10:00:00Z"); await createTestDiagram("old-diagram", "svg", oldTime); await createTestDiagram("new-diagram", "svg", newTime); const diagrams = await listDiagrams(); expect(diagrams).toHaveLength(2); expect(diagrams[0].id).toBe("new-diagram"); expect(diagrams[1].id).toBe("old-diagram"); }); it("should skip invalid directory names", async () => { await createTestDiagram("valid-diagram", "svg"); await mkdir(join(testLiveDir, "invalid..diagram"), { recursive: true }); const diagrams = await listDiagrams(); expect(diagrams).toHaveLength(1); expect(diagrams[0].id).toBe("valid-diagram"); }); it("should skip directories without diagram files", async () => { const emptyDir = join(testLiveDir, "empty-diagram"); await mkdir(emptyDir, { recursive: true }); await createTestDiagram("valid-diagram", "svg"); const diagrams = await listDiagrams(); expect(diagrams).toHaveLength(1); expect(diagrams[0].id).toBe("valid-diagram"); }); it("should include file size and modification date", async () => { await createTestDiagram("test-diagram", "svg"); const diagrams = await listDiagrams(); expect(diagrams).toHaveLength(1); expect(diagrams[0].sizeBytes).toBeGreaterThan(0); expect(diagrams[0].modifiedAt).toBeInstanceOf(Date); }); }); describe("getDiagramInfo", () => { it("should return diagram info for existing diagram", async () => { await createTestDiagram("test-diagram", "svg"); const info = await getDiagramInfo("test-diagram"); expect(info).not.toBeNull(); expect(info!.id).toBe("test-diagram"); expect(info!.format).toBe("svg"); expect(info!.sizeBytes).toBeGreaterThan(0); expect(info!.modifiedAt).toBeInstanceOf(Date); }); it("should return null for non-existent diagram", async () => { const info = await getDiagramInfo("non-existent"); expect(info).toBeNull(); }); it("should validate preview ID format", async () => { const info = await getDiagramInfo("invalid..id"); expect(info).toBeNull(); }); it("should try all formats and return first found", async () => { await createTestDiagram("test-diagram", "png"); const info = await getDiagramInfo("test-diagram"); expect(info).not.toBeNull(); expect(info!.format).toBe("png"); }); it("should prefer SVG over other formats", async () => { const diagramDir = join(testLiveDir, "test-diagram"); await mkdir(diagramDir, { recursive: true }); await writeFile(join(diagramDir, "diagram.svg"), "<svg></svg>"); await writeFile(join(diagramDir, "diagram.png"), "png-data"); const info = await getDiagramInfo("test-diagram"); expect(info).not.toBeNull(); expect(info!.format).toBe("svg"); }); }); describe("searchDiagrams", () => { beforeEach(async () => { // Create test diagrams with various IDs await createTestDiagram("user-profile", "svg"); await createTestDiagram("user-settings", "svg"); await createTestDiagram("admin-dashboard", "svg"); await createTestDiagram("product-catalog", "svg"); }); it("should return all diagrams for empty query", async () => { const results = await searchDiagrams(""); expect(results).toHaveLength(4); }); it("should return all diagrams for whitespace-only query", async () => { const results = await searchDiagrams(" "); expect(results).toHaveLength(4); }); it("should filter diagrams by exact match", async () => { const results = await searchDiagrams("user-profile"); expect(results).toHaveLength(1); expect(results[0].id).toBe("user-profile"); }); it("should filter diagrams by partial match", async () => { const results = await searchDiagrams("user"); expect(results).toHaveLength(2); expect(results.map((d) => d.id).sort()).toEqual(["user-profile", "user-settings"]); }); it("should be case-insensitive", async () => { const results = await searchDiagrams("USER"); expect(results).toHaveLength(2); }); it("should trim query whitespace", async () => { const results = await searchDiagrams(" admin "); expect(results).toHaveLength(1); expect(results[0].id).toBe("admin-dashboard"); }); it("should return empty array for no matches", async () => { const results = await searchDiagrams("nonexistent"); expect(results).toEqual([]); }); }); describe("diagramExists", () => { it("should return true for existing diagram", async () => { await createTestDiagram("test-diagram", "svg"); const exists = await diagramExists("test-diagram"); expect(exists).toBe(true); }); it("should return false for non-existent diagram", async () => { const exists = await diagramExists("non-existent"); expect(exists).toBe(false); }); it("should return false for invalid preview ID", async () => { const exists = await diagramExists("invalid..id"); expect(exists).toBe(false); }); }); describe("getDiagramCount", () => { it("should return 0 when no diagrams exist", async () => { const count = await getDiagramCount(); expect(count).toBe(0); }); it("should return correct count of diagrams", async () => { await createTestDiagram("diagram-1", "svg"); await createTestDiagram("diagram-2", "svg"); await createTestDiagram("diagram-3", "svg"); const count = await getDiagramCount(); expect(count).toBe(3); }); }); describe("deleteDiagram", () => { it("should delete an existing diagram", async () => { await createTestDiagram("test-diagram", "svg"); // Verify diagram exists let exists = await diagramExists("test-diagram"); expect(exists).toBe(true); // Delete the diagram await deleteDiagram("test-diagram"); // Verify diagram no longer exists exists = await diagramExists("test-diagram"); expect(exists).toBe(false); }); it("should delete all files in the diagram directory", async () => { const diagramDir = join(testLiveDir, "test-diagram"); await mkdir(diagramDir, { recursive: true }); await writeFile(join(diagramDir, "diagram.svg"), "<svg>test</svg>"); await writeFile(join(diagramDir, "diagram.mmd"), "graph TD"); await writeFile(join(diagramDir, "options.json"), "{}"); // Delete the diagram await deleteDiagram("test-diagram"); // Verify directory no longer exists const dirExists = await stat(diagramDir) .then(() => true) .catch(() => false); expect(dirExists).toBe(false); }); it("should remove diagram from list after deletion", async () => { await createTestDiagram("diagram-1", "svg"); await createTestDiagram("diagram-2", "svg"); await createTestDiagram("diagram-3", "svg"); let diagrams = await listDiagrams(); expect(diagrams).toHaveLength(3); // Delete middle diagram await deleteDiagram("diagram-2"); diagrams = await listDiagrams(); expect(diagrams).toHaveLength(2); expect(diagrams.map((d) => d.id).sort()).toEqual(["diagram-1", "diagram-3"]); }); it("should throw error for non-existent diagram", async () => { await expect(deleteDiagram("non-existent")).rejects.toThrow(); }); it("should throw error for invalid preview ID", async () => { await expect(deleteDiagram("invalid..id")).rejects.toThrow(); }); it("should decrement diagram count after deletion", async () => { await createTestDiagram("diagram-1", "svg"); await createTestDiagram("diagram-2", "svg"); let count = await getDiagramCount(); expect(count).toBe(2); await deleteDiagram("diagram-1"); count = await getDiagramCount(); expect(count).toBe(1); }); }); // Helper functions async function createTestDiagram(id: string, format: string, mtime?: Date): Promise<void> { const diagramDir = join(testLiveDir, id); await mkdir(diagramDir, { recursive: true }); const content = format === "svg" ? "<svg>test</svg>" : "binary-data"; const filePath = join(diagramDir, `diagram.${format}`); await writeFile(filePath, content); if (mtime) { await utimes(filePath, mtime, mtime); } } });

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/veelenga/claude-mermaid'

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