mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 03:10:36 +00:00
test(tools): polish image auth regression and fix agents vitest routing
This commit is contained in:
@@ -53,6 +53,10 @@ const AGENTS_PI_EMBEDDED_VITEST_CONFIG = "test/vitest/vitest.agents-pi-embedded.
|
||||
const AGENTS_SUPPORT_VITEST_CONFIG = "test/vitest/vitest.agents-support.config.ts";
|
||||
const AGENTS_TOOLS_VITEST_CONFIG = "test/vitest/vitest.agents-tools.config.ts";
|
||||
const AGENTS_VITEST_CONFIG = "test/vitest/vitest.agents.config.ts";
|
||||
const AGENTS_CORE_VITEST_CONFIG = "test/vitest/vitest.agents-core.config.ts";
|
||||
const AGENTS_PI_EMBEDDED_VITEST_CONFIG = "test/vitest/vitest.agents-pi-embedded.config.ts";
|
||||
const AGENTS_SUPPORT_VITEST_CONFIG = "test/vitest/vitest.agents-support.config.ts";
|
||||
const AGENTS_TOOLS_VITEST_CONFIG = "test/vitest/vitest.agents-tools.config.ts";
|
||||
const ACP_VITEST_CONFIG = "test/vitest/vitest.acp.config.ts";
|
||||
const AUTO_REPLY_CORE_VITEST_CONFIG = "test/vitest/vitest.auto-reply-core.config.ts";
|
||||
const AUTO_REPLY_VITEST_CONFIG = "test/vitest/vitest.auto-reply.config.ts";
|
||||
@@ -240,6 +244,10 @@ const VITEST_CONFIG_BY_KIND = {
|
||||
agentSupport: AGENTS_SUPPORT_VITEST_CONFIG,
|
||||
agentTools: AGENTS_TOOLS_VITEST_CONFIG,
|
||||
agent: AGENTS_VITEST_CONFIG,
|
||||
agentsCore: AGENTS_CORE_VITEST_CONFIG,
|
||||
agentsPiEmbedded: AGENTS_PI_EMBEDDED_VITEST_CONFIG,
|
||||
agentsSupport: AGENTS_SUPPORT_VITEST_CONFIG,
|
||||
agentsTools: AGENTS_TOOLS_VITEST_CONFIG,
|
||||
autoReplyCore: AUTO_REPLY_CORE_VITEST_CONFIG,
|
||||
autoReplyReply: AUTO_REPLY_REPLY_VITEST_CONFIG,
|
||||
autoReplyTopLevel: AUTO_REPLY_TOP_LEVEL_VITEST_CONFIG,
|
||||
@@ -1713,6 +1721,10 @@ export function buildVitestRunPlans(
|
||||
"agentSupport",
|
||||
"agentTools",
|
||||
"agent",
|
||||
"agentsCore",
|
||||
"agentsPiEmbedded",
|
||||
"agentsSupport",
|
||||
"agentsTools",
|
||||
"plugin",
|
||||
"ui",
|
||||
"uiE2e",
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { ModelDefinitionConfig } from "../../config/types.models.js";
|
||||
import type { ImageDescriptionRequest } from "../../plugin-sdk/media-understanding.js";
|
||||
import { getApiKeyForModel, hasUsableCustomProviderApiKey } from "../model-auth.js";
|
||||
import { resolveImageToolFactoryAvailable } from "../openclaw-tools.media-factory-plan.js";
|
||||
import { createImageTool, resolveImageModelConfigForTool, testing } from "./image-tool.js";
|
||||
import { hasProviderAuthForTool } from "./model-config.helpers.js";
|
||||
|
||||
const USER_PROVIDER = "hatchery-qwen3.6-plus";
|
||||
const USER_MODEL = "qwen3.6-plus";
|
||||
const USER_PRIMARY = `${USER_PROVIDER}/${USER_MODEL}`;
|
||||
const CONFIG_API_KEY = "sk-user-configured-key"; // pragma: allowlist secret
|
||||
|
||||
const ONE_PIXEL_PNG_B64 =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAHdElNRQfqBBsGAQr00ED3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI2LTA0LTI3VDA2OjAxOjEwKzAwOjAwPU3tXwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNi0wNC0yN1QwNjowMToxMCswMDowMEwQVeMAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjYtMDQtMjdUMDY6MDE6MTArMDA6MDAbBXQ8AAAAeElEQVRo3u3awQnDQBAEwT2Q8w/YAikIP5rF1RFMca+FO8/s7rrnqjcA1BsA6g0A9QaAesOfA77zqTf8Blj/AgAAAAAAAJsDqAOoA6gDqAOoc9TXAdQB1AHUAdQB1AHUAdQB1AHU7Qc46gEAAAAANrcecGZ2f8B/ASYSQPlKoEJ/AAAAAElFTkSuQmCC";
|
||||
|
||||
function makeVisionModel(id: string): ModelDefinitionConfig {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8_192,
|
||||
};
|
||||
}
|
||||
|
||||
function createUserReportedConfig(params?: { includeApiKey?: boolean }): OpenClawConfig {
|
||||
const includeApiKey = params?.includeApiKey ?? true;
|
||||
return {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: USER_PRIMARY },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
[USER_PROVIDER]: {
|
||||
baseUrl: "https://example.com/v1",
|
||||
api: "openai-completions",
|
||||
...(includeApiKey ? { apiKey: CONFIG_API_KEY } : {}),
|
||||
models: [makeVisionModel(USER_MODEL)],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function withEmptyAgentDir<T>(run: (agentDir: string) => Promise<T>): Promise<T> {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-image-auth-regression-"));
|
||||
try {
|
||||
return await run(agentDir);
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("image custom provider auth regression", () => {
|
||||
const priorFetch = global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
for (const key of Object.keys(process.env)) {
|
||||
if (key.endsWith("_API_KEY") || key.endsWith("_OAUTH_TOKEN")) {
|
||||
vi.stubEnv(key, "");
|
||||
}
|
||||
}
|
||||
testing.setProviderDepsForTest({
|
||||
buildProviderRegistry: () => new Map(),
|
||||
getMediaUnderstandingProvider: () => undefined,
|
||||
describeImageWithModel: async (params: ImageDescriptionRequest) => ({
|
||||
text: `seen:${params.provider}/${params.model}`,
|
||||
model: params.model,
|
||||
}),
|
||||
describeImagesWithModel: async (params) => ({
|
||||
text: `seen:${params.provider}/${params.model}`,
|
||||
model: params.model,
|
||||
}),
|
||||
resolveAutoMediaKeyProviders: () => [],
|
||||
resolveDefaultMediaModel: () => undefined,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
global.fetch = priorFetch;
|
||||
testing.setProviderDepsForTest(undefined);
|
||||
});
|
||||
|
||||
it("uses real model-auth to accept config-only custom provider credentials", async () => {
|
||||
const cfg = createUserReportedConfig();
|
||||
expect(hasUsableCustomProviderApiKey(cfg, USER_PROVIDER)).toBe(true);
|
||||
expect(hasProviderAuthForTool({ provider: USER_PROVIDER, cfg })).toBe(true);
|
||||
});
|
||||
|
||||
it("auto-discovers the user-reported vision model without env key or auth profile", async () => {
|
||||
await withEmptyAgentDir(async (agentDir) => {
|
||||
const cfg = createUserReportedConfig();
|
||||
expect(resolveImageModelConfigForTool({ cfg, agentDir })).toEqual({
|
||||
primary: USER_PRIMARY,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("registers the image tool on the production factory path when the primary model has vision", async () => {
|
||||
await withEmptyAgentDir(async (agentDir) => {
|
||||
const cfg = createUserReportedConfig();
|
||||
expect(
|
||||
resolveImageToolFactoryAvailable({
|
||||
config: cfg,
|
||||
agentDir,
|
||||
modelHasVision: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("executes deferred image tool discovery with config-backed auth and runtime key resolution", async () => {
|
||||
await withEmptyAgentDir(async (agentDir) => {
|
||||
const cfg = createUserReportedConfig();
|
||||
const auth = await getApiKeyForModel({
|
||||
model: {
|
||||
id: USER_MODEL,
|
||||
name: USER_MODEL,
|
||||
provider: USER_PROVIDER,
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://example.com/v1",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8_192,
|
||||
},
|
||||
cfg,
|
||||
agentDir,
|
||||
});
|
||||
expect(auth.apiKey).toBe(CONFIG_API_KEY);
|
||||
expect(auth.source).toContain("models.json");
|
||||
|
||||
const tool = createImageTool({
|
||||
config: cfg,
|
||||
agentDir,
|
||||
deferAutoModelResolution: true,
|
||||
modelHasVision: true,
|
||||
});
|
||||
expect(typeof tool?.execute).toBe("function");
|
||||
|
||||
const result = await tool!.execute("regression-1", {
|
||||
prompt: "Read this screenshot.",
|
||||
image: `data:image/png;base64,${ONE_PIXEL_PNG_B64}`,
|
||||
});
|
||||
|
||||
const payload = result as { content?: Array<{ type?: string; text?: string }> };
|
||||
const text = payload.content?.find((entry) => entry.type === "text")?.text ?? "";
|
||||
expect(text).toContain(`seen:${USER_PRIMARY}`);
|
||||
expect(text).not.toMatch(/No image model is configured/i);
|
||||
});
|
||||
});
|
||||
|
||||
it("still rejects the same config when apiKey is missing", async () => {
|
||||
await withEmptyAgentDir(async (agentDir) => {
|
||||
const cfg = createUserReportedConfig({ includeApiKey: false });
|
||||
expect(hasUsableCustomProviderApiKey(cfg, USER_PROVIDER)).toBe(false);
|
||||
expect(hasProviderAuthForTool({ provider: USER_PROVIDER, cfg })).toBe(false);
|
||||
expect(resolveImageModelConfigForTool({ cfg, agentDir })).toBeNull();
|
||||
|
||||
const tool = createImageTool({
|
||||
config: cfg,
|
||||
agentDir,
|
||||
deferAutoModelResolution: true,
|
||||
modelHasVision: true,
|
||||
});
|
||||
await expect(
|
||||
tool!.execute("regression-2", {
|
||||
prompt: "Read this screenshot.",
|
||||
image: `data:image/png;base64,${ONE_PIXEL_PNG_B64}`,
|
||||
}),
|
||||
).rejects.toThrow(/No image model is configured/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -936,6 +936,24 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
expect(repoSourceReads.length).toBeLessThan(100);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"test/vitest/vitest.agents-core.config.ts",
|
||||
"test/vitest/vitest.agents-pi-embedded.config.ts",
|
||||
"test/vitest/vitest.agents-support.config.ts",
|
||||
"test/vitest/vitest.agents-tools.config.ts",
|
||||
])("routes split agents vitest config %s to itself", (target) => {
|
||||
const plans = buildVitestRunPlans([target], process.cwd());
|
||||
|
||||
expect(plans).toEqual([
|
||||
{
|
||||
config: target,
|
||||
forwardedArgs: [],
|
||||
includePatterns: null,
|
||||
watchMode: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"src/gateway/gateway.test.ts",
|
||||
"src/gateway/server.startup-matrix-migration.integration.test.ts",
|
||||
|
||||
Reference in New Issue
Block a user