import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { MockAgent, setGlobalDispatcher, getGlobalDispatcher } from 'undici'; import { LocalOllamaProvider } from '@main/ai/LocalOllamaProvider.js'; describe('LocalOllamaProvider', () => { let mock: MockAgent; let original: ReturnType; beforeEach(() => { original = getGlobalDispatcher(); mock = new MockAgent(); mock.disableNetConnect(); setGlobalDispatcher(mock); }); afterEach(async () => { setGlobalDispatcher(original); await mock.close(); }); it('generate parses Ollama JSON', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/generate', method: 'POST' }).reply(200, { response: JSON.stringify({ title: '회의', summary: '첫\n둘\n셋', tags: ['api'] }) }); const r = await new LocalOllamaProvider().generate({ text: 'x', todayKst: '2026-04-26', dueDateCandidates: [] }); expect(r.title).toBe('회의'); }); it('generate throws on non-JSON', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/generate', method: 'POST' }).reply(200, { response: 'not json' }); await expect( new LocalOllamaProvider().generate({ text: 'x', todayKst: '2026-04-26', dueDateCandidates: [] }) ).rejects.toThrow(/json/i); }); it('generate aborts on timeout', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/generate', method: 'POST' }).reply((async () => { await new Promise((r) => setTimeout(r, 500)); return { statusCode: 200, data: '{}' }; }) as never); await expect( new LocalOllamaProvider({ timeoutMs: 50 }).generate({ text: 'x', todayKst: '2026-04-26', dueDateCandidates: [] }) ).rejects.toThrow(); }, 2000); it('healthCheck ok=true when model present', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/tags', method: 'GET' }).reply(200, { models: [{ name: 'gemma4:e4b' }] }); const h = await new LocalOllamaProvider().healthCheck(); expect(h.ok).toBe(true); expect(h.model).toBe('gemma4:e4b'); }); it('healthCheck ok=false when missing', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/tags', method: 'GET' }).reply(200, { models: [{ name: 'other:latest' }] }); const h = await new LocalOllamaProvider().healthCheck(); expect(h.ok).toBe(false); expect(h.reason).toMatch(/gemma4:e4b/); }); it('healthCheck ok=false on connection error', async () => { mock.get('http://localhost:11434').intercept({ path: '/api/tags', method: 'GET' }) .replyWithError(new Error('ECONNREFUSED')); const h = await new LocalOllamaProvider().healthCheck(); expect(h.ok).toBe(false); expect(h.reason).toMatch(/connect|refused|unreachable/i); }); });