summaryrefslogtreecommitdiff
path: root/packages/core/src/tools/write-file.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/tools/write-file.test.ts')
-rw-r--r--packages/core/src/tools/write-file.test.ts135
1 files changed, 130 insertions, 5 deletions
diff --git a/packages/core/src/tools/write-file.test.ts b/packages/core/src/tools/write-file.test.ts
index 563579bb..1967b99b 100644
--- a/packages/core/src/tools/write-file.test.ts
+++ b/packages/core/src/tools/write-file.test.ts
@@ -14,6 +14,7 @@ import {
type Mocked,
} from 'vitest';
import { WriteFileTool, WriteFileToolParams } from './write-file.js';
+import { ToolErrorType } from './tool-error.js';
import {
FileDiff,
ToolConfirmationOutcome,
@@ -464,18 +465,27 @@ describe('WriteFileTool', () => {
it('should return error if params are invalid (relative path)', async () => {
const params = { file_path: 'relative.txt', content: 'test' };
const result = await tool.execute(params, abortSignal);
- expect(result.llmContent).toMatch(/Error: Invalid parameters provided/);
- expect(result.returnDisplay).toMatch(/Error: File path must be absolute/);
+ expect(result.llmContent).toContain(
+ 'Could not write file due to invalid parameters:',
+ );
+ expect(result.returnDisplay).toMatch(/File path must be absolute/);
+ expect(result.error).toEqual({
+ message: 'File path must be absolute: relative.txt',
+ type: ToolErrorType.INVALID_TOOL_PARAMS,
+ });
});
it('should return error if params are invalid (path outside root)', async () => {
const outsidePath = path.resolve(tempDir, 'outside-root.txt');
const params = { file_path: outsidePath, content: 'test' };
const result = await tool.execute(params, abortSignal);
- expect(result.llmContent).toMatch(/Error: Invalid parameters provided/);
+ expect(result.llmContent).toContain(
+ 'Could not write file due to invalid parameters:',
+ );
expect(result.returnDisplay).toContain(
- 'Error: File path must be within one of the workspace directories',
+ 'File path must be within one of the workspace directories',
);
+ expect(result.error?.type).toBe(ToolErrorType.INVALID_TOOL_PARAMS);
});
it('should return error if _getCorrectedFileContent returns an error during execute', async () => {
@@ -490,10 +500,15 @@ describe('WriteFileTool', () => {
});
const result = await tool.execute(params, abortSignal);
- expect(result.llmContent).toMatch(/Error checking existing file/);
+ expect(result.llmContent).toContain('Error checking existing file:');
expect(result.returnDisplay).toMatch(
/Error checking existing file: Simulated read error for execute/,
);
+ expect(result.error).toEqual({
+ message:
+ 'Error checking existing file: Simulated read error for execute',
+ type: ToolErrorType.FILE_WRITE_FAILURE,
+ });
vi.spyOn(fs, 'readFileSync').mockImplementation(originalReadFileSync);
fs.chmodSync(filePath, 0o600);
@@ -709,4 +724,114 @@ describe('WriteFileTool', () => {
expect(error).toContain(rootDir);
});
});
+
+ describe('specific error types for write failures', () => {
+ const abortSignal = new AbortController().signal;
+
+ it('should return PERMISSION_DENIED error when write fails with EACCES', async () => {
+ const filePath = path.join(rootDir, 'permission_denied_file.txt');
+ const content = 'test content';
+
+ // Mock writeFileSync to throw EACCES error
+ const originalWriteFileSync = fs.writeFileSync;
+ vi.spyOn(fs, 'writeFileSync').mockImplementationOnce(() => {
+ const error = new Error('Permission denied') as NodeJS.ErrnoException;
+ error.code = 'EACCES';
+ throw error;
+ });
+
+ const params = { file_path: filePath, content };
+ const result = await tool.execute(params, abortSignal);
+
+ expect(result.error?.type).toBe(ToolErrorType.PERMISSION_DENIED);
+ expect(result.llmContent).toContain(
+ `Permission denied writing to file: ${filePath} (EACCES)`,
+ );
+ expect(result.returnDisplay).toContain('Permission denied');
+
+ vi.spyOn(fs, 'writeFileSync').mockImplementation(originalWriteFileSync);
+ });
+
+ it('should return NO_SPACE_LEFT error when write fails with ENOSPC', async () => {
+ const filePath = path.join(rootDir, 'no_space_file.txt');
+ const content = 'test content';
+
+ // Mock writeFileSync to throw ENOSPC error
+ const originalWriteFileSync = fs.writeFileSync;
+ vi.spyOn(fs, 'writeFileSync').mockImplementationOnce(() => {
+ const error = new Error(
+ 'No space left on device',
+ ) as NodeJS.ErrnoException;
+ error.code = 'ENOSPC';
+ throw error;
+ });
+
+ const params = { file_path: filePath, content };
+ const result = await tool.execute(params, abortSignal);
+
+ expect(result.error?.type).toBe(ToolErrorType.NO_SPACE_LEFT);
+ expect(result.llmContent).toContain(
+ `No space left on device: ${filePath} (ENOSPC)`,
+ );
+ expect(result.returnDisplay).toContain('No space left');
+
+ vi.spyOn(fs, 'writeFileSync').mockImplementation(originalWriteFileSync);
+ });
+
+ it('should return TARGET_IS_DIRECTORY error when write fails with EISDIR', async () => {
+ const dirPath = path.join(rootDir, 'test_directory');
+ const content = 'test content';
+
+ // Mock fs.existsSync to return false to bypass validation
+ const originalExistsSync = fs.existsSync;
+ vi.spyOn(fs, 'existsSync').mockImplementation((path) => {
+ if (path === dirPath) {
+ return false; // Pretend directory doesn't exist to bypass validation
+ }
+ return originalExistsSync(path as string);
+ });
+
+ // Mock writeFileSync to throw EISDIR error
+ const originalWriteFileSync = fs.writeFileSync;
+ vi.spyOn(fs, 'writeFileSync').mockImplementationOnce(() => {
+ const error = new Error('Is a directory') as NodeJS.ErrnoException;
+ error.code = 'EISDIR';
+ throw error;
+ });
+
+ const params = { file_path: dirPath, content };
+ const result = await tool.execute(params, abortSignal);
+
+ expect(result.error?.type).toBe(ToolErrorType.TARGET_IS_DIRECTORY);
+ expect(result.llmContent).toContain(
+ `Target is a directory, not a file: ${dirPath} (EISDIR)`,
+ );
+ expect(result.returnDisplay).toContain('Target is a directory');
+
+ vi.spyOn(fs, 'existsSync').mockImplementation(originalExistsSync);
+ vi.spyOn(fs, 'writeFileSync').mockImplementation(originalWriteFileSync);
+ });
+
+ it('should return FILE_WRITE_FAILURE for generic write errors', async () => {
+ const filePath = path.join(rootDir, 'generic_error_file.txt');
+ const content = 'test content';
+
+ // Ensure fs.existsSync is not mocked for this test
+ vi.restoreAllMocks();
+
+ // Mock writeFileSync to throw generic error
+ vi.spyOn(fs, 'writeFileSync').mockImplementationOnce(() => {
+ throw new Error('Generic write error');
+ });
+
+ const params = { file_path: filePath, content };
+ const result = await tool.execute(params, abortSignal);
+
+ expect(result.error?.type).toBe(ToolErrorType.FILE_WRITE_FAILURE);
+ expect(result.llmContent).toContain(
+ 'Error writing to file: Generic write error',
+ );
+ expect(result.returnDisplay).toContain('Generic write error');
+ });
+ });
});