From 939de7f3f84cef52b9fa593890f31d32f50d3034 Mon Sep 17 00:00:00 2001 From: Joe Danziger Date: Tue, 27 May 2025 17:22:19 -0400 Subject: [PATCH] mock fs for transformer tests --- .../profiles/rule-transformer-cline.test.js | 207 ++++++++++++---- .../profiles/rule-transformer-cursor.test.js | 222 ++++++++++++----- .../profiles/rule-transformer-roo.test.js | 225 ++++++++++++------ .../profiles/rule-transformer-trae.test.js | 205 ++++++++++++---- .../rule-transformer-windsurf.test.js | 207 ++++++++++++---- 5 files changed, 778 insertions(+), 288 deletions(-) diff --git a/tests/unit/profiles/rule-transformer-cline.test.js b/tests/unit/profiles/rule-transformer-cline.test.js index c96e5a87..5619001f 100644 --- a/tests/unit/profiles/rule-transformer-cline.test.js +++ b/tests/unit/profiles/rule-transformer-cline.test.js @@ -1,33 +1,42 @@ +import { jest } from '@jest/globals'; + +// Mock fs module before importing anything that uses it +jest.mock('fs', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn() +})); + +// Import modules after mocking import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js'; import { clineProfile } from '../../../scripts/profiles/cline.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - describe('Cline Rule Transformer', () => { - const testDir = path.join(__dirname, 'temp-test-dir'); + // Set up spies on the mocked modules + const mockReadFileSync = jest.spyOn(fs, 'readFileSync'); + const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + const mockExistsSync = jest.spyOn(fs, 'existsSync'); + const mockMkdirSync = jest.spyOn(fs, 'mkdirSync'); + const mockConsoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); beforeEach(() => { - // Create test directory before each test - if (!fs.existsSync(testDir)) { - fs.mkdirSync(testDir, { recursive: true }); - } + jest.clearAllMocks(); + // Setup default mocks + mockReadFileSync.mockReturnValue(''); + mockWriteFileSync.mockImplementation(() => {}); + mockExistsSync.mockReturnValue(true); + mockMkdirSync.mockImplementation(() => {}); }); afterAll(() => { - // Clean up test directory - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true, force: true }); - } + jest.restoreAllMocks(); }); it('should correctly convert basic terms', () => { - // Create a test Cursor rule file with basic terms - const testCursorRule = path.join(testDir, 'basic-terms.mdc'); const testContent = `--- description: Test Cursor rule for basic terms globs: **/* @@ -37,26 +46,36 @@ alwaysApply: true This is a Cursor rule that references cursor.so and uses the word Cursor multiple times. Also has references to .mdc files.`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testClineRule = path.join(testDir, 'basic-terms.md'); - convertRuleToProfileRule(testCursorRule, testClineRule, clineProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + clineProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testClineRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Verify file operations were called correctly + expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8'); + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('Cline'); - expect(convertedContent).toContain('cline.bot'); - expect(convertedContent).toContain('.md'); - expect(convertedContent).not.toContain('cursor.so'); - expect(convertedContent).not.toContain('Cursor rule'); + expect(transformedContent).toContain('Cline'); + expect(transformedContent).toContain('cline.bot'); + expect(transformedContent).toContain('.md'); + expect(transformedContent).not.toContain('cursor.so'); + expect(transformedContent).not.toContain('Cursor rule'); }); it('should correctly convert tool references', () => { - // Create a test Cursor rule file with tool references - const testCursorRule = path.join(testDir, 'tool-refs.mdc'); const testContent = `--- description: Test Cursor rule for tool references globs: **/* @@ -68,25 +87,31 @@ alwaysApply: true - run_command executes terminal commands - use_mcp connects to external services`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testClineRule = path.join(testDir, 'tool-refs.md'); - convertRuleToProfileRule(testCursorRule, testClineRule, clineProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + clineProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testClineRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); - // Verify transformations (Cline uses standard tool names) - expect(convertedContent).toContain('search tool'); - expect(convertedContent).toContain('edit_file tool'); - expect(convertedContent).toContain('run_command'); - expect(convertedContent).toContain('use_mcp'); + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; + + // Verify transformations (Cline uses standard tool names, so no transformation) + expect(transformedContent).toContain('search tool'); + expect(transformedContent).toContain('edit_file tool'); + expect(transformedContent).toContain('run_command'); + expect(transformedContent).toContain('use_mcp'); }); it('should correctly update file references', () => { - // Create a test Cursor rule file with file references - const testCursorRule = path.join(testDir, 'file-refs.mdc'); const testContent = `--- description: Test Cursor rule for file references globs: **/* @@ -96,18 +121,96 @@ alwaysApply: true This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and [taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testClineRule = path.join(testDir, 'file-refs.md'); - convertRuleToProfileRule(testCursorRule, testClineRule, clineProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + clineProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testClineRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('(.clinerules/dev_workflow.md)'); - expect(convertedContent).toContain('(.clinerules/taskmaster.md)'); - expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); + expect(transformedContent).toContain('(.clinerules/dev_workflow.md)'); + expect(transformedContent).toContain('(.clinerules/taskmaster.md)'); + expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); + }); + + it('should handle file read errors', () => { + // Mock file read to throw an error + mockReadFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'nonexistent.mdc', + 'target.md', + clineProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify writeFileSync was not called + expect(mockWriteFileSync).not.toHaveBeenCalled(); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: File not found' + ); + }); + + it('should handle file write errors', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock file write to throw an error + mockWriteFileSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + clineProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: Permission denied' + ); + }); + + it('should create target directory if it does not exist', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock directory doesn't exist initially + mockExistsSync.mockReturnValue(false); + + // Call the actual function + convertRuleToProfileRule( + 'source.mdc', + 'some/deep/path/target.md', + clineProfile + ); + + // Verify directory creation was called + expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', { + recursive: true + }); }); }); diff --git a/tests/unit/profiles/rule-transformer-cursor.test.js b/tests/unit/profiles/rule-transformer-cursor.test.js index 580eb670..64f2ef62 100644 --- a/tests/unit/profiles/rule-transformer-cursor.test.js +++ b/tests/unit/profiles/rule-transformer-cursor.test.js @@ -1,37 +1,42 @@ +import { jest } from '@jest/globals'; + +// Mock fs module before importing anything that uses it +jest.mock('fs', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn() +})); + +// Import modules after mocking import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; -import { - convertAllRulesToProfileRules, - convertRuleToProfileRule, - getRulesProfile -} from '../../../src/utils/rule-transformer.js'; +import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js'; import { cursorProfile } from '../../../scripts/profiles/cursor.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - describe('Cursor Rule Transformer', () => { - const testDir = path.join(__dirname, 'temp-test-dir'); + // Set up spies on the mocked modules + const mockReadFileSync = jest.spyOn(fs, 'readFileSync'); + const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + const mockExistsSync = jest.spyOn(fs, 'existsSync'); + const mockMkdirSync = jest.spyOn(fs, 'mkdirSync'); + const mockConsoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); - beforeAll(() => { - // Create test directory - if (!fs.existsSync(testDir)) { - fs.mkdirSync(testDir, { recursive: true }); - } + beforeEach(() => { + jest.clearAllMocks(); + // Setup default mocks + mockReadFileSync.mockReturnValue(''); + mockWriteFileSync.mockImplementation(() => {}); + mockExistsSync.mockReturnValue(true); + mockMkdirSync.mockImplementation(() => {}); }); afterAll(() => { - // Clean up test directory - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true, force: true }); - } + jest.restoreAllMocks(); }); it('should correctly convert basic terms', () => { - // Create a test Cursor rule file with basic terms - const testCursorRule = path.join(testDir, 'basic-terms.mdc'); const testContent = `--- description: Test Cursor rule for basic terms globs: **/* @@ -41,26 +46,35 @@ alwaysApply: true This is a Cursor rule that references cursor.so and uses the word Cursor multiple times. Also has references to .mdc files.`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testCursorOut = path.join(testDir, 'basic-terms.mdc'); - convertRuleToProfileRule(testCursorRule, testCursorOut, cursorProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.mdc', + cursorProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testCursorOut, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); - // Verify transformations (should preserve Cursor branding and references) - expect(convertedContent).toContain('Cursor rule'); - expect(convertedContent).toContain('cursor.so'); - expect(convertedContent).toContain('.mdc'); - expect(convertedContent).not.toContain('roocode.com'); - expect(convertedContent).not.toContain('windsurf.com'); + // Verify file operations were called correctly + expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8'); + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; + + // Verify transformations (Cursor profile should keep everything the same) + expect(transformedContent).toContain('Cursor'); + expect(transformedContent).toContain('cursor.so'); + expect(transformedContent).toContain('.mdc'); + expect(transformedContent).toContain('Cursor rule'); }); it('should correctly convert tool references', () => { - // Create a test Cursor rule file with tool references - const testCursorRule = path.join(testDir, 'tool-refs.mdc'); const testContent = `--- description: Test Cursor rule for tool references globs: **/* @@ -72,27 +86,31 @@ alwaysApply: true - run_command executes terminal commands - use_mcp connects to external services`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testCursorOut = path.join(testDir, 'tool-refs.mdc'); - convertRuleToProfileRule(testCursorRule, testCursorOut, cursorProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.mdc', + cursorProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testCursorOut, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); - // Verify transformations (should preserve Cursor tool references) - expect(convertedContent).toContain('search tool'); - expect(convertedContent).toContain('edit_file tool'); - expect(convertedContent).toContain('run_command'); - expect(convertedContent).toContain('use_mcp'); - expect(convertedContent).not.toContain('apply_diff'); - expect(convertedContent).not.toContain('search_files'); + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; + + // Verify transformations (Cursor uses standard tool names, so no transformation) + expect(transformedContent).toContain('search tool'); + expect(transformedContent).toContain('edit_file tool'); + expect(transformedContent).toContain('run_command'); + expect(transformedContent).toContain('use_mcp'); }); it('should correctly update file references', () => { - // Create a test Cursor rule file with file references - const testCursorRule = path.join(testDir, 'file-refs.mdc'); const testContent = `--- description: Test Cursor rule for file references globs: **/* @@ -102,19 +120,97 @@ alwaysApply: true This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and [taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testCursorOut = path.join(testDir, 'file-refs.mdc'); - convertRuleToProfileRule(testCursorRule, testCursorOut, cursorProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.mdc', + cursorProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testCursorOut, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); - // Verify transformations (should preserve Cursor file references) - expect(convertedContent).toContain('(mdc:.cursor/rules/dev_workflow.mdc)'); - expect(convertedContent).toContain('(mdc:.cursor/rules/taskmaster.mdc)'); - expect(convertedContent).not.toContain('(mdc:.roo/rules/'); - expect(convertedContent).not.toContain('(mdc:.windsurf/rules/'); + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; + + // Verify transformations (Cursor should keep the same references) + expect(transformedContent).toContain( + '(mdc:.cursor/rules/dev_workflow.mdc)' + ); + expect(transformedContent).toContain('(mdc:.cursor/rules/taskmaster.mdc)'); + }); + + it('should handle file read errors', () => { + // Mock file read to throw an error + mockReadFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'nonexistent.mdc', + 'target.mdc', + cursorProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify writeFileSync was not called + expect(mockWriteFileSync).not.toHaveBeenCalled(); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: File not found' + ); + }); + + it('should handle file write errors', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock file write to throw an error + mockWriteFileSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.mdc', + cursorProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: Permission denied' + ); + }); + + it('should create target directory if it does not exist', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock directory doesn't exist initially + mockExistsSync.mockReturnValue(false); + + // Call the actual function + convertRuleToProfileRule( + 'source.mdc', + 'some/deep/path/target.mdc', + cursorProfile + ); + + // Verify directory creation was called + expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', { + recursive: true + }); }); }); diff --git a/tests/unit/profiles/rule-transformer-roo.test.js b/tests/unit/profiles/rule-transformer-roo.test.js index 978c7115..53a3dc9e 100644 --- a/tests/unit/profiles/rule-transformer-roo.test.js +++ b/tests/unit/profiles/rule-transformer-roo.test.js @@ -1,37 +1,42 @@ +import { jest } from '@jest/globals'; + +// Mock fs module before importing anything that uses it +jest.mock('fs', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn() +})); + +// Import modules after mocking import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; -import { - convertAllRulesToProfileRules, - convertRuleToProfileRule, - getRulesProfile -} from '../../../src/utils/rule-transformer.js'; +import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js'; import { rooProfile } from '../../../scripts/profiles/roo.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - describe('Roo Rule Transformer', () => { - const testDir = path.join(__dirname, 'temp-test-dir'); + // Set up spies on the mocked modules + const mockReadFileSync = jest.spyOn(fs, 'readFileSync'); + const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + const mockExistsSync = jest.spyOn(fs, 'existsSync'); + const mockMkdirSync = jest.spyOn(fs, 'mkdirSync'); + const mockConsoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); - beforeAll(() => { - // Create test directory - if (!fs.existsSync(testDir)) { - fs.mkdirSync(testDir, { recursive: true }); - } + beforeEach(() => { + jest.clearAllMocks(); + // Setup default mocks + mockReadFileSync.mockReturnValue(''); + mockWriteFileSync.mockImplementation(() => {}); + mockExistsSync.mockReturnValue(true); + mockMkdirSync.mockImplementation(() => {}); }); afterAll(() => { - // Clean up test directory - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true, force: true }); - } + jest.restoreAllMocks(); }); it('should correctly convert basic terms', () => { - // Create a test Cursor rule file with basic terms - const testCursorRule = path.join(testDir, 'basic-terms.mdc'); const testContent = `--- description: Test Cursor rule for basic terms globs: **/* @@ -41,26 +46,36 @@ alwaysApply: true This is a Cursor rule that references cursor.so and uses the word Cursor multiple times. Also has references to .mdc files.`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testRooRule = path.join(testDir, 'basic-terms.md'); - convertRuleToProfileRule(testCursorRule, testRooRule, rooProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + rooProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testRooRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Verify file operations were called correctly + expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8'); + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('Roo Code'); - expect(convertedContent).toContain('roocode.com'); - expect(convertedContent).toContain('.md'); - expect(convertedContent).not.toContain('cursor.so'); - expect(convertedContent).not.toContain('Cursor rule'); + expect(transformedContent).toContain('Roo'); + expect(transformedContent).toContain('roocode.com'); + expect(transformedContent).toContain('.md'); + expect(transformedContent).not.toContain('cursor.so'); + expect(transformedContent).not.toContain('Cursor rule'); }); it('should correctly convert tool references', () => { - // Create a test Cursor rule file with tool references - const testCursorRule = path.join(testDir, 'tool-refs.mdc'); const testContent = `--- description: Test Cursor rule for tool references globs: **/* @@ -72,25 +87,31 @@ alwaysApply: true - run_command executes terminal commands - use_mcp connects to external services`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testRooRule = path.join(testDir, 'tool-refs.md'); - convertRuleToProfileRule(testCursorRule, testRooRule, rooProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + rooProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testRooRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); - // Verify transformations - expect(convertedContent).toContain('search_files tool'); - expect(convertedContent).toContain('apply_diff tool'); - expect(convertedContent).toContain('execute_command'); - expect(convertedContent).toContain('use_mcp_tool'); + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; + + // Verify transformations (Roo uses different tool names) + expect(transformedContent).toContain('search_files tool'); + expect(transformedContent).toContain('apply_diff tool'); + expect(transformedContent).toContain('execute_command'); + expect(transformedContent).toContain('use_mcp_tool'); }); it('should correctly update file references', () => { - // Create a test Cursor rule file with file references - const testCursorRule = path.join(testDir, 'file-refs.mdc'); const testContent = `--- description: Test Cursor rule for file references globs: **/* @@ -100,32 +121,96 @@ alwaysApply: true This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and [taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testRooRule = path.join(testDir, 'file-refs.md'); - convertRuleToProfileRule(testCursorRule, testRooRule, rooProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + rooProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testRooRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('(.roo/rules/dev_workflow.md)'); - expect(convertedContent).toContain('(.roo/rules/taskmaster.md)'); - expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); + expect(transformedContent).toContain('(.roo/rules/dev_workflow.md)'); + expect(transformedContent).toContain('(.roo/rules/taskmaster.md)'); + expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); }); - it('should run post-processing when converting all rules for Roo', () => { - // Simulate a rules directory with a .mdc file - const assetsRulesDir = path.join(testDir, 'assets', 'rules'); - fs.mkdirSync(assetsRulesDir, { recursive: true }); - const assetRule = path.join(assetsRulesDir, 'dev_workflow.mdc'); - fs.writeFileSync(assetRule, 'dummy'); - // Should create .roo/rules and call post-processing - convertAllRulesToProfileRules(testDir, rooProfile); - // Check for post-processing artifacts, e.g., rules-* folders or extra files - const rooDir = path.join(testDir, '.roo'); - const found = fs.readdirSync(rooDir).some((f) => f.startsWith('rules-')); - expect(found).toBe(true); // There should be at least one rules-* folder + it('should handle file read errors', () => { + // Mock file read to throw an error + mockReadFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'nonexistent.mdc', + 'target.md', + rooProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify writeFileSync was not called + expect(mockWriteFileSync).not.toHaveBeenCalled(); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: File not found' + ); + }); + + it('should handle file write errors', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock file write to throw an error + mockWriteFileSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + rooProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: Permission denied' + ); + }); + + it('should create target directory if it does not exist', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock directory doesn't exist initially + mockExistsSync.mockReturnValue(false); + + // Call the actual function + convertRuleToProfileRule( + 'source.mdc', + 'some/deep/path/target.md', + rooProfile + ); + + // Verify directory creation was called + expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', { + recursive: true + }); }); }); diff --git a/tests/unit/profiles/rule-transformer-trae.test.js b/tests/unit/profiles/rule-transformer-trae.test.js index f150a2b1..e538dc6c 100644 --- a/tests/unit/profiles/rule-transformer-trae.test.js +++ b/tests/unit/profiles/rule-transformer-trae.test.js @@ -1,33 +1,42 @@ +import { jest } from '@jest/globals'; + +// Mock fs module before importing anything that uses it +jest.mock('fs', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn() +})); + +// Import modules after mocking import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js'; import { traeProfile } from '../../../scripts/profiles/trae.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - describe('Trae Rule Transformer', () => { - const testDir = path.join(__dirname, 'temp-test-dir'); + // Set up spies on the mocked modules + const mockReadFileSync = jest.spyOn(fs, 'readFileSync'); + const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + const mockExistsSync = jest.spyOn(fs, 'existsSync'); + const mockMkdirSync = jest.spyOn(fs, 'mkdirSync'); + const mockConsoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); beforeEach(() => { - // Create test directory before each test - if (!fs.existsSync(testDir)) { - fs.mkdirSync(testDir, { recursive: true }); - } + jest.clearAllMocks(); + // Setup default mocks + mockReadFileSync.mockReturnValue(''); + mockWriteFileSync.mockImplementation(() => {}); + mockExistsSync.mockReturnValue(true); + mockMkdirSync.mockImplementation(() => {}); }); afterAll(() => { - // Clean up test directory - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true, force: true }); - } + jest.restoreAllMocks(); }); it('should correctly convert basic terms', () => { - // Create a test Cursor rule file with basic terms - const testCursorRule = path.join(testDir, 'basic-terms.mdc'); const testContent = `--- description: Test Cursor rule for basic terms globs: **/* @@ -37,26 +46,36 @@ alwaysApply: true This is a Cursor rule that references cursor.so and uses the word Cursor multiple times. Also has references to .mdc files.`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testTraeRule = path.join(testDir, 'basic-terms.md'); - convertRuleToProfileRule(testCursorRule, testTraeRule, traeProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + traeProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testTraeRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Verify file operations were called correctly + expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8'); + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('Trae'); - expect(convertedContent).toContain('trae.ai'); - expect(convertedContent).toContain('.md'); - expect(convertedContent).not.toContain('cursor.so'); - expect(convertedContent).not.toContain('Cursor rule'); + expect(transformedContent).toContain('Trae'); + expect(transformedContent).toContain('trae.ai'); + expect(transformedContent).toContain('.md'); + expect(transformedContent).not.toContain('cursor.so'); + expect(transformedContent).not.toContain('Cursor rule'); }); it('should correctly convert tool references', () => { - // Create a test Cursor rule file with tool references - const testCursorRule = path.join(testDir, 'tool-refs.mdc'); const testContent = `--- description: Test Cursor rule for tool references globs: **/* @@ -68,25 +87,31 @@ alwaysApply: true - run_command executes terminal commands - use_mcp connects to external services`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testTraeRule = path.join(testDir, 'tool-refs.md'); - convertRuleToProfileRule(testCursorRule, testTraeRule, traeProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + traeProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testTraeRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations (Trae uses standard tool names, so no transformation) - expect(convertedContent).toContain('search tool'); - expect(convertedContent).toContain('edit_file tool'); - expect(convertedContent).toContain('run_command'); - expect(convertedContent).toContain('use_mcp'); + expect(transformedContent).toContain('search tool'); + expect(transformedContent).toContain('edit_file tool'); + expect(transformedContent).toContain('run_command'); + expect(transformedContent).toContain('use_mcp'); }); it('should correctly update file references', () => { - // Create a test Cursor rule file with file references - const testCursorRule = path.join(testDir, 'file-refs.mdc'); const testContent = `--- description: Test Cursor rule for file references globs: **/* @@ -96,18 +121,96 @@ alwaysApply: true This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and [taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testTraeRule = path.join(testDir, 'file-refs.md'); - convertRuleToProfileRule(testCursorRule, testTraeRule, traeProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + traeProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testTraeRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('(.trae/rules/dev_workflow.md)'); - expect(convertedContent).toContain('(.trae/rules/taskmaster.md)'); - expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); + expect(transformedContent).toContain('(.trae/rules/dev_workflow.md)'); + expect(transformedContent).toContain('(.trae/rules/taskmaster.md)'); + expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); + }); + + it('should handle file read errors', () => { + // Mock file read to throw an error + mockReadFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'nonexistent.mdc', + 'target.md', + traeProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify writeFileSync was not called + expect(mockWriteFileSync).not.toHaveBeenCalled(); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: File not found' + ); + }); + + it('should handle file write errors', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock file write to throw an error + mockWriteFileSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + traeProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: Permission denied' + ); + }); + + it('should create target directory if it does not exist', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock directory doesn't exist initially + mockExistsSync.mockReturnValue(false); + + // Call the actual function + convertRuleToProfileRule( + 'source.mdc', + 'some/deep/path/target.md', + traeProfile + ); + + // Verify directory creation was called + expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', { + recursive: true + }); }); }); diff --git a/tests/unit/profiles/rule-transformer-windsurf.test.js b/tests/unit/profiles/rule-transformer-windsurf.test.js index 18aa957d..1b3699f5 100644 --- a/tests/unit/profiles/rule-transformer-windsurf.test.js +++ b/tests/unit/profiles/rule-transformer-windsurf.test.js @@ -1,33 +1,42 @@ +import { jest } from '@jest/globals'; + +// Mock fs module before importing anything that uses it +jest.mock('fs', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn() +})); + +// Import modules after mocking import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js'; import { windsurfProfile } from '../../../scripts/profiles/windsurf.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - describe('Windsurf Rule Transformer', () => { - const testDir = path.join(__dirname, 'temp-test-dir'); + // Set up spies on the mocked modules + const mockReadFileSync = jest.spyOn(fs, 'readFileSync'); + const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + const mockExistsSync = jest.spyOn(fs, 'existsSync'); + const mockMkdirSync = jest.spyOn(fs, 'mkdirSync'); + const mockConsoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); - beforeAll(() => { - // Create test directory - if (!fs.existsSync(testDir)) { - fs.mkdirSync(testDir, { recursive: true }); - } + beforeEach(() => { + jest.clearAllMocks(); + // Setup default mocks + mockReadFileSync.mockReturnValue(''); + mockWriteFileSync.mockImplementation(() => {}); + mockExistsSync.mockReturnValue(true); + mockMkdirSync.mockImplementation(() => {}); }); afterAll(() => { - // Clean up test directory - if (fs.existsSync(testDir)) { - fs.rmSync(testDir, { recursive: true, force: true }); - } + jest.restoreAllMocks(); }); it('should correctly convert basic terms', () => { - // Create a test Cursor rule file with basic terms - const testCursorRule = path.join(testDir, 'basic-terms.mdc'); const testContent = `--- description: Test Cursor rule for basic terms globs: **/* @@ -37,26 +46,36 @@ alwaysApply: true This is a Cursor rule that references cursor.so and uses the word Cursor multiple times. Also has references to .mdc files.`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testWindsurfRule = path.join(testDir, 'basic-terms.md'); - convertRuleToProfileRule(testCursorRule, testWindsurfRule, windsurfProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + windsurfProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Verify file operations were called correctly + expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8'); + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('Windsurf'); - expect(convertedContent).toContain('windsurf.com'); - expect(convertedContent).toContain('.md'); - expect(convertedContent).not.toContain('cursor.so'); - expect(convertedContent).not.toContain('Cursor rule'); + expect(transformedContent).toContain('Windsurf'); + expect(transformedContent).toContain('windsurf.com'); + expect(transformedContent).toContain('.md'); + expect(transformedContent).not.toContain('cursor.so'); + expect(transformedContent).not.toContain('Cursor rule'); }); it('should correctly convert tool references', () => { - // Create a test Cursor rule file with tool references - const testCursorRule = path.join(testDir, 'tool-refs.mdc'); const testContent = `--- description: Test Cursor rule for tool references globs: **/* @@ -68,25 +87,31 @@ alwaysApply: true - run_command executes terminal commands - use_mcp connects to external services`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testWindsurfRule = path.join(testDir, 'tool-refs.md'); - convertRuleToProfileRule(testCursorRule, testWindsurfRule, windsurfProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + windsurfProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations (Windsurf uses standard tool names, so no transformation) - expect(convertedContent).toContain('search tool'); - expect(convertedContent).toContain('edit_file tool'); - expect(convertedContent).toContain('run_command'); - expect(convertedContent).toContain('use_mcp'); + expect(transformedContent).toContain('search tool'); + expect(transformedContent).toContain('edit_file tool'); + expect(transformedContent).toContain('run_command'); + expect(transformedContent).toContain('use_mcp'); }); it('should correctly update file references', () => { - // Create a test Cursor rule file with file references - const testCursorRule = path.join(testDir, 'file-refs.mdc'); const testContent = `--- description: Test Cursor rule for file references globs: **/* @@ -96,18 +121,96 @@ alwaysApply: true This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and [taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`; - fs.writeFileSync(testCursorRule, testContent); + // Mock file read to return our test content + mockReadFileSync.mockReturnValue(testContent); - // Convert it - const testWindsurfRule = path.join(testDir, 'file-refs.md'); - convertRuleToProfileRule(testCursorRule, testWindsurfRule, windsurfProfile); + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + windsurfProfile + ); - // Read the converted file - const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8'); + // Verify the function succeeded + expect(result).toBe(true); + + // Get the transformed content that was written + const writeCall = mockWriteFileSync.mock.calls[0]; + const transformedContent = writeCall[1]; // Verify transformations - expect(convertedContent).toContain('(.windsurf/rules/dev_workflow.md)'); - expect(convertedContent).toContain('(.windsurf/rules/taskmaster.md)'); - expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); + expect(transformedContent).toContain('(.windsurf/rules/dev_workflow.md)'); + expect(transformedContent).toContain('(.windsurf/rules/taskmaster.md)'); + expect(transformedContent).not.toContain('(mdc:.cursor/rules/'); + }); + + it('should handle file read errors', () => { + // Mock file read to throw an error + mockReadFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'nonexistent.mdc', + 'target.md', + windsurfProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify writeFileSync was not called + expect(mockWriteFileSync).not.toHaveBeenCalled(); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: File not found' + ); + }); + + it('should handle file write errors', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock file write to throw an error + mockWriteFileSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + // Call the actual function + const result = convertRuleToProfileRule( + 'source.mdc', + 'target.md', + windsurfProfile + ); + + // Verify the function failed gracefully + expect(result).toBe(false); + + // Verify error was logged + expect(mockConsoleError).toHaveBeenCalledWith( + 'Error converting rule file: Permission denied' + ); + }); + + it('should create target directory if it does not exist', () => { + const testContent = 'test content'; + mockReadFileSync.mockReturnValue(testContent); + + // Mock directory doesn't exist initially + mockExistsSync.mockReturnValue(false); + + // Call the actual function + convertRuleToProfileRule( + 'source.mdc', + 'some/deep/path/target.md', + windsurfProfile + ); + + // Verify directory creation was called + expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', { + recursive: true + }); }); });