From 44521d6b20e31d4e48b4acbf7762081684256a09 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 18 Mar 2026 02:44:24 +0000 Subject: [PATCH] test: stabilize plugin contract mocks --- .../contracts/catalog.contract.test.ts | 32 +++++++------ .../contracts/runtime.contract.test.ts | 47 +++++++++++++++++-- src/plugins/contracts/wizard.contract.test.ts | 12 ++--- 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/plugins/contracts/catalog.contract.test.ts b/src/plugins/contracts/catalog.contract.test.ts index 0d12217be26..4b775bd8061 100644 --- a/src/plugins/contracts/catalog.contract.test.ts +++ b/src/plugins/contracts/catalog.contract.test.ts @@ -27,14 +27,6 @@ const resolveNonBundledProviderPluginIdsMock = vi.hoisted(() => vi.fn((_) => [] as string[]), ); -vi.mock("../providers.js", () => ({ - resolvePluginProviders: (params: unknown) => resolvePluginProvidersMock(params as never), - resolveOwningPluginIdsForProvider: (params: unknown) => - resolveOwningPluginIdsForProviderMock(params as never), - resolveNonBundledProviderPluginIds: (params: unknown) => - resolveNonBundledProviderPluginIdsMock(params as never), -})); - let augmentModelCatalogWithProviderPlugins: typeof import("../provider-runtime.js").augmentModelCatalogWithProviderPlugins; let buildProviderMissingAuthMessageWithPlugin: typeof import("../provider-runtime.js").buildProviderMissingAuthMessageWithPlugin; let resetProviderRuntimeHookCacheForTest: typeof import("../provider-runtime.js").resetProviderRuntimeHookCacheForTest; @@ -43,18 +35,12 @@ let resolveProviderBuiltInModelSuppression: typeof import("../provider-runtime.j describe("provider catalog contract", () => { beforeEach(async () => { vi.resetModules(); + vi.doUnmock("../providers.js"); ({ resolveProviderContractPluginIdsForProvider, resolveProviderContractProvidersForPluginIds, uniqueProviderContractProviders, } = await import("./registry.js")); - ({ - augmentModelCatalogWithProviderPlugins, - buildProviderMissingAuthMessageWithPlugin, - resetProviderRuntimeHookCacheForTest, - resolveProviderBuiltInModelSuppression, - } = await import("../provider-runtime.js")); - resetProviderRuntimeHookCacheForTest(); resolveOwningPluginIdsForProviderMock.mockReset(); resolveOwningPluginIdsForProviderMock.mockImplementation((params) => @@ -72,6 +58,22 @@ describe("provider catalog contract", () => { } return resolveProviderContractProvidersForPluginIds(onlyPluginIds); }); + + vi.doMock("../providers.js", () => ({ + resolvePluginProviders: (params: unknown) => resolvePluginProvidersMock(params as never), + resolveOwningPluginIdsForProvider: (params: unknown) => + resolveOwningPluginIdsForProviderMock(params as never), + resolveNonBundledProviderPluginIds: (params: unknown) => + resolveNonBundledProviderPluginIdsMock(params as never), + })); + + ({ + augmentModelCatalogWithProviderPlugins, + buildProviderMissingAuthMessageWithPlugin, + resetProviderRuntimeHookCacheForTest, + resolveProviderBuiltInModelSuppression, + } = await import("../provider-runtime.js")); + resetProviderRuntimeHookCacheForTest(); }); it("keeps codex-only missing-auth hints wired through the provider runtime", () => { diff --git a/src/plugins/contracts/runtime.contract.test.ts b/src/plugins/contracts/runtime.contract.test.ts index 1afd8356fd9..385bfe8a3bd 100644 --- a/src/plugins/contracts/runtime.contract.test.ts +++ b/src/plugins/contracts/runtime.contract.test.ts @@ -2,7 +2,9 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createCapturedPluginRegistration } from "../../test-utils/plugin-registration.js"; import { createProviderUsageFetch, makeResponse } from "../../test-utils/provider-usage-fetch.js"; +import type { OpenClawPluginApi, ProviderPlugin } from "../types.js"; import type { ProviderRuntimeModel } from "../types.js"; const getOAuthApiKeyMock = vi.hoisted(() => vi.fn()); @@ -16,11 +18,17 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => { }; }); -vi.mock("../../providers/qwen-portal-oauth.js", () => ({ - refreshQwenPortalCredentials: refreshQwenPortalCredentialsMock, -})); +vi.mock("openclaw/plugin-sdk/qwen-portal-auth", async () => { + const actual = await vi.importActual("openclaw/plugin-sdk/qwen-portal-auth"); + return { + ...actual, + refreshQwenPortalCredentials: refreshQwenPortalCredentialsMock, + }; +}); -let requireProviderContractProvider: typeof import("./registry.js").requireProviderContractProvider; +let requireBundledProviderContractProvider: typeof import("./registry.js").requireProviderContractProvider; +let openAIPlugin: (typeof import("../../../extensions/openai/index.js"))["default"]; +let qwenPortalPlugin: (typeof import("../../../extensions/qwen-portal-auth/index.js"))["default"]; function createModel(overrides: Partial & Pick) { return { @@ -37,10 +45,39 @@ function createModel(overrides: Partial & Pick) { + const captured = createCapturedPluginRegistration(); + for (const plugin of plugins) { + plugin.register(captured.api); + } + return captured.providers; +} + +function requireProvider(providers: ProviderPlugin[], providerId: string) { + const provider = providers.find((entry) => entry.id === providerId); + if (!provider) { + throw new Error(`provider ${providerId} missing`); + } + return provider; +} + +function requireProviderContractProvider(providerId: string): ProviderPlugin { + if (providerId === "openai-codex") { + return requireProvider(registerProviders(openAIPlugin), providerId); + } + if (providerId === "qwen-portal") { + return requireProvider(registerProviders(qwenPortalPlugin), providerId); + } + return requireBundledProviderContractProvider(providerId); +} + describe("provider runtime contract", () => { beforeEach(async () => { vi.resetModules(); - ({ requireProviderContractProvider } = await import("./registry.js")); + ({ requireProviderContractProvider: requireBundledProviderContractProvider } = + await import("./registry.js")); + openAIPlugin = (await import("../../../extensions/openai/index.js")).default; + qwenPortalPlugin = (await import("../../../extensions/qwen-portal-auth/index.js")).default; getOAuthApiKeyMock.mockReset(); refreshQwenPortalCredentialsMock.mockReset(); }); diff --git a/src/plugins/contracts/wizard.contract.test.ts b/src/plugins/contracts/wizard.contract.test.ts index 934a42ce59a..6e97556d91e 100644 --- a/src/plugins/contracts/wizard.contract.test.ts +++ b/src/plugins/contracts/wizard.contract.test.ts @@ -3,10 +3,6 @@ import type { ProviderPlugin } from "../types.js"; const resolvePluginProvidersMock = vi.fn(); -vi.mock("../providers.js", () => ({ - resolvePluginProviders: (...args: unknown[]) => resolvePluginProvidersMock(...args), -})); - let buildProviderPluginMethodChoice: typeof import("../provider-wizard.js").buildProviderPluginMethodChoice; let providerContractPluginIds: typeof import("./registry.js").providerContractPluginIds; let resolveProviderModelPickerEntries: typeof import("../provider-wizard.js").resolveProviderModelPickerEntries; @@ -73,16 +69,20 @@ function resolveExpectedModelPickerValues(providers: ProviderPlugin[]) { describe("provider wizard contract", () => { beforeEach(async () => { vi.resetModules(); + vi.doUnmock("../providers.js"); ({ providerContractPluginIds, uniqueProviderContractProviders } = await import("./registry.js")); + resolvePluginProvidersMock.mockReset(); + resolvePluginProvidersMock.mockReturnValue(uniqueProviderContractProviders); + vi.doMock("../providers.js", () => ({ + resolvePluginProviders: (...args: unknown[]) => resolvePluginProvidersMock(...args), + })); ({ buildProviderPluginMethodChoice, resolveProviderModelPickerEntries, resolveProviderPluginChoice, resolveProviderWizardOptions, } = await import("../provider-wizard.js")); - resolvePluginProvidersMock.mockReset(); - resolvePluginProvidersMock.mockReturnValue(uniqueProviderContractProviders); }); it("exposes every registered provider setup choice through the shared wizard layer", () => {