mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 23:21:30 +00:00
test: collapse search helper suites
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("duckduckgo plugin", () => {
|
||||
it("registers a keyless web search provider", () => {
|
||||
const webSearchProviders: unknown[] = [];
|
||||
|
||||
plugin.register({
|
||||
registerWebSearchProvider(provider: unknown) {
|
||||
webSearchProviders.push(provider);
|
||||
},
|
||||
} as never);
|
||||
|
||||
expect(plugin.id).toBe("duckduckgo");
|
||||
expect(webSearchProviders).toHaveLength(1);
|
||||
|
||||
const provider = webSearchProviders[0] as Record<string, unknown>;
|
||||
expect(provider.id).toBe("duckduckgo");
|
||||
expect(provider.requiresCredential).toBe(false);
|
||||
expect(provider.envVars).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_DDG_SAFE_SEARCH, resolveDdgRegion, resolveDdgSafeSearch } from "./config.js";
|
||||
|
||||
describe("duckduckgo config", () => {
|
||||
it("reads region from plugin config", () => {
|
||||
expect(
|
||||
resolveDdgRegion({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
region: "de-de",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBe("de-de");
|
||||
});
|
||||
|
||||
it("normalizes empty region to undefined", () => {
|
||||
expect(
|
||||
resolveDdgRegion({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
region: " ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("defaults safeSearch to moderate", () => {
|
||||
expect(resolveDdgSafeSearch(undefined)).toBe(DEFAULT_DDG_SAFE_SEARCH);
|
||||
});
|
||||
|
||||
it("accepts strict and off safeSearch values", () => {
|
||||
expect(
|
||||
resolveDdgSafeSearch({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
safeSearch: "strict",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBe("strict");
|
||||
|
||||
expect(
|
||||
resolveDdgSafeSearch({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
safeSearch: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBe("off");
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __testing } from "./ddg-client.js";
|
||||
|
||||
describe("duckduckgo html parsing", () => {
|
||||
it("decodes direct and redirect urls", () => {
|
||||
expect(
|
||||
__testing.decodeDuckDuckGoUrl(
|
||||
"https://duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dclaw",
|
||||
),
|
||||
).toBe("https://example.com/search?q=claw");
|
||||
expect(__testing.decodeDuckDuckGoUrl("https://example.com")).toBe("https://example.com");
|
||||
});
|
||||
|
||||
it("decodes common html entities", () => {
|
||||
expect(__testing.decodeHtmlEntities("Fish & Chips … 'ok'")).toBe(
|
||||
"Fish & Chips ... 'ok'",
|
||||
);
|
||||
});
|
||||
|
||||
it("parses results when href appears before class", () => {
|
||||
const html = `
|
||||
<a href="https://duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com" class="result__a">
|
||||
Example & Co
|
||||
</a>
|
||||
<a class="result__snippet">Fast search … with details</a>
|
||||
<a class="result__a" href="https://example.org/direct">Direct result</a>
|
||||
<a class="result__snippet">Second snippet</a>
|
||||
`;
|
||||
|
||||
expect(__testing.parseDuckDuckGoHtml(html)).toEqual([
|
||||
{
|
||||
title: "Example & Co",
|
||||
url: "https://example.com",
|
||||
snippet: "Fast search ... with details",
|
||||
},
|
||||
{
|
||||
title: "Direct result",
|
||||
url: "https://example.org/direct",
|
||||
snippet: "Second snippet",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns no results for bot challenge pages", () => {
|
||||
const html = `
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<h1>Are you a human?</h1>
|
||||
<div class="g-recaptcha">captcha</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
expect(__testing.isBotChallenge(html)).toBe(true);
|
||||
expect(__testing.parseDuckDuckGoHtml(html)).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not treat ordinary result snippets mentioning challenge as bot pages", () => {
|
||||
const html = `
|
||||
<a class="result__a" href="https://example.com/challenge">Coding Challenge</a>
|
||||
<a class="result__snippet">A fun coding challenge for interview prep.</a>
|
||||
`;
|
||||
|
||||
expect(__testing.isBotChallenge(html)).toBe(false);
|
||||
expect(__testing.parseDuckDuckGoHtml(html)).toEqual([
|
||||
{
|
||||
title: "Coding Challenge",
|
||||
url: "https://example.com/challenge",
|
||||
snippet: "A fun coding challenge for interview prep.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_DDG_SAFE_SEARCH, resolveDdgRegion, resolveDdgSafeSearch } from "./config.js";
|
||||
|
||||
const { runDuckDuckGoSearch } = vi.hoisted(() => ({
|
||||
runDuckDuckGoSearch: vi.fn(async (params: Record<string, unknown>) => params),
|
||||
@@ -9,15 +10,42 @@ vi.mock("./ddg-client.js", () => ({
|
||||
}));
|
||||
|
||||
describe("duckduckgo web search provider", () => {
|
||||
beforeEach(() => {
|
||||
let createDuckDuckGoWebSearchProvider: typeof import("./ddg-search-provider.js").createDuckDuckGoWebSearchProvider;
|
||||
let ddgClientTesting: typeof import("./ddg-client.js").__testing;
|
||||
let plugin: typeof import("../index.js").default;
|
||||
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
({ createDuckDuckGoWebSearchProvider } = await import("./ddg-search-provider.js"));
|
||||
({ __testing: ddgClientTesting } =
|
||||
await vi.importActual<typeof import("./ddg-client.js")>("./ddg-client.js"));
|
||||
({ default: plugin } = await import("../index.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
runDuckDuckGoSearch.mockReset();
|
||||
runDuckDuckGoSearch.mockImplementation(async (params: Record<string, unknown>) => params);
|
||||
});
|
||||
|
||||
it("exposes keyless metadata and enables the plugin in config", async () => {
|
||||
const { createDuckDuckGoWebSearchProvider } = await import("./ddg-search-provider.js");
|
||||
it("registers a keyless web search provider", () => {
|
||||
const webSearchProviders: unknown[] = [];
|
||||
|
||||
plugin.register({
|
||||
registerWebSearchProvider(provider: unknown) {
|
||||
webSearchProviders.push(provider);
|
||||
},
|
||||
} as never);
|
||||
|
||||
expect(plugin.id).toBe("duckduckgo");
|
||||
expect(webSearchProviders).toHaveLength(1);
|
||||
|
||||
const provider = webSearchProviders[0] as Record<string, unknown>;
|
||||
expect(provider.id).toBe("duckduckgo");
|
||||
expect(provider.requiresCredential).toBe(false);
|
||||
expect(provider.envVars).toEqual([]);
|
||||
});
|
||||
|
||||
it("exposes keyless metadata and enables the plugin in config", () => {
|
||||
const provider = createDuckDuckGoWebSearchProvider();
|
||||
if (!provider.applySelectionConfig) {
|
||||
throw new Error("Expected applySelectionConfig to be defined");
|
||||
@@ -32,7 +60,6 @@ describe("duckduckgo web search provider", () => {
|
||||
});
|
||||
|
||||
it("maps generic tool arguments into DuckDuckGo search params", async () => {
|
||||
const { createDuckDuckGoWebSearchProvider } = await import("./ddg-search-provider.js");
|
||||
const provider = createDuckDuckGoWebSearchProvider();
|
||||
const tool = provider.createTool({
|
||||
config: { test: true },
|
||||
@@ -63,4 +90,138 @@ describe("duckduckgo web search provider", () => {
|
||||
safeSearch: "off",
|
||||
});
|
||||
});
|
||||
|
||||
it("reads region from plugin config and normalizes empty values away", () => {
|
||||
expect(
|
||||
resolveDdgRegion({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
region: "de-de",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBe("de-de");
|
||||
|
||||
expect(
|
||||
resolveDdgRegion({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
region: " ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("defaults safeSearch to moderate and accepts strict and off", () => {
|
||||
expect(resolveDdgSafeSearch(undefined)).toBe(DEFAULT_DDG_SAFE_SEARCH);
|
||||
|
||||
expect(
|
||||
resolveDdgSafeSearch({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
safeSearch: "strict",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBe("strict");
|
||||
|
||||
expect(
|
||||
resolveDdgSafeSearch({
|
||||
plugins: {
|
||||
entries: {
|
||||
duckduckgo: {
|
||||
config: {
|
||||
webSearch: {
|
||||
safeSearch: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never),
|
||||
).toBe("off");
|
||||
});
|
||||
|
||||
it("decodes direct and redirect urls plus common html entities", () => {
|
||||
expect(
|
||||
ddgClientTesting.decodeDuckDuckGoUrl(
|
||||
"https://duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dclaw",
|
||||
),
|
||||
).toBe("https://example.com/search?q=claw");
|
||||
expect(ddgClientTesting.decodeDuckDuckGoUrl("https://example.com")).toBe("https://example.com");
|
||||
expect(ddgClientTesting.decodeHtmlEntities("Fish & Chips … 'ok'")).toBe(
|
||||
"Fish & Chips ... 'ok'",
|
||||
);
|
||||
});
|
||||
|
||||
it("parses results when href appears before class", () => {
|
||||
const html = `
|
||||
<a href="https://duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com" class="result__a">
|
||||
Example & Co
|
||||
</a>
|
||||
<a class="result__snippet">Fast search … with details</a>
|
||||
<a class="result__a" href="https://example.org/direct">Direct result</a>
|
||||
<a class="result__snippet">Second snippet</a>
|
||||
`;
|
||||
|
||||
expect(ddgClientTesting.parseDuckDuckGoHtml(html)).toEqual([
|
||||
{
|
||||
title: "Example & Co",
|
||||
url: "https://example.com",
|
||||
snippet: "Fast search ... with details",
|
||||
},
|
||||
{
|
||||
title: "Direct result",
|
||||
url: "https://example.org/direct",
|
||||
snippet: "Second snippet",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("detects bot challenge pages without flagging ordinary result snippets", () => {
|
||||
const challengeHtml = `
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<h1>Are you a human?</h1>
|
||||
<div class="g-recaptcha">captcha</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
const normalHtml = `
|
||||
<a class="result__a" href="https://example.com/challenge">Coding Challenge</a>
|
||||
<a class="result__snippet">A fun coding challenge for interview prep.</a>
|
||||
`;
|
||||
|
||||
expect(ddgClientTesting.isBotChallenge(challengeHtml)).toBe(true);
|
||||
expect(ddgClientTesting.parseDuckDuckGoHtml(challengeHtml)).toEqual([]);
|
||||
expect(ddgClientTesting.isBotChallenge(normalHtml)).toBe(false);
|
||||
expect(ddgClientTesting.parseDuckDuckGoHtml(normalHtml)).toEqual([
|
||||
{
|
||||
title: "Coding Challenge",
|
||||
url: "https://example.com/challenge",
|
||||
snippet: "A fun coding challenge for interview prep.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import type { Context } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { convertMessages } from "../../node_modules/@mariozechner/pi-ai/dist/providers/google-shared.js";
|
||||
import {
|
||||
asRecord,
|
||||
expectConvertedRoles,
|
||||
makeGeminiCliAssistantMessage,
|
||||
makeGeminiCliModel,
|
||||
makeGoogleAssistantMessage,
|
||||
makeModel,
|
||||
} from "./google-shared.test-helpers.js";
|
||||
|
||||
describe("google-shared convertTools", () => {
|
||||
it("ensures function call comes after user turn, not after model turn", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
},
|
||||
makeGoogleAssistantMessage(model.id, [{ type: "text", text: "Hi!" }]),
|
||||
makeGoogleAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: {},
|
||||
},
|
||||
]),
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessages(model, context);
|
||||
expectConvertedRoles(contents, ["user", "model", "model"]);
|
||||
const toolCallPart = contents[2].parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
expect(toolCall.functionCall).toBeTruthy();
|
||||
});
|
||||
|
||||
it("strips tool call and response ids for google-gemini-cli", () => {
|
||||
const model = makeGeminiCliModel("gemini-3-flash");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Use a tool",
|
||||
},
|
||||
makeGeminiCliAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: { arg: "value" },
|
||||
thoughtSignature: "dGVzdA==",
|
||||
},
|
||||
]),
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call_1",
|
||||
toolName: "myTool",
|
||||
content: [{ type: "text", text: "Tool result" }],
|
||||
isError: false,
|
||||
timestamp: 0,
|
||||
},
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessages(model, context);
|
||||
const parts = contents.flatMap((content) => content.parts ?? []);
|
||||
const toolCallPart = parts.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolResponsePart = parts.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionResponse" in part,
|
||||
);
|
||||
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
const toolResponse = asRecord(toolResponsePart);
|
||||
|
||||
expect(asRecord(toolCall.functionCall).id).toBeUndefined();
|
||||
expect(asRecord(toolResponse.functionResponse).id).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
asRecord,
|
||||
expectConvertedRoles,
|
||||
getFirstToolParameters,
|
||||
makeGeminiCliAssistantMessage,
|
||||
makeGeminiCliModel,
|
||||
makeGoogleAssistantMessage,
|
||||
makeModel,
|
||||
} from "./google-shared.test-helpers.js";
|
||||
@@ -285,4 +287,77 @@ describe("google-shared convertMessages", () => {
|
||||
expect(toolResponse.functionResponse).toBeTruthy();
|
||||
expect(contents[3].role).toBe("user");
|
||||
});
|
||||
|
||||
it("ensures function call comes after user turn, not after model turn", () => {
|
||||
const model = makeModel("gemini-1.5-pro");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
},
|
||||
makeGoogleAssistantMessage(model.id, [{ type: "text", text: "Hi!" }]),
|
||||
makeGoogleAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: {},
|
||||
},
|
||||
]),
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessages(model, context);
|
||||
expectConvertedRoles(contents, ["user", "model", "model"]);
|
||||
const toolCallPart = contents[2].parts?.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
expect(toolCall.functionCall).toBeTruthy();
|
||||
});
|
||||
|
||||
it("strips tool call and response ids for google-gemini-cli", () => {
|
||||
const model = makeGeminiCliModel("gemini-3-flash");
|
||||
const context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Use a tool",
|
||||
},
|
||||
makeGeminiCliAssistantMessage(model.id, [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "call_1",
|
||||
name: "myTool",
|
||||
arguments: { arg: "value" },
|
||||
thoughtSignature: "dGVzdA==",
|
||||
},
|
||||
]),
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call_1",
|
||||
toolName: "myTool",
|
||||
content: [{ type: "text", text: "Tool result" }],
|
||||
isError: false,
|
||||
timestamp: 0,
|
||||
},
|
||||
],
|
||||
} as unknown as Context;
|
||||
|
||||
const contents = convertMessages(model, context);
|
||||
const parts = contents.flatMap((content) => content.parts ?? []);
|
||||
const toolCallPart = parts.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionCall" in part,
|
||||
);
|
||||
const toolResponsePart = parts.find(
|
||||
(part) => typeof part === "object" && part !== null && "functionResponse" in part,
|
||||
);
|
||||
|
||||
const toolCall = asRecord(toolCallPart);
|
||||
const toolResponse = asRecord(toolResponsePart);
|
||||
|
||||
expect(asRecord(toolCall.functionCall).id).toBeUndefined();
|
||||
expect(asRecord(toolResponse.functionResponse).id).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __testing } from "./grok-web-search-provider.js";
|
||||
|
||||
describe("grok web search provider helpers", () => {
|
||||
it("prefers configured api keys and resolves grok scoped defaults", () => {
|
||||
expect(__testing.resolveGrokApiKey({ apiKey: "xai-secret" })).toBe("xai-secret");
|
||||
expect(__testing.resolveGrokModel()).toBe("grok-4-1-fast");
|
||||
expect(__testing.resolveGrokInlineCitations()).toBe(false);
|
||||
});
|
||||
|
||||
it("reads grok-specific overrides from scoped config", () => {
|
||||
expect(__testing.resolveGrokModel({ model: "xai/grok-4-fast" })).toBe("xai/grok-4-fast");
|
||||
expect(__testing.resolveGrokInlineCitations({ inlineCitations: true })).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __testing } from "./web-search-shared.js";
|
||||
|
||||
describe("xai web search shared helpers", () => {
|
||||
it("uses sane defaults for model and inline citations", () => {
|
||||
expect(__testing.resolveXaiWebSearchModel()).toBe(__testing.XAI_DEFAULT_WEB_SEARCH_MODEL);
|
||||
expect(__testing.resolveXaiInlineCitations()).toBe(false);
|
||||
});
|
||||
|
||||
it("reads grok-scoped overrides for model and inline citations", () => {
|
||||
const searchConfig = {
|
||||
grok: {
|
||||
model: "xai/grok-4-fast",
|
||||
inlineCitations: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(__testing.resolveXaiWebSearchModel(searchConfig)).toBe("xai/grok-4-fast");
|
||||
expect(__testing.resolveXaiInlineCitations(searchConfig)).toBe(true);
|
||||
});
|
||||
|
||||
it("extracts text and deduplicated citations from response output", () => {
|
||||
expect(
|
||||
__testing.extractXaiWebSearchContent({
|
||||
output: [
|
||||
{
|
||||
type: "message",
|
||||
content: [
|
||||
{
|
||||
type: "output_text",
|
||||
text: "hello",
|
||||
annotations: [
|
||||
{ type: "url_citation", url: "https://a.test" },
|
||||
{ type: "url_citation", url: "https://a.test" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
text: "hello",
|
||||
annotationCitations: ["https://a.test"],
|
||||
});
|
||||
});
|
||||
|
||||
it("builds wrapped payloads with optional inline citations", () => {
|
||||
expect(
|
||||
__testing.buildXaiWebSearchPayload({
|
||||
query: "q",
|
||||
provider: "grok",
|
||||
model: "grok-4-fast",
|
||||
tookMs: 12,
|
||||
content: "body",
|
||||
citations: ["https://a.test"],
|
||||
}),
|
||||
).toMatchObject({
|
||||
query: "q",
|
||||
provider: "grok",
|
||||
model: "grok-4-fast",
|
||||
tookMs: 12,
|
||||
citations: ["https://a.test"],
|
||||
externalContent: expect.objectContaining({ wrapped: true }),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,12 +4,19 @@ import {
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { withEnv } from "../../test/helpers/extensions/env.js";
|
||||
import { __testing as grokProviderTesting } from "./src/grok-web-search-provider.js";
|
||||
import { __testing } from "./web-search.js";
|
||||
|
||||
const { extractXaiWebSearchContent, resolveXaiInlineCitations, resolveXaiWebSearchModel } =
|
||||
__testing;
|
||||
|
||||
describe("xai web search config resolution", () => {
|
||||
it("prefers configured api keys and resolves grok scoped defaults", () => {
|
||||
expect(grokProviderTesting.resolveGrokApiKey({ apiKey: "xai-secret" })).toBe("xai-secret");
|
||||
expect(grokProviderTesting.resolveGrokModel()).toBe("grok-4-1-fast");
|
||||
expect(grokProviderTesting.resolveGrokInlineCitations()).toBe(false);
|
||||
});
|
||||
|
||||
it("uses config apiKey when provided", () => {
|
||||
const searchConfig = { grok: { apiKey: "xai-test-key" } }; // pragma: allowlist secret
|
||||
expect(
|
||||
@@ -66,6 +73,26 @@ describe("xai web search config resolution", () => {
|
||||
expect(resolveXaiInlineCitations({ grok: { inlineCitations: true } })).toBe(true);
|
||||
expect(resolveXaiInlineCitations({ grok: { inlineCitations: false } })).toBe(false);
|
||||
});
|
||||
|
||||
it("builds wrapped payloads with optional inline citations", () => {
|
||||
expect(
|
||||
grokProviderTesting.buildXaiWebSearchPayload({
|
||||
query: "q",
|
||||
provider: "grok",
|
||||
model: "grok-4-fast",
|
||||
tookMs: 12,
|
||||
content: "body",
|
||||
citations: ["https://a.test"],
|
||||
}),
|
||||
).toMatchObject({
|
||||
query: "q",
|
||||
provider: "grok",
|
||||
model: "grok-4-fast",
|
||||
tookMs: 12,
|
||||
citations: ["https://a.test"],
|
||||
externalContent: expect.objectContaining({ wrapped: true }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("xai web search response parsing", () => {
|
||||
|
||||
Reference in New Issue
Block a user