test: speed up provider and security tests

This commit is contained in:
Peter Steinberger
2026-04-26 07:59:27 +01:00
parent d1e5f4bd3c
commit 54f8e4145e
10 changed files with 149 additions and 274 deletions

View File

@@ -1,32 +0,0 @@
import { beforeAll, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
getProviderRegistryAllowlistMocks,
installProviderRegistryAllowlistMockDefaults,
primeBundledProviderAllowlistFallback,
} from "../test-utils/provider-registry-allowlist.test-helpers.js";
let getImageGenerationProvider: typeof import("./provider-registry.js").getImageGenerationProvider;
let listImageGenerationProviders: typeof import("./provider-registry.js").listImageGenerationProviders;
const mocks = getProviderRegistryAllowlistMocks();
installProviderRegistryAllowlistMockDefaults();
describe("image-generation provider registry allowlist fallback", () => {
beforeAll(async () => {
({ getImageGenerationProvider, listImageGenerationProviders } =
await import("./provider-registry.js"));
});
it("adds bundled capability plugin ids to plugins.allow before fallback registry load", () => {
const { cfg, compatConfig } = primeBundledProviderAllowlistFallback({
contractKey: "imageGenerationProviders",
});
expect(listImageGenerationProviders(cfg as OpenClawConfig)).toEqual([]);
expect(getImageGenerationProvider("openai", cfg as OpenClawConfig)).toBeUndefined();
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: compatConfig,
activate: false,
});
});
});

View File

@@ -1,4 +1,5 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.js";
import type { ImageGenerationProviderPlugin } from "../plugins/types.js";
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
@@ -40,10 +41,12 @@ describe("image-generation provider registry", () => {
});
it("delegates provider resolution to the capability provider boundary", () => {
expect(listImageGenerationProviders()).toEqual([]);
const cfg = {} as OpenClawConfig;
expect(listImageGenerationProviders(cfg)).toEqual([]);
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "imageGenerationProviders",
cfg: undefined,
cfg,
});
});

View File

@@ -1,33 +0,0 @@
import { beforeAll, describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/types.js";
import {
getProviderRegistryAllowlistMocks,
installProviderRegistryAllowlistMockDefaults,
primeBundledProviderAllowlistFallback,
} from "../test-utils/provider-registry-allowlist.test-helpers.js";
let buildMediaUnderstandingRegistry: typeof import("./provider-registry.js").buildMediaUnderstandingRegistry;
let getMediaUnderstandingProvider: typeof import("./provider-registry.js").getMediaUnderstandingProvider;
const mocks = getProviderRegistryAllowlistMocks();
installProviderRegistryAllowlistMockDefaults();
describe("media-understanding provider registry allowlist fallback", () => {
beforeAll(async () => {
({ buildMediaUnderstandingRegistry, getMediaUnderstandingProvider } =
await import("./provider-registry.js"));
});
it("adds bundled capability plugin ids to plugins.allow before fallback registry load", () => {
const { cfg, compatConfig } = primeBundledProviderAllowlistFallback({
contractKey: "mediaUnderstandingProviders",
});
const registry = buildMediaUnderstandingRegistry(undefined, cfg as OpenClawConfig);
expect(getMediaUnderstandingProvider("openai", registry)).toBeUndefined();
expect(mocks.resolveRuntimePluginRegistry).toHaveBeenCalledWith({
config: compatConfig,
activate: false,
});
});
});

View File

@@ -1,88 +1,53 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
buildMediaUnderstandingRegistry,
getMediaUnderstandingProvider,
} from "./provider-registry.js";
import type { MediaUnderstandingProvider } from "./types.js";
vi.mock("../plugins/capability-provider-runtime.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/capability-provider-runtime.js")>(
"../plugins/capability-provider-runtime.js",
);
const runtime =
await vi.importActual<typeof import("../plugins/runtime.js")>("../plugins/runtime.js");
return {
...actual,
resolvePluginCapabilityProviders: ({ key }: { key: string }) =>
key !== "mediaUnderstandingProviders"
? []
: (() => {
const activeProviders =
runtime
.getActivePluginRegistry()
?.mediaUnderstandingProviders.map((entry) => entry.provider) ?? [];
return activeProviders.length > 0
? activeProviders
: [
{ id: "groq", capabilities: ["image", "audio"] },
{ id: "deepgram", capabilities: ["audio"] },
];
})(),
};
});
const resolvePluginCapabilityProvidersMock = vi.hoisted(() => vi.fn());
vi.mock("../plugins/capability-provider-runtime.js", () => ({
resolvePluginCapabilityProviders: resolvePluginCapabilityProvidersMock,
}));
function createMediaProvider(
params: Pick<MediaUnderstandingProvider, "id" | "capabilities"> &
Partial<MediaUnderstandingProvider>,
): MediaUnderstandingProvider {
return params;
}
describe("media-understanding provider registry", () => {
afterEach(() => {
setActivePluginRegistry(createEmptyPluginRegistry());
beforeEach(() => {
resolvePluginCapabilityProvidersMock.mockReset();
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
});
it("loads bundled providers by default when no active registry is present", () => {
it("loads media providers from the capability runtime", () => {
resolvePluginCapabilityProvidersMock.mockReturnValue([
createMediaProvider({ id: "groq", capabilities: ["image", "audio"] }),
createMediaProvider({ id: "deepgram", capabilities: ["audio"] }),
]);
const registry = buildMediaUnderstandingRegistry();
expect(getMediaUnderstandingProvider("groq", registry)?.id).toBe("groq");
expect(getMediaUnderstandingProvider("deepgram", registry)?.id).toBe("deepgram");
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "mediaUnderstandingProviders",
cfg: undefined,
});
});
it("merges plugin-registered media providers into the active registry", async () => {
const pluginRegistry = createEmptyPluginRegistry();
pluginRegistry.mediaUnderstandingProviders.push({
pluginId: "google",
pluginName: "Google Plugin",
source: "test",
provider: {
id: "google",
capabilities: ["image", "audio", "video"],
describeImage: async () => ({ text: "plugin image" }),
transcribeAudio: async () => ({ text: "plugin audio" }),
describeVideo: async () => ({ text: "plugin video" }),
},
});
setActivePluginRegistry(pluginRegistry);
it("keeps provider id normalization behavior for capability providers", () => {
resolvePluginCapabilityProvidersMock.mockReturnValue([
createMediaProvider({ id: "google", capabilities: ["image", "audio", "video"] }),
]);
const registry = buildMediaUnderstandingRegistry();
const provider = getMediaUnderstandingProvider("gemini", registry);
expect(provider?.id).toBe("google");
expect(await provider?.describeVideo?.({} as never)).toEqual({ text: "plugin video" });
});
it("keeps provider id normalization behavior for plugin-owned providers", () => {
const pluginRegistry = createEmptyPluginRegistry();
pluginRegistry.mediaUnderstandingProviders.push({
pluginId: "google",
pluginName: "Google Plugin",
source: "test",
provider: {
id: "google",
capabilities: ["image", "audio", "video"],
},
});
setActivePluginRegistry(pluginRegistry);
const registry = buildMediaUnderstandingRegistry();
const provider = getMediaUnderstandingProvider("gemini", registry);
expect(provider?.id).toBe("google");
expect(getMediaUnderstandingProvider("gemini", registry)?.id).toBe("google");
});
it("auto-registers media-understanding for config providers with image-capable models (#51392)", () => {
@@ -109,21 +74,15 @@ describe("media-understanding provider registry", () => {
expect(textOnlyProvider).toBeUndefined();
});
it("does not override plugin-registered providers when config also has image-capable models", async () => {
const pluginRegistry = createEmptyPluginRegistry();
pluginRegistry.mediaUnderstandingProviders.push({
pluginId: "google",
pluginName: "Google Plugin",
source: "test",
provider: {
it("does not override capability providers when config also has image-capable models", async () => {
resolvePluginCapabilityProvidersMock.mockReturnValue([
createMediaProvider({
id: "google",
capabilities: ["image", "audio", "video"],
describeImage: async () => ({ text: "plugin image" }),
transcribeAudio: async () => ({ text: "plugin audio" }),
},
});
setActivePluginRegistry(pluginRegistry);
}),
]);
const cfg = {
models: {
providers: {
@@ -140,6 +99,10 @@ describe("media-understanding provider registry", () => {
expect(provider?.capabilities).toEqual(["image", "audio", "video"]);
expect(await provider?.describeImage?.({} as never)).toEqual({ text: "plugin image" });
expect(await provider?.transcribeAudio?.({} as never)).toEqual({ text: "plugin audio" });
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "mediaUnderstandingProviders",
cfg,
});
});
it("does not auto-register providers with audio or video only inputs", () => {

View File

@@ -68,6 +68,18 @@ vi.mock("./plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
}));
vi.mock("./secrets/channel-env-vars.js", () => ({
getChannelEnvVars: (channelId: string) => {
const varsByChannel: Record<string, string[]> = {
discord: ["DISCORD_BOT_TOKEN"],
irc: ["IRC_HOST", "IRC_NICK"],
slack: ["SLACK_BOT_TOKEN"],
telegram: ["TELEGRAM_BOT_TOKEN"],
};
return varsByChannel[channelId] ?? [];
},
}));
vi.mock("./plugin-sdk/facade-loader.js", () => ({
...facadeMockHelpers,
listImportedBundledPluginFacadeIds: () => [],
@@ -114,7 +126,6 @@ describe("plugin activation boundary", () => {
});
expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled();
expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled();
expect(parseBrowserMajorVersion("Google Chrome 144.0.7534.0")).toBe(144);
expect(
loadBundledPluginPublicSurfaceModuleSync.mock.calls.map(

View File

@@ -83,6 +83,24 @@ vi.mock("../plugins/config-state.js", () => ({
}),
}));
vi.mock("../plugins/plugin-registry.js", () => ({
createPluginRegistryIdNormalizer: () => (id: string) => id,
loadPluginRegistrySnapshot: () => ({
diagnostics: [],
plugins: [{ pluginId: "discord" }],
}),
}));
vi.mock("../config/commands.js", () => ({
resolveNativeSkillsEnabled: ({
globalSetting,
providerSetting,
}: {
globalSetting?: boolean | "auto";
providerSetting?: boolean | "auto";
}) => providerSetting === true || (providerSetting === undefined && globalSetting === true),
}));
vi.mock("../channels/plugins/read-only.js", () => ({
listReadOnlyChannelPluginsForConfig: () => mockChannelPlugins,
}));

View File

@@ -2,12 +2,19 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { collectEnabledInsecureOrDangerousFlags } from "./dangerous-config-flags.js";
const { loadPluginManifestRegistryMock } = vi.hoisted(() => ({
loadPluginManifestRegistryMock: vi.fn(),
const { resolvePluginConfigContractsByIdMock } = vi.hoisted(() => ({
resolvePluginConfigContractsByIdMock: vi.fn(),
}));
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry: loadPluginManifestRegistryMock,
vi.mock("../plugins/config-contracts.js", () => ({
collectPluginConfigContractMatches: ({
pathPattern,
root,
}: {
pathPattern: string;
root: Record<string, unknown>;
}) => (Object.hasOwn(root, pathPattern) ? [{ path: pathPattern, value: root[pathPattern] }] : []),
resolvePluginConfigContractsById: resolvePluginConfigContractsByIdMock,
}));
function asConfig(value: unknown): OpenClawConfig {
@@ -16,21 +23,23 @@ function asConfig(value: unknown): OpenClawConfig {
describe("collectEnabledInsecureOrDangerousFlags", () => {
beforeEach(() => {
loadPluginManifestRegistryMock.mockReset();
resolvePluginConfigContractsByIdMock.mockReset();
resolvePluginConfigContractsByIdMock.mockReturnValue(new Map());
});
it("collects manifest-declared dangerous plugin config values", () => {
loadPluginManifestRegistryMock.mockReturnValue({
plugins: [
{
id: "acpx",
configContracts: {
dangerousFlags: [{ path: "permissionMode", equals: "approve-all" }],
resolvePluginConfigContractsByIdMock.mockReturnValue(
new Map([
[
"acpx",
{
configContracts: {
dangerousFlags: [{ path: "permissionMode", equals: "approve-all" }],
},
},
},
],
diagnostics: [],
});
],
]),
);
expect(
collectEnabledInsecureOrDangerousFlags(
@@ -50,17 +59,18 @@ describe("collectEnabledInsecureOrDangerousFlags", () => {
});
it("ignores plugin config values that are not declared as dangerous", () => {
loadPluginManifestRegistryMock.mockReturnValue({
plugins: [
{
id: "other",
configContracts: {
dangerousFlags: [{ path: "mode", equals: "danger" }],
resolvePluginConfigContractsByIdMock.mockReturnValue(
new Map([
[
"other",
{
configContracts: {
dangerousFlags: [{ path: "mode", equals: "danger" }],
},
},
},
],
diagnostics: [],
});
],
]),
);
expect(
collectEnabledInsecureOrDangerousFlags(

View File

@@ -1,5 +1,5 @@
import { beforeEach, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
const providerRegistryAllowlistMocks = vi.hoisted(() => ({
resolveRuntimePluginRegistry: vi.fn<

View File

@@ -18,8 +18,8 @@ vi.mock("../infra/os-summary.js", () => ({
}),
}));
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry,
vi.mock("../plugins/plugin-registry.js", () => ({
loadPluginManifestRegistryForPluginRegistry: loadPluginManifestRegistry,
}));
import { buildTrajectoryArtifacts, buildTrajectoryRunMetadata } from "./metadata.js";

View File

@@ -1,26 +1,13 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.js";
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
import type { SpeechProviderPlugin } from "../plugins/types.js";
const resolveRuntimePluginRegistryMock = vi.fn();
const loadPluginManifestRegistryMock = vi.fn(() => ({
plugins: [
{ id: "elevenlabs", origin: "bundled", contracts: { speechProviders: [{}] } },
{ id: "microsoft", origin: "bundled", contracts: { speechProviders: [{}] } },
{ id: "openai", origin: "bundled", contracts: { speechProviders: [{}] } },
{ id: "tts-local-cli", origin: "bundled", contracts: { speechProviders: [{}] } },
],
}));
const resolvePluginCapabilityProviderMock = vi.hoisted(() => vi.fn());
const resolvePluginCapabilityProvidersMock = vi.hoisted(() => vi.fn());
vi.mock("../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: (...args: Parameters<typeof resolveRuntimePluginRegistryMock>) =>
resolveRuntimePluginRegistryMock(...args),
}));
vi.mock("../plugins/manifest-registry.js", () => ({
loadPluginManifestRegistry: (...args: Parameters<typeof loadPluginManifestRegistryMock>) =>
loadPluginManifestRegistryMock(...args),
vi.mock("../plugins/capability-provider-runtime.js", () => ({
resolvePluginCapabilityProvider: resolvePluginCapabilityProviderMock,
resolvePluginCapabilityProviders: resolvePluginCapabilityProvidersMock,
}));
let getSpeechProvider: typeof import("./provider-registry.js").getSpeechProvider;
@@ -54,100 +41,48 @@ describe("speech provider registry", () => {
});
beforeEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
loadPluginManifestRegistryMock.mockClear();
});
it("uses active plugin speech providers without reloading plugins", () => {
resolveRuntimePluginRegistryMock.mockReturnValue({
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "test-demo-speech",
source: "test",
provider: createSpeechProvider("demo-speech"),
},
],
});
const providers = listSpeechProviders();
expect(providers.map((provider) => provider.id)).toEqual(["demo-speech"]);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
resolvePluginCapabilityProviderMock.mockReset();
resolvePluginCapabilityProviderMock.mockReturnValue(undefined);
resolvePluginCapabilityProvidersMock.mockReset();
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
});
it("uses active plugin speech providers even when config is provided", () => {
resolveRuntimePluginRegistryMock.mockReturnValue({
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "test-microsoft",
source: "test",
provider: createSpeechProvider("microsoft", ["edge"]),
},
],
});
it("lists providers from the speech capability runtime", () => {
const cfg = {} as OpenClawConfig;
resolvePluginCapabilityProvidersMock.mockReturnValue([createSpeechProvider("demo-speech")]);
expect(listSpeechProviders(cfg).map((provider) => provider.id)).toEqual(["microsoft"]);
expect(getSpeechProvider("edge", cfg)?.id).toBe("microsoft");
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
expect(listSpeechProviders(cfg).map((provider) => provider.id)).toEqual(["demo-speech"]);
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "speechProviders",
cfg,
});
});
it("loads speech providers from plugins when config is provided and no active providers exist", () => {
resolveRuntimePluginRegistryMock.mockImplementation((params?: unknown) =>
params === undefined
? createEmptyPluginRegistry()
: {
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "test-microsoft",
source: "test",
provider: createSpeechProvider("microsoft", ["edge"]),
},
],
},
);
it("gets providers by normalized id through the capability runtime", () => {
const cfg = {} as OpenClawConfig;
const provider = createSpeechProvider("microsoft", ["edge"]);
resolvePluginCapabilityProviderMock.mockReturnValue(provider);
expect(listSpeechProviders(cfg).map((provider) => provider.id)).toEqual(["microsoft"]);
expect(getSpeechProvider("edge", cfg)?.id).toBe("microsoft");
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith({
config: {
plugins: {
entries: {
elevenlabs: { enabled: true },
microsoft: { enabled: true },
openai: { enabled: true },
"tts-local-cli": { enabled: true },
},
},
},
activate: false,
expect(getSpeechProvider(" MICROSOFT ", cfg)).toBe(provider);
expect(resolvePluginCapabilityProviderMock).toHaveBeenCalledWith({
key: "speechProviders",
providerId: "microsoft",
cfg,
});
});
it("returns no providers when neither plugins nor active registry provide speech support", () => {
expect(listSpeechProviders()).toEqual([]);
expect(getSpeechProvider("demo-speech")).toBeUndefined();
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
});
it("canonicalizes the legacy edge alias to microsoft", () => {
resolveRuntimePluginRegistryMock.mockReturnValue({
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "test-microsoft",
source: "test",
provider: createSpeechProvider("microsoft", ["edge"]),
},
],
});
it("canonicalizes aliases from listed providers when direct lookup misses", () => {
resolvePluginCapabilityProvidersMock.mockReturnValue([
createSpeechProvider("microsoft", ["edge"]),
]);
expect(normalizeSpeechProviderId("edge")).toBe("edge");
expect(canonicalizeSpeechProviderId("edge")).toBe("microsoft");
});
it("returns empty results when the capability runtime has no speech providers", () => {
expect(listSpeechProviders()).toEqual([]);
expect(getSpeechProvider("demo-speech")).toBeUndefined();
expect(canonicalizeSpeechProviderId("demo-speech")).toBe("demo-speech");
});
});