import type { RequestClient } from '../request-client'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { SSE } from './sse'; // 模拟 TextDecoder const OriginalTextDecoder = globalThis.TextDecoder; beforeEach(() => { vi.stubGlobal( 'TextDecoder', class { private decoder = new OriginalTextDecoder(); decode(value: Uint8Array, opts?: any) { return this.decoder.decode(value, opts); } }, ); }); // 创建 fetch mock const createFetchMock = (chunks: string[], ok = true) => { const encoder = new TextEncoder(); let index = 0; return vi.fn().mockResolvedValue({ ok, status: ok ? 200 : 500, body: { getReader: () => ({ read: async () => { if (index < chunks.length) { return { done: false, value: encoder.encode(chunks[index++]) }; } return { done: true, value: undefined }; }, }), }, }); }; describe('sSE', () => { let client: RequestClient; let sse: SSE; beforeEach(() => { vi.restoreAllMocks(); client = { getBaseUrl: () => 'http://localhost', instance: { interceptors: { request: { handlers: [], }, }, }, } as unknown as RequestClient; sse = new SSE(client); }); it('should call requestSSE when postSSE is used', async () => { const spy = vi.spyOn(sse, 'requestSSE').mockResolvedValue(undefined); await sse.postSSE('/test', { foo: 'bar' }, { headers: { a: '1' } }); expect(spy).toHaveBeenCalledWith( '/test', { foo: 'bar' }, { headers: { a: '1' }, method: 'POST', }, ); }); it('should throw error if fetch response not ok', async () => { vi.stubGlobal('fetch', createFetchMock([], false)); await expect(sse.requestSSE('/bad')).rejects.toThrow( 'HTTP error! status: 500', ); }); it('should trigger onMessage and onEnd callbacks', async () => { const messages: string[] = []; const onMessage = vi.fn((msg: string) => messages.push(msg)); const onEnd = vi.fn(); vi.stubGlobal('fetch', createFetchMock(['hello', ' world'])); await sse.requestSSE('/sse', undefined, { onMessage, onEnd }); expect(onMessage).toHaveBeenCalledTimes(2); expect(messages.join('')).toBe('hello world'); expect(onEnd).toHaveBeenCalledWith('hello world'); }); it('should apply request interceptors', async () => { const interceptor = vi.fn(async (config) => { config.headers['x-test'] = 'intercepted'; return config; }); (client.instance.interceptors.request as any).handlers.push({ fulfilled: interceptor, }); vi.stubGlobal('fetch', createFetchMock(['data'])); // 创建 fetch mock,并挂到全局 const fetchMock = createFetchMock(['data']); vi.stubGlobal('fetch', fetchMock); await sse.requestSSE('/sse', undefined, {}); expect(interceptor).toHaveBeenCalled(); expect(fetchMock).toHaveBeenCalledWith( 'http://localhost//sse', expect.objectContaining({ headers: expect.objectContaining({ 'x-test': 'intercepted' }), }), ); }); it('should throw error when no reader', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, body: null, }), ); await expect(sse.requestSSE('/sse')).rejects.toThrow('No reader'); }); });