import { afterEach, describe, expect, it, vi } from 'vitest'; import { requestPlainTextCompletion, streamPlainTextCompletion, } from './llmClient'; function createSseResponse(body: string) { const encoder = new TextEncoder(); const stream = new ReadableStream({ start(controller) { controller.enqueue(encoder.encode(body)); controller.close(); }, }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream; charset=utf-8', }, }); } describe('llmClient streamPlainTextCompletion', () => { afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); }); it('reads OpenAI compatible SSE through the shared stream reader', async () => { const onUpdate = vi.fn(); const fetchMock = vi.fn().mockResolvedValue( createSseResponse( [ 'data: {"choices":[{"delta":{"content":"溪上"}}]}\r\n\r\n', 'data: not-json\r\n\r\n', 'data: {"choices":[{"delta":{"content":"春风"}}]}\r\n\r\n', 'data: [DONE]\r\n\r\n', 'data: {"choices":[{"delta":{"content":"不应读取"}}]}\r\n\r\n', ].join(''), ), ); vi.stubGlobal('fetch', fetchMock); const result = await streamPlainTextCompletion('system', 'user', { onUpdate, }); expect(result).toBe('溪上春风'); expect(onUpdate).toHaveBeenNthCalledWith(1, '溪上'); expect(onUpdate).toHaveBeenNthCalledWith(2, '溪上春风'); expect(onUpdate).toHaveBeenCalledTimes(2); }); it('reads api-server SSE delta events', async () => { const onUpdate = vi.fn(); const fetchMock = vi.fn().mockResolvedValue( createSseResponse( [ 'event: delta\r\n', 'data: {"delta":"你","content":"你","finishReason":null}\r\n\r\n', 'event: delta\r\n', 'data: {"delta":"好","content":"你好","finishReason":null}\r\n\r\n', 'event: complete\r\n', 'data: {"id":"resp_01","model":"gpt-5.4-mini","content":"你好","finishReason":"stop"}\r\n\r\n', 'data: [DONE]\r\n\r\n', ].join(''), ), ); vi.stubGlobal('fetch', fetchMock); const result = await streamPlainTextCompletion('system', 'user', { onUpdate, }); expect(result).toBe('你好'); expect(onUpdate).toHaveBeenNthCalledWith(1, '你'); expect(onUpdate).toHaveBeenNthCalledWith(2, '你好'); expect(onUpdate).toHaveBeenCalledTimes(2); }); }); describe('llmClient requestPlainTextCompletion', () => { afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); }); it('reads api-server response envelope content', async () => { const fetchMock = vi.fn().mockResolvedValue( new Response( JSON.stringify({ ok: true, data: { id: 'resp_01', model: 'gpt-5.4-mini', content: '代理成功', finishReason: 'stop', }, error: null, meta: {requestId: 'req_01'}, }), ), ); vi.stubGlobal('fetch', fetchMock); const result = await requestPlainTextCompletion('system', 'user'); expect(result).toBe('代理成功'); }); });