Skip to content

Commit

Permalink
✅ test: fix test
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed May 11, 2024
1 parent 5faa578 commit 0737e82
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 2 deletions.
155 changes: 154 additions & 1 deletion src/utils/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { FetchEventSourceInit } from '@microsoft/fetch-event-source';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { ErrorResponse } from '@/types/fetch';

import { getMessageError, parseToolCalls } from './fetch';
import { fetchSSE, getMessageError, parseToolCalls } from './fetch';

// 模拟 i18next
vi.mock('i18next', () => ({
Expand Down Expand Up @@ -39,6 +41,10 @@ const createMockResponse = (body: any, ok: boolean, status: number = 200) => ({
},
});

vi.mock('@microsoft/fetch-event-source', () => ({
fetchEventSource: vi.fn(),
}));

// 在每次测试后清理所有模拟
afterEach(() => {
vi.restoreAllMocks();
Expand Down Expand Up @@ -168,3 +174,150 @@ describe('parseToolCalls', () => {
]);
});
});

describe('fetchSSE', () => {
it('should handle text event correctly', async () => {
const mockOnMessageHandle = vi.fn();
const mockOnFinish = vi.fn();

(fetchEventSource as any).mockImplementationOnce(
(url: string, options: FetchEventSourceInit) => {
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
options.onmessage!({ event: 'text', data: JSON.stringify('Hello') } as any);
options.onmessage!({ event: 'text', data: JSON.stringify(' World') } as any);
},
);

await fetchSSE('/', { onMessageHandle: mockOnMessageHandle, onFinish: mockOnFinish });

expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hello', type: 'text' });
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, { text: ' World', type: 'text' });
expect(mockOnFinish).toHaveBeenCalledWith('Hello World', {
observationId: null,
toolCalls: undefined,
traceId: null,
type: 'done',
});
});

it('should handle tool_calls event correctly', async () => {
const mockOnMessageHandle = vi.fn();
const mockOnFinish = vi.fn();

(fetchEventSource as any).mockImplementationOnce(
(url: string, options: FetchEventSourceInit) => {
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
options.onmessage!({
event: 'tool_calls',
data: JSON.stringify([
{ index: 0, id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } },
]),
} as any);
options.onmessage!({
event: 'tool_calls',
data: JSON.stringify([
{ index: 1, id: '2', type: 'function', function: { name: 'func2', arguments: 'arg2' } },
]),
} as any);
},
);

await fetchSSE('/', { onMessageHandle: mockOnMessageHandle, onFinish: mockOnFinish });

expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, {
tool_calls: [{ id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } }],
type: 'tool_calls',
});
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, {
tool_calls: [
{ id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } },
{ id: '2', type: 'function', function: { name: 'func2', arguments: 'arg2' } },
],
type: 'tool_calls',
});
expect(mockOnFinish).toHaveBeenCalledWith('', {
observationId: null,
toolCalls: [
{ id: '1', type: 'function', function: { name: 'func1', arguments: 'arg1' } },
{ id: '2', type: 'function', function: { name: 'func2', arguments: 'arg2' } },
],
traceId: null,
type: 'done',
});
});

it('should call onAbort when AbortError is thrown', async () => {
const mockOnAbort = vi.fn();

(fetchEventSource as any).mockImplementationOnce(
(url: string, options: FetchEventSourceInit) => {
options.onmessage!({ event: 'text', data: JSON.stringify('Hello') } as any);
options.onerror!({ name: 'AbortError' });
},
);

await fetchSSE('/', { onAbort: mockOnAbort });

expect(mockOnAbort).toHaveBeenCalledWith('Hello');
});

it('should call onErrorHandle when other error is thrown', async () => {
const mockOnErrorHandle = vi.fn();
const mockError = new Error('Unknown error');

(fetchEventSource as any).mockImplementationOnce(
(url: string, options: FetchEventSourceInit) => {
options.onerror!(mockError);
},
);

await fetchSSE('/', { onErrorHandle: mockOnErrorHandle });

expect(mockOnErrorHandle).not.toHaveBeenCalled();
});

it('should call onErrorHandle when response is not ok', async () => {
const mockOnErrorHandle = vi.fn();

(fetchEventSource as any).mockImplementationOnce(
(url: string, options: FetchEventSourceInit) => {
const res = new Response(JSON.stringify({ errorType: 'SomeError' }), {
status: 400,
statusText: 'Error',
});

options.onopen!(res as any);
},
);

await fetchSSE('/', { onErrorHandle: mockOnErrorHandle });

expect(mockOnErrorHandle).toHaveBeenCalledWith({
body: undefined,
message: 'translated_response.SomeError',
type: 'SomeError',
});
});

it('should call onMessageHandle with full text if no message event', async () => {
const mockOnMessageHandle = vi.fn();
const mockOnFinish = vi.fn();

(fetchEventSource as any).mockImplementationOnce(
(url: string, options: FetchEventSourceInit) => {
const res = new Response('Hello World', { status: 200, statusText: 'OK' });
options.onopen!(res as any);
},
);

await fetchSSE('/', { onMessageHandle: mockOnMessageHandle, onFinish: mockOnFinish });

expect(mockOnMessageHandle).toHaveBeenCalledWith({ text: 'Hello World', type: 'text' });
expect(mockOnFinish).toHaveBeenCalledWith('Hello World', {
observationId: null,
toolCalls: undefined,
traceId: null,
type: 'done',
});
});
});
3 changes: 2 additions & 1 deletion src/utils/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
if (response) {
// if there is no onMessageHandler, we should call onHandleMessage first
if (!triggerOnMessageHandler) {
options.onMessageHandle?.({ text: await response.clone().text(), type: 'text' });
output = await response.clone().text();
options.onMessageHandle?.({ text: output, type: 'text' });
}

const traceId = response.headers.get(LOBE_CHAT_TRACE_ID);
Expand Down

0 comments on commit 0737e82

Please sign in to comment.