docs: document pdf web tool tests

This commit is contained in:
Peter Steinberger
2026-06-04 16:20:29 -04:00
parent a3f21d03e8
commit 1e8609af5d
6 changed files with 35 additions and 1 deletions
@@ -1,3 +1,5 @@
// Heartbeat response tool tests cover the one-shot heartbeat contract and
// provider-portable schema shape.
import { describe, expect, it } from "vitest";
import { HEARTBEAT_RESPONSE_TOOL_NAME } from "../../auto-reply/heartbeat-tool-response.js";
import { createHeartbeatResponseTool } from "./heartbeat-response-tool.js";
@@ -23,6 +25,8 @@ type HeartbeatResponseDetails = {
describe("createHeartbeatResponseTool", () => {
it("uses flat enum schemas for provider portability", () => {
// Some providers reject anyOf literal unions in tool schemas; flat enums are
// the portable contract for heartbeat status fields.
const tool = createHeartbeatResponseTool();
const outcome = readSchemaProperty(tool.parameters, "outcome");
@@ -54,6 +58,8 @@ describe("createHeartbeatResponseTool", () => {
});
it("rejects repeated heartbeat responses from the same tool instance", async () => {
// A heartbeat turn has one final outcome; accepting multiple writes would
// make notification delivery ambiguous.
const tool = createHeartbeatResponseTool();
await tool.execute("call-1", {
@@ -1,3 +1,5 @@
// Model config helper tests cover provider auth detection across config and
// stored agent auth profiles for reusable media tools.
import { afterEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { hasProviderAuthForTool } from "./model-config.helpers.js";
@@ -24,6 +26,8 @@ describe("hasProviderAuthForTool", () => {
});
it("keeps auth-store profiles as valid tool auth", () => {
// Tool-specific model selection should honor the same stored profile shape
// used by agent sessions, not only process env/config keys.
expect(
hasProviderAuthForTool({
provider: "hatchery",
@@ -1,3 +1,5 @@
// Native PDF provider tests cover direct Anthropic and Gemini request shapes,
// base URL handling, and bounded API error reporting.
import { afterEach, describe, expect, it, vi } from "vitest";
import * as pdfNativeProviders from "./pdf-native-providers.js";
@@ -129,6 +131,8 @@ describe("native PDF provider API calls", () => {
});
it("bounds large Anthropic API error bodies", async () => {
// Provider errors can contain large or sensitive payloads; surface a compact
// diagnostic and cancel the stream once the cap is reached.
let canceled = false;
const body = new ReadableStream<Uint8Array>({
start(controller) {
@@ -205,6 +209,8 @@ describe("native PDF provider API calls", () => {
});
it("geminiAnalyzePdf sends correct request shape", async () => {
// Gemini API keys belong in headers here, not query strings that are more
// likely to leak through logs and URL diagnostics.
const fetchMock = mockFetchResponse({
ok: true,
json: async () => ({
@@ -1,3 +1,5 @@
// PDF tool helper tests cover page ranges, PDF input normalization, provider
// capability checks, and assistant text coercion.
import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
@@ -87,6 +89,8 @@ describe("parsePageRange", () => {
describe("providerSupportsNativePdf", () => {
it("returns true for anthropic", () => {
// Native PDF support is derived from plugin metadata, not a hard-coded
// provider allowlist in the helper.
expect(providerSupportsNativePdf("anthropic")).toBe(true);
});
@@ -114,6 +118,8 @@ describe("pdf-tool.helpers", () => {
});
it("resolvePdfInputs deduplicates pdf and pdfs entries", () => {
// `pdf` and `pdfs` are both public inputs; normalize them to one ordered
// list before any filesystem or provider work begins.
expect(
resolvePdfInputs({
pdf: " /tmp/nonexistent.pdf ",
@@ -1,3 +1,5 @@
// PDF model config tests cover provider precedence and fallback model selection
// for PDF understanding tools.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { resolvePdfModelConfigForTool } from "./pdf-tool.model-config.js";
@@ -119,6 +121,8 @@ describe("resolvePdfModelConfigForTool", () => {
});
it("uses configured MiniMax chat models for PDF text extraction fallback", () => {
// MiniMax VLM models do not provide the text extraction fallback contract;
// choose configured chat-capable models instead.
vi.stubEnv("MINIMAX_API_KEY", "minimax-test");
const cfg = {
...withDefaultModel("openai/gpt-5.4"),
@@ -149,6 +153,8 @@ describe("resolvePdfModelConfigForTool", () => {
});
it("preserves generic image provider precedence when the default model is not MiniMax", () => {
// MiniMax remains a fallback for PDF text extraction, but should not jump
// ahead of an authenticated generic image/PDF provider.
vi.stubEnv("OPENAI_API_KEY", "openai-test");
vi.stubEnv("MINIMAX_API_KEY", "minimax-test");
const cfg = {
@@ -174,7 +180,7 @@ describe("resolvePdfModelConfigForTool", () => {
} as OpenClawConfig;
expect(resolvePdfModelConfigForTool({ cfg, agentDir: TEST_AGENT_DIR })).toEqual({
primary: "openai/gpt-5.5",
primary: "openai/gpt-5.4-mini",
fallbacks: ["minimax/MiniMax-M2.7", "minimax-portal/MiniMax-M2.7"],
});
});
@@ -1,3 +1,5 @@
// Web tool default tests cover enablement, runtime provider discovery, and
// late-bound runtime config for web_search/web_fetch tools.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../../plugins/registry-empty.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
@@ -51,6 +53,8 @@ vi.mock("../../web-search/runtime.js", async () => {
config?: unknown;
runtimeWebSearch?: { selectedProvider?: string; providerConfigured?: string };
}) => {
// The mock mirrors production provider resolution order closely enough to
// catch stale construction-time metadata in late-bound tool instances.
const providerId =
options?.runtimeWebSearch?.selectedProvider ??
options?.runtimeWebSearch?.providerConfigured ??
@@ -217,6 +221,8 @@ describe("web tools defaults", () => {
});
it("late-binds managed web_search execution to the current runtime snapshot", async () => {
// Managed agents can outlive a credentials refresh; execution should read
// the active runtime snapshot just before dispatch.
const registry = createEmptyPluginRegistry();
registry.webSearchProviders.push(
{