Files
OmniRoute/tests/unit/search-handler-extended.test.mjs
diegosouzapw 592ca9b5c4 fix: remove hardcoded localhost default arg from GET /api/keys, unify coverage to single coverage/ dir, fix test to pass explicit Request
- Remove `new Request('http://localhost/api/keys')` default arg from GET handler in src/app/api/keys/route.ts (line 26)
- Fix api-key-reveal-route.test.mjs to pass explicit Request instead of calling GET() with no args
- Add --output-dir coverage to all c8 scripts in package.json
- Add coverage.reportsDirectory: 'coverage' to vitest.config.ts and vitest.mcp.config.ts
- Fix CHANGELOG.md structure (# Changelog + [Unreleased] to top)
- Remove 30+ stale coverage-* directories from project root
- Coverage: Statements 78.76% | Branches 72.75% | Functions 80.93% | Lines 78.76% (all thresholds passed)
2026-04-05 23:21:08 -03:00

432 lines
12 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-search-"));
const { handleSearch } = await import("../../open-sse/handlers/search.ts");
test("handleSearch builds Serper web requests and normalizes organic results", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (url, init = {}) => {
captured = {
url: String(url),
headers: init.headers,
body: JSON.parse(String(init.body || "{}")),
};
return new Response(
JSON.stringify({
organic: [
{
title: "Latest AI",
link: "https://www.example.com/news?id=1",
snippet: "Top story",
date: "2026-04-05",
},
{
title: "Second",
link: "https://docs.example.com/page",
description: "Fallback description",
},
],
searchParameters: { totalResults: 20 },
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: " latest ai ",
provider: "serper-search",
maxResults: 3,
searchType: "web",
country: "US",
language: "en",
credentials: { apiKey: "serper-key" },
log: null,
});
assert.equal(result.success, true);
assert.equal(captured.url, "https://google.serper.dev/search");
assert.equal(captured.headers["X-API-Key"], "serper-key");
assert.deepEqual(captured.body, {
q: "latest ai",
num: 3,
gl: "us",
hl: "en",
});
assert.equal(result.data.provider, "serper-search");
assert.equal(result.data.query, "latest ai");
assert.equal(result.data.results[0].display_url, "example.com/news");
assert.equal(result.data.results[0].citation.provider, "serper-search");
assert.equal(result.data.metrics.total_results_available, 20);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch builds Brave news requests and normalizes favicon metadata", async () => {
const originalFetch = globalThis.fetch;
let capturedUrl;
let capturedHeaders;
globalThis.fetch = async (url, init = {}) => {
capturedUrl = String(url);
capturedHeaders = init.headers;
return new Response(
JSON.stringify({
results: [
{
title: "Brave item",
url: "https://news.example.com/story",
description: "Breaking news",
age: "2026-04-05",
meta_url: { favicon: "https://news.example.com/favicon.ico" },
},
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: "market update",
provider: "brave-search",
maxResults: 1,
searchType: "news",
country: "BR",
language: "pt",
credentials: { apiKey: "brave-key" },
log: null,
});
const url = new URL(capturedUrl);
assert.equal(url.origin + url.pathname, "https://api.search.brave.com/res/v1/news/search");
assert.equal(url.searchParams.get("q"), "market update");
assert.equal(url.searchParams.get("count"), "1");
assert.equal(url.searchParams.get("country"), "BR");
assert.equal(url.searchParams.get("search_lang"), "pt");
assert.equal(capturedHeaders["X-Subscription-Token"], "brave-key");
assert.equal(result.success, true);
assert.equal(result.data.results[0].favicon_url, "https://news.example.com/favicon.ico");
assert.equal(result.data.results[0].published_at, "2026-04-05");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch builds Perplexity requests, forwards filters, and enforces maxResults", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (url, init = {}) => {
captured = {
url: String(url),
headers: init.headers,
body: JSON.parse(String(init.body || "{}")),
};
return new Response(
JSON.stringify({
results: [
{ title: "One", url: "https://one.example.com", snippet: "First", date: "2026-04-05" },
{ title: "Two", url: "https://two.example.com", snippet: "Second" },
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: "ai agents",
provider: "perplexity-search",
maxResults: 1,
searchType: "web",
country: "US",
language: "en",
domainFilter: ["example.com", "-spam.com"],
credentials: { apiKey: "perplexity-key" },
log: null,
});
assert.equal(captured.url, "https://api.perplexity.ai/search");
assert.equal(captured.headers.Authorization, "Bearer perplexity-key");
assert.deepEqual(captured.body, {
query: "ai agents",
max_results: 1,
country: "US",
search_language_filter: ["en"],
search_domain_filter: ["example.com", "-spam.com"],
});
assert.equal(result.success, true);
assert.equal(result.data.results.length, 1);
assert.equal(result.data.usage.queries_used, 1);
assert.equal(result.data.usage.search_cost_usd, 0.005);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch builds Exa requests with include/exclude domains and preserves rich result fields", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (_url, init = {}) => {
captured = JSON.parse(String(init.body || "{}"));
return new Response(
JSON.stringify({
results: [
{
title: "Exa result",
url: "https://exa.example.com/page",
highlights: ["Highlighted snippet"],
score: 1.2,
publishedDate: "2026-04-04",
favicon: "https://exa.example.com/favicon.ico",
author: "Author",
image: "https://exa.example.com/image.png",
text: "Full document text",
},
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: "agentic workflows",
provider: "exa-search",
maxResults: 5,
searchType: "news",
domainFilter: ["allowed.com", "-blocked.com"],
credentials: { apiKey: "exa-key" },
log: null,
});
assert.deepEqual(captured, {
query: "agentic workflows",
numResults: 5,
type: "auto",
text: true,
highlights: true,
includeDomains: ["allowed.com"],
excludeDomains: ["blocked.com"],
category: "news",
});
assert.equal(result.success, true);
assert.equal(result.data.results[0].snippet, "Highlighted snippet");
assert.equal(result.data.results[0].score, 1);
assert.equal(result.data.results[0].content.text, "Full document text");
assert.equal(result.data.results[0].metadata.author, "Author");
assert.equal(result.data.results[0].metadata.image_url, "https://exa.example.com/image.png");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch builds Tavily requests with topic and raw content normalization", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (_url, init = {}) => {
captured = JSON.parse(String(init.body || "{}"));
return new Response(
JSON.stringify({
results: [
{
title: "Tavily result",
url: "https://tavily.example.com/page",
content: "Summary",
score: 0.7,
published_date: "2026-04-03",
raw_content: "Long form content",
},
],
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: "policy changes",
provider: "tavily-search",
maxResults: 2,
searchType: "news",
country: "us",
domainFilter: ["good.com", "-bad.com"],
credentials: { apiKey: "tavily-key" },
log: null,
});
assert.deepEqual(captured, {
query: "policy changes",
max_results: 2,
topic: "news",
include_domains: ["good.com"],
exclude_domains: ["bad.com"],
country: "us",
});
assert.equal(result.success, true);
assert.equal(result.data.results[0].snippet, "Summary");
assert.equal(result.data.results[0].content.text, "Long form content");
assert.equal(result.data.results[0].citation.rank, 1);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch rejects queries with invalid control characters", async () => {
const result = await handleSearch({
query: "bad\u0000query",
provider: "serper-search",
maxResults: 5,
searchType: "web",
credentials: { apiKey: "x" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.equal(result.error, "Query contains invalid control characters");
});
test("handleSearch rejects queries that become empty after normalization", async () => {
const result = await handleSearch({
query: " \n\t ",
provider: "serper-search",
maxResults: 5,
searchType: "web",
credentials: { apiKey: "x" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.equal(result.error, "Query is empty after normalization");
});
test("handleSearch rejects unknown providers", async () => {
const result = await handleSearch({
query: "hello",
provider: "unknown-search",
maxResults: 5,
searchType: "web",
credentials: { apiKey: "x" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.equal(result.error, "Unknown search provider: unknown-search");
});
test("handleSearch requires credentials for the configured provider", async () => {
const result = await handleSearch({
query: "hello",
provider: "serper-search",
maxResults: 5,
searchType: "web",
credentials: {},
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 401);
assert.equal(result.error, "No credentials for search provider: serper-search");
});
test("handleSearch fails over to the alternate provider on retriable upstream errors", async () => {
const originalFetch = globalThis.fetch;
const calls = [];
globalThis.fetch = async (url) => {
calls.push(String(url));
if (String(url).startsWith("https://google.serper.dev")) {
return new Response("upstream broken", { status: 500 });
}
return new Response(
JSON.stringify({
web: {
results: [
{
title: "Fallback result",
url: "https://fallback.example.com",
description: "Recovered",
},
],
totalCount: 1,
},
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleSearch({
query: "fallback test",
provider: "serper-search",
maxResults: 5,
searchType: "web",
credentials: { apiKey: "serper-key" },
alternateProvider: "brave-search",
alternateCredentials: { apiKey: "brave-key" },
log: null,
});
assert.equal(result.success, true);
assert.deepEqual(calls, [
"https://google.serper.dev/search",
"https://api.search.brave.com/res/v1/web/search?q=fallback+test&count=5",
]);
assert.equal(result.data.provider, "brave-search");
assert.equal(result.data.results[0].title, "Fallback result");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleSearch does not fail over on non-retriable upstream errors", async () => {
const originalFetch = globalThis.fetch;
let callCount = 0;
globalThis.fetch = async () => {
callCount += 1;
return new Response("unauthorized", { status: 401 });
};
try {
const result = await handleSearch({
query: "no failover",
provider: "serper-search",
maxResults: 5,
searchType: "web",
credentials: { apiKey: "serper-key" },
alternateProvider: "brave-search",
alternateCredentials: { apiKey: "brave-key" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 401);
assert.equal(result.error, "Search provider serper-search returned 401");
assert.equal(callCount, 1);
} finally {
globalThis.fetch = originalFetch;
}
});