andy-stack_vaultkeeper-ai/__tests__/Services/FileSystemService.test.ts

763 lines
28 KiB
TypeScript
Raw Normal View History

2025-11-01 15:15:16 +00:00
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';
2025-11-01 15:15:16 +00:00
// 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(),
2025-11-01 15:15:16 +00:00
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
2025-11-01 15:15:16 +00:00
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
vi.spyOn(Exception, 'log').mockImplementation(() => {});
2025-11-01 15:15:16 +00:00
});
afterEach(() => {
DeregisterAllServices();
consoleErrorSpy.mockRestore();
vi.restoreAllMocks();
2025-11-01 15:15:16 +00:00
});
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 () => {
2025-11-01 15:15:16 +00:00
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
const result = await fileSystemService.readFile('nonexistent.md');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('File does not exist: ');
2025-11-01 15:15:16 +00:00
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 () => {
2025-11-01 15:15:16 +00:00
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');
2025-11-01 15:15:16 +00:00
expect(mockVaultService.read).not.toHaveBeenCalled();
});
});
describe('writeFile', () => {
it('should create new file when it does not exist', async () => {
const mockFile = createMockFile('new.md');
2025-11-01 15:15:16 +00:00
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeFile('new.md', 'content');
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('new.md', 'content', false, true);
2025-11-01 15:15:16 +00:00
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);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeFile('existing.md', 'new content');
expect(result).toBe(mockFile);
expect(mockVaultService.modify).toHaveBeenCalledWith(mockFile, 'new content', false, true);
2025-11-01 15:15:16 +00:00
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);
2025-11-01 15:15:16 +00:00
});
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);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeFile('error.md', 'content');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Create failed');
2025-11-01 15:15:16 +00:00
});
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);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeFile('existing.md', 'content');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Modify failed');
2025-11-01 15:15:16 +00:00
});
});
describe('patchFile', () => {
it('should patch existing file successfully', async () => {
const mockFile = createMockFile('existing.md');
const oldContent = 'old content';
const newContent = 'new content';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.patchFile('existing.md', oldContent, newContent);
expect(result).toBe(mockFile);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, oldContent, newContent, false, true);
});
it('should create file with empty content when file does not exist', async () => {
const mockFile = createMockFile('new.md');
const oldContent = '';
const newContent = '# New File\nContent 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', oldContent, newContent);
// writeFile should create the file with empty content
expect(mockVaultService.create).toHaveBeenCalledWith('new.md', '', false, true);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, oldContent, newContent, false, true);
expect(result).toBe(mockFile);
});
it('should return error when patch fails', async () => {
const mockFile = createMockFile('existing.md');
const oldContent = 'old';
const newContent = 'new';
const error = new Error('Content to replace was not found in the file');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(error);
const result = await fileSystemService.patchFile('existing.md', oldContent, newContent);
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toContain('Content to replace was not found in the file');
});
it('should return error when creating empty file fails', async () => {
const oldContent = '';
const newContent = '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', oldContent, newContent);
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 oldContent = 'setting=old';
const newContent = 'setting=new';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
await fileSystemService.patchFile('plugin/config.md', oldContent, newContent, true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/config.md', true);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, oldContent, newContent, true, true);
});
it('should handle complex multi-line replacement', async () => {
const mockFile = createMockFile('document.md');
const oldContent = '# Title\nOld intro\nContent';
const newContent = '# Title\nNew intro\nContent';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
const result = await fileSystemService.patchFile('document.md', oldContent, newContent);
expect(result).toBe(mockFile);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, oldContent, newContent, false, true);
});
it('should handle file creation when getAbstractFileByPath returns null after create', async () => {
const oldContent = '';
const newContent = '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', oldContent, newContent);
// 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 oldContent = 'old';
const newContent = 'new';
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFile);
mockVaultService.patch = vi.fn().mockResolvedValue(mockFile);
await fileSystemService.patchFile('test.md', oldContent, newContent, false, true);
expect(mockVaultService.patch).toHaveBeenCalledWith(mockFile, oldContent, newContent, false, true);
});
});
2025-11-01 15:15:16 +00:00
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);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.deleteFile('delete-me.md');
expect(result).toBeUndefined();
expect(mockVaultService.delete).toHaveBeenCalledWith(mockFile, false, true);
2025-11-01 15:15:16 +00:00
});
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');
2025-11-01 15:15:16 +00:00
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);
2025-11-01 15:15:16 +00:00
await fileSystemService.deleteFile('plugin/temp.json', true);
expect(mockVaultService.getAbstractFileByPath).toHaveBeenCalledWith('plugin/temp.json', true);
expect(mockVaultService.delete).toHaveBeenCalledWith(mockFile, true, true);
2025-11-01 15:15:16 +00:00
});
it('should delete folder successfully (supports both files and folders)', async () => {
2025-11-01 15:15:16 +00:00
const mockFolder = createMockFolder('folder');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(mockFolder);
mockVaultService.delete = vi.fn().mockResolvedValue(undefined);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.deleteFile('folder');
expect(result).toBeUndefined();
expect(mockVaultService.delete).toHaveBeenCalledWith(mockFolder, false, true);
2025-11-01 15:15:16 +00:00
});
});
describe('moveFile', () => {
it('should move file successfully', async () => {
mockVaultService.move = vi.fn().mockResolvedValue(undefined);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.moveFile('old/path.md', 'new/path.md');
expect(result).toBeUndefined();
2025-11-01 15:15:16 +00:00
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);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.moveFile('nonexistent.md', 'new.md');
expect(result).toBeInstanceOf(Error);
expect((result as Error).message).toBe('Source file not found');
2025-11-01 15:15:16 +00:00
});
it('should respect allowAccessToPluginRoot parameter', async () => {
mockVaultService.move = vi.fn().mockResolvedValue(undefined);
2025-11-01 15:15:16 +00:00
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 () => {
2025-11-01 15:15:16 +00:00
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');
2025-11-01 15:15:16 +00:00
});
it('should throw SyntaxError when JSON is invalid', async () => {
2025-11-01 15:15:16 +00:00
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);
2025-11-01 15:15:16 +00:00
});
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');
2025-11-01 15:15:16 +00:00
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeObjectToFile('data.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('data.json', expectedJson, false, true);
2025-11-01 15:15:16 +00:00
});
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);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeObjectToFile('existing.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.modify).toHaveBeenCalledWith(mockFile, expectedJson, false, true);
2025-11-01 15:15:16 +00:00
});
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);
2025-11-01 15:15:16 +00:00
// 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');
2025-11-01 15:15:16 +00:00
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeObjectToFile('empty.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('empty.json', expectedJson, false, true);
2025-11-01 15:15:16 +00:00
});
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');
2025-11-01 15:15:16 +00:00
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(mockFile);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeObjectToFile('array.json', data);
expect(result).toBe(mockFile);
expect(mockVaultService.create).toHaveBeenCalledWith('array.json', expectedJson, false, true);
2025-11-01 15:15:16 +00:00
});
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);
2025-11-01 15:15:16 +00:00
});
it('should return Error on write error', async () => {
2025-11-01 15:15:16 +00:00
const data = { test: 'value' };
const error = new Error('Write failed');
mockVaultService.getAbstractFileByPath = vi.fn().mockReturnValue(null);
mockVaultService.create = vi.fn().mockResolvedValue(error);
2025-11-01 15:15:16 +00:00
const result = await fileSystemService.writeObjectToFile('error.json', data);
expect(result).toBe(error);
2025-11-01 15:15:16 +00:00
});
});
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);
});
});
});