070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { searchResearch, ResearchError } from '../src/research/index.js';
|
|
|
|
const TAVILY_ENV_KEYS = ['OD_TAVILY_API_KEY', 'TAVILY_API_KEY'];
|
|
type FetchInput = Parameters<typeof fetch>[0];
|
|
type FetchInit = Parameters<typeof fetch>[1];
|
|
|
|
describe('research search', () => {
|
|
const originalEnv = Object.fromEntries(
|
|
TAVILY_ENV_KEYS.map((key) => [key, process.env[key]]),
|
|
);
|
|
let projectRoot: string | null = null;
|
|
|
|
afterEach(async () => {
|
|
vi.unstubAllGlobals();
|
|
for (const key of TAVILY_ENV_KEYS) {
|
|
if (originalEnv[key] == null) delete process.env[key];
|
|
else process.env[key] = originalEnv[key];
|
|
}
|
|
const dir = projectRoot;
|
|
projectRoot = null;
|
|
if (dir) await rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
async function tempProjectRoot() {
|
|
projectRoot = await mkdtemp(path.join(tmpdir(), 'od-research-project-'));
|
|
return projectRoot;
|
|
}
|
|
|
|
it('requires a Tavily API key', async () => {
|
|
for (const key of TAVILY_ENV_KEYS) delete process.env[key];
|
|
|
|
await expect(
|
|
searchResearch({ projectRoot: await tempProjectRoot(), query: 'EV trends' }),
|
|
).rejects.toMatchObject({
|
|
code: 'TAVILY_API_KEY_MISSING',
|
|
status: 400,
|
|
} satisfies Partial<ResearchError>);
|
|
});
|
|
|
|
it('uses shallow Tavily search and normalizes JSON findings', async () => {
|
|
process.env.OD_TAVILY_API_KEY = 'tvly-test';
|
|
const fetchMock = vi.fn(async (_input: FetchInput, _init?: FetchInit) =>
|
|
new Response(
|
|
JSON.stringify({
|
|
answer: 'EV sales are growing.',
|
|
results: [
|
|
{
|
|
title: 'EV report',
|
|
url: 'https://example.com/ev',
|
|
content: 'EV adoption increased in 2025.',
|
|
published_date: '2025-05-01',
|
|
},
|
|
],
|
|
}),
|
|
{ status: 200, headers: { 'content-type': 'application/json' } },
|
|
),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
const findings = await searchResearch({
|
|
projectRoot: await tempProjectRoot(),
|
|
query: 'EV market 2025 trends',
|
|
maxSources: 50,
|
|
});
|
|
|
|
expect(findings).toMatchObject({
|
|
query: 'EV market 2025 trends',
|
|
summary: 'EV sales are growing.',
|
|
provider: 'tavily',
|
|
depth: 'shallow',
|
|
sources: [
|
|
{
|
|
title: 'EV report',
|
|
url: 'https://example.com/ev',
|
|
snippet: 'EV adoption increased in 2025.',
|
|
provider: 'tavily',
|
|
publishedAt: '2025-05-01',
|
|
},
|
|
],
|
|
});
|
|
const [, init] = fetchMock.mock.calls[0] as [FetchInput, FetchInit];
|
|
const body = JSON.parse(String(init!.body));
|
|
expect(body).toMatchObject({
|
|
query: 'EV market 2025 trends',
|
|
search_depth: 'basic',
|
|
max_results: 20,
|
|
include_answer: true,
|
|
include_raw_content: false,
|
|
});
|
|
});
|
|
});
|