Skip to main content
Glama
local_find_files.test.ts32.2 kB
/** * Tests for local_find_files tool */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { ERROR_CODES } from '../../src/errors/errorCodes.js'; import { findFiles } from '../../src/tools/local_find_files.js'; import type { FindFilesResult } from '../../src/types.js'; import * as exec from '../../src/utils/exec.js'; import * as pathValidator from '../../src/security/pathValidator.js'; // Mock dependencies vi.mock('../../src/utils/exec.js', () => ({ safeExec: vi.fn(), })); vi.mock('../../src/security/pathValidator.js', () => ({ pathValidator: { validate: vi.fn(), }, })); // Mock fs for file details vi.mock('fs', () => ({ promises: { lstat: vi.fn(), }, })); const mockFs = vi.mocked(await import('fs')) as unknown as { promises: { lstat: ReturnType<typeof vi.fn> } }; const expectDefinedFiles = (result: FindFilesResult) => { expect(result.files).toBeDefined(); return result.files!; }; describe('local_find_files', () => { const mockSafeExec = vi.mocked(exec.safeExec); const mockValidate = vi.mocked(pathValidator.pathValidator.validate); beforeEach(() => { vi.clearAllMocks(); mockValidate.mockReturnValue({ isValid: true, sanitizedPath: '/test/path', }); }); describe('Basic file discovery', () => { it('should find files by name pattern', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/file1.js\0/test/path/file2.js\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.js', }); expect(result.status).toBe('hasResults'); const files = expectDefinedFiles(result); expect(files).toHaveLength(2); expect(files[0].path).toBe('/test/path/file1.js'); }); it('should handle case-insensitive search', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/FILE.JS\0', stderr: '', }); const result = await findFiles({ path: '/test/path', iname: '*.js', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-iname', '*.js']) ); }); it('should handle empty results', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.nonexistent', }); expect(result.status).toBe('empty'); }); }); describe('File type filtering', () => { it('should filter by file type', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/file1.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', type: 'f', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-type', 'f']) ); }); it('should find directories only', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/dir1\0/test/path/dir2\0', stderr: '', }); const result = await findFiles({ path: '/test/path', type: 'd', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-type', 'd']) ); }); }); describe('Time-based filtering', () => { it('should find recently modified files', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/recent.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', modifiedWithin: '7d', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-mtime', '-7']) ); }); it('should find files modified before date', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/old.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', modifiedBefore: '30d', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-mtime', '+30']) ); }); }); describe('Size filtering', () => { it('should find files larger than threshold', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/large.bin\0', stderr: '', }); const result = await findFiles({ path: '/test/path', sizeGreater: '1M', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-size', '+1M']) ); }); it('should find files smaller than threshold', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/small.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', sizeLess: '1k', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-size', '-1k']) ); }); }); describe('Permission filtering', () => { it('should find executable files', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/script.sh\0', stderr: '', }); const result = await findFiles({ path: '/test/path', executable: true, }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-executable']) ); }); it('should filter by permissions', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/file.sh\0', stderr: '', }); const result = await findFiles({ path: '/test/path', permissions: '755', }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-perm', '755']) ); }); }); describe('Depth control', () => { it('should limit search depth', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/file1.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', maxDepth: 2, }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-maxdepth', '2']) ); }); it('should set minimum depth', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/sub/file.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', minDepth: 1, }); expect(result.status).toBe('hasResults'); expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-mindepth', '1']) ); }); }); describe('Directory exclusion', () => { it('should exclude specific directories', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/src/file.js\0', stderr: '', }); const result = await findFiles({ path: '/test/path', excludeDir: ['node_modules', '.git'], }); expect(result.status).toBe('hasResults'); // The implementation uses a more complex pattern for excluding directories: // ( -path */node_modules -o -path */node_modules/* ) -prune -o expect(mockSafeExec).toHaveBeenCalledWith( 'find', expect.arrayContaining(['-path', '*/node_modules', '-prune']) ); }); }); describe('Result limiting', () => { it('should apply result limit', async () => { const files = Array.from({ length: 150 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', limit: 50, }); expect(result.status).toBe('hasResults'); const limitedFiles = expectDefinedFiles(result); expect(limitedFiles.length).toBeLessThanOrEqual(50); }); it('should require pagination for large result sets', async () => { const files = Array.from({ length: 150 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', // No charLength specified }); // Should either return results or error requesting pagination expect(['hasResults', 'error']).toContain(result.status); if (result.status === 'error') { // Should have error code for pagination expect(result.errorCode).toBeDefined(); } }); }); describe('Concurrency behavior', () => { it('should cap concurrent lstat calls to 24', async () => { // Generate 100 file paths from find output const files = Array.from({ length: 100 }, (_, i) => `/test/file${i}.txt`).join('\0') + '\0'; mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files, stderr: '', }); let inFlight = 0; let maxInFlight = 0; // Mock lstat with small delay to expose concurrency vi.mocked(mockFs.promises.lstat).mockImplementation(async () => { inFlight++; maxInFlight = Math.max(maxInFlight, inFlight); await new Promise((r) => setTimeout(r, 2)); inFlight--; return { isDirectory: () => false, isSymbolicLink: () => false, isFile: () => true, size: 123, mode: parseInt('100644', 8), mtime: new Date(), } as unknown as import('fs').Stats; }); const result = await findFiles({ path: '/test/path', details: true }); expect(result.status).toBe('hasResults'); // Bounded concurrency should never exceed 24 expect(maxInFlight).toBeLessThanOrEqual(24); }); }); describe('Multiple name patterns', () => { it('should handle multiple name patterns with OR logic', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/file1.ts\0/test/path/file2.js\0', stderr: '', }); const result = await findFiles({ path: '/test/path', names: ['*.ts', '*.js'], }); expect(result.status).toBe('hasResults'); const files = expectDefinedFiles(result); expect(files).toHaveLength(2); }); }); describe('Path validation', () => { it('should reject invalid paths', async () => { mockValidate.mockReturnValue({ isValid: false, error: 'Path is outside allowed directories', }); const result = await findFiles({ path: '/etc/passwd', }); expect(result.status).toBe('error'); expect(result.errorCode).toBe(ERROR_CODES.PATH_VALIDATION_FAILED); }); }); describe('Error handling', () => { it('should handle command failure', async () => { mockSafeExec.mockResolvedValue({ success: false, code: 1, stdout: '', stderr: 'find: invalid option', }); const result = await findFiles({ path: '/test/path', name: '*.txt', }); expect(result.status).toBe('error'); expect(result.errorCode).toBe(ERROR_CODES.COMMAND_EXECUTION_FAILED); }); }); describe('NEW FEATURE: File-based pagination with automatic sorting', () => { it('should paginate with default 20 files per page', async () => { const files = Array.from({ length: 50 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', }); expect(result.status).toBe('hasResults'); const filesDefaultPage = expectDefinedFiles(result); expect(filesDefaultPage.length).toBeLessThanOrEqual(20); expect(result.totalFiles).toBe(50); expect(result.pagination?.totalPages).toBe(3); expect(result.pagination?.hasMore).toBe(true); }); it('should navigate to second page', async () => { const files = Array.from({ length: 50 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', filePageNumber: 2, }); expect(result.status).toBe('hasResults'); expect(result.pagination?.currentPage).toBe(2); expect(result.pagination?.hasMore).toBe(true); }); it('should support custom filesPerPage', async () => { const files = Array.from({ length: 50 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', filesPerPage: 10, }); expect(result.status).toBe('hasResults'); const filesCustomPerPage = expectDefinedFiles(result); expect(filesCustomPerPage.length).toBeLessThanOrEqual(10); expect(result.pagination?.totalPages).toBe(5); }); it('should handle last page correctly', async () => { const files = Array.from({ length: 25 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', filesPerPage: 20, filePageNumber: 2, }); expect(result.status).toBe('hasResults'); const filesLastPage = expectDefinedFiles(result); expect(filesLastPage.length).toBe(5); expect(result.pagination?.hasMore).toBe(false); }); }); describe('NEW FEATURE: ALWAYS sorted by modification time', () => { it('should ALWAYS sort by modification time (most recent first)', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/old.txt\0/test/new.txt\0/test/mid.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', }); expect(result.status).toBe('hasResults'); const filesSorted = expectDefinedFiles(result); expect(filesSorted.length).toBe(3); }); it('should sort even with pagination', async () => { const files = Array.from({ length: 30 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 10, }); expect(result.status).toBe('hasResults'); expect(result.totalFiles).toBe(30); }); it('should sort with time-based filters', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/recent1.txt\0/test/recent2.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', modifiedWithin: '7d', }); expect(result.status).toBe('hasResults'); }); }); describe('NEW FEATURE: Pagination hints', () => { it('should include pagination hints with page info', async () => { const files = Array.from({ length: 50 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, }); expect(result.status).toBe('hasResults'); expect(result.hints).toBeDefined(); expect(result.hints).toBeDefined(); expect(result.hints!.length).toBeGreaterThan(0); }); it('should show final page hint on last page', async () => { const files = Array.from({ length: 25 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, filePageNumber: 2, }); expect(result.status).toBe('hasResults'); expect(result.hints).toBeDefined(); expect(result.hints!.length).toBeGreaterThan(0); }); }); describe('Research context fields', () => { it('should return researchGoal and reasoning in hasResults', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/path/file1.txt\0/test/path/file2.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', researchGoal: 'Find text files', reasoning: 'Need to locate documentation', }); expect(result.status).toBe('hasResults'); expect(result.researchGoal).toBe('Find text files'); expect(result.reasoning).toBe('Need to locate documentation'); }); it('should return researchGoal and reasoning in empty results', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.nonexistent', researchGoal: 'Search for missing files', reasoning: 'Verify files do not exist', }); expect(result.status).toBe('empty'); expect(result.researchGoal).toBe('Search for missing files'); expect(result.reasoning).toBe('Verify files do not exist'); }); it('should return researchGoal and reasoning in error results', async () => { mockValidate.mockReturnValue({ isValid: false, error: 'Invalid path', }); const result = await findFiles({ path: '/invalid/path', researchGoal: 'Test invalid path', reasoning: 'Testing error handling', }); expect(result.status).toBe('error'); expect(result.researchGoal).toBe('Test invalid path'); expect(result.reasoning).toBe('Testing error handling'); }); }); describe('Character-based pagination (charOffset + charLength)', () => { it('should paginate output with charOffset and charLength', async () => { const files = Array.from({ length: 200 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', name: '*.txt', charLength: 500, charOffset: 0, }); expect(result.status).toBe('hasResults'); // charPagination is only added when pagination is actually applied if (result.charPagination) { expect(result.charPagination.charLength).toBeLessThanOrEqual(500); } }); it('should return first chunk by default', async () => { const files = Array.from({ length: 100 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 1000, }); expect(result.status).toBe('hasResults'); expect(result.charPagination?.charOffset).toBe(0); }); it('should navigate to second chunk with charOffset', async () => { const files = Array.from({ length: 100 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 1000, charOffset: 1000, }); expect(result.status).toBe('hasResults'); // charPagination is only added when pagination is actually applied if (result.charPagination) { expect(result.charPagination.charOffset).toBe(1000); } }); it('should handle charOffset = 0', async () => { const files = '/test/file1.txt\0/test/file2.txt\0'; mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files, stderr: '', }); const result = await findFiles({ path: '/test/path', charOffset: 0, charLength: 100, }); expect(result.status).toBe('hasResults'); expect(result.charPagination?.charOffset).toBe(0); }); it('should handle charOffset beyond output length', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/file.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charOffset: 10000, charLength: 100, }); // When charOffset is beyond content, we still get hasResults with empty data expect(result.status).toBe('hasResults'); }); it('should handle charLength = 1', async () => { const files = Array.from({ length: 10 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 1, }); expect(result.status).toBe('hasResults'); // charPagination is only added when pagination is actually applied if (result.charPagination) { expect(result.charPagination.charLength).toBe(1); } }); it('should handle charLength = 10000 (max)', async () => { const files = Array.from({ length: 500 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 10000, }); expect(result.status).toBe('hasResults'); expect(result.charPagination?.charLength).toBeLessThanOrEqual(10000); }); it('should handle file paths with UTF-8 chars', async () => { const files = '/test/café.txt\0/test/résumé.txt\0'; mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files, stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 1000, }); expect(result.status).toBe('hasResults'); const filesUtf = expectDefinedFiles(result); expect(filesUtf.some((f) => f.path.includes('café'))).toBe(true); }); it('should handle 2-byte UTF-8 in paths', async () => { const files = '/test/niño.txt\0/test/español.txt\0'; mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files, stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 500, }); expect(result.status).toBe('hasResults'); const filesUtf2 = expectDefinedFiles(result); expect(JSON.stringify(filesUtf2)).not.toMatch(/\uFFFD/); }); it('should handle 3-byte UTF-8 in paths', async () => { const files = '/test/文件.txt\0/test/中文.txt\0'; mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files, stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 500, }); expect(result.status).toBe('hasResults'); const filesUtf3 = expectDefinedFiles(result); expect(JSON.stringify(filesUtf3)).not.toMatch(/\uFFFD/); }); it('should handle emoji in paths', async () => { const files = '/test/😀test.txt\0/test/🎉party.txt\0'; mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files, stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 500, }); expect(result.status).toBe('hasResults'); const filesEmoji = expectDefinedFiles(result); expect(JSON.stringify(filesEmoji)).not.toMatch(/\uFFFD/); }); it('should show character pagination hints when truncated', async () => { const files = Array.from({ length: 200 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 500, }); expect(result.status).toBe('hasResults'); if (result.charPagination?.hasMore) { expect(result.hints).toBeDefined(); } }); it('should include charOffset value for next chunk', async () => { const files = Array.from({ length: 200 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', charLength: 500, charOffset: 0, }); expect(result.status).toBe('hasResults'); if (result.charPagination?.hasMore) { expect(result.hints).toBeDefined(); const hasCharOffsetHint = result.hints?.some(h => h.includes('charOffset') || h.includes('next') ); expect(hasCharOffsetHint).toBe(true); } }); }); describe('File pagination - Edge cases', () => { it('should handle filePageNumber = 0 or negative (defaults to 1)', async () => { const files = Array.from({ length: 50 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); // Schema should validate, but test with valid value const result = await findFiles({ path: '/test/path', filePageNumber: 1, filesPerPage: 20, }); expect(result.status).toBe('hasResults'); expect(result.pagination?.currentPage).toBe(1); }); it('should handle filePageNumber > total pages', async () => { const files = Array.from({ length: 25 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filePageNumber: 10, filesPerPage: 20, }); expect(['hasResults', 'empty']).toContain(result.status); if (result.status === 'hasResults') { expect(result.pagination?.currentPage).toBe(10); } }); it('should handle filesPerPage = 1', async () => { const files = Array.from({ length: 5 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 1, }); expect(result.status).toBe('hasResults'); const filesPerPageOne = expectDefinedFiles(result); expect(filesPerPageOne.length).toBe(1); expect(result.pagination?.totalPages).toBe(5); }); it('should handle filesPerPage = 20 (max)', async () => { const files = Array.from({ length: 150 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, }); expect(result.status).toBe('hasResults'); const filesMaxPerPage = expectDefinedFiles(result); expect(filesMaxPerPage.length).toBeLessThanOrEqual(20); expect(result.pagination?.totalPages).toBe(8); }); it('should handle single file result', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '/test/single-file.txt\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, }); expect(result.status).toBe('hasResults'); const filesSingle = expectDefinedFiles(result); expect(filesSingle.length).toBe(1); expect(result.pagination?.totalPages).toBe(1); expect(result.pagination?.hasMore).toBe(false); }); it('should handle exact boundary (20 files, 20 per page)', async () => { const files = Array.from({ length: 20 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, }); expect(result.status).toBe('hasResults'); const filesExactBoundary = expectDefinedFiles(result); expect(filesExactBoundary.length).toBe(20); expect(result.pagination?.totalPages).toBe(1); expect(result.pagination?.hasMore).toBe(false); }); it('should handle one over boundary (21 files, 20 per page)', async () => { const files = Array.from({ length: 21 }, (_, i) => `/test/file${i}.txt`).join('\0'); mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: files + '\0', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, }); expect(result.status).toBe('hasResults'); const filesOverBoundary = expectDefinedFiles(result); expect(filesOverBoundary.length).toBe(20); expect(result.pagination?.totalPages).toBe(2); expect(result.pagination?.hasMore).toBe(true); }); it('should handle empty result set with pagination params', async () => { mockSafeExec.mockResolvedValue({ success: true, code: 0, stdout: '', stderr: '', }); const result = await findFiles({ path: '/test/path', filesPerPage: 20, filePageNumber: 1, }); expect(result.status).toBe('empty'); }); }); });

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/bgauryy/local-explorer-mcp'

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