Files
oh-my-pi/packages/coding-agent/test/tools/web-search-code-context.test.ts
T
can1357 7aafa7547a feat(coding-agent): introduced pluggable code search provider system
- Added pluggable code search provider system supporting Exa and grep.app with provider selection via `providers.codeSearch` setting.
- Removed Exa-specific tools (`exa_linkedin`, `exa_company`, `exa_search_deep`, `exa_crawl`) and simplified web search tools to focus on core functionality.
- Refactored code search from Exa-only to provider-agnostic architecture with new `code_search` tool supporting context-aware grep.app queries and Exa fallback.
- Removed `exa.enableLinkedin` and `exa.enableCompany` configuration settings in favor of provider-based architecture.
- Added comprehensive test coverage for code search functionality including grep.app result normalization and provider fallback behavior.
2026-03-13 15:05:10 +01:00

176 lines
6.3 KiB
TypeScript

import { afterEach, describe, expect, it } from "bun:test";
import { hookFetch } from "@oh-my-pi/pi-utils";
import {
executeCodeSearch,
searchCodeWithGrep,
setPreferredCodeSearchProvider,
} from "../../src/web/search/code-search";
function getFirstTextContent(result: { content: Array<{ type: string; text?: string }> }): string {
const firstContent = result.content[0];
if (firstContent?.type === "text") return firstContent.text ?? "";
return "";
}
describe("code_search", () => {
afterEach(() => {
setPreferredCodeSearchProvider("exa");
});
it("maps grep.app results into normalized code search sources", async () => {
let requestedUrl = "";
using _hook = hookFetch(input => {
requestedUrl = String(input);
return new Response(
JSON.stringify({
hits: {
total: 42,
hits: [
{
repo: "misc/example",
branch: "main",
path: "src/other.ts",
content: {
snippet:
'<table class="highlight-table"><tr data-line="2"><td><div class="lineno">2</div></td><td><div class="highlight"><pre><span class="kd">const</span><span class="w"> </span><span class="nx">withResolvers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"polyfill"</span></pre></div></td></tr></table>',
},
total_matches: "2",
},
{
repo: "oven-sh/bun",
branch: "main",
path: "src/runtime.ts",
content: {
snippet:
'<table class="highlight-table"><tr data-line="12"><td><div class="lineno">12</div></td><td><div class="highlight"><pre><span class="kd">const</span><span class="w"> </span><span class="nx"><mark>Promise</mark>.withResolvers</span><span class="p">();</span></pre></div></td></tr><tr data-line="13"><td><div class="lineno">13</div></td><td><div class="highlight"><pre><span class="k">return</span><span class="w"> </span><span class="nx">pair</span><span class="p">;</span></pre></div></td></tr></table>',
},
total_matches: "12",
},
],
},
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
});
const result = await searchCodeWithGrep({
query: "Promise.withResolvers",
code_context: "bun runtime",
});
expect(requestedUrl).toContain("https://grep.app/api/search?q=Promise.withResolvers");
expect(result).toMatchObject({
provider: "grep",
query: "Promise.withResolvers",
totalResults: 42,
sources: [
{
title: "oven-sh/bun/src/runtime.ts",
url: "https://github.com/oven-sh/bun/blob/main/src/runtime.ts",
repository: "oven-sh/bun",
path: "src/runtime.ts",
branch: "main",
totalMatches: "12",
},
],
});
expect(result.sources[0]?.snippet).toContain("12: const Promise.withResolvers();");
expect(result.sources[0]?.snippet).toContain("13: return pair;");
expect(result.sources[0]?.repository).toBe("oven-sh/bun");
});
it("does not append code_context to grep.app q", async () => {
let requestedUrl = "";
using _hook = hookFetch(input => {
requestedUrl = String(input);
return new Response(JSON.stringify({ hits: { total: 0, hits: [] } }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
await searchCodeWithGrep({ query: "DIRENV_LOG_FORMAT", code_context: "direnv shell" });
expect(requestedUrl).toContain("https://grep.app/api/search?q=DIRENV_LOG_FORMAT");
expect(requestedUrl).not.toContain("direnv");
});
it("executes code search through the configured grep provider", async () => {
setPreferredCodeSearchProvider("grep");
using _hook = hookFetch(
() =>
new Response(
JSON.stringify({
hits: {
total: 1,
hits: [
{
repo: "facebook/react",
branch: "main",
path: "packages/react/src/ReactHooks.js",
content: {
snippet:
'<table class="highlight-table"><tr data-line="101"><td><div class="lineno">101</div></td><td><div class="highlight"><pre><span class="k">export</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">useState</span><span class="p">()</span></pre></div></td></tr></table>',
},
total_matches: "1",
},
],
},
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
const result = await executeCodeSearch({ query: "useState" });
const output = getFirstTextContent(result);
expect(output).toContain("Code search via grep");
expect(output).toContain("https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js");
expect(result.details?.provider).toBe("grep");
expect(result.details?.response?.sources[0]?.snippet).toContain("101: export function useState()");
});
it("preserves Exa raw-response fallback for code search", async () => {
setPreferredCodeSearchProvider("exa");
using _hook = hookFetch(
() =>
new Response(
JSON.stringify({
result: {
content: [
{
type: "text",
text: "Need the official or source-backed way to silence direnv loading output.",
},
],
},
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
const result = await executeCodeSearch({ query: "DIRENV_LOG_FORMAT direnv loading silence" });
const output = getFirstTextContent(result);
expect(output).toContain("Code search via exa");
expect(output).toContain("Need the official or source-backed way to silence direnv loading output.");
expect(result.details?.provider).toBe("exa");
expect(result.details?.response?.sources[0]?.snippet).toContain(
"Need the official or source-backed way to silence direnv loading output.",
);
});
it("returns a structured error when grep.app responds with an unexpected shape", async () => {
setPreferredCodeSearchProvider("grep");
using _hook = hookFetch(
() =>
new Response(JSON.stringify({ hits: { total: 1, hits: [{ repo: "missing-fields" }] } }), { status: 200 }),
);
const result = await executeCodeSearch({ query: "broken" });
expect(getFirstTextContent(result)).toBe("Error: grep.app returned an unexpected response shape.");
expect(result.details?.provider).toBe("grep");
expect(result.details?.error).toBe("grep.app returned an unexpected response shape.");
});
});