Skip to main content
Glama
fs-utils.test.ts7.38 kB
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import { listDirectory, readFileContent, normalizeAndValidatePath } from '../fs-utils.js'; // Mock fs module vi.mock('fs', () => ({ promises: { readdir: vi.fn(), stat: vi.fn(), readFile: vi.fn() } })); describe('fs-utils', () => { const mockRoot = '/test/root'; beforeEach(() => { vi.clearAllMocks(); }); describe('normalizeAndValidatePath (non-security)', () => { test('normalizes . to root directory', () => { const result = normalizeAndValidatePath(mockRoot, '.'); expect(result).toBe(path.resolve(mockRoot)); }); test('handles empty string path', () => { const result = normalizeAndValidatePath(mockRoot, ''); expect(result).toBe(path.resolve(mockRoot)); }); test('handles paths with trailing slashes', () => { const result = normalizeAndValidatePath(mockRoot, 'dir/subdir/'); expect(result).toBe(path.join(path.resolve(mockRoot), 'dir/subdir')); }); test('handles paths with multiple slashes', () => { const result = normalizeAndValidatePath(mockRoot, 'dir///subdir//file.txt'); expect(result).toBe(path.join(path.resolve(mockRoot), 'dir/subdir/file.txt')); }); }); describe('listDirectory', () => { const mockDirContents = [ { name: 'file1.txt', isDirectory: () => false }, { name: 'file2.js', isDirectory: () => false }, { name: 'subdir', isDirectory: () => true } ]; const mockStats = { size: 1024, mtime: new Date('2024-01-01') }; beforeEach(() => { vi.mocked(fs.readdir).mockResolvedValue(mockDirContents as any); vi.mocked(fs.stat).mockResolvedValue(mockStats as any); }); test('lists files in root directory', async () => { const result = await listDirectory(mockRoot, '.', false); expect(result).toHaveLength(3); expect(result[0]).toMatchObject({ name: 'file1.txt', path: '/file1.txt', type: 'file', size: 1024 }); expect(result[1]).toMatchObject({ name: 'file2.js', path: '/file2.js', type: 'file' }); expect(result[2]).toMatchObject({ name: 'subdir', path: '/subdir', type: 'directory' }); }); test('lists nested directories when recursive=true', async () => { const nestedContents = [ { name: 'nested.txt', isDirectory: () => false } ]; vi.mocked(fs.readdir) .mockResolvedValueOnce(mockDirContents as any) .mockResolvedValueOnce(nestedContents as any); const result = await listDirectory(mockRoot, '.', true, 10); expect(result).toHaveLength(3); expect(result[2].children).toBeDefined(); expect(result[2].children).toHaveLength(1); expect(result[2].children![0]).toMatchObject({ name: 'nested.txt', path: '/subdir/nested.txt', type: 'file' }); }); test('respects maxDepth parameter', async () => { vi.mocked(fs.readdir).mockResolvedValue(mockDirContents as any); const result = await listDirectory(mockRoot, '.', true, 0); // Should not recurse into subdirectories when maxDepth is 0 expect(result[2].children).toBeUndefined(); expect(vi.mocked(fs.readdir)).toHaveBeenCalledTimes(1); }); test('stops at depth 1 when recursive=false', async () => { const result = await listDirectory(mockRoot, '.', false); expect(result[2].children).toBeUndefined(); expect(vi.mocked(fs.readdir)).toHaveBeenCalledTimes(1); }); test('handles empty directories', async () => { vi.mocked(fs.readdir).mockResolvedValue([]); const result = await listDirectory(mockRoot, '.'); expect(result).toHaveLength(0); }); test('handles permission denied errors gracefully', async () => { const permissionError = new Error('Permission denied'); (permissionError as any).code = 'EACCES'; vi.mocked(fs.stat) .mockResolvedValueOnce(mockStats as any) .mockRejectedValueOnce(permissionError); const result = await listDirectory(mockRoot, '.'); // Should still return the items, but without full metadata expect(result).toHaveLength(3); expect(result[1].size).toBeUndefined(); }); test('returns correct file metadata', async () => { const detailedStats = { size: 2048, mtime: new Date('2024-12-25T10:30:00Z'), isFile: () => true, isDirectory: () => false }; vi.mocked(fs.stat).mockResolvedValue(detailedStats as any); const result = await listDirectory(mockRoot, '.'); expect(result[0]).toMatchObject({ size: 2048, modified: new Date('2024-12-25T10:30:00Z') }); }); test('throws error for non-existent directory', async () => { const notFoundError = new Error('Not found'); (notFoundError as any).code = 'ENOENT'; vi.mocked(fs.readdir).mockRejectedValue(notFoundError); await expect(listDirectory(mockRoot, 'nonexistent')).rejects.toThrow('Directory not found'); }); test('throws error when path is not a directory', async () => { const notDirError = new Error('Not a directory'); (notDirError as any).code = 'ENOTDIR'; vi.mocked(fs.readdir).mockRejectedValue(notDirError); await expect(listDirectory(mockRoot, 'file.txt')).rejects.toThrow('Not a directory'); }); }); describe('readFileContent', () => { test('reads text file content', async () => { const fileContent = 'Hello, world!\nThis is a test file.'; vi.mocked(fs.readFile).mockResolvedValue(fileContent); const result = await readFileContent('/test/file.txt'); expect(result).toBe(fileContent); expect(vi.mocked(fs.readFile)).toHaveBeenCalledWith('/test/file.txt', 'utf-8'); }); test('throws error for non-existent file', async () => { const notFoundError = new Error('File not found'); (notFoundError as any).code = 'ENOENT'; vi.mocked(fs.readFile).mockRejectedValue(notFoundError); await expect(readFileContent('/test/missing.txt')).rejects.toThrow('File not found'); }); test('throws error for directory', async () => { const isDirError = new Error('Is a directory'); (isDirError as any).code = 'EISDIR'; vi.mocked(fs.readFile).mockRejectedValue(isDirError); await expect(readFileContent('/test/directory')).rejects.toThrow('Cannot read directory as file'); }); test('handles permission denied', async () => { const permError = new Error('Permission denied'); (permError as any).code = 'EACCES'; vi.mocked(fs.readFile).mockRejectedValue(permError); await expect(readFileContent('/test/secret.txt')).rejects.toThrow('Permission denied'); }); test('propagates other errors', async () => { const unknownError = new Error('Disk error'); vi.mocked(fs.readFile).mockRejectedValue(unknownError); await expect(readFileContent('/test/file.txt')).rejects.toThrow('Disk error'); }); }); });

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/davstr1/peekabooMCP'

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