Skip to main content
Glama
fs-read.test.ts8.03 kB
/** * Integration tests for fs_read tool. * * These tests use the actual filesystem with test fixtures. */ // IMPORTANT: Setup must be imported first to set env vars before config loads import { FIXTURES_PATH } from '../setup.js'; import { describe, expect, test } from 'bun:test'; import fs from 'node:fs/promises'; import path from 'node:path'; // Import the tool handler AFTER setup import { fsReadTool } from '../../src/tools/fs-read.tool.js'; // Helper to run the tool and parse result async function runFsRead(args: Record<string, unknown>): Promise<Record<string, unknown>> { const result = await fsReadTool.handler(args, {} as never); const text = (result.content[0] as { text: string }).text; return JSON.parse(text); } describe('fs_read: directory listing', () => { test('lists root directory', async () => { const result = await runFsRead({ path: '.' }); expect(result.success).toBe(true); expect(result.type).toBe('directory'); expect(result.tree).toBeDefined(); }); test('lists subdirectory', async () => { const result = await runFsRead({ path: 'vault' }); expect(result.success).toBe(true); expect(result.type).toBe('directory'); expect((result.tree as { entries: unknown[] }).entries.length).toBeGreaterThan(0); }); test('respects depth parameter', async () => { const shallow = await runFsRead({ path: 'vault', depth: 1 }); const deep = await runFsRead({ path: 'vault', depth: 5 }); const shallowEntries = (shallow.tree as { entries: unknown[] }).entries; const deepEntries = (deep.tree as { entries: unknown[] }).entries; // Deep should have more or equal entries expect(deepEntries.length).toBeGreaterThanOrEqual(shallowEntries.length); }); test('returns error for non-existent path', async () => { const result = await runFsRead({ path: 'nonexistent' }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); describe('fs_read: file reading', () => { test('reads file content with line numbers', async () => { const result = await runFsRead({ path: 'vault/notes/todo.md' }); expect(result.success).toBe(true); expect(result.type).toBe('file'); expect(result.content).toBeDefined(); const content = result.content as { text: string; checksum: string; totalLines: number }; expect(content.text).toContain('1|'); expect(content.checksum).toBeDefined(); expect(content.totalLines).toBeGreaterThan(0); }); test('reads specific line range', async () => { const result = await runFsRead({ path: 'vault/notes/todo.md', lines: '1-5' }); expect(result.success).toBe(true); const content = result.content as { range: { start: number; end: number } }; expect(content.range).toBeDefined(); expect(content.range.start).toBe(1); expect(content.range.end).toBe(5); }); test('returns checksum for file integrity', async () => { const result1 = await runFsRead({ path: 'vault/notes/todo.md' }); const result2 = await runFsRead({ path: 'vault/notes/todo.md' }); const checksum1 = (result1.content as { checksum: string }).checksum; const checksum2 = (result2.content as { checksum: string }).checksum; expect(checksum1).toBe(checksum2); }); }); describe('fs_read: find files', () => { test('finds file by exact name', async () => { const result = await runFsRead({ path: 'vault', find: 'todo.md', depth: 10 }); expect(result.success).toBe(true); const entries = (result.tree as { entries: { path: string }[] }).entries; expect(entries.some((e) => e.path.endsWith('todo.md'))).toBe(true); }); test('finds files by wildcard pattern', async () => { const result = await runFsRead({ path: 'vault', find: '*.md', depth: 10 }); expect(result.success).toBe(true); const entries = (result.tree as { entries: { path: string }[] }).entries; expect(entries.every((e) => e.path.endsWith('.md'))).toBe(true); }); test('returns empty for no matches', async () => { const result = await runFsRead({ path: 'vault', find: 'nonexistent-file.xyz', depth: 10 }); expect(result.success).toBe(true); const entries = (result.tree as { entries: unknown[] }).entries; expect(entries.length).toBe(0); }); }); describe('fs_read: content search', () => { test('searches for literal pattern', async () => { const result = await runFsRead({ path: 'vault', pattern: 'keyword', depth: 10, }); expect(result.success).toBe(true); expect(result.type).toBe('search'); expect((result.matches as unknown[]).length).toBeGreaterThan(0); }); test('searches with regex pattern', async () => { // Search for something we know exists in the fixtures const result = await runFsRead({ path: 'vault', pattern: 'line\\s+\\d+', patternMode: 'regex', depth: 10, }); expect(result.success).toBe(true); // Regex should find "line 2", "line 3" etc in journal files }); test('returns context lines around matches', async () => { const result = await runFsRead({ path: 'vault', pattern: 'keyword', context: 3, depth: 10, }); expect(result.success).toBe(true); const matches = result.matches as { context: { before: string[]; after: string[] } }[]; if (matches.length > 0) { expect(matches[0]?.context).toBeDefined(); } }); test('respects output mode: summary', async () => { const result = await runFsRead({ path: 'vault', pattern: 'the', output: 'summary', depth: 10, }); expect(result.success).toBe(true); expect(result.stats).toBeDefined(); }); test('respects output mode: count', async () => { const result = await runFsRead({ path: 'vault', pattern: 'the', output: 'count', depth: 10, }); expect(result.success).toBe(true); expect(result.matchCount).toBeDefined(); }); }); describe('fs_read: pattern modes', () => { test('literal mode escapes special characters', async () => { // Create a file with special chars for this test const testFile = path.join(FIXTURES_PATH, 'vault/test-special.md'); await fs.writeFile(testFile, 'Price: $100.00 (USD)'); try { const result = await runFsRead({ path: 'vault/test-special.md', pattern: '$100.00', patternMode: 'literal', }); expect(result.success).toBe(true); expect((result.matches as unknown[]).length).toBe(1); } finally { await fs.unlink(testFile); } }); test('whole word matching', async () => { const result = await runFsRead({ path: 'vault/programming/javascript.md', pattern: 'const', wholeWord: true, }); expect(result.success).toBe(true); // Should match "const" but not if it were part of another word }); }); describe('fs_read: edge cases', () => { test('handles path outside allowed directory', async () => { const result = await runFsRead({ path: '../../../etc/passwd' }); expect(result.success).toBe(false); expect((result.error as { code: string }).code).toBeDefined(); }); test('handles empty directory', async () => { const emptyDir = path.join(FIXTURES_PATH, 'empty-test'); await fs.mkdir(emptyDir, { recursive: true }); try { const result = await runFsRead({ path: 'empty-test' }); expect(result.success).toBe(true); expect((result.tree as { entries: unknown[] }).entries.length).toBe(0); } finally { await fs.rmdir(emptyDir); } }); test('handles binary file gracefully', async () => { const binaryFile = path.join(FIXTURES_PATH, 'vault/test.bin'); await fs.writeFile(binaryFile, Buffer.from([0x00, 0x01, 0x02])); try { const result = await runFsRead({ path: 'vault/test.bin' }); expect(result.success).toBe(false); expect((result.error as { code: string }).code).toBe('NOT_TEXT'); } finally { await fs.unlink(binaryFile); } }); });

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/iceener/files-stdio-mcp-server'

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