Files
inkling/tests/unit/LocalOllamaProvider.test.ts
altair823 adae90eb61 feat(ai): AiWorker merges rule parser + AI due_date
GenerateInput gains todayKst field. AiWorker computes KST-aligned
date once per job, runs parseDueDate on rawText, calls provider.generate
with todayKst, then merges: rule.iso wins if matched (deterministic),
else AI's due_date, else null. Logs dueDateSource (rule|ai|none) for
debugging. now() injection for testability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:14:46 +09:00

74 lines
2.7 KiB
TypeScript

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<typeof getGlobalDispatcher>;
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' });
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' })
).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<void>((r) => setTimeout(r, 500));
return { statusCode: 200, data: '{}' };
}) as never);
await expect(
new LocalOllamaProvider({ timeoutMs: 50 }).generate({ text: 'x', todayKst: '2026-04-26' })
).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);
});
});