mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 01:37:50 +00:00
# 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483)
This commit is contained in:
@@ -13,7 +13,11 @@ import {
|
||||
vi,
|
||||
type Mocked,
|
||||
} from 'vitest';
|
||||
import { WriteFileTool, WriteFileToolParams } from './write-file.js';
|
||||
import {
|
||||
getCorrectedFileContent,
|
||||
WriteFileTool,
|
||||
WriteFileToolParams,
|
||||
} from './write-file.js';
|
||||
import { ToolErrorType } from './tool-error.js';
|
||||
import {
|
||||
FileDiff,
|
||||
@@ -33,6 +37,7 @@ import {
|
||||
CorrectedEditResult,
|
||||
} from '../utils/editCorrector.js';
|
||||
import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js';
|
||||
import { StandardFileSystemService } from '../services/fileSystemService.js';
|
||||
|
||||
const rootDir = path.resolve(os.tmpdir(), 'qwen-code-test-root');
|
||||
|
||||
@@ -51,11 +56,13 @@ vi.mocked(ensureCorrectFileContent).mockImplementation(
|
||||
);
|
||||
|
||||
// Mock Config
|
||||
const fsService = new StandardFileSystemService();
|
||||
const mockConfigInternal = {
|
||||
getTargetDir: () => rootDir,
|
||||
getApprovalMode: vi.fn(() => ApprovalMode.DEFAULT),
|
||||
setApprovalMode: vi.fn(),
|
||||
getGeminiClient: vi.fn(), // Initialize as a plain mock function
|
||||
getFileSystemService: () => fsService,
|
||||
getIdeClient: vi.fn(),
|
||||
getIdeMode: vi.fn(() => false),
|
||||
getWorkspaceContext: () => createMockWorkspaceContext(rootDir),
|
||||
@@ -174,74 +181,67 @@ describe('WriteFileTool', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('validateToolParams', () => {
|
||||
it('should return null for valid absolute path within root', () => {
|
||||
describe('build', () => {
|
||||
it('should return an invocation for a valid absolute path within root', () => {
|
||||
const params = {
|
||||
file_path: path.join(rootDir, 'test.txt'),
|
||||
content: 'hello',
|
||||
};
|
||||
expect(tool.validateToolParams(params)).toBeNull();
|
||||
const invocation = tool.build(params);
|
||||
expect(invocation).toBeDefined();
|
||||
expect(invocation.params).toEqual(params);
|
||||
});
|
||||
|
||||
it('should return error for relative path', () => {
|
||||
it('should throw an error for a relative path', () => {
|
||||
const params = { file_path: 'test.txt', content: 'hello' };
|
||||
expect(tool.validateToolParams(params)).toMatch(
|
||||
/File path must be absolute/,
|
||||
);
|
||||
expect(() => tool.build(params)).toThrow(/File path must be absolute/);
|
||||
});
|
||||
|
||||
it('should return error for path outside root', () => {
|
||||
it('should throw an error for a path outside root', () => {
|
||||
const outsidePath = path.resolve(tempDir, 'outside-root.txt');
|
||||
const params = {
|
||||
file_path: outsidePath,
|
||||
content: 'hello',
|
||||
};
|
||||
const error = tool.validateToolParams(params);
|
||||
expect(error).toContain(
|
||||
'File path must be within one of the workspace directories',
|
||||
expect(() => tool.build(params)).toThrow(
|
||||
/File path must be within one of the workspace directories/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error if path is a directory', () => {
|
||||
it('should throw an error if path is a directory', () => {
|
||||
const dirAsFilePath = path.join(rootDir, 'a_directory');
|
||||
fs.mkdirSync(dirAsFilePath);
|
||||
const params = {
|
||||
file_path: dirAsFilePath,
|
||||
content: 'hello',
|
||||
};
|
||||
expect(tool.validateToolParams(params)).toMatch(
|
||||
expect(() => tool.build(params)).toThrow(
|
||||
`Path is a directory, not a file: ${dirAsFilePath}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error if the content is null', () => {
|
||||
it('should throw an error if the content is null', () => {
|
||||
const dirAsFilePath = path.join(rootDir, 'a_directory');
|
||||
fs.mkdirSync(dirAsFilePath);
|
||||
const params = {
|
||||
file_path: dirAsFilePath,
|
||||
content: null,
|
||||
} as unknown as WriteFileToolParams; // Intentionally non-conforming
|
||||
expect(tool.validateToolParams(params)).toMatch(
|
||||
`params/content must be string`,
|
||||
);
|
||||
expect(() => tool.build(params)).toThrow('params/content must be string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDescription', () => {
|
||||
it('should return error if the file_path is empty', () => {
|
||||
it('should throw error if the file_path is empty', () => {
|
||||
const dirAsFilePath = path.join(rootDir, 'a_directory');
|
||||
fs.mkdirSync(dirAsFilePath);
|
||||
const params = {
|
||||
file_path: '',
|
||||
content: '',
|
||||
};
|
||||
expect(tool.getDescription(params)).toMatch(
|
||||
`Model did not provide valid parameters for write file tool, missing or empty "file_path"`,
|
||||
);
|
||||
expect(() => tool.build(params)).toThrow(`Missing or empty "file_path"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_getCorrectedFileContent', () => {
|
||||
describe('getCorrectedFileContent', () => {
|
||||
it('should call ensureCorrectFileContent for a new file', async () => {
|
||||
const filePath = path.join(rootDir, 'new_corrected_file.txt');
|
||||
const proposedContent = 'Proposed new content.';
|
||||
@@ -250,8 +250,8 @@ describe('WriteFileTool', () => {
|
||||
// Ensure the mock is set for this specific test case if needed, or rely on beforeEach
|
||||
mockEnsureCorrectFileContent.mockResolvedValue(correctedContent);
|
||||
|
||||
// @ts-expect-error _getCorrectedFileContent is private
|
||||
const result = await tool._getCorrectedFileContent(
|
||||
const result = await getCorrectedFileContent(
|
||||
mockConfig,
|
||||
filePath,
|
||||
proposedContent,
|
||||
abortSignal,
|
||||
@@ -287,8 +287,8 @@ describe('WriteFileTool', () => {
|
||||
occurrences: 1,
|
||||
} as CorrectedEditResult);
|
||||
|
||||
// @ts-expect-error _getCorrectedFileContent is private
|
||||
const result = await tool._getCorrectedFileContent(
|
||||
const result = await getCorrectedFileContent(
|
||||
mockConfig,
|
||||
filePath,
|
||||
proposedContent,
|
||||
abortSignal,
|
||||
@@ -319,19 +319,18 @@ describe('WriteFileTool', () => {
|
||||
fs.writeFileSync(filePath, 'content', { mode: 0o000 });
|
||||
|
||||
const readError = new Error('Permission denied');
|
||||
const originalReadFileSync = fs.readFileSync;
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
|
||||
throw readError;
|
||||
});
|
||||
vi.spyOn(fsService, 'readTextFile').mockImplementationOnce(() =>
|
||||
Promise.reject(readError),
|
||||
);
|
||||
|
||||
// @ts-expect-error _getCorrectedFileContent is private
|
||||
const result = await tool._getCorrectedFileContent(
|
||||
const result = await getCorrectedFileContent(
|
||||
mockConfig,
|
||||
filePath,
|
||||
proposedContent,
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(filePath, 'utf8');
|
||||
expect(fsService.readTextFile).toHaveBeenCalledWith(filePath);
|
||||
expect(mockEnsureCorrectEdit).not.toHaveBeenCalled();
|
||||
expect(mockEnsureCorrectFileContent).not.toHaveBeenCalled();
|
||||
expect(result.correctedContent).toBe(proposedContent);
|
||||
@@ -342,25 +341,12 @@ describe('WriteFileTool', () => {
|
||||
code: undefined,
|
||||
});
|
||||
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementation(originalReadFileSync);
|
||||
fs.chmodSync(filePath, 0o600);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldConfirmExecute', () => {
|
||||
const abortSignal = new AbortController().signal;
|
||||
it('should return false if params are invalid (relative path)', async () => {
|
||||
const params = { file_path: 'relative.txt', content: 'test' };
|
||||
const confirmation = await tool.shouldConfirmExecute(params, abortSignal);
|
||||
expect(confirmation).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if params are invalid (outside root)', async () => {
|
||||
const outsidePath = path.resolve(tempDir, 'outside-root.txt');
|
||||
const params = { file_path: outsidePath, content: 'test' };
|
||||
const confirmation = await tool.shouldConfirmExecute(params, abortSignal);
|
||||
expect(confirmation).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if _getCorrectedFileContent returns an error', async () => {
|
||||
const filePath = path.join(rootDir, 'confirm_error_file.txt');
|
||||
@@ -368,15 +354,14 @@ describe('WriteFileTool', () => {
|
||||
fs.writeFileSync(filePath, 'original', { mode: 0o000 });
|
||||
|
||||
const readError = new Error('Simulated read error for confirmation');
|
||||
const originalReadFileSync = fs.readFileSync;
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
|
||||
throw readError;
|
||||
});
|
||||
vi.spyOn(fsService, 'readTextFile').mockImplementationOnce(() =>
|
||||
Promise.reject(readError),
|
||||
);
|
||||
|
||||
const confirmation = await tool.shouldConfirmExecute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const confirmation = await invocation.shouldConfirmExecute(abortSignal);
|
||||
expect(confirmation).toBe(false);
|
||||
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementation(originalReadFileSync);
|
||||
fs.chmodSync(filePath, 0o600);
|
||||
});
|
||||
|
||||
@@ -387,8 +372,8 @@ describe('WriteFileTool', () => {
|
||||
mockEnsureCorrectFileContent.mockResolvedValue(correctedContent); // Ensure this mock is active
|
||||
|
||||
const params = { file_path: filePath, content: proposedContent };
|
||||
const confirmation = (await tool.shouldConfirmExecute(
|
||||
params,
|
||||
const invocation = tool.build(params);
|
||||
const confirmation = (await invocation.shouldConfirmExecute(
|
||||
abortSignal,
|
||||
)) as ToolEditConfirmationDetails;
|
||||
|
||||
@@ -430,8 +415,8 @@ describe('WriteFileTool', () => {
|
||||
});
|
||||
|
||||
const params = { file_path: filePath, content: proposedContent };
|
||||
const confirmation = (await tool.shouldConfirmExecute(
|
||||
params,
|
||||
const invocation = tool.build(params);
|
||||
const confirmation = (await invocation.shouldConfirmExecute(
|
||||
abortSignal,
|
||||
)) as ToolEditConfirmationDetails;
|
||||
|
||||
@@ -461,45 +446,20 @@ describe('WriteFileTool', () => {
|
||||
|
||||
describe('execute', () => {
|
||||
const abortSignal = new AbortController().signal;
|
||||
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).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).toContain(
|
||||
'Could not write file due to invalid parameters:',
|
||||
);
|
||||
expect(result.returnDisplay).toContain(
|
||||
'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 () => {
|
||||
const filePath = path.join(rootDir, 'execute_error_file.txt');
|
||||
const params = { file_path: filePath, content: 'test content' };
|
||||
fs.writeFileSync(filePath, 'original', { mode: 0o000 });
|
||||
|
||||
const readError = new Error('Simulated read error for execute');
|
||||
const originalReadFileSync = fs.readFileSync;
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementationOnce(() => {
|
||||
throw readError;
|
||||
vi.spyOn(fsService, 'readTextFile').mockImplementationOnce(() => {
|
||||
const readError = new Error('Simulated read error for execute');
|
||||
return Promise.reject(readError);
|
||||
});
|
||||
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
expect(result.llmContent).toContain('Error checking existing file:');
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
expect(result.llmContent).toContain('Error checking existing file');
|
||||
expect(result.returnDisplay).toMatch(
|
||||
/Error checking existing file: Simulated read error for execute/,
|
||||
);
|
||||
@@ -509,7 +469,6 @@ describe('WriteFileTool', () => {
|
||||
type: ToolErrorType.FILE_WRITE_FAILURE,
|
||||
});
|
||||
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementation(originalReadFileSync);
|
||||
fs.chmodSync(filePath, 0o600);
|
||||
});
|
||||
|
||||
@@ -520,11 +479,9 @@ describe('WriteFileTool', () => {
|
||||
mockEnsureCorrectFileContent.mockResolvedValue(correctedContent);
|
||||
|
||||
const params = { file_path: filePath, content: proposedContent };
|
||||
const invocation = tool.build(params);
|
||||
|
||||
const confirmDetails = await tool.shouldConfirmExecute(
|
||||
params,
|
||||
abortSignal,
|
||||
);
|
||||
const confirmDetails = await invocation.shouldConfirmExecute(abortSignal);
|
||||
if (
|
||||
typeof confirmDetails === 'object' &&
|
||||
'onConfirm' in confirmDetails &&
|
||||
@@ -533,7 +490,7 @@ describe('WriteFileTool', () => {
|
||||
await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce);
|
||||
}
|
||||
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(mockEnsureCorrectFileContent).toHaveBeenCalledWith(
|
||||
proposedContent,
|
||||
@@ -544,7 +501,8 @@ describe('WriteFileTool', () => {
|
||||
/Successfully created and wrote to new file/,
|
||||
);
|
||||
expect(fs.existsSync(filePath)).toBe(true);
|
||||
expect(fs.readFileSync(filePath, 'utf8')).toBe(correctedContent);
|
||||
const writtenContent = await fsService.readTextFile(filePath);
|
||||
expect(writtenContent).toBe(correctedContent);
|
||||
const display = result.returnDisplay as FileDiff;
|
||||
expect(display.fileName).toBe('execute_new_corrected_file.txt');
|
||||
expect(display.fileDiff).toMatch(
|
||||
@@ -578,11 +536,9 @@ describe('WriteFileTool', () => {
|
||||
});
|
||||
|
||||
const params = { file_path: filePath, content: proposedContent };
|
||||
const invocation = tool.build(params);
|
||||
|
||||
const confirmDetails = await tool.shouldConfirmExecute(
|
||||
params,
|
||||
abortSignal,
|
||||
);
|
||||
const confirmDetails = await invocation.shouldConfirmExecute(abortSignal);
|
||||
if (
|
||||
typeof confirmDetails === 'object' &&
|
||||
'onConfirm' in confirmDetails &&
|
||||
@@ -591,7 +547,7 @@ describe('WriteFileTool', () => {
|
||||
await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce);
|
||||
}
|
||||
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(mockEnsureCorrectEdit).toHaveBeenCalledWith(
|
||||
filePath,
|
||||
@@ -605,7 +561,8 @@ describe('WriteFileTool', () => {
|
||||
abortSignal,
|
||||
);
|
||||
expect(result.llmContent).toMatch(/Successfully overwrote file/);
|
||||
expect(fs.readFileSync(filePath, 'utf8')).toBe(correctedProposedContent);
|
||||
const writtenContent = await fsService.readTextFile(filePath);
|
||||
expect(writtenContent).toBe(correctedProposedContent);
|
||||
const display = result.returnDisplay as FileDiff;
|
||||
expect(display.fileName).toBe('execute_existing_corrected_file.txt');
|
||||
expect(display.fileDiff).toMatch(
|
||||
@@ -623,11 +580,9 @@ describe('WriteFileTool', () => {
|
||||
mockEnsureCorrectFileContent.mockResolvedValue(content); // Ensure this mock is active
|
||||
|
||||
const params = { file_path: filePath, content };
|
||||
const invocation = tool.build(params);
|
||||
// Simulate confirmation if your logic requires it before execute, or remove if not needed for this path
|
||||
const confirmDetails = await tool.shouldConfirmExecute(
|
||||
params,
|
||||
abortSignal,
|
||||
);
|
||||
const confirmDetails = await invocation.shouldConfirmExecute(abortSignal);
|
||||
if (
|
||||
typeof confirmDetails === 'object' &&
|
||||
'onConfirm' in confirmDetails &&
|
||||
@@ -636,7 +591,7 @@ describe('WriteFileTool', () => {
|
||||
await confirmDetails.onConfirm(ToolConfirmationOutcome.ProceedOnce);
|
||||
}
|
||||
|
||||
await tool.execute(params, abortSignal);
|
||||
await invocation.execute(abortSignal);
|
||||
|
||||
expect(fs.existsSync(dirPath)).toBe(true);
|
||||
expect(fs.statSync(dirPath).isDirectory()).toBe(true);
|
||||
@@ -654,7 +609,8 @@ describe('WriteFileTool', () => {
|
||||
content,
|
||||
modified_by_user: true,
|
||||
};
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(result.llmContent).toMatch(/User modified the `content`/);
|
||||
});
|
||||
@@ -669,7 +625,8 @@ describe('WriteFileTool', () => {
|
||||
content,
|
||||
modified_by_user: false,
|
||||
};
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(result.llmContent).not.toMatch(/User modified the `content`/);
|
||||
});
|
||||
@@ -683,7 +640,8 @@ describe('WriteFileTool', () => {
|
||||
file_path: filePath,
|
||||
content,
|
||||
};
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(result.llmContent).not.toMatch(/User modified the `content`/);
|
||||
});
|
||||
@@ -695,7 +653,7 @@ describe('WriteFileTool', () => {
|
||||
file_path: path.join(rootDir, 'file.txt'),
|
||||
content: 'test content',
|
||||
};
|
||||
expect(tool.validateToolParams(params)).toBeNull();
|
||||
expect(() => tool.build(params)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should reject paths outside workspace root', () => {
|
||||
@@ -703,24 +661,9 @@ describe('WriteFileTool', () => {
|
||||
file_path: '/etc/passwd',
|
||||
content: 'malicious',
|
||||
};
|
||||
const error = tool.validateToolParams(params);
|
||||
expect(error).toContain(
|
||||
'File path must be within one of the workspace directories',
|
||||
expect(() => tool.build(params)).toThrow(
|
||||
/File path must be within one of the workspace directories/,
|
||||
);
|
||||
expect(error).toContain(rootDir);
|
||||
});
|
||||
|
||||
it('should provide clear error message with workspace directories', () => {
|
||||
const outsidePath = path.join(tempDir, 'outside-root.txt');
|
||||
const params = {
|
||||
file_path: outsidePath,
|
||||
content: 'test',
|
||||
};
|
||||
const error = tool.validateToolParams(params);
|
||||
expect(error).toContain(
|
||||
'File path must be within one of the workspace directories',
|
||||
);
|
||||
expect(error).toContain(rootDir);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -731,50 +674,50 @@ describe('WriteFileTool', () => {
|
||||
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(() => {
|
||||
// Mock FileSystemService writeTextFile to throw EACCES error
|
||||
vi.spyOn(fsService, 'writeTextFile').mockImplementationOnce(() => {
|
||||
const error = new Error('Permission denied') as NodeJS.ErrnoException;
|
||||
error.code = 'EACCES';
|
||||
throw error;
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
const params = { file_path: filePath, content };
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(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);
|
||||
expect(result.returnDisplay).toContain(
|
||||
`Permission denied writing to file: ${filePath} (EACCES)`,
|
||||
);
|
||||
});
|
||||
|
||||
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(() => {
|
||||
// Mock FileSystemService writeTextFile to throw ENOSPC error
|
||||
vi.spyOn(fsService, 'writeTextFile').mockImplementationOnce(() => {
|
||||
const error = new Error(
|
||||
'No space left on device',
|
||||
) as NodeJS.ErrnoException;
|
||||
error.code = 'ENOSPC';
|
||||
throw error;
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
const params = { file_path: filePath, content };
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(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);
|
||||
expect(result.returnDisplay).toContain(
|
||||
`No space left on device: ${filePath} (ENOSPC)`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return TARGET_IS_DIRECTORY error when write fails with EISDIR', async () => {
|
||||
@@ -790,25 +733,26 @@ describe('WriteFileTool', () => {
|
||||
return originalExistsSync(path as string);
|
||||
});
|
||||
|
||||
// Mock writeFileSync to throw EISDIR error
|
||||
const originalWriteFileSync = fs.writeFileSync;
|
||||
vi.spyOn(fs, 'writeFileSync').mockImplementationOnce(() => {
|
||||
// Mock FileSystemService writeTextFile to throw EISDIR error
|
||||
vi.spyOn(fsService, 'writeTextFile').mockImplementationOnce(() => {
|
||||
const error = new Error('Is a directory') as NodeJS.ErrnoException;
|
||||
error.code = 'EISDIR';
|
||||
throw error;
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
const params = { file_path: dirPath, content };
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(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');
|
||||
expect(result.returnDisplay).toContain(
|
||||
`Target is a directory, not a file: ${dirPath} (EISDIR)`,
|
||||
);
|
||||
|
||||
vi.spyOn(fs, 'existsSync').mockImplementation(originalExistsSync);
|
||||
vi.spyOn(fs, 'writeFileSync').mockImplementation(originalWriteFileSync);
|
||||
});
|
||||
|
||||
it('should return FILE_WRITE_FAILURE for generic write errors', async () => {
|
||||
@@ -818,19 +762,22 @@ describe('WriteFileTool', () => {
|
||||
// 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');
|
||||
});
|
||||
// Mock FileSystemService writeTextFile to throw generic error
|
||||
vi.spyOn(fsService, 'writeTextFile').mockImplementationOnce(() =>
|
||||
Promise.reject(new Error('Generic write error')),
|
||||
);
|
||||
|
||||
const params = { file_path: filePath, content };
|
||||
const result = await tool.execute(params, abortSignal);
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(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');
|
||||
expect(result.returnDisplay).toContain(
|
||||
'Error writing to file: Generic write error',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user