test: slim provider registry mocks

This commit is contained in:
Peter Steinberger
2026-04-17 01:27:49 +01:00
parent a2753e2d9f
commit 2e08c77582
8 changed files with 153 additions and 144 deletions

View File

@@ -20,10 +20,20 @@ vi.mock("../../../../src/infra/net/fetch-guard.js", () => ({
},
}));
vi.mock("../../../../src/agents/model-auth.js", async () => {
const { createModelAuthMockModule } =
await import("../../../../src/test-utils/model-auth-mock.js");
return createModelAuthMockModule();
const { resolveApiKeyForProviderMock } = vi.hoisted(() => ({
resolveApiKeyForProviderMock: vi.fn(),
}));
vi.mock("../../../../src/agents/model-auth.js", () => {
return {
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
if (auth.apiKey) {
return auth.apiKey;
}
throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`);
},
};
});
const createGeminiFetchMock = (embeddingValues = [1, 2, 3]) =>

View File

@@ -22,10 +22,20 @@ vi.mock("../../../../src/infra/net/fetch-guard.js", () => ({
},
}));
vi.mock("../../../../src/agents/model-auth.js", async () => {
const { createModelAuthMockModule } =
await import("../../../../src/test-utils/model-auth-mock.js");
return createModelAuthMockModule();
const { resolveApiKeyForProviderMock } = vi.hoisted(() => ({
resolveApiKeyForProviderMock: vi.fn(),
}));
vi.mock("../../../../src/agents/model-auth.js", () => {
return {
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
if (auth.apiKey) {
return auth.apiKey;
}
throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`);
},
};
});
const createFetchMock = () => {

View File

@@ -4,10 +4,20 @@ import { createEmbeddingProvider, DEFAULT_LOCAL_MODEL } from "./embeddings.js";
import * as nodeLlamaModule from "./node-llama.js";
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
vi.mock("../../../../src/agents/model-auth.js", async () => {
const { createModelAuthMockModule } =
await import("../../../../src/test-utils/model-auth-mock.js");
return createModelAuthMockModule();
const { resolveApiKeyForProviderMock } = vi.hoisted(() => ({
resolveApiKeyForProviderMock: vi.fn(),
}));
vi.mock("../../../../src/agents/model-auth.js", () => {
return {
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
if (auth.apiKey) {
return auth.apiKey;
}
throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`);
},
};
});
vi.mock("../../../../src/infra/net/fetch-guard.js", () => ({

View File

@@ -1,19 +1,33 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import type { ImageGenerationProviderPlugin } from "../plugins/types.js";
const { resolveRuntimePluginRegistryMock } = vi.hoisted(() => ({
resolveRuntimePluginRegistryMock: vi.fn<
(params?: unknown) => ReturnType<typeof createEmptyPluginRegistry> | undefined
>(() => undefined),
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
resolvePluginCapabilityProvidersMock: vi.fn<() => ImageGenerationProviderPlugin[]>(() => []),
}));
vi.mock("../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: resolveRuntimePluginRegistryMock,
vi.mock("../plugins/capability-provider-runtime.js", () => ({
resolvePluginCapabilityProviders: resolvePluginCapabilityProvidersMock,
}));
let getImageGenerationProvider: typeof import("./provider-registry.js").getImageGenerationProvider;
let listImageGenerationProviders: typeof import("./provider-registry.js").listImageGenerationProviders;
function createProvider(
params: Pick<ImageGenerationProviderPlugin, "id"> & Partial<ImageGenerationProviderPlugin>,
): ImageGenerationProviderPlugin {
return {
label: params.id,
capabilities: {
generate: {},
edit: { enabled: false },
},
generateImage: async () => ({
images: [{ buffer: Buffer.from("image"), mimeType: "image/png" }],
}),
...params,
};
}
describe("image-generation provider registry", () => {
beforeAll(async () => {
({ getImageGenerationProvider, listImageGenerationProviders } =
@@ -21,78 +35,35 @@ describe("image-generation provider registry", () => {
});
beforeEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
resolvePluginCapabilityProvidersMock.mockReset();
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
});
it("does not load plugins when listing without config", () => {
it("delegates provider resolution to the capability provider boundary", () => {
expect(listImageGenerationProviders()).toEqual([]);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "imageGenerationProviders",
cfg: undefined,
});
});
it("uses active plugin providers without loading from disk", () => {
const registry = createEmptyPluginRegistry();
registry.imageGenerationProviders.push({
pluginId: "custom-image",
pluginName: "Custom Image",
source: "test",
provider: {
id: "custom-image",
label: "Custom Image",
capabilities: {
generate: {},
edit: { enabled: false },
},
generateImage: async () => ({
images: [{ buffer: Buffer.from("image"), mimeType: "image/png" }],
}),
},
});
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
resolvePluginCapabilityProvidersMock.mockReturnValue([createProvider({ id: "custom-image" })]);
const provider = getImageGenerationProvider("custom-image");
expect(provider?.id).toBe("custom-image");
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "imageGenerationProviders",
cfg: undefined,
});
});
it("ignores prototype-like provider ids and aliases", () => {
const registry = createEmptyPluginRegistry();
registry.imageGenerationProviders.push(
{
pluginId: "blocked-image",
pluginName: "Blocked Image",
source: "test",
provider: {
id: "__proto__",
aliases: ["constructor", "prototype"],
capabilities: {
generate: {},
edit: { enabled: false },
},
generateImage: async () => ({
images: [{ buffer: Buffer.from("image"), mimeType: "image/png" }],
}),
},
},
{
pluginId: "safe-image",
pluginName: "Safe Image",
source: "test",
provider: {
id: "safe-image",
aliases: ["safe-alias", "constructor"],
capabilities: {
generate: {},
edit: { enabled: false },
},
generateImage: async () => ({
images: [{ buffer: Buffer.from("image"), mimeType: "image/png" }],
}),
},
},
);
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
resolvePluginCapabilityProvidersMock.mockReturnValue([
createProvider({ id: "__proto__", aliases: ["constructor", "prototype"] }),
createProvider({ id: "safe-image", aliases: ["safe-alias", "constructor"] }),
]);
expect(listImageGenerationProviders().map((provider) => provider.id)).toEqual(["safe-image"]);
expect(getImageGenerationProvider("__proto__")).toBeUndefined();

View File

@@ -20,9 +20,20 @@ import {
type JsonFetchMock,
} from "./embeddings-provider.test-support.js";
vi.mock("../../agents/model-auth.js", async () => {
const { createModelAuthMockModule } = await import("../../test-utils/model-auth-mock.js");
return createModelAuthMockModule();
const { resolveApiKeyForProviderMock } = vi.hoisted(() => ({
resolveApiKeyForProviderMock: vi.fn(),
}));
vi.mock("../../agents/model-auth.js", () => {
return {
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
if (auth.apiKey) {
return auth.apiKey;
}
throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`);
},
};
});
beforeEach(() => {

View File

@@ -9,9 +9,20 @@ import {
} from "./embeddings-provider.test-support.js";
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
vi.mock("../../agents/model-auth.js", async () => {
const { createModelAuthMockModule } = await import("../../test-utils/model-auth-mock.js");
return createModelAuthMockModule();
const { resolveApiKeyForProviderMock } = vi.hoisted(() => ({
resolveApiKeyForProviderMock: vi.fn(),
}));
vi.mock("../../agents/model-auth.js", () => {
return {
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
if (auth.apiKey) {
return auth.apiKey;
}
throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`);
},
};
});
let createVoyageEmbeddingProvider: typeof import("./embeddings-voyage.js").createVoyageEmbeddingProvider;

View File

@@ -17,6 +17,7 @@ const {
bedrockSendMock,
createOllamaEmbeddingProviderMock,
defaultProviderMock,
resolveApiKeyForProviderMock,
resolveCredentialsMock,
} = vi.hoisted(() => ({
bedrockSendMock: vi.fn(),
@@ -25,11 +26,19 @@ const {
}),
defaultProviderMock: vi.fn(),
resolveCredentialsMock: vi.fn(),
resolveApiKeyForProviderMock: vi.fn(),
}));
vi.mock("../../agents/model-auth.js", async () => {
const { createModelAuthMockModule } = await import("../../test-utils/model-auth-mock.js");
return createModelAuthMockModule();
vi.mock("../../agents/model-auth.js", () => {
return {
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
if (auth.apiKey) {
return auth.apiKey;
}
throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`);
},
};
});
vi.mock("./embeddings-ollama.js", () => ({

View File

@@ -1,19 +1,30 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import type { VideoGenerationProviderPlugin } from "../plugins/types.js";
const { resolveRuntimePluginRegistryMock } = vi.hoisted(() => ({
resolveRuntimePluginRegistryMock: vi.fn<
(params?: unknown) => ReturnType<typeof createEmptyPluginRegistry> | undefined
>(() => undefined),
const { resolvePluginCapabilityProvidersMock } = vi.hoisted(() => ({
resolvePluginCapabilityProvidersMock: vi.fn<() => VideoGenerationProviderPlugin[]>(() => []),
}));
vi.mock("../plugins/loader.js", () => ({
resolveRuntimePluginRegistry: resolveRuntimePluginRegistryMock,
vi.mock("../plugins/capability-provider-runtime.js", () => ({
resolvePluginCapabilityProviders: resolvePluginCapabilityProvidersMock,
}));
let getVideoGenerationProvider: typeof import("./provider-registry.js").getVideoGenerationProvider;
let listVideoGenerationProviders: typeof import("./provider-registry.js").listVideoGenerationProviders;
function createProvider(
params: Pick<VideoGenerationProviderPlugin, "id"> & Partial<VideoGenerationProviderPlugin>,
): VideoGenerationProviderPlugin {
return {
label: params.id,
capabilities: {},
generateVideo: async () => ({
videos: [{ buffer: Buffer.from("video"), mimeType: "video/mp4" }],
}),
...params,
};
}
describe("video-generation provider registry", () => {
beforeAll(async () => {
({ getVideoGenerationProvider, listVideoGenerationProviders } =
@@ -21,69 +32,35 @@ describe("video-generation provider registry", () => {
});
beforeEach(() => {
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
resolvePluginCapabilityProvidersMock.mockReset();
resolvePluginCapabilityProvidersMock.mockReturnValue([]);
});
it("does not load plugins when listing without config", () => {
it("delegates provider resolution to the capability provider boundary", () => {
expect(listVideoGenerationProviders()).toEqual([]);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "videoGenerationProviders",
cfg: undefined,
});
});
it("uses active plugin providers without loading from disk", () => {
const registry = createEmptyPluginRegistry();
registry.videoGenerationProviders.push({
pluginId: "custom-video",
pluginName: "Custom Video",
source: "test",
provider: {
id: "custom-video",
label: "Custom Video",
capabilities: {},
generateVideo: async () => ({
videos: [{ buffer: Buffer.from("video"), mimeType: "video/mp4" }],
}),
},
});
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
resolvePluginCapabilityProvidersMock.mockReturnValue([createProvider({ id: "custom-video" })]);
const provider = getVideoGenerationProvider("custom-video");
expect(provider?.id).toBe("custom-video");
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith();
expect(resolvePluginCapabilityProvidersMock).toHaveBeenCalledWith({
key: "videoGenerationProviders",
cfg: undefined,
});
});
it("ignores prototype-like provider ids and aliases", () => {
const registry = createEmptyPluginRegistry();
registry.videoGenerationProviders.push(
{
pluginId: "blocked-video",
pluginName: "Blocked Video",
source: "test",
provider: {
id: "__proto__",
aliases: ["constructor", "prototype"],
capabilities: {},
generateVideo: async () => ({
videos: [{ buffer: Buffer.from("video"), mimeType: "video/mp4" }],
}),
},
},
{
pluginId: "safe-video",
pluginName: "Safe Video",
source: "test",
provider: {
id: "safe-video",
aliases: ["safe-alias", "constructor"],
capabilities: {},
generateVideo: async () => ({
videos: [{ buffer: Buffer.from("video"), mimeType: "video/mp4" }],
}),
},
},
);
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
resolvePluginCapabilityProvidersMock.mockReturnValue([
createProvider({ id: "__proto__", aliases: ["constructor", "prototype"] }),
createProvider({ id: "safe-video", aliases: ["safe-alias", "constructor"] }),
]);
expect(listVideoGenerationProviders().map((provider) => provider.id)).toEqual(["safe-video"]);
expect(getVideoGenerationProvider("__proto__")).toBeUndefined();