From 21e237c80a36a869bb1d5aa3b57dfab9081d00aa Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Thu, 23 Apr 2026 16:10:03 +0100 Subject: [PATCH] test: rename FileSystemService methods in mocks to match interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update test mocks to use renamed methods: readFile → readFilePath, writeFile → writeToFilePath, patchFile → patchFileAtPath. Add activeWindow and activeDocument globals to test setup for Obsidian compatibility. --- __tests__/Services/AIToolService.test.ts | 64 +++++++++++----------- __tests__/Services/MemoriesService.test.ts | 36 ++++++------ __tests__/Services/SettingsService.test.ts | 15 +++-- __tests__/setup.ts | 14 +++++ 4 files changed, 75 insertions(+), 54 deletions(-) diff --git a/__tests__/Services/AIToolService.test.ts b/__tests__/Services/AIToolService.test.ts index ad3d171..c34948b 100644 --- a/__tests__/Services/AIToolService.test.ts +++ b/__tests__/Services/AIToolService.test.ts @@ -33,9 +33,9 @@ describe('AIToolService - Integration Tests', () => { mockFileSystemService = { searchVaultFiles: vi.fn(), listFilesInDirectory: vi.fn(), - readFile: vi.fn(), - writeFile: vi.fn(), - patchFile: vi.fn(), + readFilePath: vi.fn(), + writeToFilePath: vi.fn(), + patchFileAtPath: vi.fn(), deleteFile: vi.fn(), moveFile: vi.fn() }; @@ -192,7 +192,7 @@ describe('AIToolService - Integration Tests', () => { describe('performAITool - ReadVaultFiles', () => { it('should read multiple files successfully', async () => { - mockFileSystemService.readFile + mockFileSystemService.readFilePath .mockResolvedValueOnce('Content of file 1') .mockResolvedValueOnce('Content of file 2') .mockResolvedValueOnce('Content of file 3'); @@ -216,7 +216,7 @@ describe('AIToolService - Integration Tests', () => { const error1 = new Error('File not found'); const error2 = new Error('File not found'); - mockFileSystemService.readFile + mockFileSystemService.readFilePath .mockResolvedValueOnce('Existing content') .mockResolvedValueOnce(error1) .mockResolvedValueOnce(error2); @@ -237,7 +237,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should handle mixed success and failure', async () => { - mockFileSystemService.readFile + mockFileSystemService.readFilePath .mockResolvedValueOnce('Content A') .mockResolvedValueOnce(new Error('File not found')) .mockResolvedValueOnce('Content B'); @@ -265,7 +265,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should handle single file read', async () => { - mockFileSystemService.readFile.mockResolvedValue('Single file content'); + mockFileSystemService.readFilePath.mockResolvedValue('Single file content'); const result = await service.performAITool({ name: AITool.ReadVaultFiles, @@ -280,7 +280,7 @@ describe('AIToolService - Integration Tests', () => { describe('performAITool - WriteVaultFile', () => { it('should write file successfully', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const result = await service.performAITool({ name: AITool.WriteVaultFile, @@ -292,7 +292,7 @@ describe('AIToolService - Integration Tests', () => { toolId: 'tool_11' } as any); - expect(mockFileSystemService.writeFile).toHaveBeenCalledWith( + expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledWith( 'notes/new-note.md', '# New Note\n\nContent here' ); @@ -301,7 +301,7 @@ describe('AIToolService - Integration Tests', () => { it('should handle write failure', async () => { const error = new Error('Permission denied'); - mockFileSystemService.writeFile.mockResolvedValue(error); + mockFileSystemService.writeToFilePath.mockResolvedValue(error); const result = await service.performAITool({ name: AITool.WriteVaultFile, @@ -318,7 +318,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should normalize file path', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); await service.performAITool({ name: AITool.WriteVaultFile, @@ -331,14 +331,14 @@ describe('AIToolService - Integration Tests', () => { } as any); // normalizePath should convert backslashes to forward slashes - expect(mockFileSystemService.writeFile).toHaveBeenCalledWith( + expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledWith( expect.stringContaining('/'), 'Content' ); }); it('should handle empty content', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const result = await service.performAITool({ name: AITool.WriteVaultFile, @@ -350,14 +350,14 @@ describe('AIToolService - Integration Tests', () => { toolId: 'tool_14' } as any); - expect(mockFileSystemService.writeFile).toHaveBeenCalledWith('empty.md', ''); + 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.patchFile.mockResolvedValue(createMockFile('notes/test.md', 'test')); + mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('notes/test.md', 'test')); const oldContent = ['old content']; const newContent = ['new content']; @@ -373,13 +373,13 @@ describe('AIToolService - Integration Tests', () => { toolId: 'tool_patch_1' } as any); - expect(mockFileSystemService.patchFile).toHaveBeenCalledWith('notes/test.md', oldContent, newContent); + 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.patchFile.mockResolvedValue(error); + mockFileSystemService.patchFileAtPath.mockResolvedValue(error); const oldContent = ['old content']; const newContent = ['new content']; @@ -400,7 +400,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should normalize file path', async () => { - mockFileSystemService.patchFile.mockResolvedValue(createMockFile('folder/file.md', 'file')); + mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('folder/file.md', 'file')); const oldContent = ['old']; const newContent = ['new']; @@ -417,7 +417,7 @@ describe('AIToolService - Integration Tests', () => { } as any); // normalizePath should convert backslashes to forward slashes - expect(mockFileSystemService.patchFile).toHaveBeenCalledWith( + expect(mockFileSystemService.patchFileAtPath).toHaveBeenCalledWith( expect.stringContaining('/'), oldContent, newContent @@ -426,7 +426,7 @@ describe('AIToolService - Integration Tests', () => { it('should handle file not found error', async () => { const error = new Error('File does not exist: missing.md'); - mockFileSystemService.patchFile.mockResolvedValue(error); + mockFileSystemService.patchFileAtPath.mockResolvedValue(error); const oldContent = ['old']; const newContent = ['new']; @@ -447,7 +447,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should handle complex multi-line replacement', async () => { - mockFileSystemService.patchFile.mockResolvedValue(createMockFile('complex.md', 'complex')); + mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('complex.md', 'complex')); const oldContent = ['# Title\nOld line 1\nContext line']; const newContent = ['# Title\nNew line 1\nContext line']; @@ -463,12 +463,12 @@ describe('AIToolService - Integration Tests', () => { toolId: 'tool_patch_5' } as any); - expect(mockFileSystemService.patchFile).toHaveBeenCalledWith('complex.md', oldContent, newContent); + 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.patchFile.mockResolvedValue(createMockFile('additions.md', 'additions')); + mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('additions.md', 'additions')); const oldContent = ['# Title\nExisting content']; const newContent = ['# Title\nExisting content\nNew line 1\nNew line 2']; @@ -488,7 +488,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should handle removing content', async () => { - mockFileSystemService.patchFile.mockResolvedValue(createMockFile('deletions.md', 'deletions')); + 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']; @@ -509,7 +509,7 @@ describe('AIToolService - Integration Tests', () => { it('should handle permission denied error', async () => { const error = new Error('Permission denied'); - mockFileSystemService.patchFile.mockResolvedValue(error); + mockFileSystemService.patchFileAtPath.mockResolvedValue(error); const oldContent = ['old']; const newContent = ['new']; @@ -530,7 +530,7 @@ describe('AIToolService - Integration Tests', () => { }); it('should return correct toolId in response', async () => { - mockFileSystemService.patchFile.mockResolvedValue(createMockFile('test.md', 'test')); + mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('test.md', 'test')); const oldContent = 'old'; const newContent = 'new'; @@ -562,7 +562,7 @@ describe('AIToolService - Integration Tests', () => { } as any); expect((result.payload.response as any).error).toContain('Invalid arguments for patch_vault_file'); - expect(mockFileSystemService.patchFile).not.toHaveBeenCalled(); + expect(mockFileSystemService.patchFileAtPath).not.toHaveBeenCalled(); }); }); @@ -836,7 +836,7 @@ describe('AIToolService - Integration Tests', () => { const foundPath = (searchResult.payload.response as any)[0].results[0].path; // Then read - mockFileSystemService.readFile.mockResolvedValue('File content here'); + mockFileSystemService.readFilePath.mockResolvedValue('File content here'); const readResult = await service.performAITool({ name: AITool.ReadVaultFiles, @@ -850,7 +850,7 @@ describe('AIToolService - Integration Tests', () => { it('should handle write -> move workflow', async () => { // First write - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const writeResult = await service.performAITool({ name: AITool.WriteVaultFile, @@ -882,7 +882,7 @@ describe('AIToolService - Integration Tests', () => { it('should handle read -> patch workflow', async () => { // First read the file - mockFileSystemService.readFile.mockResolvedValue('# Original Title\n\nOriginal content'); + mockFileSystemService.readFilePath.mockResolvedValue('# Original Title\n\nOriginal content'); const readResult = await service.performAITool({ name: AITool.ReadVaultFiles, @@ -893,7 +893,7 @@ describe('AIToolService - Integration Tests', () => { expect((readResult.payload.response as any).results[0].contents).toContain('Original'); // Then patch it - mockFileSystemService.patchFile.mockResolvedValue(createMockFile('document.md', 'document')); + mockFileSystemService.patchFileAtPath.mockResolvedValue(createMockFile('document.md', 'document')); const oldContent = ['# Original Title']; const newContent = ['# Updated Title']; @@ -910,7 +910,7 @@ describe('AIToolService - Integration Tests', () => { } as any); expect((patchResult.payload.response as any).success).toBe(true); - expect(mockFileSystemService.patchFile).toHaveBeenCalledWith('document.md', oldContent, newContent); + expect(mockFileSystemService.patchFileAtPath).toHaveBeenCalledWith('document.md', oldContent, newContent); }); }); }); diff --git a/__tests__/Services/MemoriesService.test.ts b/__tests__/Services/MemoriesService.test.ts index ff921dc..b17b988 100644 --- a/__tests__/Services/MemoriesService.test.ts +++ b/__tests__/Services/MemoriesService.test.ts @@ -12,8 +12,8 @@ describe('MemoriesService', () => { beforeEach(() => { mockFileSystemService = { exists: vi.fn(), - readFile: vi.fn(), - writeFile: vi.fn() + readFilePath: vi.fn(), + writeToFilePath: vi.fn() }; mockWorkSpaceService = { openNoteByPath: vi.fn() @@ -30,7 +30,7 @@ describe('MemoriesService', () => { describe('readMemories', () => { it('should return file contents when file exists', async () => { - mockFileSystemService.readFile.mockResolvedValue('Remember: user prefers TypeScript.'); + mockFileSystemService.readFilePath.mockResolvedValue('Remember: user prefers TypeScript.'); const result = await service.readMemories(); @@ -38,7 +38,7 @@ describe('MemoriesService', () => { }); it('should return MemoriesEmpty copy when file does not exist', async () => { - mockFileSystemService.readFile.mockResolvedValue(new Error('File not found')); + mockFileSystemService.readFilePath.mockResolvedValue(new Error('File not found')); const result = await service.readMemories(); @@ -46,7 +46,7 @@ describe('MemoriesService', () => { }); it('should return MemoriesEmpty copy when read returns any error', async () => { - mockFileSystemService.readFile.mockResolvedValue(new Error('Permission denied')); + mockFileSystemService.readFilePath.mockResolvedValue(new Error('Permission denied')); const result = await service.readMemories(); @@ -56,16 +56,16 @@ describe('MemoriesService', () => { describe('updateMemories', () => { it('should write memories and return success message', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const result = await service.updateMemories('Line one\nLine two'); - expect(mockFileSystemService.writeFile).toHaveBeenCalledOnce(); + expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledOnce(); expect(result).toBe(Copy.MemoriesUpdatedSuccess); }); it('should write empty memories successfully', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const result = await service.updateMemories(''); @@ -74,7 +74,7 @@ describe('MemoriesService', () => { it('should return error when write fails', async () => { const writeError = new Error('Disk full'); - mockFileSystemService.writeFile.mockResolvedValue(writeError); + mockFileSystemService.writeToFilePath.mockResolvedValue(writeError); const result = await service.updateMemories('some content'); @@ -86,13 +86,13 @@ describe('MemoriesService', () => { const result = await service.updateMemories(elevenLines); - expect(mockFileSystemService.writeFile).not.toHaveBeenCalled(); + expect(mockFileSystemService.writeToFilePath).not.toHaveBeenCalled(); expect(result).toBeTypeOf('string'); expect(result as string).toContain('10'); }); it('should accept exactly 10 lines', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const tenLines = Array(10).fill('valid line').join('\n'); const result = await service.updateMemories(tenLines); @@ -105,13 +105,13 @@ describe('MemoriesService', () => { const result = await service.updateMemories(longLine); - expect(mockFileSystemService.writeFile).not.toHaveBeenCalled(); + expect(mockFileSystemService.writeToFilePath).not.toHaveBeenCalled(); expect(result).toBeTypeOf('string'); expect(result as string).toContain('200'); }); it('should accept a line of exactly 200 characters', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const maxLine = 'a'.repeat(200); const result = await service.updateMemories(maxLine); @@ -120,7 +120,7 @@ describe('MemoriesService', () => { }); it('should handle Windows-style line endings (CRLF) in line count', async () => { - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); const tenLinesCRLF = Array(10).fill('valid line').join('\r\n'); const result = await service.updateMemories(tenLinesCRLF); @@ -133,7 +133,7 @@ describe('MemoriesService', () => { const result = await service.updateMemories(elevenLinesCRLF); - expect(mockFileSystemService.writeFile).not.toHaveBeenCalled(); + expect(mockFileSystemService.writeToFilePath).not.toHaveBeenCalled(); }); }); @@ -143,17 +143,17 @@ describe('MemoriesService', () => { await service.openMemories(); - expect(mockFileSystemService.writeFile).not.toHaveBeenCalled(); + expect(mockFileSystemService.writeToFilePath).not.toHaveBeenCalled(); expect(mockWorkSpaceService.openNoteByPath).toHaveBeenCalledOnce(); }); it('should create memories file before opening when it does not exist', async () => { mockFileSystemService.exists.mockResolvedValue(false); - mockFileSystemService.writeFile.mockResolvedValue(undefined); + mockFileSystemService.writeToFilePath.mockResolvedValue(undefined); await service.openMemories(); - expect(mockFileSystemService.writeFile).toHaveBeenCalledOnce(); + expect(mockFileSystemService.writeToFilePath).toHaveBeenCalledOnce(); expect(mockWorkSpaceService.openNoteByPath).toHaveBeenCalledOnce(); }); }); diff --git a/__tests__/Services/SettingsService.test.ts b/__tests__/Services/SettingsService.test.ts index ddca9a9..6d3139a 100644 --- a/__tests__/Services/SettingsService.test.ts +++ b/__tests__/Services/SettingsService.test.ts @@ -41,6 +41,7 @@ describe('SettingsService', () => { it('should merge loaded settings with defaults', () => { const loadedSettings: Partial = { firstTimeStart: false, + provider: AIProvider.Gemini, model: AIProviderModel.GeminiFlash_2_5, planningModel: AIProviderModel.GeminiPro_2_5, apiKeys: { @@ -65,6 +66,7 @@ describe('SettingsService', () => { it('should handle partially loaded settings and fill missing properties with defaults', () => { const loadedSettings: Partial = { + provider: AIProvider.OpenAI, model: AIProviderModel.GPT_5_4, apiKeys: { claude: '', @@ -178,8 +180,8 @@ describe('SettingsService', () => { allowUpdatingMemories: true, enableWebSearch: true, enableWebViewer: false, - provider: AIProvider.Claude, - quickActionModel: AIProviderModel.ClaudeSonnet_4_6 + provider: AIProvider.OpenAI, + quickActionModel: AIProviderModel.GPT_5_4_Mini }; settingsService = new SettingsService(loadedSettings); @@ -205,8 +207,8 @@ describe('SettingsService', () => { allowUpdatingMemories: true, enableWebSearch: true, enableWebViewer: false, - provider: AIProvider.Claude, - quickActionModel: AIProviderModel.ClaudeSonnet_4_6 + provider: AIProvider.Gemini, + quickActionModel: AIProviderModel.GeminiFlash_2_5 }; settingsService = new SettingsService(loadedSettings); @@ -217,6 +219,7 @@ describe('SettingsService', () => { it('should use fromModel to determine provider', () => { // Test with various Claude models settingsService = new SettingsService({ + provider: AIProvider.Claude, model: AIProviderModel.ClaudeOpus_4, apiKeys: { claude: 'opus-key', openai: '', gemini: '', mistral: '' } }); @@ -224,6 +227,7 @@ describe('SettingsService', () => { // Test with various Gemini models settingsService = new SettingsService({ + provider: AIProvider.Gemini, model: AIProviderModel.GeminiPro_2_5, apiKeys: { claude: '', openai: '', gemini: 'pro-key', mistral: '' } }); @@ -231,6 +235,7 @@ describe('SettingsService', () => { // Test with various GPT models settingsService = new SettingsService({ + provider: AIProvider.OpenAI, model: AIProviderModel.GPT_5_4, apiKeys: { claude: '', openai: 'gpt5-key', gemini: '', mistral: '' } }); @@ -382,6 +387,7 @@ describe('SettingsService', () => { geminiModels.forEach(model => { settingsService = new SettingsService({ + provider: AIProvider.Gemini, model, apiKeys: { claude: '', openai: '', gemini: 'test-gemini', mistral: '' } }); @@ -400,6 +406,7 @@ describe('SettingsService', () => { openaiModels.forEach(model => { settingsService = new SettingsService({ + provider: AIProvider.OpenAI, model, apiKeys: { claude: '', openai: 'test-openai', gemini: '', mistral: '' } }); diff --git a/__tests__/setup.ts b/__tests__/setup.ts index f58fe75..dd41cf9 100644 --- a/__tests__/setup.ts +++ b/__tests__/setup.ts @@ -9,6 +9,20 @@ if (typeof global.window === 'undefined') { global.window = {} as any; } +// Obsidian global aliases used in source code +if (typeof (global as any).activeWindow === 'undefined') { + (global as any).activeWindow = { + setTimeout: globalThis.setTimeout.bind(globalThis), + clearTimeout: globalThis.clearTimeout.bind(globalThis), + getSelection: () => (typeof document !== 'undefined' ? document.getSelection() : null), + window: typeof window !== 'undefined' ? window : global.window, + document: typeof document !== 'undefined' ? document : undefined, + }; +} +if (typeof (global as any).activeDocument === 'undefined') { + (global as any).activeDocument = typeof document !== 'undefined' ? document : undefined; +} + // Note: Component class is now defined in __mocks__/obsidian.ts // The vitest alias in vitest.config.ts handles mocking 'obsidian' imports