import { describe, it, expect, vi, beforeEach } from 'vitest'; import { streamChat, streamGenerate } from './stream.js'; const BASE_URL = 'http://localhost:11434'; function createNdjsonResponse(chunks: object[]): Response { const encoder = new TextEncoder(); const ndjson = chunks.map(c => JSON.stringify(c)).join('\n') + '\n'; const stream = new ReadableStream({ start(controller) { controller.enqueue(encoder.encode(ndjson)); controller.close(); }, }); return { ok: true, status: 200, body: stream } as unknown as Response; } describe('streamChat', () => { beforeEach(() => { vi.restoreAllMocks(); }); it('yields chat stream chunks', async () => { const chunks = [ { model: 'llama3', message: { role: 'assistant', content: 'Hello' }, done: false }, { model: 'llama3', message: { role: 'assistant', content: ' world' }, done: false }, { model: 'llama3', message: { role: 'assistant', content: '' }, done: true, eval_count: 10 }, ]; globalThis.fetch = vi.fn().mockResolvedValue(createNdjsonResponse(chunks)); const results = []; for await (const chunk of streamChat(BASE_URL, { model: 'llama3', messages: [{ role: 'user', content: 'Hi' }], })) { results.push(chunk); } expect(results).toHaveLength(3); expect(results[0].message.content).toBe('Hello'); expect(results[2].done).toBe(true); expect(results[2].eval_count).toBe(10); }); it('throws on non-ok response', async () => { globalThis.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, text: () => Promise.resolve('internal error'), }); const gen = streamChat(BASE_URL, { model: 'llama3', messages: [{ role: 'user', content: 'Hi' }], }); await expect(gen.next()).rejects.toThrow('Ollama chat failed (500)'); }); it('throws when response has no body', async () => { globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, body: null, }); const gen = streamChat(BASE_URL, { model: 'llama3', messages: [{ role: 'user', content: 'Hi' }], }); await expect(gen.next()).rejects.toThrow('No response body'); }); }); describe('streamGenerate', () => { beforeEach(() => { vi.restoreAllMocks(); }); it('yields generate stream chunks', async () => { const chunks = [ { model: 'llama3', response: 'Hello', done: false }, { model: 'llama3', response: ' world', done: false }, { model: 'llama3', response: '', done: true, eval_count: 5 }, ]; globalThis.fetch = vi.fn().mockResolvedValue(createNdjsonResponse(chunks)); const results = []; for await (const chunk of streamGenerate(BASE_URL, { model: 'llama3', prompt: 'Say hello', })) { results.push(chunk); } expect(results).toHaveLength(3); expect(results[0].response).toBe('Hello'); expect(results[2].done).toBe(true); }); });