andy-stack_vaultkeeper-ai/__tests__/Services/AIToolService.test.ts
Andrew Beal 72bf43a8ea refactor: clarify binary file attachment handling for AI agents
Add explicit instructions across all agent prompts explaining that binary
files (PDFs, images, documents) return content as attachments in the
message following the tool result, not as text in the result itself.

Update tool response messages to clearly state the attachment delivery
mechanism and prevent agents from re-reading the same file expecting
different output.

Improve build safety by neutralizing dynamic eval constructs and
hardening the officeparser plugin against bundle shape changes.
2026-05-31 17:58:23 +01:00

918 lines
28 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { AIToolService } from '../../Services/AIServices/AIToolService';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { AITool } from '../../Enums/AITool';
import { TFile } from 'obsidian';
import { Exception } from '../../Helpers/Exception';
import { AbortService } from '../../Services/AbortService';
import { ChatMode } from '../../Enums/ChatMode';
/**
* INTEGRATION TESTS - AIToolService
*
* Tests the AI function dispatcher with real FileSystemService integration.
* Mocks only the Obsidian API layer (VaultService).
*
* Tests all AI functions:
* - SearchVaultFiles
* - ReadVaultFiles
* - WriteVaultFile
* - PatchVaultFile
* - DeleteVaultFiles
* - MoveVaultFiles
* - RequestWebSearch (Gemini only)
*/
describe('AIToolService - Integration Tests', () => {
let service: AIToolService;
let mockFileSystemService: any;
let abortService: AbortService;
beforeEach(() => {
// Mock FileSystemService with common operations
mockFileSystemService = {
searchVaultFiles: vi.fn(),
listFilesInDirectory: vi.fn(),
readFilePath: vi.fn(),
writeToFilePath: vi.fn(),
patchFileAtPath: vi.fn(),
deleteFile: vi.fn(),
moveFile: vi.fn()
};
// Create real AbortService instance
abortService = new AbortService();
// Register the mocks
RegisterSingleton(Services.FileSystemService, mockFileSystemService);
RegisterSingleton(Services.AbortService, abortService);
RegisterSingleton(Services.MemoriesService, {
readMemories: vi.fn().mockResolvedValue(''),
updateMemories: vi.fn().mockResolvedValue(undefined)
});
RegisterSingleton(Services.SettingsService, {
settings: { enableMemories: false, allowUpdatingMemories: false, chatMode: ChatMode.Edit }
});
RegisterSingleton(Services.WebViewerService, {
getWebViewContent: vi.fn(),
takeScreenshot: vi.fn()
});
// Mock Exception.log
vi.spyOn(Exception, 'log').mockImplementation(() => {});
// Create service - it will resolve the mock FileSystemService and real AbortService
service = new AIToolService();
});
afterEach(() => {
// Clear singleton registry to prevent memory leaks
DeregisterAllServices();
vi.restoreAllMocks();
});
// Helper to create mock TFile
function createMockFile(path: string, basename: string): TFile {
const file = new TFile();
file.path = path;
file.basename = basename;
file.name = basename + '.md';
file.extension = 'md';
return file;
}
describe('performAITool - SearchVaultFiles', () => {
it('should return search results with snippets', async () => {
const mockMatches = [
{
file: createMockFile('notes/test.md', 'test'),
snippets: [
{ text: 'This is a test note', matchIndex: 10 },
{ text: 'Another test match', matchIndex: 5 }
]
},
{
file: createMockFile('docs/guide.md', 'guide'),
snippets: [
{ text: 'Guide for testing', matchIndex: 0 }
]
}
];
mockFileSystemService.searchVaultFiles.mockResolvedValue(mockMatches);
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['test'], user_message: 'test search' },
toolId: 'tool_1'
} as any);
expect(result.name).toBe(AITool.SearchVaultFiles);
expect(result.toolId).toBe('tool_1');
expect(result.payload.response).toEqual([{searchTerm: 'test', results: [
{
path: 'notes/test.md',
snippets: [
{ text: 'This is a test note', matchPosition: 10 },
{ text: 'Another test match', matchPosition: 5 }
]
},
{
path: 'docs/guide.md',
snippets: [
{ text: 'Guide for testing', matchPosition: 0 }
]
}
]}]);
});
it('should return empty array when search term is empty', async () => {
// Mock returns empty array for empty search term
mockFileSystemService.searchVaultFiles.mockResolvedValue([]);
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: [''], user_message: 'test search' },
toolId: 'tool_2'
} as any);
// Empty search terms return empty results
expect(result.payload.response).toEqual([{searchTerm: '', results: []}]);
});
it('should return empty array when search term is whitespace', async () => {
// Mock returns empty array for whitespace search term
mockFileSystemService.searchVaultFiles.mockResolvedValue([]);
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: [' '], user_message: 'test search' },
toolId: 'tool_3'
} as any);
// Whitespace search terms return empty results (after trim)
expect(result.payload.response).toEqual([{searchTerm: ' ', results: []}]);
});
it('should return empty array when no matches found', async () => {
mockFileSystemService.searchVaultFiles.mockResolvedValue([]);
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['nonexistent'], user_message: 'test search' },
toolId: 'tool_4'
} as any);
// No matches returns empty results
expect(result.payload.response).toEqual([{searchTerm: 'nonexistent', results: []}]);
});
it('should handle single match', async () => {
const mockMatches = [
{
file: createMockFile('single.md', 'single'),
snippets: [{ text: 'Single result', matchIndex: 0 }]
}
];
mockFileSystemService.searchVaultFiles.mockResolvedValue(mockMatches);
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['single'], user_message: 'test search' },
toolId: 'tool_5'
} as any);
expect(result.payload.response).toHaveLength(1);
expect((result.payload.response as any)[0].searchTerm).toBe('single');
expect((result.payload.response as any)[0].results).toHaveLength(1);
expect((result.payload.response as any)[0].results[0].path).toBe('single.md');
});
});
describe('performAITool - ReadVaultFiles', () => {
it('should read multiple files successfully', async () => {
mockFileSystemService.readFilePath
.mockResolvedValueOnce('Content of file 1')
.mockResolvedValueOnce('Content of file 2')
.mockResolvedValueOnce('Content of file 3');
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['file1.md', 'file2.md', 'file3.md'], user_message: 'test search' },
toolId: 'tool_6'
} as any);
expect(result.payload.response).toEqual({
results: [
{ type: 'md', path: 'file1.md', contents: 'Content of file 1' },
{ type: 'md', path: 'file2.md', contents: 'Content of file 2' },
{ type: 'md', path: 'file3.md', contents: 'Content of file 3' }
]
});
});
it('should handle missing files with error messages', async () => {
const error1 = new Error('File not found');
const error2 = new Error('File not found');
mockFileSystemService.readFilePath
.mockResolvedValueOnce('Existing content')
.mockResolvedValueOnce(error1)
.mockResolvedValueOnce(error2);
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['exists.md', 'missing1.md', 'missing2.md'], user_message: 'test search' },
toolId: 'tool_7'
} as any);
expect(result.payload.response).toEqual({
results: [
{ type: 'md', path: 'exists.md', contents: 'Existing content' },
{ path: 'missing1.md', error: 'File not found' },
{ path: 'missing2.md', error: 'File not found' }
]
});
});
it('should handle mixed success and failure', async () => {
mockFileSystemService.readFilePath
.mockResolvedValueOnce('Content A')
.mockResolvedValueOnce(new Error('File not found'))
.mockResolvedValueOnce('Content B');
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['a.md', 'missing.md', 'b.md'], user_message: 'test search' },
toolId: 'tool_8'
} as any);
const results = (result.payload.response as any).results;
expect(results.find((r: any) => r.contents === 'Content A')).toBeDefined();
expect(results.find((r: any) => r.contents === 'Content B')).toBeDefined();
expect(results.find((r: any) => r.error === 'File not found')).toBeDefined();
});
it('should handle empty file list', async () => {
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: [], user_message: 'test search' },
toolId: 'tool_9'
} as any);
expect((result.payload.response as any).count).toBe(0);
expect((result.payload.response as any).message).toContain('attached to the conversation');
});
it('should handle single file read', async () => {
mockFileSystemService.readFilePath.mockResolvedValue('Single file content');
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['single.md'], user_message: 'test search' },
toolId: 'tool_10'
} as any);
expect((result.payload.response as any).results).toHaveLength(1);
expect((result.payload.response as any).results[0].contents).toBe('Single file content');
});
});
describe('performAITool - WriteVaultFile', () => {
it('should write file successfully', async () => {
mockFileSystemService.writeToFilePath.mockResolvedValue(undefined);
const result = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'notes/new-note.md',
content: '# New Note\n\nContent here',
user_message: 'test search'
},
toolId: 'tool_11'
} as any);
expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledWith(
'notes/new-note.md',
'# New Note\n\nContent here'
);
expect(result.payload.response).toEqual({ success: true });
});
it('should handle write failure', async () => {
const error = new Error('Permission denied');
mockFileSystemService.writeToFilePath.mockResolvedValue(error);
const result = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'protected.md',
content: 'Content',
user_message: 'test search'
},
toolId: 'tool_12'
} as any);
expect((result.payload.response as any).success).toBe(false);
expect((result.payload.response as any).error).toBeDefined();
});
it('should normalize file path', async () => {
mockFileSystemService.writeToFilePath.mockResolvedValue(undefined);
await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'folder\\subfolder\\file.md',
content: 'Content',
user_message: 'test search'
},
toolId: 'tool_13'
} as any);
// normalizePath should convert backslashes to forward slashes
expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledWith(
expect.stringContaining('/'),
'Content'
);
});
it('should handle empty content', async () => {
mockFileSystemService.writeToFilePath.mockResolvedValue(undefined);
const result = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'empty.md',
content: '',
user_message: 'test search'
},
toolId: 'tool_14'
} as any);
expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledWith('empty.md', '');
expect((result.payload.response as any).success).toBe(true);
});
});
describe('performAITool - PatchVaultFile', () => {
it('should apply patch successfully', async () => {
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('notes/test.md', 'test'));
const oldContent = ['old content'];
const newContent = ['new content'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'notes/test.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Updating test note'
},
toolId: 'tool_patch_1'
} as any);
expect(mockFileSystemService.patchFileAtPath).toHaveBeenCalledWith('notes/test.md', oldContent, newContent);
expect(result.payload.response).toEqual({ success: true });
});
it('should handle patch failure', async () => {
const error = new Error('Content to replace was not found in the file');
mockFileSystemService.patchFileAtPath.mockResolvedValue(error);
const oldContent = ['old content'];
const newContent = ['new content'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'notes/test.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Updating note'
},
toolId: 'tool_patch_2'
} as any);
expect((result.payload.response as any).success).toBe(false);
expect((result.payload.response as any).error).toBe('Content to replace was not found in the file');
});
it('should normalize file path', async () => {
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('folder/file.md', 'file'));
const oldContent = ['old'];
const newContent = ['new'];
await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'folder\\subfolder\\file.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Patching file'
},
toolId: 'tool_patch_3'
} as any);
// normalizePath should convert backslashes to forward slashes
expect(mockFileSystemService.patchFileAtPath).toHaveBeenCalledWith(
expect.stringContaining('/'),
oldContent,
newContent
);
});
it('should handle file not found error', async () => {
const error = new Error('File does not exist: missing.md');
mockFileSystemService.patchFileAtPath.mockResolvedValue(error);
const oldContent = ['old'];
const newContent = ['new'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'missing.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Patching missing file'
},
toolId: 'tool_patch_4'
} as any);
expect((result.payload.response as any).success).toBe(false);
expect((result.payload.response as any).error).toContain('File does not exist');
});
it('should handle complex multi-line replacement', async () => {
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('complex.md', 'complex'));
const oldContent = ['# Title\nOld line 1\nContext line'];
const newContent = ['# Title\nNew line 1\nContext line'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'complex.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Applying complex patch'
},
toolId: 'tool_patch_5'
} as any);
expect(mockFileSystemService.patchFileAtPath).toHaveBeenCalledWith('complex.md', oldContent, newContent);
expect((result.payload.response as any).success).toBe(true);
});
it('should handle adding new content', async () => {
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('additions.md', 'additions'));
const oldContent = ['# Title\nExisting content'];
const newContent = ['# Title\nExisting content\nNew line 1\nNew line 2'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'additions.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Adding new lines'
},
toolId: 'tool_patch_6'
} as any);
expect((result.payload.response as any).success).toBe(true);
});
it('should handle removing content', async () => {
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('deletions.md', 'deletions'));
const oldContent = ['# Title\nLine to remove 1\nLine to remove 2\nRemaining content'];
const newContent = ['# Title\nRemaining content'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'deletions.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Removing lines'
},
toolId: 'tool_patch_7'
} as any);
expect((result.payload.response as any).success).toBe(true);
});
it('should handle permission denied error', async () => {
const error = new Error('Permission denied');
mockFileSystemService.patchFileAtPath.mockResolvedValue(error);
const oldContent = ['old'];
const newContent = ['new'];
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'protected.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Patching protected file'
},
toolId: 'tool_patch_8'
} as any);
expect((result.payload.response as any).success).toBe(false);
expect((result.payload.response as any).error).toBe('Permission denied');
});
it('should return correct toolId in response', async () => {
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('test.md', 'test'));
const oldContent = 'old';
const newContent = 'new';
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'test.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Test patch'
},
toolId: 'unique_tool_id_123'
} as any);
expect(result.toolId).toBe('unique_tool_id_123');
expect(result.name).toBe(AITool.PatchVaultFile);
});
it('should handle invalid arguments', async () => {
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
// missing required fields
file_path: 'test.md'
// oldContent, newContent and user_message are missing
},
toolId: 'tool_patch_invalid'
} as any);
expect((result.payload.response as any).error).toContain('Invalid arguments for patch_vault_file');
expect(mockFileSystemService.patchFileAtPath).not.toHaveBeenCalled();
});
});
describe('performAITool - DeleteVaultFiles', () => {
it('should delete multiple files successfully', async () => {
mockFileSystemService.deleteFile
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true });
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md', 'file3.md'],
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_15'
} as any);
expect(result.payload.response).toEqual({
results: [
{ path: 'file1.md', success: true },
{ path: 'file2.md', success: true },
{ path: 'file3.md', success: true }
]
});
});
it('should reject deletion when confirmation is false', async () => {
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md'],
confirm_deletion: false,
user_message: 'test search'
},
toolId: 'tool_16'
} as any);
expect(result.payload.response).toEqual({
error: 'Confirmation was false, no action taken'
});
expect(mockFileSystemService.deleteFile).not.toHaveBeenCalled();
});
it('should handle mixed success and failure', async () => {
const error = new Error('File not found');
mockFileSystemService.deleteFile
.mockResolvedValueOnce(undefined) // void = success
.mockResolvedValueOnce(error)
.mockResolvedValueOnce(undefined); // void = success
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['a.md', 'missing.md', 'c.md'],
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_17'
} as any);
expect((result.payload.response as any).results).toEqual([
{ path: 'a.md', success: true },
{ path: 'missing.md', success: false, error: 'File not found' },
{ path: 'c.md', success: true }
]);
});
it('should handle all failures', async () => {
mockFileSystemService.deleteFile
.mockResolvedValueOnce(new Error('Error 1'))
.mockResolvedValueOnce(new Error('Error 2'));
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md'],
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_18'
} as any);
const results = (result.payload.response as any).results;
expect(results[0].success).toBe(false);
expect(results[1].success).toBe(false);
});
it('should handle empty file list with confirmation', async () => {
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: [],
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_19'
} as any);
expect((result.payload.response as any).results).toEqual([]);
});
});
describe('performAITool - MoveVaultFiles', () => {
it('should move multiple files successfully', async () => {
mockFileSystemService.moveFile
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true });
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md', 'c.md'],
destination_paths: ['dest/a.md', 'dest/b.md', 'dest/c.md'],
user_message: 'test search'
},
toolId: 'tool_20'
} as any);
expect(result.payload.response).toEqual({
results: [
{ path: 'dest/a.md', success: true },
{ path: 'dest/b.md', success: true },
{ path: 'dest/c.md', success: true }
]
});
});
it('should reject when array lengths dont match', async () => {
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md'],
destination_paths: ['dest/a.md'],
user_message: 'test search'
},
toolId: 'tool_21'
} as any);
expect(result.payload.response).toEqual({
error: 'Source paths array length does not equal destination paths array length'
});
expect(mockFileSystemService.moveFile).not.toHaveBeenCalled();
});
it('should handle mixed success and failure', async () => {
const error = new Error('Destination exists');
mockFileSystemService.moveFile
.mockResolvedValueOnce(undefined) // void = success
.mockResolvedValueOnce(error)
.mockResolvedValueOnce(undefined); // void = success
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md', 'c.md'],
destination_paths: ['new/a.md', 'existing.md', 'new/c.md'],
user_message: 'test search'
},
toolId: 'tool_22'
} as any);
expect((result.payload.response as any).results).toEqual([
{ path: 'new/a.md', success: true },
{ path: 'existing.md', success: false, error: 'Destination exists' },
{ path: 'new/c.md', success: true }
]);
});
it('should call moveFile with correct parameters', async () => {
mockFileSystemService.moveFile.mockResolvedValue(undefined); // void = success
await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['old/file.md'],
destination_paths: ['new/file.md'],
user_message: 'test search'
},
toolId: 'tool_23'
} as any);
expect(mockFileSystemService.moveFile).toHaveBeenCalledWith('old/file.md', 'new/file.md');
});
it('should handle empty arrays', async () => {
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: [],
destination_paths: [],
user_message: 'test search'
},
toolId: 'tool_24'
} as any);
expect((result.payload.response as any).results).toEqual([]);
});
});
describe('performAITool - RequestWebSearch', () => {
it('should return empty object for Gemini web search', async () => {
const result = await service.performAITool({
name: AITool.RequestWebSearch,
arguments: {},
toolId: 'tool_25'
} as any);
expect(result.name).toBe(AITool.RequestWebSearch);
expect(result.payload.response).toEqual({});
expect(result.toolId).toBe('tool_25');
});
it('should handle web search without arguments', async () => {
const result = await service.performAITool({
name: AITool.RequestWebSearch,
arguments: {},
toolId: 'tool_26'
} as any);
expect(result.payload.response).toEqual({});
});
});
describe('performAITool - Unknown Function', () => {
it('should return unknown response for unknown function', async () => {
const result = await service.performAITool({
name: 'UnknownFunction' as any,
arguments: {},
toolId: 'tool_27'
} as any);
expect(result.name).toBe('unknown');
expect(result.toolId).toBe('tool_27');
expect(result.payload.response).toEqual({ error: 'Unknown function request unknown' });
});
it('should return unknown response for invalid function', async () => {
const result = await service.performAITool({
name: 'InvalidFunction' as any,
arguments: {},
toolId: 'tool_error'
} as any);
expect(result.name).toBe('unknown');
expect(result.toolId).toBe('tool_error');
expect(result.payload.response).toEqual({ error: 'Unknown function request unknown' });
});
});
describe('Integration - Complete Workflows', () => {
it('should handle search -> read workflow', async () => {
// First search
const mockMatches = [
{
file: createMockFile('found.md', 'found'),
snippets: [{ text: 'Found content', matchIndex: 0 }]
}
];
mockFileSystemService.searchVaultFiles.mockResolvedValue(mockMatches);
const searchResult = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['test'], user_message: 'test search' },
toolId: 'search_1'
} as any);
const foundPath = (searchResult.payload.response as any)[0].results[0].path;
// Then read
mockFileSystemService.readFilePath.mockResolvedValue('File content here');
const readResult = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: [foundPath], user_message: 'test search' },
toolId: 'read_1'
} as any);
expect((readResult.payload.response as any).results[0].contents).toBe('File content here');
expect((readResult.payload.response as any).results[0].error).toBeUndefined();
});
it('should handle write -> move workflow', async () => {
// First write
mockFileSystemService.writeToFilePath.mockResolvedValue(undefined);
const writeResult = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'temp.md',
content: 'Temporary content',
user_message: 'test search'
},
toolId: 'write_1'
} as any);
expect((writeResult.payload.response as any).success).toBe(true);
// Then move
mockFileSystemService.moveFile.mockResolvedValue({ success: true });
const moveResult = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['temp.md'],
destination_paths: ['archive/temp.md'],
user_message: 'test search'
},
toolId: 'move_1'
} as any);
expect((moveResult.payload.response as any).results[0].success).toBe(true);
});
it('should handle read -> patch workflow', async () => {
// First read the file
mockFileSystemService.readFilePath.mockResolvedValue('# Original Title\n\nOriginal content');
const readResult = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['document.md'], user_message: 'Reading file' },
toolId: 'read_2'
} as any);
expect((readResult.payload.response as any).results[0].contents).toContain('Original');
// Then patch it
mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('document.md', 'document'));
const oldContent = ['# Original Title'];
const newContent = ['# Updated Title'];
const patchResult = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'document.md',
oldContent: oldContent,
newContent: newContent,
user_message: 'Updating title'
},
toolId: 'patch_1'
} as any);
expect((patchResult.payload.response as any).success).toBe(true);
expect(mockFileSystemService.patchFileAtPath).toHaveBeenCalledWith('document.md', oldContent, newContent);
});
});
});