test: add unit tests for search impls (brave, exa, tavily) (#12960)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
LobeHub Bot
2026-03-13 19:43:31 +08:00
committed by GitHub
parent 7d2f88f384
commit e4d960376c
3 changed files with 861 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { BraveImpl } from './index';
const createMockResponse = (body: object, ok = true, status = 200, statusText = 'OK') =>
({
ok,
status,
statusText,
json: vi.fn().mockResolvedValue(body),
text: vi.fn().mockResolvedValue(JSON.stringify(body)),
}) as unknown as Response;
const makeBraveResponse = (results: object[]) => ({
type: 'search',
mixed: {},
web: {
type: 'web',
results,
},
});
describe('BraveImpl', () => {
let impl: BraveImpl;
beforeEach(() => {
impl = new BraveImpl();
vi.stubGlobal('fetch', vi.fn());
process.env.BRAVE_API_KEY = 'test-brave-api-key';
});
afterEach(() => {
vi.unstubAllGlobals();
delete process.env.BRAVE_API_KEY;
});
describe('query', () => {
it('should return mapped results for a successful query', async () => {
const braveResults = [
{
title: 'Example Title',
url: 'https://example.com/page',
description: 'Example description',
type: 'web',
},
{
title: 'Another Result',
url: 'https://another.com/page',
description: 'Another description',
type: 'web',
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse(braveResults)));
const result = await impl.query('test query');
expect(result.query).toBe('test query');
expect(result.resultNumbers).toBe(2);
expect(result.results).toHaveLength(2);
expect(result.results[0]).toMatchObject({
title: 'Example Title',
url: 'https://example.com/page',
content: 'Example description',
engines: ['brave'],
category: 'general',
score: 1,
parsedUrl: 'example.com',
});
});
it('should return empty results when web.results is empty', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
const result = await impl.query('empty query');
expect(result.resultNumbers).toBe(0);
expect(result.results).toHaveLength(0);
});
it('should include freshness param for day time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test', { searchTimeRange: 'day' });
const fetchCall = vi.mocked(fetch).mock.calls[0];
const url = fetchCall[0] as string;
expect(url).toContain('freshness=pd');
});
it('should include freshness=pw for week time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test', { searchTimeRange: 'week' });
const url = vi.mocked(fetch).mock.calls[0][0] as string;
expect(url).toContain('freshness=pw');
});
it('should include freshness=pm for month time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test', { searchTimeRange: 'month' });
const url = vi.mocked(fetch).mock.calls[0][0] as string;
expect(url).toContain('freshness=pm');
});
it('should include freshness=py for year time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test', { searchTimeRange: 'year' });
const url = vi.mocked(fetch).mock.calls[0][0] as string;
expect(url).toContain('freshness=py');
});
it('should not include a valid freshness value for anytime time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test', { searchTimeRange: 'anytime' });
const url = vi.mocked(fetch).mock.calls[0][0] as string;
// Should not include any of the valid freshness values
expect(url).not.toContain('freshness=pd');
expect(url).not.toContain('freshness=pw');
expect(url).not.toContain('freshness=pm');
expect(url).not.toContain('freshness=py');
});
it('should use the API key in request headers', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect((options.headers as Record<string, string>)['X-Subscription-Token']).toBe(
'test-brave-api-key',
);
});
it('should use empty string for API key when not set', async () => {
delete process.env.BRAVE_API_KEY;
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect((options.headers as Record<string, string>)['X-Subscription-Token']).toBe('');
});
it('should throw SERVICE_UNAVAILABLE when fetch throws a network error', async () => {
vi.mocked(fetch).mockRejectedValue(new Error('Network error'));
await expect(impl.query('test')).rejects.toMatchObject({
code: 'SERVICE_UNAVAILABLE',
message: 'Failed to connect to Brave.',
});
});
it('should throw SERVICE_UNAVAILABLE when response is not ok', async () => {
vi.mocked(fetch).mockResolvedValue(
createMockResponse({ error: 'Unauthorized' }, false, 401, 'Unauthorized'),
);
await expect(impl.query('test')).rejects.toMatchObject({
code: 'SERVICE_UNAVAILABLE',
message: 'Brave request failed: Unauthorized',
});
});
it('should throw INTERNAL_SERVER_ERROR when response JSON parsing fails', async () => {
vi.mocked(fetch).mockResolvedValue({
ok: true,
status: 200,
json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
text: vi.fn().mockResolvedValue('invalid json'),
} as unknown as Response);
await expect(impl.query('test')).rejects.toMatchObject({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to parse Brave response.',
});
});
it('should correctly parse parsedUrl from result url', async () => {
const braveResults = [
{
title: 'Test',
url: 'https://subdomain.example.com/path?query=1',
description: 'Test desc',
type: 'web',
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse(braveResults)));
const result = await impl.query('test');
expect(result.results[0].parsedUrl).toBe('subdomain.example.com');
});
it('should return empty parsedUrl for invalid url', async () => {
const braveResults = [
{
title: 'Test',
url: 'not-a-valid-url',
description: 'Test desc',
type: 'web',
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse(braveResults)));
// Should handle URL parsing errors gracefully or return empty string
// BraveImpl uses new URL(result.url) which throws for invalid URLs
await expect(impl.query('test')).rejects.toThrow();
});
it('should include costTime in the response', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
const result = await impl.query('test');
expect(typeof result.costTime).toBe('number');
expect(result.costTime).toBeGreaterThanOrEqual(0);
});
it('should use GET method', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect(options.method).toBe('GET');
});
it('should include query string in request URL', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse([])));
await impl.query('my search query');
const url = vi.mocked(fetch).mock.calls[0][0] as string;
expect(url).toContain('q=my+search+query');
});
it('should use empty string for description when not provided', async () => {
const braveResults = [
{
title: 'No Desc',
url: 'https://example.com',
description: '',
type: 'web',
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeBraveResponse(braveResults)));
const result = await impl.query('test');
expect(result.results[0].content).toBe('');
});
});
});

View File

@@ -0,0 +1,281 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ExaImpl } from './index';
const createMockResponse = (body: object, ok = true, status = 200, statusText = 'OK') =>
({
ok,
status,
statusText,
json: vi.fn().mockResolvedValue(body),
text: vi.fn().mockResolvedValue(JSON.stringify(body)),
}) as unknown as Response;
const makeExaResponse = (results: object[]) => ({
requestId: 'req-123',
resolvedSearchType: 'auto',
results,
});
describe('ExaImpl', () => {
let impl: ExaImpl;
beforeEach(() => {
impl = new ExaImpl();
vi.stubGlobal('fetch', vi.fn());
process.env.EXA_API_KEY = 'test-exa-api-key';
});
afterEach(() => {
vi.unstubAllGlobals();
delete process.env.EXA_API_KEY;
});
describe('query', () => {
it('should return mapped results for a successful query', async () => {
const exaResults = [
{
id: 'result-1',
url: 'https://example.com/page',
title: 'Example Title',
text: 'Example content text',
score: 0.95,
publishedDate: '2024-01-01',
},
{
id: 'result-2',
url: 'https://another.com/page',
title: 'Another Result',
text: 'Another content text',
score: 0.85,
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse(exaResults)));
const result = await impl.query('test query');
expect(result.query).toBe('test query');
expect(result.resultNumbers).toBe(2);
expect(result.results).toHaveLength(2);
expect(result.results[0]).toMatchObject({
title: 'Example Title',
url: 'https://example.com/page',
content: 'Example content text',
engines: ['exa'],
category: 'general',
score: 0.95,
parsedUrl: 'example.com',
});
});
it('should return empty results when results array is empty', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
const result = await impl.query('empty query');
expect(result.resultNumbers).toBe(0);
expect(result.results).toHaveLength(0);
});
it('should handle missing results field gracefully', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse({ requestId: 'req-123' }));
const result = await impl.query('test');
expect(result.resultNumbers).toBe(0);
expect(result.results).toHaveLength(0);
});
it('should set date range for day time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test', { searchTimeRange: 'day' });
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
const body = JSON.parse(options.body as string);
expect(body.startPublishedDate).toBeDefined();
expect(body.endPublishedDate).toBeDefined();
});
it('should set date range for week time range', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test', { searchTimeRange: 'week' });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.startPublishedDate).toBeDefined();
expect(body.endPublishedDate).toBeDefined();
});
it('should not set date range for anytime', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test', { searchTimeRange: 'anytime' });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.startPublishedDate).toBeUndefined();
expect(body.endPublishedDate).toBeUndefined();
});
it('should filter category to news only', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test', { searchCategories: ['news', 'general'] });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.category).toBe('news');
});
it('should not include category for non-news categories', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test', { searchCategories: ['general'] });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.category).toBeUndefined();
});
it('should include API key in request headers', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect((options.headers as Record<string, string>)['x-api-key']).toBe('test-exa-api-key');
});
it('should use empty string for API key when not set', async () => {
delete process.env.EXA_API_KEY;
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect((options.headers as Record<string, string>)['x-api-key']).toBe('');
});
it('should use POST method', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect(options.method).toBe('POST');
});
it('should include query in request body', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
await impl.query('my search query');
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.query).toBe('my search query');
expect(body.numResults).toBe(15);
expect(body.type).toBe('auto');
});
it('should throw SERVICE_UNAVAILABLE when fetch throws a network error', async () => {
vi.mocked(fetch).mockRejectedValue(new Error('Connection refused'));
await expect(impl.query('test')).rejects.toMatchObject({
code: 'SERVICE_UNAVAILABLE',
message: 'Failed to connect to Exa.',
});
});
it('should throw SERVICE_UNAVAILABLE when response is not ok', async () => {
vi.mocked(fetch).mockResolvedValue(
createMockResponse({ error: 'Forbidden' }, false, 403, 'Forbidden'),
);
await expect(impl.query('test')).rejects.toMatchObject({
code: 'SERVICE_UNAVAILABLE',
message: 'Exa request failed: Forbidden',
});
});
it('should throw INTERNAL_SERVER_ERROR when response JSON parsing fails', async () => {
vi.mocked(fetch).mockResolvedValue({
ok: true,
status: 200,
json: vi.fn().mockRejectedValue(new Error('JSON parse error')),
text: vi.fn().mockResolvedValue('not json'),
} as unknown as Response);
await expect(impl.query('test')).rejects.toMatchObject({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to parse Exa response.',
});
});
it('should use score 0 when result score is undefined', async () => {
const exaResults = [
{
id: 'result-1',
url: 'https://example.com',
title: 'Test',
text: 'Content',
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse(exaResults)));
const result = await impl.query('test');
expect(result.results[0].score).toBe(0);
});
it('should use category from body for result category when news', async () => {
const exaResults = [
{
id: 'result-1',
url: 'https://example.com',
title: 'News Article',
text: 'News content',
score: 0.9,
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse(exaResults)));
const result = await impl.query('test', { searchCategories: ['news'] });
expect(result.results[0].category).toBe('news');
});
it('should include costTime in the response', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse([])));
const result = await impl.query('test');
expect(typeof result.costTime).toBe('number');
expect(result.costTime).toBeGreaterThanOrEqual(0);
});
it('should use empty content when text is missing', async () => {
const exaResults = [
{
id: 'result-1',
url: 'https://example.com',
title: 'Test',
text: '',
score: 0.9,
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeExaResponse(exaResults)));
const result = await impl.query('test');
expect(result.results[0].content).toBe('');
});
});
});

View File

@@ -0,0 +1,311 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { TavilyImpl } from './index';
const createMockResponse = (body: object, ok = true, status = 200, statusText = 'OK') =>
({
ok,
status,
statusText,
json: vi.fn().mockResolvedValue(body),
text: vi.fn().mockResolvedValue(JSON.stringify(body)),
}) as unknown as Response;
const makeTavilyResponse = (results: object[], query = 'test') => ({
query,
response_time: 0.5,
results,
});
describe('TavilyImpl', () => {
let impl: TavilyImpl;
beforeEach(() => {
impl = new TavilyImpl();
vi.stubGlobal('fetch', vi.fn());
process.env.TAVILY_API_KEY = 'test-tavily-api-key';
delete process.env.TAVILY_SEARCH_DEPTH;
});
afterEach(() => {
vi.unstubAllGlobals();
delete process.env.TAVILY_API_KEY;
delete process.env.TAVILY_SEARCH_DEPTH;
});
describe('query', () => {
it('should return mapped results for a successful query', async () => {
const tavilyResults = [
{
title: 'Example Title',
url: 'https://example.com/page',
content: 'Example content text',
score: 0.9,
},
{
title: 'Another Result',
url: 'https://another.com/article',
content: 'Another content text',
score: 0.7,
},
];
vi.mocked(fetch).mockResolvedValueOnce(
createMockResponse(makeTavilyResponse(tavilyResults, 'test query')),
);
const result = await impl.query('test query');
expect(result.query).toBe('test query');
expect(result.resultNumbers).toBe(2);
expect(result.results).toHaveLength(2);
expect(result.results[0]).toMatchObject({
title: 'Example Title',
url: 'https://example.com/page',
content: 'Example content text',
engines: ['tavily'],
category: 'general',
score: 0.9,
parsedUrl: 'example.com',
});
});
it('should return empty results when results array is empty', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
const result = await impl.query('empty query');
expect(result.resultNumbers).toBe(0);
expect(result.results).toHaveLength(0);
});
it('should set time_range for day', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test', { searchTimeRange: 'day' });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.time_range).toBe('day');
});
it('should set time_range for week', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test', { searchTimeRange: 'week' });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.time_range).toBe('week');
});
it('should not set time_range for anytime', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test', { searchTimeRange: 'anytime' });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.time_range).toBeUndefined();
});
it('should set topic to news when news category included', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test', { searchCategories: ['news', 'general'] });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.topic).toBe('news');
});
it('should set topic to general when general category included', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test', { searchCategories: ['general'] });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.topic).toBe('general');
});
it('should not include topic for unsupported categories', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test', { searchCategories: ['images', 'videos'] });
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.topic).toBeUndefined();
});
it('should use default search_depth basic when env not set', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test');
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.search_depth).toBe('basic');
});
it('should use TAVILY_SEARCH_DEPTH from env when set', async () => {
process.env.TAVILY_SEARCH_DEPTH = 'advanced';
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test');
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.search_depth).toBe('advanced');
});
it('should include Bearer token in authorization header', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect((options.headers as Record<string, string>)['Authorization']).toBe(
'Bearer test-tavily-api-key',
);
});
it('should use empty string in authorization header when API key not set', async () => {
delete process.env.TAVILY_API_KEY;
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect((options.headers as Record<string, string>)['Authorization']).toBe('');
});
it('should use POST method', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('test');
const fetchCall = vi.mocked(fetch).mock.calls[0];
const options = fetchCall[1] as RequestInit;
expect(options.method).toBe('POST');
});
it('should include query in request body', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
await impl.query('my search query');
const body = JSON.parse((vi.mocked(fetch).mock.calls[0][1] as RequestInit).body as string);
expect(body.query).toBe('my search query');
expect(body.max_results).toBe(15);
});
it('should throw SERVICE_UNAVAILABLE when fetch throws a network error', async () => {
vi.mocked(fetch).mockRejectedValue(new Error('ECONNREFUSED'));
await expect(impl.query('test')).rejects.toMatchObject({
code: 'SERVICE_UNAVAILABLE',
message: 'Failed to connect to Tavily.',
});
});
it('should throw SERVICE_UNAVAILABLE when response is not ok', async () => {
vi.mocked(fetch).mockResolvedValue(
createMockResponse({ error: 'Too Many Requests' }, false, 429, 'Too Many Requests'),
);
await expect(impl.query('test')).rejects.toMatchObject({
code: 'SERVICE_UNAVAILABLE',
message: 'Tavily request failed: Too Many Requests',
});
});
it('should throw INTERNAL_SERVER_ERROR when response JSON parsing fails', async () => {
vi.mocked(fetch).mockResolvedValue({
ok: true,
status: 200,
json: vi.fn().mockRejectedValue(new Error('JSON error')),
text: vi.fn().mockResolvedValue('bad json'),
} as unknown as Response);
await expect(impl.query('test')).rejects.toMatchObject({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to parse Tavily response.',
});
});
it('should default score to 0 when not provided', async () => {
const tavilyResults = [
{
title: 'No Score',
url: 'https://example.com',
content: 'Content',
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse(tavilyResults)));
const result = await impl.query('test');
expect(result.results[0].score).toBe(0);
});
it('should use topic as category in mapped results', async () => {
const tavilyResults = [
{
title: 'News Article',
url: 'https://example.com',
content: 'News content',
score: 0.8,
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse(tavilyResults)));
const result = await impl.query('test', { searchCategories: ['news'] });
expect(result.results[0].category).toBe('news');
});
it('should default category to general when topic not set', async () => {
const tavilyResults = [
{
title: 'Article',
url: 'https://example.com',
content: 'Content',
score: 0.8,
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse(tavilyResults)));
const result = await impl.query('test');
expect(result.results[0].category).toBe('general');
});
it('should correctly parse parsedUrl from result url', async () => {
const tavilyResults = [
{
title: 'Test',
url: 'https://www.example.co.uk/path',
content: 'Content',
score: 0.5,
},
];
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse(tavilyResults)));
const result = await impl.query('test');
expect(result.results[0].parsedUrl).toBe('www.example.co.uk');
});
it('should include costTime in the response', async () => {
vi.mocked(fetch).mockResolvedValueOnce(createMockResponse(makeTavilyResponse([])));
const result = await impl.query('test');
expect(typeof result.costTime).toBe('number');
expect(result.costTime).toBeGreaterThanOrEqual(0);
});
});
});