roots-utils.test.ts
1 import { describe, it, expect, beforeEach, afterEach, jest } from "@jest/globals"; 2 import { getValidRootDirectories } from "../utils/roots-utils.js"; 3 import { 4 mkdtempSync, 5 rmSync, 6 mkdirSync, 7 writeFileSync, 8 realpathSync, 9 } from "fs"; 10 import { tmpdir } from "os"; 11 import { join } from "path"; 12 import type { Root } from "@modelcontextprotocol/sdk/types.js"; 13 14 describe("getValidRootDirectories", () => { 15 let testDir1: string; 16 let testDir2: string; 17 let testDir3: string; 18 let testFile: string; 19 let consoleErrorSpy: ReturnType<typeof jest.spyOn>; 20 21 beforeEach(() => { 22 consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {}); 23 24 // Create test directories 25 testDir1 = realpathSync(mkdtempSync(join(tmpdir(), "mcp-roots-test1-"))); 26 testDir2 = realpathSync(mkdtempSync(join(tmpdir(), "mcp-roots-test2-"))); 27 testDir3 = realpathSync(mkdtempSync(join(tmpdir(), "mcp-roots-test3-"))); 28 29 // Create a test file (not a directory) 30 testFile = join(testDir1, "test-file.txt"); 31 writeFileSync(testFile, "test content"); 32 }); 33 34 afterEach(() => { 35 // Cleanup 36 rmSync(testDir1, { recursive: true, force: true }); 37 rmSync(testDir2, { recursive: true, force: true }); 38 rmSync(testDir3, { recursive: true, force: true }); 39 consoleErrorSpy.mockRestore(); 40 }); 41 42 describe("valid directory processing", () => { 43 it("should process all URI formats and edge cases", async () => { 44 const roots = [ 45 { uri: `file://${testDir1}`, name: "File URI" }, 46 { uri: testDir2, name: "Plain path" }, 47 { uri: testDir3 }, // Plain path without name property 48 ]; 49 50 const result = await getValidRootDirectories(roots); 51 52 expect(result).toContain(testDir1); 53 expect(result).toContain(testDir2); 54 expect(result).toContain(testDir3); 55 expect(result).toHaveLength(3); 56 }); 57 58 it("should normalize complex paths", async () => { 59 const subDir = join(testDir1, "subdir"); 60 mkdirSync(subDir); 61 62 const roots = [ 63 { uri: `file://${testDir1}/./subdir/../subdir`, name: "Complex Path" }, 64 ]; 65 66 const result = await getValidRootDirectories(roots); 67 68 expect(result).toHaveLength(1); 69 expect(result[0]).toBe(subDir); 70 }); 71 }); 72 73 describe("error handling", () => { 74 it("should handle various error types", async () => { 75 const nonExistentDir = join(tmpdir(), "non-existent-directory-12345"); 76 const invalidPath = "\0invalid\0path"; // Null bytes cause different error types 77 const roots = [ 78 { uri: `file://${testDir1}`, name: "Valid Dir" }, 79 { uri: `file://${nonExistentDir}`, name: "Non-existent Dir" }, 80 { uri: `file://${testFile}`, name: "File Not Dir" }, 81 { uri: `file://${invalidPath}`, name: "Invalid Path" }, 82 ]; 83 84 const result = await getValidRootDirectories(roots); 85 86 expect(result).toContain(testDir1); 87 expect(result).not.toContain(nonExistentDir); 88 expect(result).not.toContain(testFile); 89 expect(result).not.toContain(invalidPath); 90 expect(result).toHaveLength(1); 91 expect(consoleErrorSpy).toHaveBeenCalledTimes(3); 92 }); 93 }); 94 });