Skip to main content
Glama
snapshot-tools.spec.tsβ€’9.9 kB
/** * TDD Test Suite for MCP Snapshot Tools * RED PHASE: Define expected behavior for MCP tools integration */ import { existsSync } from "node:fs"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { CreateSnapshotSchema, createSnapshot } from "../../src/tools/create-snapshot.js"; import { listSnapshots } from "../../src/tools/list-snapshots.js"; import { restoreSnapshot } from "../../src/tools/restore-snapshot.js"; import { clearRegistry, resetStorage } from "../../src/tools/storage-adapter.js"; describe("MCP Snapshot Tools - Integration with SnapshotStorage", () => { let testDir: string; let workspaceDir: string; beforeEach(async () => { // Reset storage and registry before each test resetStorage(); clearRegistry(); testDir = path.join(process.cwd(), `.test-mcp-${Date.now()}`); workspaceDir = path.join(testDir, "workspace"); await fs.mkdir(workspaceDir, { recursive: true }); // Set environment for tools to use test directory process.env.SNAPBACK_WORKSPACE = workspaceDir; }); afterEach(async () => { if (existsSync(testDir)) { await fs.rm(testDir, { recursive: true, force: true }); } delete process.env.SNAPBACK_WORKSPACE; // Clean up after tests resetStorage(); clearRegistry(); }); describe("create_snapshot Tool", () => { it("should validate input schema", () => { const validInput = { reason: "Manual snapshot", files: [{ path: "test.ts", content: 'console.log("test");' }], }; const result = CreateSnapshotSchema.safeParse(validInput); expect(result.success).toBe(true); }); it("should create snapshot from files array", async () => { const result = await createSnapshot({ reason: "Test snapshot", files: [ { path: "src/index.ts", content: 'export const version = "1.0.0";' }, { path: "README.md", content: "# Test Project" }, ], }); expect(result.success).toBe(true); expect(result.snapshot).toBeDefined(); expect(result.snapshot?.id).toMatch(/^snap-/); expect(result.snapshot?.fileCount).toBe(2); expect(result.snapshot?.reason).toBe("Test snapshot"); }); it("should create snapshot from single content", async () => { const result = await createSnapshot({ reason: "Quick save", content: "const x = 42;", filePath: "script.js", }); expect(result.success).toBe(true); expect(result.snapshot?.id).toMatch(/^snap-/); expect(result.snapshot?.fileCount).toBe(1); }); it("should generate unique IDs for different content", async () => { const result1 = await createSnapshot({ files: [{ path: "file1.txt", content: "content 1" }], }); const result2 = await createSnapshot({ files: [{ path: "file2.txt", content: "content 2" }], }); expect(result1.snapshot?.id).not.toBe(result2.snapshot?.id); }); it("should generate deterministic IDs for same content", async () => { const input = { files: [{ path: "test.txt", content: "same content" }], }; const result1 = await createSnapshot(input); const result2 = await createSnapshot(input); // Same content should generate same hash-based ID expect(result1.snapshot?.id).toBe(result2.snapshot?.id); }); it("should handle empty files array", async () => { const result = await createSnapshot({ reason: "Empty snapshot", files: [], }); expect(result.success).toBe(true); expect(result.snapshot?.fileCount).toBe(0); }); it("should include timestamp in snapshot metadata", async () => { const before = Date.now(); const result = await createSnapshot({ files: [{ path: "test.txt", content: "test" }], }); const after = Date.now(); expect(result.snapshot?.timestamp).toBeGreaterThanOrEqual(before); expect(result.snapshot?.timestamp).toBeLessThanOrEqual(after); }); it("should handle errors gracefully", async () => { // Test with invalid input that might cause internal error const result = await createSnapshot({ files: undefined as any, }); if (!result.success) { expect(result.error).toBeDefined(); } // Should still return a result structure expect(result).toHaveProperty("success"); }); }); describe("list_snapshots Tool", () => { it("should list all snapshots", async () => { // Create multiple snapshots await createSnapshot({ files: [{ path: "file1.txt", content: "content 1" }], reason: "Snapshot 1", }); await createSnapshot({ files: [{ path: "file2.txt", content: "content 2" }], reason: "Snapshot 2", }); const result = await listSnapshots(); expect(result.success).toBe(true); expect(result.snapshots).toBeDefined(); expect(result.snapshots?.length).toBeGreaterThanOrEqual(2); }); it("should return snapshots in reverse chronological order", async () => { const _snap1 = await createSnapshot({ files: [{ path: "old.txt", content: "old" }], }); // Wait to ensure different timestamp await new Promise((resolve) => setTimeout(resolve, 10)); const _snap2 = await createSnapshot({ files: [{ path: "new.txt", content: "new" }], }); const result = await listSnapshots(); if (result.success && result.snapshots && result.snapshots.length >= 2) { // Most recent should be first expect(result.snapshots[0].timestamp).toBeGreaterThan( result.snapshots[result.snapshots.length - 1].timestamp, ); } }); it("should include snapshot metadata", async () => { await createSnapshot({ files: [{ path: "test.txt", content: "test" }], reason: "Test reason", }); const result = await listSnapshots(); expect(result.success).toBe(true); expect(result.snapshots?.length).toBeGreaterThan(0); const snapshot = result.snapshots?.[0]; expect(snapshot).toHaveProperty("id"); expect(snapshot).toHaveProperty("timestamp"); expect(snapshot).toHaveProperty("reason"); expect(snapshot).toHaveProperty("fileCount"); }); it("should handle empty snapshot list", async () => { const result = await listSnapshots(); expect(result.success).toBe(true); expect(result.snapshots).toBeDefined(); expect(Array.isArray(result.snapshots)).toBe(true); }); }); describe("restore_snapshot Tool", () => { it("should restore snapshot by ID", async () => { const created = await createSnapshot({ files: [{ path: "restored.txt", content: "restored content" }], reason: "For restore test", }); expect(created.success).toBe(true); const snapshotId = created.snapshot?.id; const result = await restoreSnapshot(snapshotId); expect(result.success).toBe(true); expect(result.snapshot).toBeDefined(); expect(result.snapshot?.id).toBe(snapshotId); expect(result.snapshot?.content).toBeDefined(); }); it("should return snapshot files content", async () => { const created = await createSnapshot({ files: [ { path: "file1.txt", content: "content 1" }, { path: "file2.txt", content: "content 2" }, ], }); const result = await restoreSnapshot(created.snapshot?.id); expect(result.success).toBe(true); expect(result.snapshot?.content).toHaveLength(2); expect(result.snapshot?.content).toEqual( expect.arrayContaining([ expect.objectContaining({ path: "file1.txt", content: "content 1" }), expect.objectContaining({ path: "file2.txt", content: "content 2" }), ]), ); }); it("should fail gracefully for non-existent snapshot", async () => { const result = await restoreSnapshot("snap-nonexistent"); expect(result.success).toBe(false); expect(result.error).toBeDefined(); expect(result.error).toMatch(/not found/i); }); it("should preserve original file content exactly", async () => { const originalContent = 'function test() {\n return "exact content";\n}'; const created = await createSnapshot({ files: [{ path: "exact.ts", content: originalContent }], }); const result = await restoreSnapshot(created.snapshot?.id); expect(result.success).toBe(true); const restoredFile = result.snapshot?.content?.find((f) => f.path === "exact.ts"); expect(restoredFile?.content).toBe(originalContent); }); }); describe("MCP Tool Integration Flow", () => { it("should support complete create -> list -> restore workflow", async () => { // 1. Create snapshot const created = await createSnapshot({ files: [{ path: "workflow.txt", content: "workflow test" }], reason: "Workflow test", }); expect(created.success).toBe(true); const snapshotId = created.snapshot?.id; // 2. List snapshots const listed = await listSnapshots(); expect(listed.success).toBe(true); const found = listed.snapshots?.find((s) => s.id === snapshotId); expect(found).toBeDefined(); expect(found?.reason).toBe("Workflow test"); // 3. Restore snapshot const restored = await restoreSnapshot(snapshotId); expect(restored.success).toBe(true); expect(restored.snapshot?.content).toEqual([{ path: "workflow.txt", content: "workflow test" }]); }); it("should handle multiple snapshots independently", async () => { // Create multiple snapshots const snap1 = await createSnapshot({ files: [{ path: "version1.txt", content: "v1" }], reason: "Version 1", }); const snap2 = await createSnapshot({ files: [{ path: "version2.txt", content: "v2" }], reason: "Version 2", }); // List should show both const listed = await listSnapshots(); expect(listed.snapshots?.length).toBeGreaterThanOrEqual(2); // Restore each independently const restored1 = await restoreSnapshot(snap1.snapshot?.id); const restored2 = await restoreSnapshot(snap2.snapshot?.id); expect(restored1.snapshot?.content?.[0].content).toBe("v1"); expect(restored2.snapshot?.content?.[0].content).toBe("v2"); }); }); });

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/snapback-dev/mcp-server'

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