Files
openclaw/extensions/google/web-search-provider.test.ts
2026-05-02 05:00:35 +01:00

211 lines
6.5 KiB
TypeScript

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
import { withEnv, withEnvAsync, withFetchPreconnect } from "openclaw/plugin-sdk/test-env";
import { afterEach, describe, expect, it, vi } from "vitest";
import { __testing, createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js";
function installGeminiFetch() {
const mockFetch = vi.fn((_input?: unknown, _init?: unknown) =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
candidates: [
{
content: { parts: [{ text: "Grounded answer" }] },
groundingMetadata: {
groundingChunks: [{ web: { uri: "https://example.com", title: "Example" } }],
},
},
],
}),
} as Response),
);
global.fetch = withFetchPreconnect(mockFetch);
return mockFetch;
}
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
describe("google web search provider", () => {
it("points missing-key users to fetch/browser alternatives", async () => {
await withEnvAsync({ GEMINI_API_KEY: undefined }, async () => {
const provider = createGeminiWebSearchProvider();
const tool = provider.createTool({ config: {}, searchConfig: {} });
if (!tool) {
throw new Error("Expected tool definition");
}
await expect(tool.execute({ query: "OpenClaw docs" })).resolves.toMatchObject({
error: "missing_gemini_api_key",
message: expect.stringContaining("use web_fetch for a specific URL or the browser tool"),
});
});
});
it("falls back to GEMINI_API_KEY from the environment", () => {
withEnv({ GEMINI_API_KEY: "AIza-env-test" }, () => {
expect(__testing.resolveGeminiApiKey()).toBe("AIza-env-test");
});
});
it("prefers configured api keys over env fallbacks", () => {
withEnv({ GEMINI_API_KEY: "AIza-env-test" }, () => {
expect(__testing.resolveGeminiApiKey({ apiKey: "AIza-configured-test" })).toBe(
"AIza-configured-test",
);
});
});
it("stores configured credentials at the canonical plugin config path", () => {
const provider = createGeminiWebSearchProvider();
const config = {} as OpenClawConfig;
provider.setConfiguredCredentialValue?.(config, "AIza-plugin-test");
expect(provider.credentialPath).toBe("plugins.entries.google.config.webSearch.apiKey");
expect(provider.getConfiguredCredentialValue?.(config)).toBe("AIza-plugin-test");
});
it("defaults the Gemini web search model and trims explicit overrides", () => {
expect(__testing.resolveGeminiModel()).toBe("gemini-2.5-flash");
expect(__testing.resolveGeminiModel({ model: " gemini-2.5-pro " })).toBe("gemini-2.5-pro");
});
it("routes Gemini web search through plugin webSearch.baseUrl", async () => {
const mockFetch = installGeminiFetch();
const provider = createGeminiWebSearchProvider();
const tool = provider.createTool({
config: {
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: "AIza-plugin-test",
baseUrl: "https://generativelanguage.googleapis.com/proxy/v1beta/",
},
},
},
},
},
},
searchConfig: { provider: "gemini" },
});
await tool?.execute({ query: "OpenClaw docs" });
expect(String(mockFetch.mock.calls[0]?.[0])).toBe(
"https://generativelanguage.googleapis.com/proxy/v1beta/models/gemini-2.5-flash:generateContent",
);
});
it("passes freshness to Gemini Google Search grounding as a time range", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-15T12:00:00Z"));
const mockFetch = installGeminiFetch();
const provider = createGeminiWebSearchProvider();
const tool = provider.createTool({
config: {
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: "AIza-plugin-test",
},
},
},
},
},
},
searchConfig: { provider: "gemini" },
});
await tool?.execute({ query: "latest ai news", freshness: "week" });
const body = JSON.parse(String(mockFetch.mock.calls[0]?.[1]?.body)) as {
tools?: Array<{ google_search?: { timeRangeFilter?: unknown } }>;
};
expect(body.tools?.[0]?.google_search?.timeRangeFilter).toEqual({
startTime: "2026-04-08T12:00:00.000Z",
endTime: "2026-04-15T12:00:00.000Z",
});
});
it("passes date ranges to Gemini Google Search grounding", async () => {
const mockFetch = installGeminiFetch();
const provider = createGeminiWebSearchProvider();
const tool = provider.createTool({
config: {
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: "AIza-plugin-test",
},
},
},
},
},
},
searchConfig: { provider: "gemini" },
});
await tool?.execute({
query: "OpenClaw release notes",
date_after: "2026-04-01",
date_before: "2026-04-30",
});
const body = JSON.parse(String(mockFetch.mock.calls[0]?.[1]?.body)) as {
tools?: Array<{ google_search?: { timeRangeFilter?: unknown } }>;
};
expect(body.tools?.[0]?.google_search?.timeRangeFilter).toEqual({
startTime: "2026-04-01T00:00:00Z",
endTime: "2026-05-01T00:00:00.000Z",
});
});
it("returns validation errors for invalid Gemini time filters before fetch", async () => {
const mockFetch = installGeminiFetch();
const provider = createGeminiWebSearchProvider();
const tool = provider.createTool({
config: {
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: "AIza-plugin-test",
},
},
},
},
},
},
searchConfig: { provider: "gemini" },
});
await expect(
tool?.execute({
query: "OpenClaw release notes",
freshness: "week",
date_after: "2026-04-01",
}),
).resolves.toMatchObject({
error: "conflicting_time_filters",
});
expect(mockFetch).not.toHaveBeenCalled();
});
it("normalizes Gemini shorthand base URLs", () => {
expect(
__testing.resolveGeminiBaseUrl({ baseUrl: "https://generativelanguage.googleapis.com" }),
).toBe("https://generativelanguage.googleapis.com/v1beta");
});
});