/ src / tests / roots-utils.test.ts
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  });