test: add unit tests for Anthropic native web search provider (#49949)

- 15 tests for config resolution, tool version, and server tool building
- 3 tests for stream wrapper (injection, dedup, non-anthropic passthrough)
- All 18 tests green
This commit is contained in:
Henry the Frog 2026-03-19 17:18:13 -06:00
parent a19cb27f9e
commit 706dce9c00
2 changed files with 175 additions and 0 deletions

View File

@ -0,0 +1,53 @@
import { describe, expect, it, vi } from "vitest";
import { createAnthropicNativeSearchStreamWrapper } from "./anthropic-native-search-wrapper.js";
describe("createAnthropicNativeSearchStreamWrapper", () => {
it("injects web_search server tool into anthropic-messages payloads", () => {
const capturedPayloads: any[] = [];
const fakeStream = vi.fn((_model: any, _context: any, options: any) => {
// Simulate calling onPayload
if (options?.onPayload) {
const payload = { tools: [{ type: "function", name: "other_tool" }] };
options.onPayload(payload);
capturedPayloads.push(payload);
}
return { type: "stream", events: [] } as any;
});
const wrapped = createAnthropicNativeSearchStreamWrapper(fakeStream as any);
wrapped({ api: "anthropic-messages" } as any, {} as any, {} as any);
expect(fakeStream).toHaveBeenCalledOnce();
expect(capturedPayloads[0].tools).toHaveLength(2);
expect(capturedPayloads[0].tools[1].type).toMatch(/^web_search_/);
expect(capturedPayloads[0].tools[1].name).toBe("web_search");
});
it("does not inject if web_search server tool already present", () => {
const capturedPayloads: any[] = [];
const fakeStream = vi.fn((_model: any, _context: any, options: any) => {
if (options?.onPayload) {
const payload = { tools: [{ type: "web_search_20260209", name: "web_search" }] };
options.onPayload(payload);
capturedPayloads.push(payload);
}
return { type: "stream", events: [] } as any;
});
const wrapped = createAnthropicNativeSearchStreamWrapper(fakeStream as any);
wrapped({ api: "anthropic-messages" } as any, {} as any, {} as any);
expect(capturedPayloads[0].tools).toHaveLength(1);
});
it("passes through non-anthropic API calls unchanged", () => {
const fakeStream = vi.fn(() => ({ type: "stream" }) as any);
const wrapped = createAnthropicNativeSearchStreamWrapper(fakeStream as any);
wrapped({ api: "openai-chat" } as any, {} as any, { onPayload: vi.fn() } as any);
// onPayload should be the original, not our wrapper
const passedOptions = fakeStream.mock.calls[0][2];
expect(passedOptions.onPayload).toBeDefined();
});
});

View File

@ -0,0 +1,122 @@
import { describe, expect, it } from "vitest";
import { __testing, buildAnthropicWebSearchServerTool } from "./anthropic-web-search-provider.js";
const { resolveAnthropicWebSearchConfig, resolveToolVersion, DEFAULT_TOOL_VERSION } = __testing;
describe("resolveAnthropicWebSearchConfig", () => {
it("returns empty object when no config", () => {
expect(resolveAnthropicWebSearchConfig(undefined)).toEqual({});
expect(resolveAnthropicWebSearchConfig({})).toEqual({});
});
it("returns empty object for non-object anthropic value", () => {
expect(resolveAnthropicWebSearchConfig({ anthropic: "invalid" } as any)).toEqual({});
expect(resolveAnthropicWebSearchConfig({ anthropic: [] } as any)).toEqual({});
});
it("passes through anthropic config object", () => {
const config = { anthropic: { maxUses: 5, allowedDomains: ["example.com"] } };
expect(resolveAnthropicWebSearchConfig(config as any)).toEqual({
maxUses: 5,
allowedDomains: ["example.com"],
});
});
});
describe("resolveToolVersion", () => {
it("defaults to latest version", () => {
expect(resolveToolVersion({})).toBe(DEFAULT_TOOL_VERSION);
});
it("accepts valid versions", () => {
expect(resolveToolVersion({ toolVersion: "web_search_20250305" })).toBe("web_search_20250305");
expect(resolveToolVersion({ toolVersion: "web_search_20260209" })).toBe("web_search_20260209");
});
it("falls back to default for invalid versions", () => {
expect(resolveToolVersion({ toolVersion: "invalid" })).toBe(DEFAULT_TOOL_VERSION);
expect(resolveToolVersion({ toolVersion: " " })).toBe(DEFAULT_TOOL_VERSION);
});
});
describe("buildAnthropicWebSearchServerTool", () => {
it("builds minimal tool with defaults", () => {
const tool = buildAnthropicWebSearchServerTool();
expect(tool).toEqual({
type: DEFAULT_TOOL_VERSION,
name: "web_search",
});
});
it("includes allowed_domains when configured", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: { allowedDomains: ["example.com", "docs.dev"] },
} as any);
expect(tool.allowed_domains).toEqual(["example.com", "docs.dev"]);
});
it("includes blocked_domains when configured", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: { blockedDomains: ["spam.com"] },
} as any);
expect(tool.blocked_domains).toEqual(["spam.com"]);
});
it("includes max_uses when positive", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: { maxUses: 3 },
} as any);
expect(tool.max_uses).toBe(3);
});
it("excludes max_uses when zero or negative", () => {
expect(
buildAnthropicWebSearchServerTool({ anthropic: { maxUses: 0 } } as any).max_uses,
).toBeUndefined();
expect(
buildAnthropicWebSearchServerTool({ anthropic: { maxUses: -1 } } as any).max_uses,
).toBeUndefined();
});
it("includes user_location when configured", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: {
userLocation: {
type: "approximate",
city: "Denver",
region: "Colorado",
country: "US",
timezone: "America/Denver",
},
},
} as any);
expect(tool.user_location).toEqual({
type: "approximate",
city: "Denver",
region: "Colorado",
country: "US",
timezone: "America/Denver",
});
});
it("omits user_location when empty", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: { userLocation: {} },
} as any);
expect(tool.user_location).toBeUndefined();
});
it("respects custom tool version", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: { toolVersion: "web_search_20250305" },
} as any);
expect(tool.type).toBe("web_search_20250305");
});
it("omits empty allowed_domains array", () => {
const tool = buildAnthropicWebSearchServerTool({
anthropic: { allowedDomains: [] },
} as any);
expect(tool.allowed_domains).toBeUndefined();
});
});