From 883f66eef31dcb7c2b048c20a8d056c8ea811cad Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 21 Apr 2026 00:29:09 +0100 Subject: [PATCH] test: share provider catalog fixtures --- extensions/arcee/index.test.ts | 45 ++++---------- extensions/deepseek/index.test.ts | 27 ++------ extensions/fireworks/index.test.ts | 61 ++++--------------- extensions/google/provider-models.test.ts | 28 +-------- .../provider-model-test-helpers.ts | 58 ++++++++++++++++++ 5 files changed, 91 insertions(+), 128 deletions(-) create mode 100644 extensions/test-support/provider-model-test-helpers.ts diff --git a/extensions/arcee/index.test.ts b/extensions/arcee/index.test.ts index b2aac6ad601..2190d5051cc 100644 --- a/extensions/arcee/index.test.ts +++ b/extensions/arcee/index.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js"; import { resolveProviderAuthEnvVarCandidates } from "../../src/secrets/provider-env-vars.js"; import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; +import { runSingleProviderCatalog } from "../test-support/provider-model-test-helpers.js"; import arceePlugin from "./index.js"; describe("arcee provider plugin", () => { @@ -77,28 +78,14 @@ describe("arcee provider plugin", () => { it("builds the direct Arcee AI model catalog", async () => { const provider = await registerSingleProviderPlugin(arceePlugin); - expect(provider.catalog).toBeDefined(); - - const catalog = await provider.catalog!.run({ - config: {}, - env: {}, - resolveProviderApiKey: (id: string) => + const catalogProvider = await runSingleProviderCatalog(provider, { + resolveProviderApiKey: (id?: string) => id === "arcee" ? { apiKey: "test-key" } : { apiKey: undefined }, - resolveProviderAuth: () => ({ - apiKey: "test-key", - mode: "api_key", - source: "env", - }), - } as never); + }); - expect(catalog && "provider" in catalog).toBe(true); - if (!catalog || !("provider" in catalog)) { - throw new Error("expected single-provider catalog"); - } - - expect(catalog.provider.api).toBe("openai-completions"); - expect(catalog.provider.baseUrl).toBe("https://api.arcee.ai/api/v1"); - expect(catalog.provider.models?.map((model) => model.id)).toEqual([ + expect(catalogProvider.api).toBe("openai-completions"); + expect(catalogProvider.baseUrl).toBe("https://api.arcee.ai/api/v1"); + expect(catalogProvider.models?.map((model) => model.id)).toEqual([ "trinity-mini", "trinity-large-preview", "trinity-large-thinking", @@ -107,26 +94,18 @@ describe("arcee provider plugin", () => { it("builds the OpenRouter-backed Arcee AI model catalog", async () => { const provider = await registerSingleProviderPlugin(arceePlugin); - - const catalog = await provider.catalog!.run({ - config: {}, - env: {}, - resolveProviderApiKey: (id: string) => + const catalogProvider = await runSingleProviderCatalog(provider, { + resolveProviderApiKey: (id?: string) => id === "openrouter" ? { apiKey: "sk-or-test" } : { apiKey: undefined }, resolveProviderAuth: () => ({ apiKey: "sk-or-test", mode: "api_key", source: "env", }), - } as never); + }); - expect(catalog && "provider" in catalog).toBe(true); - if (!catalog || !("provider" in catalog)) { - throw new Error("expected single-provider catalog"); - } - - expect(catalog.provider.baseUrl).toBe("https://openrouter.ai/api/v1"); - expect(catalog.provider.models?.map((model) => model.id)).toEqual([ + expect(catalogProvider.baseUrl).toBe("https://openrouter.ai/api/v1"); + expect(catalogProvider.models?.map((model) => model.id)).toEqual([ "arcee/trinity-mini", "arcee/trinity-large-preview", "arcee/trinity-large-thinking", diff --git a/extensions/deepseek/index.test.ts b/extensions/deepseek/index.test.ts index b9103b9e05a..28e63cbceb6 100644 --- a/extensions/deepseek/index.test.ts +++ b/extensions/deepseek/index.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js"; import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; +import { runSingleProviderCatalog } from "../test-support/provider-model-test-helpers.js"; import deepseekPlugin from "./index.js"; describe("deepseek provider plugin", () => { @@ -22,32 +23,16 @@ describe("deepseek provider plugin", () => { it("builds the static DeepSeek model catalog", async () => { const provider = await registerSingleProviderPlugin(deepseekPlugin); - expect(provider.catalog).toBeDefined(); + const catalogProvider = await runSingleProviderCatalog(provider); - const catalog = await provider.catalog!.run({ - config: {}, - env: {}, - resolveProviderApiKey: () => ({ apiKey: "test-key" }), - resolveProviderAuth: () => ({ - apiKey: "test-key", - mode: "api_key", - source: "env", - }), - } as never); - - expect(catalog && "provider" in catalog).toBe(true); - if (!catalog || !("provider" in catalog)) { - throw new Error("expected single-provider catalog"); - } - - expect(catalog.provider.api).toBe("openai-completions"); - expect(catalog.provider.baseUrl).toBe("https://api.deepseek.com"); - expect(catalog.provider.models?.map((model) => model.id)).toEqual([ + expect(catalogProvider.api).toBe("openai-completions"); + expect(catalogProvider.baseUrl).toBe("https://api.deepseek.com"); + expect(catalogProvider.models?.map((model) => model.id)).toEqual([ "deepseek-chat", "deepseek-reasoner", ]); expect( - catalog.provider.models?.find((model) => model.id === "deepseek-reasoner")?.reasoning, + catalogProvider.models?.find((model) => model.id === "deepseek-reasoner")?.reasoning, ).toBe(true); }); diff --git a/extensions/fireworks/index.test.ts b/extensions/fireworks/index.test.ts index 0e6a3f2a38b..5de7bbd801a 100644 --- a/extensions/fireworks/index.test.ts +++ b/extensions/fireworks/index.test.ts @@ -1,11 +1,11 @@ -import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; -import type { - ProviderResolveDynamicModelContext, - ProviderRuntimeModel, -} from "openclaw/plugin-sdk/plugin-entry"; +import type { ProviderRuntimeModel } from "openclaw/plugin-sdk/plugin-entry"; import { describe, expect, it } from "vitest"; import { resolveProviderPluginChoice } from "../../src/plugins/provider-auth-choice.runtime.js"; import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js"; +import { + createProviderDynamicModelContext, + runSingleProviderCatalog, +} from "../test-support/provider-model-test-helpers.js"; import fireworksPlugin from "./index.js"; import { FIREWORKS_BASE_URL, @@ -14,27 +14,6 @@ import { FIREWORKS_DEFAULT_MODEL_ID, } from "./provider-catalog.js"; -function createDynamicContext(params: { - provider: string; - modelId: string; - models: ProviderRuntimeModel[]; -}): ProviderResolveDynamicModelContext { - return { - provider: params.provider, - modelId: params.modelId, - modelRegistry: { - find(providerId: string, modelId: string) { - return ( - params.models.find( - (model) => - model.provider === providerId && model.id.toLowerCase() === modelId.toLowerCase(), - ) ?? null - ); - }, - } as ModelRegistry, - }; -} - function createFireworksDefaultRuntimeModel(params: { reasoning: boolean }): ProviderRuntimeModel { return { id: FIREWORKS_DEFAULT_MODEL_ID, @@ -69,26 +48,12 @@ describe("fireworks provider plugin", () => { it("builds the Fireworks Fire Pass starter catalog", async () => { const provider = await registerSingleProviderPlugin(fireworksPlugin); - const catalog = await provider.catalog?.run({ - config: {}, - env: {}, - resolveProviderApiKey: () => ({ apiKey: "test-key" }), - resolveProviderAuth: () => ({ - apiKey: "test-key", - mode: "api_key", - source: "env", - }), - } as never); + const catalogProvider = await runSingleProviderCatalog(provider); - expect(catalog && "provider" in catalog).toBe(true); - if (!catalog || !("provider" in catalog)) { - throw new Error("expected single-provider catalog"); - } - - expect(catalog.provider.api).toBe("openai-completions"); - expect(catalog.provider.baseUrl).toBe(FIREWORKS_BASE_URL); - expect(catalog.provider.models?.map((model) => model.id)).toEqual([FIREWORKS_DEFAULT_MODEL_ID]); - expect(catalog.provider.models?.[0]).toMatchObject({ + expect(catalogProvider.api).toBe("openai-completions"); + expect(catalogProvider.baseUrl).toBe(FIREWORKS_BASE_URL); + expect(catalogProvider.models?.map((model) => model.id)).toEqual([FIREWORKS_DEFAULT_MODEL_ID]); + expect(catalogProvider.models?.[0]).toMatchObject({ reasoning: false, input: ["text", "image"], contextWindow: FIREWORKS_DEFAULT_CONTEXT_WINDOW, @@ -99,7 +64,7 @@ describe("fireworks provider plugin", () => { it("resolves forward-compat Fireworks model ids from the default template", async () => { const provider = await registerSingleProviderPlugin(fireworksPlugin); const resolved = provider.resolveDynamicModel?.( - createDynamicContext({ + createProviderDynamicModelContext({ provider: "fireworks", modelId: "accounts/fireworks/models/qwen3.6-plus", models: [createFireworksDefaultRuntimeModel({ reasoning: true })], @@ -118,7 +83,7 @@ describe("fireworks provider plugin", () => { it("disables reasoning metadata for Fireworks Kimi dynamic models", async () => { const provider = await registerSingleProviderPlugin(fireworksPlugin); const resolved = provider.resolveDynamicModel?.( - createDynamicContext({ + createProviderDynamicModelContext({ provider: "fireworks", modelId: "accounts/fireworks/models/kimi-k2p5", models: [createFireworksDefaultRuntimeModel({ reasoning: false })], @@ -135,7 +100,7 @@ describe("fireworks provider plugin", () => { it("disables reasoning metadata for Fireworks Kimi k2.5 aliases", async () => { const provider = await registerSingleProviderPlugin(fireworksPlugin); const resolved = provider.resolveDynamicModel?.( - createDynamicContext({ + createProviderDynamicModelContext({ provider: "fireworks", modelId: "accounts/fireworks/routers/kimi-k2.5-turbo", models: [createFireworksDefaultRuntimeModel({ reasoning: false })], diff --git a/extensions/google/provider-models.test.ts b/extensions/google/provider-models.test.ts index 8633d4492a9..3bee176765d 100644 --- a/extensions/google/provider-models.test.ts +++ b/extensions/google/provider-models.test.ts @@ -1,9 +1,6 @@ -import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; -import type { - ProviderResolveDynamicModelContext, - ProviderRuntimeModel, -} from "openclaw/plugin-sdk/plugin-entry"; +import type { ProviderRuntimeModel } from "openclaw/plugin-sdk/plugin-entry"; import { describe, expect, it } from "vitest"; +import { createProviderDynamicModelContext as createContext } from "../test-support/provider-model-test-helpers.js"; import { isModernGoogleModel, resolveGoogleGeminiForwardCompatModel } from "./provider-models.js"; function createTemplateModel( @@ -29,27 +26,6 @@ function createTemplateModel( } as ProviderRuntimeModel; } -function createContext(params: { - provider: string; - modelId: string; - models: ProviderRuntimeModel[]; -}): ProviderResolveDynamicModelContext { - return { - provider: params.provider, - modelId: params.modelId, - modelRegistry: { - find(providerId: string, modelId: string) { - return ( - params.models.find( - (model) => - model.provider === providerId && model.id.toLowerCase() === modelId.toLowerCase(), - ) ?? null - ); - }, - } as ModelRegistry, - }; -} - describe("resolveGoogleGeminiForwardCompatModel", () => { it("resolves stable gemini 2.5 flash-lite from direct google templates for Gemini CLI when available", () => { const model = resolveGoogleGeminiForwardCompatModel({ diff --git a/extensions/test-support/provider-model-test-helpers.ts b/extensions/test-support/provider-model-test-helpers.ts new file mode 100644 index 00000000000..10536e19fb2 --- /dev/null +++ b/extensions/test-support/provider-model-test-helpers.ts @@ -0,0 +1,58 @@ +import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; +import type { + ProviderCatalogContext, + ProviderResolveDynamicModelContext, + ProviderRuntimeModel, +} from "openclaw/plugin-sdk/plugin-entry"; +import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared"; + +export function createProviderDynamicModelContext(params: { + provider: string; + modelId: string; + models: ProviderRuntimeModel[]; +}): ProviderResolveDynamicModelContext { + return { + provider: params.provider, + modelId: params.modelId, + modelRegistry: { + find(providerId: string, modelId: string) { + return ( + params.models.find( + (model) => + model.provider === providerId && model.id.toLowerCase() === modelId.toLowerCase(), + ) ?? null + ); + }, + } as ModelRegistry, + }; +} + +export async function runSingleProviderCatalog( + provider: Pick, + params: { + resolveProviderApiKey?: ProviderCatalogContext["resolveProviderApiKey"]; + resolveProviderAuth?: ProviderCatalogContext["resolveProviderAuth"]; + } = {}, +) { + if (!provider.catalog) { + throw new Error("expected provider catalog"); + } + + const catalog = await provider.catalog.run({ + config: {}, + env: {}, + resolveProviderApiKey: params.resolveProviderApiKey ?? (() => ({ apiKey: "test-key" })), + resolveProviderAuth: + params.resolveProviderAuth ?? + (() => ({ + apiKey: "test-key", + mode: "api_key", + source: "env", + })), + } as ProviderCatalogContext); + + if (!catalog || !("provider" in catalog)) { + throw new Error("expected single-provider catalog"); + } + return catalog.provider; +}