andy-stack_vaultkeeper-ai/__tests__/Services/FileSystemService.test.ts
Andrew Beal f7ff10699d Add PatchVaultFile function for incremental file updates
Implements unified diff patching as an alternative to WriteVaultFile for making targeted changes without replacing entire files. Includes new PatchVaultFile AI function, DiffService.applyPatch method, and comprehensive test coverage.
2025-12-05 22:48:54 +00:00

776 lines
28 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { FileSystemService } from '../../Services/FileSystemService';
import { VaultService } from '../../Services/VaultService';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { TFile, TFolder, TAbstractFile } from 'obsidian';
import type { ISearchMatch } from '../../Helpers/SearchTypes';
import { Exception } from '../../Helpers/Exception';
// Helper function to create mock TFile
function createMockFile(path: string, extension: string = 'md'): TFile {
const file = new TFile();
file.path = path;
file.name = path.split('/').pop() || '';
file.basename = file.name.replace(/\.[^/.]+$/, '');
file.extension = extension;
file.parent = null as any;
file.vault = null as any;
file.stat = {
ctime: Date.now(),
mtime: Date.now(),
size: 100
};
return file;
}
// Helper function to create mock TFolder
function createMockFolder(path: string, children: TAbstractFile[] = []): TFolder {
const folder = new TFolder();
folder.path = path;
folder.name = path.split('/').pop() || '';
folder.children = children;
folder.parent = null as any;
folder.vault = null as any;
folder.isRoot = () => path === '/';
return folder;
}
describe('FileSystemService', () => {
let fileSystemService: FileSystemService;
let mockVaultService: VaultService;
let consoleErrorSpy: any;
beforeEach(() => {
// Create mock VaultService
mockVaultService = {
getMarkdownFiles: vi.fn(),
getAbstractFileByPath: vi.fn(),
read: vi.fn(),
create: vi.fn(),
modify: vi.fn(),
patch: vi.fn(),
delete: vi.fn(),
move: vi.fn(),
listFilesInDirectory: vi.fn(),
listFoldersInDirectory: vi.fn(),
listDirectoryContents: vi.fn(),
searchVaultFiles: vi.fn()
} as any;
// Register mock VaultService
RegisterSingleton(Services.VaultService, mockVaultService);
// Create FileSystemService instance
fileSystemService = new FileSystemService();
// Spy on console.error and Exception methods
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
vi.spyOn(Exception, 'log').mockImplementation(() => {});
});
afterEach(() => {
DeregisterAllServices();
consoleErrorSpy.mockRestore();
vi.restoreAllMocks();
});
describe('readFile', () => {
it('should read file content successfully', async () => {
const mockFile = createMockFile('test.md');
const fileContent = 'This is test content';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(fileContent);
const result = await fileSystemService.readFile('test.md');
expect(result).toBe(fileContent);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('test.md', false);
expect(mockVaultService.read).toHaveBeenCalledWith(mockFile, false);
});
it('should return Error when file does not exist', async () => {
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
const result = await fileSystemService.readFile('nonexistent.md');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('Path is a folder, not a file');
expect(mockVaultService.read).not.toHaveBeenCalled();
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockFile = createMockFile('plugin/config.json', 'json');
const fileContent = '{"key": "value"}';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(fileContent);
await fileSystemService.readFile('plugin/config.json', true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/config.json', true);
expect(mockVaultService.read).toHaveBeenCalledWith(mockFile, true);
});
it('should return Error when path is not a file', async () => {
const mockFolder = createMockFolder('folder');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFolder);
const result = await fileSystemService.readFile('folder');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('Path is a folder, not a file');
expect(mockVaultService.read).not.toHaveBeenCalled();
});
});
describe('writeFile', () => {
it('should create new file when it does not exist', async () => {
const mockFile = createMockFile('new.md');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.writeFile('new.md', 'content');
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('new.md', 'content', false, true);
expect(mockVaultService.modify).not.toHaveBeenCalled();
});
it('should modify existing file', async () => {
const mockFile = createMockFile('existing.md');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.modify = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.writeFile('existing.md', 'new content');
expect(result).toBe(mockFile);
expect(mockVaultService.modify).toHaveBeenCalledWith(mockFile, 'new content', false, true);
expect(mockVaultService.create).not.toHaveBeenCalled();
});
it('should respect allowAccessToPluginRoot parameter when creating', async () => {
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(undefined);
await fileSystemService.writeFile('plugin/data.json', 'content', true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/data.json', true);
expect(mockVaultService.create).toHaveBeenCalledWith('plugin/data.json', 'content', true, true);
});
it('should return error object when create fails', async () => {
const error = new Error('Create failed');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.writeFile('error.md', 'content');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Create failed');
});
it('should return error object when modify fails', async () => {
const mockFile = createMockFile('existing.md');
const error = new Error('Modify failed');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.modify = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.writeFile('existing.md', 'content');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Modify failed');
});
});
describe('patchFile', () => {
it('should patch existing file successfully', async () => {
const mockFile = createMockFile('existing.md');
const patch = `@@ -1,3 +1,3 @@
# Title
-old content
+new content
more lines`;
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.patchFile('existing.md', patch);
expect(result).toBe(mockFile);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, patch, false, true);
});
it('should create file with empty content when file does not exist', async () => {
const mockFile = createMockFile('new.md');
const patch = `@@ -1,0 +1,2 @@
+# New File
+Content here`;
// Mock sequence: first call returns null (file doesn't exist), second call returns null (writeFile checks), then create succeeds
mockVaultService.getAbstractFileByPath = vi.fn()
.mockReturnValueOnce(null) // patchFile checks if file exists
.mockReturnValueOnce(null); // writeFile (called by patchFile) checks if file exists
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.patchFile('new.md', patch);
// writeFile should create the file with empty content
expect(mockVaultService.create).toHaveBeenCalledWith('new.md', '', false, true);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, patch, false, true);
expect(result).toBe(mockFile);
});
it('should return error when patch fails', async () => {
const mockFile = createMockFile('existing.md');
const patch = `@@ -1,1 +1,1 @@
-old
+new`;
const error = new Error('Failed to apply patch - the file may have been modified since the patch was created');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.patchFile('existing.md', patch);
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('Failed to apply patch');
});
it('should return error when creating empty file fails', async () => {
const patch = `@@ -1,0 +1,1 @@
+New line`;
const error = new Error('Create failed');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.patchFile('new.md', patch);
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Create failed');
expect(mockVaultService.patch).not.toHaveBeenCalled();
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockFile = createMockFile('plugin/config.md');
const patch = `@@ -1,1 +1,1 @@
-setting=old
+setting=new`;
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
await fileSystemService.patchFile('plugin/config.md', patch, true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/config.md', true);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, patch, true, true);
});
it('should handle complex multi-hunk patches', async () => {
const mockFile = createMockFile('document.md');
const patch = `@@ -1,3 +1,3 @@
# Title
-Old intro
+New intro
Content
@@ -10,2 +10,2 @@
Section
-Old conclusion
+New conclusion`;
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.patchFile('document.md', patch);
expect(result).toBe(mockFile);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, patch, false, true);
});
it('should handle file creation when getAbstractFileByPath returns null after create', async () => {
const patch = `@@ -1,0 +1,1 @@
+content`;
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(createMockFile('new.md'));
mockVaultService.patch = vi.fn().mockResolvedValue(createMockFile('new.md'));
await fileSystemService.patchFile('new.md', patch);
// Should call patch on the created file
expect(mockVaultService.create).toHaveBeenCalledWith('new.md', '', false, true);
expect(mockVaultService.patch).toHaveBeenCalled();
});
it('should respect requiresConfirmation parameter', async () => {
const mockFile = createMockFile('test.md');
const patch = `@@ -1,1 +1,1 @@
-old
+new`;
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
await fileSystemService.patchFile('test.md', patch, false, true);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, patch, false, true);
});
});
describe('deleteFile', () => {
it('should delete file successfully', async () => {
const mockFile = createMockFile('delete-me.md');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.delete = vi.fn().mockResolvedValue(undefined);
const result = await fileSystemService.deleteFile('delete-me.md');
expect(result).toBeUndefined();
expect(mockVaultService.delete).toHaveBeenCalledWith(mockFile, false, true);
});
it('should return error when file does not exist', async () => {
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
const result = await fileSystemService.deleteFile('nonexistent.md');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('File does not exist');
expect(mockVaultService.delete).not.toHaveBeenCalled();
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockFile = createMockFile('plugin/temp.json', 'json');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.delete = vi.fn().mockResolvedValue(undefined);
await fileSystemService.deleteFile('plugin/temp.json', true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/temp.json', true);
expect(mockVaultService.delete).toHaveBeenCalledWith(mockFile, true, true);
});
it('should delete folder successfully (supports both files and folders)', async () => {
const mockFolder = createMockFolder('folder');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFolder);
mockVaultService.delete = vi.fn().mockResolvedValue(undefined);
const result = await fileSystemService.deleteFile('folder');
expect(result).toBeUndefined();
expect(mockVaultService.delete).toHaveBeenCalledWith(mockFolder, false, true);
});
});
describe('moveFile', () => {
it('should move file successfully', async () => {
mockVaultService.move = vi.fn().mockResolvedValue(undefined);
const result = await fileSystemService.moveFile('old/path.md', 'new/path.md');
expect(result).toBeUndefined();
expect(mockVaultService.move).toHaveBeenCalledWith('old/path.md', 'new/path.md', false);
});
it('should return error when move fails', async () => {
const error = new Error('Source file not found');
mockVaultService.move = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.moveFile('nonexistent.md', 'new.md');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Source file not found');
});
it('should respect allowAccessToPluginRoot parameter', async () => {
mockVaultService.move = vi.fn().mockResolvedValue(undefined);
await fileSystemService.moveFile('plugin/old.json', 'plugin/new.json', true);
expect(mockVaultService.move).toHaveBeenCalledWith('plugin/old.json', 'plugin/new.json', true);
});
});
describe('listFilesInDirectory', () => {
it('should list files recursively by default', async () => {
const mockFiles = [
createMockFile('test-dir/file1.md'),
createMockFile('test-dir/subfolder/file2.md')
];
mockVaultService.listFilesInDirectory = vi.fn().mockResolvedValue(mockFiles);
const result = await fileSystemService.listFilesInDirectory('test-dir');
expect(result).toEqual(mockFiles);
expect(mockVaultService.listFilesInDirectory).toHaveBeenCalledWith('test-dir', true, false);
});
it('should list files non-recursively when specified', async () => {
const mockFiles = [createMockFile('test-dir/file1.md')];
mockVaultService.listFilesInDirectory = vi.fn().mockResolvedValue(mockFiles);
const result = await fileSystemService.listFilesInDirectory('test-dir', false);
expect(result).toEqual(mockFiles);
expect(mockVaultService.listFilesInDirectory).toHaveBeenCalledWith('test-dir', false, false);
});
it('should return empty array when directory does not exist', async () => {
mockVaultService.listFilesInDirectory = vi.fn().mockResolvedValue([]);
const result = await fileSystemService.listFilesInDirectory('nonexistent');
expect(result).toEqual([]);
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockFiles = [createMockFile('plugin/config.json', 'json')];
mockVaultService.listFilesInDirectory = vi.fn().mockResolvedValue(mockFiles);
await fileSystemService.listFilesInDirectory('plugin', true, true);
expect(mockVaultService.listFilesInDirectory).toHaveBeenCalledWith('plugin', true, true);
});
});
describe('listFoldersInDirectory', () => {
it('should list folders recursively by default', async () => {
const mockFolders = [
createMockFolder('test-dir/subfolder1'),
createMockFolder('test-dir/subfolder1/nested')
];
mockVaultService.listFoldersInDirectory = vi.fn().mockResolvedValue(mockFolders);
const result = await fileSystemService.listFoldersInDirectory('test-dir');
expect(result).toEqual(mockFolders);
expect(mockVaultService.listFoldersInDirectory).toHaveBeenCalledWith('test-dir', true, false);
});
it('should list folders non-recursively when specified', async () => {
const mockFolders = [createMockFolder('test-dir/subfolder1')];
mockVaultService.listFoldersInDirectory = vi.fn().mockResolvedValue(mockFolders);
const result = await fileSystemService.listFoldersInDirectory('test-dir', false);
expect(result).toEqual(mockFolders);
expect(mockVaultService.listFoldersInDirectory).toHaveBeenCalledWith('test-dir', false, false);
});
it('should return empty array when directory does not exist', async () => {
mockVaultService.listFoldersInDirectory = vi.fn().mockResolvedValue([]);
const result = await fileSystemService.listFoldersInDirectory('nonexistent');
expect(result).toEqual([]);
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockFolders = [createMockFolder('plugin/subdir')];
mockVaultService.listFoldersInDirectory = vi.fn().mockResolvedValue(mockFolders);
await fileSystemService.listFoldersInDirectory('plugin', true, true);
expect(mockVaultService.listFoldersInDirectory).toHaveBeenCalledWith('plugin', true, true);
});
});
describe('listDirectoryContents', () => {
it('should list both files and folders recursively by default', async () => {
const mockContents = [
createMockFile('test-dir/file.md'),
createMockFolder('test-dir/subfolder')
];
mockVaultService.listDirectoryContents = vi.fn().mockResolvedValue(mockContents);
const result = await fileSystemService.listDirectoryContents('test-dir');
expect(result).toEqual(mockContents);
expect(mockVaultService.listDirectoryContents).toHaveBeenCalledWith('test-dir', true, false);
});
it('should list contents non-recursively when specified', async () => {
const mockContents = [createMockFile('test-dir/file.md')];
mockVaultService.listDirectoryContents = vi.fn().mockResolvedValue(mockContents);
const result = await fileSystemService.listDirectoryContents('test-dir', false);
expect(result).toEqual(mockContents);
expect(mockVaultService.listDirectoryContents).toHaveBeenCalledWith('test-dir', false, false);
});
it('should return empty array when directory does not exist', async () => {
mockVaultService.listDirectoryContents = vi.fn().mockResolvedValue([]);
const result = await fileSystemService.listDirectoryContents('nonexistent');
expect(result).toEqual([]);
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockContents = [
createMockFile('plugin/main.js', 'js'),
createMockFolder('plugin/modules')
];
mockVaultService.listDirectoryContents = vi.fn().mockResolvedValue(mockContents);
await fileSystemService.listDirectoryContents('plugin', true, true);
expect(mockVaultService.listDirectoryContents).toHaveBeenCalledWith('plugin', true, true);
});
});
describe('readObjectFromFile', () => {
it('should read and parse valid JSON file', async () => {
const mockFile = createMockFile('data.json', 'json');
const jsonContent = '{"name": "test", "value": 42}';
const expectedObject = { name: 'test', value: 42 };
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(jsonContent);
const result = await fileSystemService.readObjectFromFile('data.json');
expect(result).toEqual(expectedObject);
});
it('should return Error when file does not exist', async () => {
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
const result = await fileSystemService.readObjectFromFile('nonexistent.json');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('File not found');
});
it('should throw SyntaxError when JSON is invalid', async () => {
const mockFile = createMockFile('invalid.json', 'json');
const invalidJson = '{name: "test", invalid}';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(invalidJson);
await expect(async () => {
await fileSystemService.readObjectFromFile('invalid.json');
}).rejects.toThrow(SyntaxError);
});
it('should handle nested objects', async () => {
const mockFile = createMockFile('nested.json', 'json');
const jsonContent = '{"user": {"name": "John", "age": 30}, "active": true}';
const expectedObject = { user: { name: 'John', age: 30 }, active: true };
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(jsonContent);
const result = await fileSystemService.readObjectFromFile('nested.json');
expect(result).toEqual(expectedObject);
});
it('should handle arrays', async () => {
const mockFile = createMockFile('array.json', 'json');
const jsonContent = '[1, 2, 3, 4, 5]';
const expectedArray = [1, 2, 3, 4, 5];
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(jsonContent);
const result = await fileSystemService.readObjectFromFile('array.json');
expect(result).toEqual(expectedArray);
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const mockFile = createMockFile('plugin/settings.json', 'json');
const jsonContent = '{"setting": "value"}';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.read = vi.fn().mockResolvedValue(jsonContent);
await fileSystemService.readObjectFromFile('plugin/settings.json', true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/settings.json', true);
expect(mockVaultService.read).toHaveBeenCalledWith(mockFile, true);
});
});
describe('writeObjectToFile', () => {
it('should serialize and write object to new file', async () => {
const data = { name: 'test', value: 42 };
const expectedJson = JSON.stringify(data, null, 4);
const mockFile = createMockFile('data.json', 'json');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.writeObjectToFile('data.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('data.json', expectedJson, false, true);
});
it('should serialize and write object to existing file', async () => {
const mockFile = createMockFile('existing.json', 'json');
const data = { updated: true };
const expectedJson = JSON.stringify(data, null, 4);
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.modify = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.writeObjectToFile('existing.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.modify).toHaveBeenCalledWith(mockFile, expectedJson, false, true);
});
it('should format JSON with 4-space indentation', async () => {
const data = { nested: { key: 'value' }, array: [1, 2, 3] };
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(undefined);
await fileSystemService.writeObjectToFile('formatted.json', data);
const expectedJson = JSON.stringify(data, null, 4);
expect(mockVaultService.create).toHaveBeenCalledWith('formatted.json', expectedJson, false, true);
// Verify it contains newlines and indentation
expect(expectedJson).toContain('\n');
expect(expectedJson).toContain(' ');
});
it('should handle empty objects', async () => {
const data = {};
const expectedJson = JSON.stringify(data, null, 4);
const mockFile = createMockFile('empty.json', 'json');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.writeObjectToFile('empty.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('empty.json', expectedJson, false, true);
});
it('should handle arrays', async () => {
const data = [1, 2, 3, 4, 5];
const expectedJson = JSON.stringify(data, null, 4);
const mockFile = createMockFile('array.json', 'json');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.writeObjectToFile('array.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('array.json', expectedJson, false, true);
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const data = { config: 'value' };
const expectedJson = JSON.stringify(data, null, 4);
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(undefined);
await fileSystemService.writeObjectToFile('plugin/config.json', data, true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/config.json', true);
expect(mockVaultService.create).toHaveBeenCalledWith('plugin/config.json', expectedJson, true, true);
});
it('should return Error on write error', async () => {
const data = { test: 'value' };
const error = new Error('Write failed');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.writeObjectToFile('error.json', data);
expect(result).toBe(error);
});
});
describe('searchVaultFiles', () => {
it('should search files and return matches', async () => {
const searchTerm = 'test query';
const mockMatches: ISearchMatch[] = [
{
file: createMockFile('note1.md'),
snippets: [
{
text: 'This is a test query in the content',
matchIndex: 10,
matchLength: 10
}
]
},
{
file: createMockFile('note2.md'),
snippets: [
{
text: 'Another test query here',
matchIndex: 8,
matchLength: 10
}
]
}
];
mockVaultService.searchVaultFiles = vi.fn().mockResolvedValue(mockMatches);
const result = await fileSystemService.searchVaultFiles(searchTerm);
expect(result).toEqual(mockMatches);
expect(mockVaultService.searchVaultFiles).toHaveBeenCalledWith(searchTerm, false);
});
it('should return empty array when no matches found', async () => {
mockVaultService.searchVaultFiles = vi.fn().mockResolvedValue([]);
const result = await fileSystemService.searchVaultFiles('nonexistent term');
expect(result).toEqual([]);
});
it('should respect allowAccessToPluginRoot parameter', async () => {
const searchTerm = 'config';
mockVaultService.searchVaultFiles = vi.fn().mockResolvedValue([]);
await fileSystemService.searchVaultFiles(searchTerm, true);
expect(mockVaultService.searchVaultFiles).toHaveBeenCalledWith(searchTerm, true);
});
it('should handle empty search term', async () => {
mockVaultService.searchVaultFiles = vi.fn().mockResolvedValue([]);
const result = await fileSystemService.searchVaultFiles('');
expect(result).toEqual([]);
expect(mockVaultService.searchVaultFiles).toHaveBeenCalledWith('', false);
});
});
});