128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const proxyFetchMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('@/lib/server/proxy-fetch', () => ({
|
|
proxyFetch: proxyFetchMock,
|
|
}));
|
|
|
|
import { searchWithBocha } from '@/lib/web-search/bocha';
|
|
|
|
describe('searchWithBocha', () => {
|
|
beforeEach(() => {
|
|
proxyFetchMock.mockReset();
|
|
});
|
|
|
|
it('calls Bocha Web Search API and maps web page results', async () => {
|
|
proxyFetchMock.mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
code: 200,
|
|
log_id: 'log-ok',
|
|
msg: null,
|
|
data: {
|
|
queryContext: { originalQuery: '阿里巴巴 ESG' },
|
|
webPages: {
|
|
value: [
|
|
{
|
|
name: 'Alibaba ESG report',
|
|
url: 'https://example.com/esg',
|
|
snippet: 'Short snippet',
|
|
summary: 'Long summary',
|
|
},
|
|
{
|
|
name: 'Snippet only',
|
|
url: 'https://example.com/snippet',
|
|
snippet: 'Fallback snippet',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
),
|
|
);
|
|
|
|
const result = await searchWithBocha({
|
|
query: '阿里巴巴 ESG',
|
|
apiKey: 'bocha-key',
|
|
maxResults: 100,
|
|
});
|
|
|
|
expect(proxyFetchMock).toHaveBeenCalledWith(
|
|
'https://api.bocha.cn/v1/web-search',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: 'Bearer bocha-key',
|
|
},
|
|
body: JSON.stringify({
|
|
query: '阿里巴巴 ESG',
|
|
freshness: 'noLimit',
|
|
summary: true,
|
|
count: 50,
|
|
}),
|
|
}),
|
|
);
|
|
expect(result.query).toBe('阿里巴巴 ESG');
|
|
expect(result.answer).toBe('');
|
|
expect(result.sources).toEqual([
|
|
{
|
|
title: 'Alibaba ESG report',
|
|
url: 'https://example.com/esg',
|
|
content: 'Long summary',
|
|
score: 0,
|
|
},
|
|
{
|
|
title: 'Snippet only',
|
|
url: 'https://example.com/snippet',
|
|
content: 'Fallback snippet',
|
|
score: 0,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('supports custom base URLs ending at either host, /v1, or full endpoint', async () => {
|
|
proxyFetchMock.mockImplementation(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ code: 200, data: { webPages: { value: [] } } }), {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' },
|
|
}),
|
|
),
|
|
);
|
|
|
|
await searchWithBocha({ query: 'q1', apiKey: 'key', baseUrl: 'https://proxy.example.com' });
|
|
await searchWithBocha({ query: 'q2', apiKey: 'key', baseUrl: 'https://proxy.example.com/v1' });
|
|
await searchWithBocha({
|
|
query: 'q3',
|
|
apiKey: 'key',
|
|
baseUrl: 'https://proxy.example.com/v1/web-search',
|
|
});
|
|
|
|
expect(proxyFetchMock.mock.calls.map((call) => call[0])).toEqual([
|
|
'https://proxy.example.com/v1/web-search',
|
|
'https://proxy.example.com/v1/web-search',
|
|
'https://proxy.example.com/v1/web-search',
|
|
]);
|
|
});
|
|
|
|
it('includes Bocha error details when requests fail', async () => {
|
|
proxyFetchMock.mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify({
|
|
code: '403',
|
|
message: 'You do not have enough money',
|
|
log_id: 'bocha-log-id',
|
|
}),
|
|
{ status: 403, statusText: 'Forbidden', headers: { 'content-type': 'application/json' } },
|
|
),
|
|
);
|
|
|
|
await expect(searchWithBocha({ query: 'q', apiKey: 'key' })).rejects.toThrow(
|
|
'Bocha API error (403): You do not have enough money, log_id: bocha-log-id',
|
|
);
|
|
});
|
|
});
|