Skip to content

Commit e50d766

Browse files
committed
feat(cozeloop-ai): increment ut
1 parent 94f5558 commit e50d766

File tree

4 files changed

+212
-4
lines changed

4 files changed

+212
-4
lines changed

packages/cozeloop-ai/__tests__/http.test.ts renamed to packages/cozeloop-ai/__tests__/api/api-client.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
22
// SPDX-License-Identifier: MIT
3-
import { simpleConsoleLogger } from '../src/utils/logger';
4-
import { ApiClient } from '../src/api/api-client';
5-
import { setupBaseHttpMock } from './__mock__/base-http';
3+
import { setupBaseHttpMock } from '../__mock__/base-http';
4+
import { simpleConsoleLogger } from '../../src/utils/logger';
5+
import { ApiClient } from '../../src/api/api-client';
66

77
interface BaseHttpResp {
88
method: string;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
2+
// SPDX-License-Identifier: MIT
3+
import axios from 'axios';
4+
5+
import { setupBaseHttpMock } from '../__mock__/base-http';
6+
import { compareVersions } from '../../src/utils/common';
7+
import {
8+
getNodeStreamAdapter,
9+
isAxiosStatic,
10+
} from '../../src/api/api-client/utils';
11+
import { http } from '../../src/api';
12+
13+
vi.mock('axios');
14+
vi.mock('../../src/utils/common');
15+
vi.mock('../../src/api/api-client/utils');
16+
17+
describe('http', () => {
18+
const httpMock = setupBaseHttpMock();
19+
beforeAll(() => httpMock.start());
20+
afterAll(() => httpMock.close());
21+
beforeEach(() => vi.clearAllMocks());
22+
afterEach(() => httpMock.reset());
23+
24+
it('should throw an error for streaming requests with axios version below 1.7.1', async () => {
25+
(compareVersions as any).mockReturnValue(-1);
26+
(isAxiosStatic as any).mockReturnValue(true);
27+
28+
await expect(http({ streaming: true })).rejects.toThrowError(
29+
'Streaming requests require axios version 1.7.1 or higher. Please upgrade your axios version.',
30+
);
31+
});
32+
33+
it('should call axios with correct options for non-streaming request', async () => {
34+
const axiosInstance = axios.create();
35+
const mockResponse = { data: 'mock data' };
36+
(axios as any).mockResolvedValue(mockResponse);
37+
38+
const result = await http({ axiosInstance, url: 'https://example.com' });
39+
40+
expect(axios).toHaveBeenCalledWith({
41+
responseType: 'json',
42+
adapter: getNodeStreamAdapter(false),
43+
url: 'https://example.com',
44+
});
45+
expect(result.json()).toBe(mockResponse.data);
46+
});
47+
48+
it('should call axios with correct options for streaming request', async () => {
49+
const axiosInstance = axios.create();
50+
const mockStream = { on: vi.fn(), pipe: vi.fn() };
51+
(axios as any).mockResolvedValue({ data: mockStream });
52+
(compareVersions as any).mockReturnValue(1);
53+
(isAxiosStatic as any).mockReturnValue(true);
54+
55+
await http({
56+
axiosInstance,
57+
streaming: true,
58+
url: 'https://example.com/stream',
59+
});
60+
61+
expect(axios).toHaveBeenCalledWith({
62+
responseType: 'stream',
63+
adapter: getNodeStreamAdapter(true),
64+
url: 'https://example.com/stream',
65+
});
66+
});
67+
68+
it('should handle errors correctly', async () => {
69+
const mockError = new Error('mock error');
70+
(axios as any).mockRejectedValue(mockError);
71+
72+
await expect(http({ url: 'https://example.com/basic' })).rejects.toThrow(
73+
'mock error',
74+
);
75+
});
76+
});
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
2+
// SPDX-License-Identifier: MIT
3+
import nodeFetch from 'node-fetch';
4+
5+
import {
6+
getNodeStreamAdapter,
7+
isAxiosStatic,
8+
parseEventChunk,
9+
generateChunks,
10+
} from '../../src/api/api-client/utils';
11+
12+
vi.mock('node-fetch', () => ({
13+
default: vi.fn(),
14+
}));
15+
16+
describe('getNodeStreamAdapter', () => {
17+
it('should return undefined if streaming is false', () => {
18+
const adapter = getNodeStreamAdapter(false);
19+
expect(adapter).toBeUndefined();
20+
});
21+
22+
it('should return "fetch" if window.fetch is available', async () => {
23+
vi.stubGlobal('fetch', vi.fn());
24+
const adapter = getNodeStreamAdapter(true);
25+
const mockResponse = { data: { val: 1 } };
26+
(fetch as any).mockResolvedValueOnce(mockResponse);
27+
expect(adapter).toBe('fetch');
28+
// @ts-expect-error skip
29+
const result = await fetch({
30+
url: 'https://example.com/api',
31+
method: 'post',
32+
});
33+
expect(result).toBe(mockResponse);
34+
vi.unstubAllGlobals();
35+
});
36+
37+
it('should return a function for node-fetch', async () => {
38+
vi.stubGlobal('fetch', undefined);
39+
const mockResponse = {
40+
status: 200,
41+
body: 'Hello, World!',
42+
headers: new Map<string, string>(),
43+
};
44+
(nodeFetch as any).mockResolvedValueOnce(mockResponse);
45+
46+
const adapter = getNodeStreamAdapter(true);
47+
expect(typeof adapter).toBe('function');
48+
49+
// @ts-expect-error skip
50+
const response = await adapter({
51+
url: 'https://example.com/api',
52+
});
53+
54+
expect(nodeFetch).toHaveBeenCalledWith(
55+
'https://example.com/api',
56+
expect.anything(),
57+
);
58+
expect(response).toEqual({
59+
data: 'Hello, World!',
60+
status: 200,
61+
statusText: undefined,
62+
headers: {},
63+
config: { url: 'https://example.com/api' },
64+
request: { method: 'GET', headers: {}, timeout: undefined },
65+
});
66+
vi.unstubAllGlobals();
67+
});
68+
});
69+
70+
describe('isAxiosStatic', () => {
71+
it('should return true if the instance has Axios property', () => {
72+
const instance = { Axios: true };
73+
expect(isAxiosStatic(instance)).toBe(true);
74+
});
75+
76+
it('should return false if the instance does not have Axios property', () => {
77+
const instance = {};
78+
expect(isAxiosStatic(instance)).toBe(false);
79+
});
80+
});
81+
82+
describe('parseEventChunk', () => {
83+
it('should parse the event and data from the chunk', () => {
84+
const chunk = 'event: my-event\ndata: {"foo":"bar"}';
85+
const result = parseEventChunk(chunk);
86+
expect(result).toEqual({ foo: 'bar' });
87+
});
88+
89+
it('should throw an error if the event is "gateway-error"', () => {
90+
const chunk = 'event: gateway-error\ndata: Something went wrong';
91+
expect(() => parseEventChunk(chunk)).toThrow('Something went wrong');
92+
});
93+
});
94+
95+
describe('generateChunks', () => {
96+
it('should yield an empty object if stream is undefined or null', async () => {
97+
const parseChunk = vi.fn();
98+
const generator = generateChunks(undefined, parseChunk);
99+
const result = await generator.next();
100+
101+
expect(result.value).toBeUndefined();
102+
expect(parseChunk).toHaveBeenCalledWith('');
103+
});
104+
105+
it('should parse chunks from a readable stream', async () => {
106+
const parseChunk = vi.fn(chunk => chunk);
107+
const mockStream = new ReadableStream({
108+
start(controller) {
109+
controller.enqueue(
110+
new Uint8Array([
111+
0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x31, 0x0a, 0x0a,
112+
]),
113+
); // 'data: 1\n\n'
114+
controller.enqueue(
115+
new Uint8Array([
116+
0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x32, 0x0a, 0x0a,
117+
]),
118+
); // 'data: 2\n\n'
119+
controller.close();
120+
},
121+
});
122+
123+
const generator = generateChunks(mockStream, parseChunk);
124+
const result1 = await generator.next();
125+
const result2 = await generator.next();
126+
const result3 = await generator.next();
127+
128+
expect(result1.value).toBe('data: 1');
129+
expect(result2.value).toBe('data: 2');
130+
expect(result3.done).toBe(true);
131+
});
132+
});

packages/cozeloop-ai/src/api/api-client/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function getNodeStreamAdapter(
5757
data: response.body,
5858
status: response.status,
5959
statusText: response.statusText,
60-
headers: Object.fromEntries(response.headers),
60+
headers: Object.fromEntries(response.headers ?? {}),
6161
config,
6262
request,
6363
};

0 commit comments

Comments
 (0)