mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
212 lines
6.8 KiB
TypeScript
212 lines
6.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { ChatService } from '../../Services/ChatService';
|
|
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
|
|
import { Services } from '../../Services/Services';
|
|
import { AITool, fromString } from '../../Enums/AITool';
|
|
import { AbortService } from '../../Services/AbortService';
|
|
|
|
/**
|
|
* INTEGRATION TESTS - Simplified
|
|
*
|
|
* These tests focus on synchronous methods only, avoiding the complex async streaming
|
|
* functionality that caused memory issues. Tests the actual integration between ChatService
|
|
* and its dependencies.
|
|
*
|
|
* Note: The complex submit() method with async generators is better tested through E2E tests.
|
|
*/
|
|
|
|
describe('ChatService - Integration Tests (Sync Methods Only)', () => {
|
|
let service: ChatService;
|
|
let mockConversationService: any;
|
|
let mockAIControllerService: any;
|
|
let mockNamingService: any;
|
|
let mockEventService: any;
|
|
let mockWorkSpaceService: any;
|
|
let abortService: AbortService;
|
|
|
|
beforeEach(() => {
|
|
// Setup minimal mocks
|
|
mockConversationService = {
|
|
saveConversation: vi.fn()
|
|
};
|
|
|
|
mockAIControllerService = {
|
|
runMainAgent: vi.fn(),
|
|
setSaveCallback: vi.fn(),
|
|
resolveAIProvider: vi.fn()
|
|
};
|
|
|
|
mockNamingService = {
|
|
requestName: vi.fn()
|
|
};
|
|
|
|
// Mock EventService since it extends Obsidian's Events class
|
|
mockEventService = {
|
|
trigger: vi.fn(),
|
|
on: vi.fn(),
|
|
off: vi.fn()
|
|
};
|
|
|
|
// Mock WorkSpaceService
|
|
mockWorkSpaceService = {
|
|
openNote: vi.fn(),
|
|
getActiveFile: vi.fn().mockReturnValue(null)
|
|
};
|
|
|
|
// Create real AbortService instance
|
|
abortService = new AbortService();
|
|
|
|
// Register dependencies
|
|
RegisterSingleton(Services.ConversationFileSystemService, mockConversationService);
|
|
RegisterSingleton(Services.MainAgent, mockAIControllerService);
|
|
RegisterSingleton(Services.ConversationNamingService, mockNamingService);
|
|
RegisterSingleton(Services.EventService, mockEventService);
|
|
RegisterSingleton(Services.WorkSpaceService, mockWorkSpaceService);
|
|
RegisterSingleton(Services.AbortService, abortService);
|
|
|
|
// Create service
|
|
service = new ChatService();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clear singleton registry to prevent memory leaks
|
|
DeregisterAllServices();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should initialize with all required services', () => {
|
|
const testService = new ChatService();
|
|
|
|
expect(testService).toBeDefined();
|
|
});
|
|
|
|
it('should initialize onNameChanged as undefined', () => {
|
|
expect(service.onNameChanged).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('stop', () => {
|
|
it('should not throw when called with no active request', () => {
|
|
expect(() => service.stop()).not.toThrow();
|
|
});
|
|
|
|
it('should be callable multiple times', () => {
|
|
service.stop();
|
|
service.stop();
|
|
service.stop();
|
|
|
|
expect(service).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('onNameChanged callback', () => {
|
|
it('should allow setting callback function', () => {
|
|
const callback = vi.fn();
|
|
|
|
service.onNameChanged = callback;
|
|
|
|
expect(service.onNameChanged).toBe(callback);
|
|
});
|
|
|
|
it('should be initially undefined', () => {
|
|
const newService = new ChatService();
|
|
|
|
expect(newService.onNameChanged).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('fromString', () => {
|
|
it('should convert valid function name strings to AITool enum', () => {
|
|
expect(fromString('search_vault_files')).toBe(AITool.SearchVaultFiles);
|
|
expect(fromString('read_vault_files')).toBe(AITool.ReadVaultFiles);
|
|
expect(fromString('write_vault_file')).toBe(AITool.WriteVaultFile);
|
|
expect(fromString('delete_vault_files')).toBe(AITool.DeleteVaultFiles);
|
|
expect(fromString('move_vault_files')).toBe(AITool.MoveVaultFiles);
|
|
expect(fromString('list_vault_files')).toBe(AITool.ListVaultFiles);
|
|
expect(fromString('request_web_search')).toBe(AITool.RequestWebSearch);
|
|
});
|
|
|
|
it('should return Unknown for unknown function names', () => {
|
|
expect(fromString('unknown_function')).toBe(AITool.Unknown);
|
|
expect(fromString('test_function')).toBe(AITool.Unknown);
|
|
expect(fromString('')).toBe(AITool.Unknown);
|
|
});
|
|
});
|
|
|
|
describe('requestWithContext (private method tests via reflection)', () => {
|
|
// Access private method for testing
|
|
const getRequestWithContextMethod = (service: ChatService) => {
|
|
return (service as any).requestWithContext.bind(service);
|
|
};
|
|
|
|
it('should return request unchanged when no active file exists', () => {
|
|
mockWorkSpaceService.getActiveFile.mockReturnValue(null);
|
|
const requestWithContext = getRequestWithContextMethod(service);
|
|
const request = 'Please help me with this task';
|
|
|
|
const result = requestWithContext(request);
|
|
|
|
expect(result).toBe(request);
|
|
expect(mockWorkSpaceService.getActiveFile).toHaveBeenCalledWith();
|
|
});
|
|
|
|
it('should append active file path when active file exists', () => {
|
|
const mockFile = {
|
|
path: 'notes/my-note.md',
|
|
name: 'my-note.md',
|
|
basename: 'my-note'
|
|
};
|
|
mockWorkSpaceService.getActiveFile.mockReturnValue(mockFile);
|
|
const requestWithContext = getRequestWithContextMethod(service);
|
|
const request = 'Please help me with this task';
|
|
|
|
const result = requestWithContext(request);
|
|
|
|
expect(result).toBe('Please help me with this task\nUser current active file: "notes/my-note.md"');
|
|
expect(mockWorkSpaceService.getActiveFile).toHaveBeenCalledWith();
|
|
});
|
|
|
|
it('should handle empty request with active file', () => {
|
|
const mockFile = {
|
|
path: 'test.md'
|
|
};
|
|
mockWorkSpaceService.getActiveFile.mockReturnValue(mockFile);
|
|
const requestWithContext = getRequestWithContextMethod(service);
|
|
const request = '';
|
|
|
|
const result = requestWithContext(request);
|
|
|
|
expect(result).toBe('\nUser current active file: "test.md"');
|
|
expect(mockWorkSpaceService.getActiveFile).toHaveBeenCalledWith();
|
|
});
|
|
|
|
it('should handle request with special characters in file path', () => {
|
|
const mockFile = {
|
|
path: 'folder/my file (v2).md'
|
|
};
|
|
mockWorkSpaceService.getActiveFile.mockReturnValue(mockFile);
|
|
const requestWithContext = getRequestWithContextMethod(service);
|
|
const request = 'Update this file';
|
|
|
|
const result = requestWithContext(request);
|
|
|
|
expect(result).toBe('Update this file\nUser current active file: "folder/my file (v2).md"');
|
|
expect(mockWorkSpaceService.getActiveFile).toHaveBeenCalledWith();
|
|
});
|
|
|
|
it('should handle multiline requests with active file', () => {
|
|
const mockFile = {
|
|
path: 'docs/readme.md'
|
|
};
|
|
mockWorkSpaceService.getActiveFile.mockReturnValue(mockFile);
|
|
const requestWithContext = getRequestWithContextMethod(service);
|
|
const request = 'Please help me:\n1. First task\n2. Second task';
|
|
|
|
const result = requestWithContext(request);
|
|
|
|
expect(result).toBe('Please help me:\n1. First task\n2. Second task\nUser current active file: "docs/readme.md"');
|
|
expect(mockWorkSpaceService.getActiveFile).toHaveBeenCalledWith();
|
|
});
|
|
});
|
|
});
|