UNPKG

cmte

Version:

Design by Committee™ except it's just you and LLMs

229 lines (199 loc) 9.53 kB
import { describe, test, expect, vi, beforeEach } from 'vitest'; import { FileCollectionManager } from '../file-collection-manager.js'; import { glob } from 'glob'; import path from 'path'; import fs from 'fs/promises'; // Mock the fs/promises module vi.mock('fs/promises'); // Mock the glob module vi.mock('glob'); vi.mock('fs'); // Also mock fs if needed for readFile later describe('FileCollectionManager', () => { let manager; let mockGlobImplementation; // Declare here const basePath = '/test/base/path'; const resolvePath = (p) => path.resolve(basePath, p); beforeEach(async () => { vi.clearAllMocks(); // Define realistic absolute paths based on basePath const absolutePaths = { 'file1.js': resolvePath('file1.js'), 'file2.ts': resolvePath('file2.ts'), 'src/file3.js': resolvePath('src/file3.js'), 'test/file4.spec.js': resolvePath('test/file4.spec.js'), 'dir/file2.js': resolvePath('dir/file2.js'), }; // More robust mock implementation mockGlobImplementation = vi.fn((pattern, options) => { // Default to empty array let results = []; const resolvedPattern = path.resolve(options.cwd || basePath, pattern); // Handle specific patterns used in tests if (pattern === 'file*.js') { results = [absolutePaths['file1.js']]; } else if (pattern === 'file?.ts') { results = [absolutePaths['file2.ts']]; } else if (pattern === 'src/file*.js') { results = [absolutePaths['src/file3.js']]; } else if (pattern === 'test/*.spec.js') { results = [absolutePaths['test/file4.spec.js']]; } else if (pattern === 'dir/file*.js') { results = [absolutePaths['dir/file2.js']]; } else if (pattern === '*.js') { // Used in filtering test results = [absolutePaths['file1.js'], absolutePaths['src/file3.js'], absolutePaths['test/file4.spec.js']]; } else if (pattern === 'file1.js') { // Handle exact path from registration test results = [absolutePaths['file1.js']]; } // Ensure options.absolute is respected (though we always return absolute here) if (options?.absolute) { return Promise.resolve(results); } else { // If absolute:false was intended, we'd need to make relative, but seems unlikely here return Promise.resolve(results.map(p => path.relative(options.cwd || basePath, p))); } }); glob.mockImplementation(mockGlobImplementation); manager = new FileCollectionManager(basePath); await manager.registerCollection('test', { include: [ 'file*.js', 'file?.ts', 'src/file*.js', 'test/*.spec.js' ] }); await manager.registerCollection('another', { include: ['another.txt'] }); }); describe('registerCollection', () => { test('registers files and allows retrieval', async () => { const filesDef = { include: ['file1.js', 'dir/file*.js'] }; // Includes an exact path and a pattern await manager.registerCollection('normalized_test', filesDef); expect(manager.hasCollection('normalized_test')).toBe(true); // Expect relative paths based on basePath const expectedFiles = ['file1.js', 'dir/file2.js']; expect(manager.getFiles('normalized_test')).toEqual(expectedFiles); // Check that glob was called correctly for both includes expect(mockGlobImplementation).toHaveBeenCalledWith('file1.js', expect.objectContaining({ absolute: true, cwd: basePath })); expect(mockGlobImplementation).toHaveBeenCalledWith('dir/file*.js', expect.objectContaining({ absolute: true, cwd: basePath })); }); test('throws error if definition is not an object', async () => { const invalidCall = async () => manager.registerCollection('test', 'not-an-object'); await expect(invalidCall()).rejects.toThrow("Invalid definition for file collection 'test'. Must be an object."); }); test('throws error if include is not an array', async () => { await expect(manager.registerCollection('test_include_err', { include: 'not-an-array' })) .rejects.toThrow("Invalid definition for file collection 'test_include_err'. 'include' and 'exclude' must be arrays."); }); test('throws error if exclude is not an array', async () => { await expect(manager.registerCollection('test_exclude_err', { include: ['*.js'], exclude: 'not-an-array' })) .rejects.toThrow("Invalid definition for file collection 'test_exclude_err'. 'include' and 'exclude' must be arrays."); }); }); describe('getFiles', () => { test('returns all files when no pattern is provided', () => { const expectedFiles = [ 'file1.js', 'file2.ts', 'src/file3.js', 'test/file4.spec.js' ]; expect(manager.getFiles('test')).toEqual(expectedFiles); }); test('filters files by pattern', () => { const expectedFiles = [ 'file1.js', 'src/file3.js', 'test/file4.spec.js' ]; // The getFiles filtering uses micromatch on the *relative* paths stored expect(manager.getFiles('test', '**/*.js')).toEqual(expectedFiles); }); test('returns empty array for pattern with no matches', () => { expect(manager.getFiles('test', '*.css')).toEqual([]); }); test('throws error for non-existent collection', () => { expect(() => manager.getFiles('missing')).toThrow('File collection not found: missing'); }); }); describe('hasCollection', () => { test('returns true for existing collection', () => { expect(manager.hasCollection('test')).toBe(true); }); test('returns false for non-existent collection', () => { expect(manager.hasCollection('missing')).toBe(false); }); }); describe('getCollectionNames', () => { test('returns all collection names', () => { expect(manager.getCollectionNames()).toEqual(['test', 'another']); }); }); describe('readFileRelative', () => { beforeEach(() => { // Configure mock for fs.readFile fs.readFile.mockImplementation(async (filePath, encoding) => { const expectedPath1 = resolvePath('src/file3.js'); const expectedPath2 = resolvePath('nonexistent.txt'); if (filePath === expectedPath1 && encoding === 'utf8') { return Promise.resolve('Content of file3.js'); } if (filePath === expectedPath2) { // Simulate file not found const error = new Error(`ENOENT: no such file or directory, open '${filePath}'`); error.code = 'ENOENT'; return Promise.reject(error); } // Default rejection for unexpected paths return Promise.reject(new Error(`Mock fs.readFile called with unexpected path: ${filePath}`)); }); }); test('reads a file with a valid relative path', async () => { const content = await manager.readFileRelative('src/file3.js'); expect(content).toBe('Content of file3.js'); expect(fs.readFile).toHaveBeenCalledWith(resolvePath('src/file3.js'), 'utf8'); }); test('throws error if underlying fs.readFile fails', async () => { // This path is valid relative to base, but fs.readFile mock will reject it await expect(manager.readFileRelative('nonexistent.txt')) .rejects.toThrow(/ENOENT: no such file or directory/); }); }); describe('readFileFromCollection', () => { beforeEach(() => { // Configure mock for fs.readFile fs.readFile.mockImplementation(async (filePath, encoding) => { const expectedPath = resolvePath('file1.js'); const unreadablePath = resolvePath('file2.ts'); // Assume this exists but read fails if (filePath === expectedPath && encoding === 'utf8') { return Promise.resolve('Content of file1.js'); } if (filePath === unreadablePath) { // Simulate read error const error = new Error(`EACCES: permission denied, open '${filePath}'`); error.code = 'EACCES'; return Promise.reject(error); } // Default rejection return Promise.reject(new Error(`Mock fs.readFile called with unexpected path: ${filePath}`)); }); }); test('reads a file that exists in the specified collection', async () => { // 'file1.js' is added to the 'test' collection in the outer beforeEach const content = await manager.readFileFromCollection('test', 'file1.js'); expect(content).toBe('Content of file1.js'); expect(fs.readFile).toHaveBeenCalledWith(resolvePath('file1.js'), 'utf8'); }); test('throws error if the file is not in the specified collection', async () => { // 'dir/file2.js' exists but is not in the 'test' collection await expect(manager.readFileFromCollection('test', 'dir/file2.js')) .rejects.toThrow(/File 'dir\/file2.js' not found in collection 'test'/); }); test('throws error if the collection name does not exist', async () => { await expect(manager.readFileFromCollection('nonexistent_collection', 'file1.js')) .rejects.toThrow('File collection not found: nonexistent_collection'); }); test('throws error if underlying fs.readFile fails for a file in the collection', async () => { // 'file2.ts' is in the 'test' collection, but the mock fs.readFile will fail for it await expect(manager.readFileFromCollection('test', 'file2.ts')) .rejects.toThrow(/EACCES: permission denied/); }); }); });