Refactor error handling, client, and tests for Ollama integration

This commit is contained in:
2026-05-06 16:30:16 +02:00
parent 59df2f6856
commit fd49abcdb9
35 changed files with 4970 additions and 3765 deletions
+92 -29
View File
@@ -1,50 +1,113 @@
// Enhanced Obsidian mock for testing
// Mock Vault
export class Vault {
getMarkdownFiles() {
return [];
}
getMarkdownFiles: () => any[];
read: (file: any) => Promise<string>;
create: (path: string, content: string) => Promise<any>;
async read(file: any) {
return '';
}
async create(path: string, content: string) {
return null;
constructor() {
this.getMarkdownFiles = () => [];
this.read = async () => '';
this.create = async () => null;
}
}
// Mock Workspace
export class Workspace {
getLeaf() {
return {
getLeaf: () => any;
constructor() {
this.getLeaf = () => ({
setViewState: jest.fn(),
};
revealLeaf: jest.fn(),
});
}
}
// Mock App
export class App {
vault = new Vault();
workspace = new Workspace();
vault: Vault;
workspace: Workspace;
constructor() {
this.vault = new Vault();
this.workspace = new Workspace();
}
}
// Mock WorkspaceLeaf
export class WorkspaceLeaf {
app: App;
view: any;
setViewState: jest.Mock;
constructor() {
this.app = new App();
this.view = null;
this.setViewState = jest.fn();
}
}
// Mock ItemView - accepts a leaf and derives app from it
export class ItemView {
contentEl: HTMLElement = document.createElement('div');
contentEl: HTMLElement;
app: App;
constructor(leaf?: WorkspaceLeaf) {
this.contentEl = document.createElement('div');
if (leaf && leaf.app) {
this.app = leaf.app;
} else {
this.app = new App();
}
}
}
// Mock Notice - can be called with `new Notice(msg)` or as a function
export class Notice {
message: string;
constructor(message: string) {
this.message = message;
// Also record via jest for testing
(Notice as any).lastMessage = message;
}
}
(Notice as any).lastMessage = '';
// Mock Setting
export class Setting {
containerEl: HTMLElement;
constructor(containerEl: HTMLElement) {
this.containerEl = containerEl;
}
setName(_name: string): this {
return this;
}
setDesc(_desc: string): this {
return this;
}
addText(_callback: (text: any) => void): this {
return this;
}
}
// TFile type
export interface TFile {
basename: string;
path: string;
}
// Plugin class (used by main.ts)
export class Plugin {
app: App;
constructor() {
this.app = new App();
}
}
export class Notice {
static create(message: string) {}
}
// Mock types for DOM elements
export type TFile = {
basename: string;
};
// Export additional types that might be used in tests
export const Plugin: any = jest.fn();
export const WorkspaceLeaf: any = jest.fn();
export const Setting: any = jest.fn();
+20 -25
View File
@@ -1,49 +1,44 @@
// Mock for ollama-client for testing
import { OllamaMessage, ToolCall } from '../src/types';
import type { APIError } from '../src/error-handler';
import type { OllamaMessage, OllamaTool } from '../src/types';
export class OllamaClient {
private url: string;
private model: string;
// Mock fetch function for testing
private fetchFn: jest.Mock<Promise<Response>, [string, RequestInit?]>> = jest.fn();
private fetchFn: typeof fetch;
constructor(url: string, model: string, fetchFn?: typeof fetch) {
this.url = url;
this.model = model;
if (fetchFn) this.fetchFn = jest.fn(fetchFn);
this.fetchFn = fetchFn ?? (jest.fn() as typeof fetch);
}
async *streamChatMessages(
prompt: string,
options: { abortSignal?: AbortSignal } = {}
async *streamChat(
_messages: OllamaMessage[],
_tools: OllamaTool[] = []
): AsyncGenerator<OllamaMessage, void, unknown> {
// Mock implementation - simulate streaming response
const mockResponse = [
{ role: 'assistant', content: 'Part 1' },
{ role: 'assistant', content: 'Part 2' }
const mockMessages: OllamaMessage[] = [
{ role: 'assistant', content: 'Part 1', tool_calls: [] },
{ role: 'assistant', content: 'Part 2', tool_calls: [] },
];
for (const message of mockResponse) {
for (const message of mockMessages) {
yield message;
await new Promise(resolve => setTimeout(resolve, 100)); // Simulate delay
await new Promise((resolve) => setTimeout(resolve, 10));
}
}
async *streamToolMessages(
toolCall: ToolCall,
options: { abortSignal?: AbortSignal } = {}
): AsyncGenerator<OllamaMessage, void, unknown> {
// Mock implementation - simulate streaming response for tool
const mockResponse = [
{ role: 'assistant', content: `Tool ${toolCall.tool_name} Part 1` },
{ role: 'assistant', content: `Tool ${toolCall.tool_name} Part 2` }
];
async chat(_messages: OllamaMessage[], _tools: OllamaTool[] = []): Promise<OllamaMessage> {
return {
role: 'assistant',
content: 'Mock response',
tool_calls: [],
};
}
for (const message of mockResponse) {
yield message;
await new Promise(resolve => setTimeout(resolve, 100)); // Simulate delay
}
cancelStream(): void {
// No-op in mock
}
}