From a3dd1dbaf96b1ab43282d4eb3f8f93b8766b1e64 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 8 Feb 2026 15:28:31 +0900 Subject: [PATCH] test(mcp): restore Tavily tests and add encoding edge case (#1627) --- src/mcp/websearch.test.ts | 151 +++++++++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 18 deletions(-) diff --git a/src/mcp/websearch.test.ts b/src/mcp/websearch.test.ts index 050c4297a..2b09e395d 100644 --- a/src/mcp/websearch.test.ts +++ b/src/mcp/websearch.test.ts @@ -1,18 +1,61 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test" import { createWebsearchConfig } from "./websearch" -declare const describe: (name: string, callback: () => void) => void -declare const test: (name: string, callback: () => void) => void -declare const expect: (value: unknown) => { - toContain: (expected: string) => void - toBeUndefined: () => void -} -declare const process: { env: Record } +describe("websearch MCP provider configuration", () => { + let originalExaApiKey: string | undefined + let originalTavilyApiKey: string | undefined + + beforeEach(() => { + originalExaApiKey = process.env.EXA_API_KEY + originalTavilyApiKey = process.env.TAVILY_API_KEY + + delete process.env.EXA_API_KEY + delete process.env.TAVILY_API_KEY + }) + + afterEach(() => { + if (originalExaApiKey === undefined) { + delete process.env.EXA_API_KEY + } else { + process.env.EXA_API_KEY = originalExaApiKey + } + + if (originalTavilyApiKey === undefined) { + delete process.env.TAVILY_API_KEY + } else { + process.env.TAVILY_API_KEY = originalTavilyApiKey + } + }) + + test("returns Exa config when no config provided", () => { + //#given - no config + + //#when + const result = createWebsearchConfig() + + //#then + expect(result.url).toContain("mcp.exa.ai") + expect(result.url).toContain("tools=web_search_exa") + expect(result.type).toBe("remote") + expect(result.enabled).toBe(true) + }) + + test("returns Exa config when provider is 'exa'", () => { + //#given + const config = { provider: "exa" as const } + + //#when + const result = createWebsearchConfig(config) + + //#then + expect(result.url).toContain("mcp.exa.ai") + expect(result.url).toContain("tools=web_search_exa") + expect(result.type).toBe("remote") + }) -describe("createWebsearchConfig (Exa)", () => { test("appends exaApiKey query param when EXA_API_KEY is set", () => { //#given const apiKey = "test-exa-key-12345" - const originalExaApiKey = process.env.EXA_API_KEY process.env.EXA_API_KEY = apiKey //#when @@ -20,25 +63,97 @@ describe("createWebsearchConfig (Exa)", () => { //#then expect(result.url).toContain(`exaApiKey=${encodeURIComponent(apiKey)}`) - - process.env.EXA_API_KEY = originalExaApiKey }) test("does not set x-api-key header when EXA_API_KEY is set", () => { //#given - const apiKey = "test-exa-key-12345" - const originalExaApiKey = process.env.EXA_API_KEY - process.env.EXA_API_KEY = apiKey + process.env.EXA_API_KEY = "test-exa-key-12345" //#when const result = createWebsearchConfig() //#then expect(result.headers).toBeUndefined() - if (result.headers) { - expect(result.headers["x-api-key"]).toBeUndefined() - } + }) - process.env.EXA_API_KEY = originalExaApiKey + test("URL-encodes EXA_API_KEY when it contains special characters", () => { + //#given an EXA_API_KEY with special characters (+ & =) + const apiKey = "a+b&c=d" + process.env.EXA_API_KEY = apiKey + + //#when createWebsearchConfig is called + const result = createWebsearchConfig() + + //#then the URL contains the properly encoded key via encodeURIComponent + expect(result.url).toContain(`exaApiKey=${encodeURIComponent(apiKey)}`) + }) + + test("returns Tavily config when provider is 'tavily' and TAVILY_API_KEY set", () => { + //#given + const tavilyKey = "test-tavily-key-67890" + process.env.TAVILY_API_KEY = tavilyKey + const config = { provider: "tavily" as const } + + //#when + const result = createWebsearchConfig(config) + + //#then + expect(result.url).toContain("mcp.tavily.com") + expect(result.headers).toEqual({ Authorization: `Bearer ${tavilyKey}` }) + }) + + test("throws error when provider is 'tavily' but TAVILY_API_KEY missing", () => { + //#given + delete process.env.TAVILY_API_KEY + const config = { provider: "tavily" as const } + + //#when + const createTavilyConfig = () => createWebsearchConfig(config) + + //#then + expect(createTavilyConfig).toThrow("TAVILY_API_KEY environment variable is required") + }) + + test("returns Exa when both keys present but no explicit provider", () => { + //#given + const exaKey = "test-exa-key" + process.env.EXA_API_KEY = exaKey + process.env.TAVILY_API_KEY = "test-tavily-key" + + //#when + const result = createWebsearchConfig() + + //#then + expect(result.url).toContain("mcp.exa.ai") + expect(result.url).toContain(`exaApiKey=${encodeURIComponent(exaKey)}`) + expect(result.headers).toBeUndefined() + }) + + test("Tavily config uses Authorization Bearer header format", () => { + //#given + const tavilyKey = "tavily-secret-key-xyz" + process.env.TAVILY_API_KEY = tavilyKey + const config = { provider: "tavily" as const } + + //#when + const result = createWebsearchConfig(config) + + //#then + expect(result.headers?.Authorization).toMatch(/^Bearer /) + expect(result.headers?.Authorization).toBe(`Bearer ${tavilyKey}`) + }) + + test("Exa config has no headers when EXA_API_KEY not set", () => { + //#given + delete process.env.EXA_API_KEY + + //#when + const result = createWebsearchConfig() + + //#then + expect(result.url).toContain("mcp.exa.ai") + expect(result.url).toContain("tools=web_search_exa") + expect(result.url).not.toContain("exaApiKey=") + expect(result.headers).toBeUndefined() }) })