UNPKG

mycoder-agent

Version:

Agent module for mycoder - an AI-powered software development assistant

311 lines 12.5 kB
import { randomUUID } from 'crypto'; import { mkdtemp, readFile } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { MockLogger } from '../../utils/mockLogger.js'; import { getMockToolContext } from '../getTools.test.js'; import { shellExecuteTool } from '../shell/shellExecute.js'; import { textEditorTool } from './textEditor.js'; const toolContext = getMockToolContext(); describe('textEditor', () => { let testDir; beforeEach(async () => { testDir = await mkdtemp(join(tmpdir(), 'texteditor-test-')); }); afterEach(async () => { await shellExecuteTool.execute({ command: `rm -rf "${testDir}"`, description: 'test' }, toolContext); }); it('should create a file', async () => { const testContent = 'test content'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create the file const result = await textEditorTool.execute({ command: 'create', path: testPath, file_text: testContent, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.message).toContain('File created'); // Verify content const content = await readFile(testPath, 'utf8'); expect(content).toBe(testContent); }); it('should view a file', async () => { const testContent = 'line 1\nline 2\nline 3'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create the file await textEditorTool.execute({ command: 'create', path: testPath, file_text: testContent, description: 'test', }, toolContext); // View the file const result = await textEditorTool.execute({ command: 'view', path: testPath, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.content).toContain('1: line 1'); expect(result.content).toContain('2: line 2'); expect(result.content).toContain('3: line 3'); }); it('should view a file with range', async () => { const testContent = 'line 1\nline 2\nline 3\nline 4\nline 5'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create the file await textEditorTool.execute({ command: 'create', path: testPath, file_text: testContent, description: 'test', }, toolContext); // View the file with range const result = await textEditorTool.execute({ command: 'view', path: testPath, view_range: [2, 4], description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.content).not.toContain('1: line 1'); expect(result.content).toContain('2: line 2'); expect(result.content).toContain('3: line 3'); expect(result.content).toContain('4: line 4'); expect(result.content).not.toContain('5: line 5'); }); it('should replace text in a file', async () => { const initialContent = 'Hello world! This is a test.'; const oldStr = 'world'; const newStr = 'universe'; const expectedContent = 'Hello universe! This is a test.'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create initial file await textEditorTool.execute({ command: 'create', path: testPath, file_text: initialContent, description: 'test', }, toolContext); // Replace text const result = await textEditorTool.execute({ command: 'str_replace', path: testPath, old_str: oldStr, new_str: newStr, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.message).toContain('Successfully replaced'); // Verify content const content = await readFile(testPath, 'utf8'); expect(content).toBe(expectedContent); }); it('should insert text at a specific line', async () => { const initialContent = 'line 1\nline 2\nline 4'; const insertLine = 2; // After "line 2" const newStr = 'line 3'; const expectedContent = 'line 1\nline 2\nline 3\nline 4'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create initial file await textEditorTool.execute({ command: 'create', path: testPath, file_text: initialContent, description: 'test', }, toolContext); // Insert text const result = await textEditorTool.execute({ command: 'insert', path: testPath, insert_line: insertLine, new_str: newStr, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.message).toContain('Successfully inserted'); // Verify content const content = await readFile(testPath, 'utf8'); expect(content).toBe(expectedContent); }); it('should undo an edit', async () => { const initialContent = 'Hello world!'; const modifiedContent = 'Hello universe!'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create initial file await textEditorTool.execute({ command: 'create', path: testPath, file_text: initialContent, description: 'test', }, toolContext); // Modify the file await textEditorTool.execute({ command: 'str_replace', path: testPath, old_str: 'world', new_str: 'universe', description: 'test', }, toolContext); // Verify modified content let content = await readFile(testPath, 'utf8'); expect(content).toBe(modifiedContent); // Undo the edit const result = await textEditorTool.execute({ command: 'undo_edit', path: testPath, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.message).toContain('Successfully reverted'); // Verify content is back to initial content = await readFile(testPath, 'utf8'); expect(content).toBe(initialContent); }); it('should handle errors for non-existent files', async () => { const testPath = join(testDir, `${randomUUID()}.txt`); // Try to view a non-existent file await expect(async () => { await textEditorTool.execute({ command: 'view', path: testPath, description: 'test', }, toolContext); }).rejects.toThrow(/not found/); }); it('should handle errors for duplicate string replacements', async () => { const initialContent = 'Hello world! This is a world test.'; const oldStr = 'world'; const newStr = 'universe'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create initial file await textEditorTool.execute({ command: 'create', path: testPath, file_text: initialContent, description: 'test', }, toolContext); // Try to replace text with multiple occurrences await expect(async () => { await textEditorTool.execute({ command: 'str_replace', path: testPath, old_str: oldStr, new_str: newStr, description: 'test', }, toolContext); }).rejects.toThrow(/Found 2 occurrences/); }); it('should overwrite an existing file with create command', async () => { const initialContent = 'Initial content'; const newContent = 'New content that overwrites the file'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create initial file await textEditorTool.execute({ command: 'create', path: testPath, file_text: initialContent, description: 'test', }, toolContext); // Verify initial content let content = await readFile(testPath, 'utf8'); expect(content).toBe(initialContent); // Overwrite the file using create command const result = await textEditorTool.execute({ command: 'create', path: testPath, file_text: newContent, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.message).toContain('File overwritten'); // Verify content has been updated content = await readFile(testPath, 'utf8'); expect(content).toBe(newContent); }); it('should be able to undo file overwrite', async () => { const initialContent = 'Initial content that will be restored'; const overwrittenContent = 'This content will be undone'; const testPath = join(testDir, `${randomUUID()}.txt`); // Create initial file await textEditorTool.execute({ command: 'create', path: testPath, file_text: initialContent, description: 'test', }, toolContext); // Overwrite the file await textEditorTool.execute({ command: 'create', path: testPath, file_text: overwrittenContent, description: 'test', }, toolContext); // Verify overwritten content let content = await readFile(testPath, 'utf8'); expect(content).toBe(overwrittenContent); // Undo the overwrite const result = await textEditorTool.execute({ command: 'undo_edit', path: testPath, description: 'test', }, toolContext); // Verify return value expect(result.success).toBe(true); expect(result.message).toContain('Successfully reverted'); // Verify content is back to initial content = await readFile(testPath, 'utf8'); expect(content).toBe(initialContent); }); it('should convert absolute paths to relative paths in log messages', () => { // Create a mock logger with a spy on the info method const mockLogger = new MockLogger(); const logSpy = vi.spyOn(mockLogger, 'log'); // Create a context with a specific working directory const contextWithWorkingDir = { ...toolContext, logger: mockLogger, workingDirectory: '/home/user/project', }; // Test with an absolute path within the working directory const absolutePath = '/home/user/project/packages/agent/src/file.ts'; textEditorTool.logParameters?.({ command: 'view', path: absolutePath, description: 'test path conversion', }, contextWithWorkingDir); // Verify the log message contains the relative path expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('./packages/agent/src/file.ts')); // Test with an absolute path outside the working directory logSpy.mockClear(); const externalPath = '/etc/config.json'; textEditorTool.logParameters?.({ command: 'view', path: externalPath, description: 'test external path', }, contextWithWorkingDir); // Verify the log message keeps the absolute path expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(externalPath)); // Test with a relative path logSpy.mockClear(); const relativePath = 'src/file.ts'; textEditorTool.logParameters?.({ command: 'view', path: relativePath, description: 'test relative path', }, contextWithWorkingDir); // Verify the log message keeps the relative path as is expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(relativePath)); }); }); //# sourceMappingURL=textEditor.test.js.map