Contracts: stabilize provider plugin test imports

This commit is contained in:
Vincent Koc 2026-03-18 08:29:24 -07:00
parent a0e7a2fcc1
commit 6a381e80bc
3 changed files with 52 additions and 41 deletions

View File

@ -2,10 +2,13 @@ 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 openAIPlugin from "../../../extensions/openai/index.js";
import qwenPortalPlugin from "../../../extensions/qwen-portal-auth/index.js";
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";
import { requireProviderContractProvider } from "./registry.js";
import { registerProviders, requireProvider } from "./testkit.js";
import { requireProviderContractProvider as requireBundledProviderContractProvider } from "./registry.js";
const CONTRACT_SETUP_TIMEOUT_MS = 300_000;
@ -43,11 +46,38 @@ function createModel(overrides: Partial<ProviderRuntimeModel> & Pick<ProviderRun
} satisfies ProviderRuntimeModel;
}
function registerProviders(...plugins: Array<{ register(api: OpenClawPluginApi): void }>) {
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(() => {
getOAuthApiKeyMock.mockReset();
refreshQwenPortalCredentialsMock.mockReset();
}, CONTRACT_SETUP_TIMEOUT_MS);
describe("anthropic", () => {
it("owns anthropic 4.6 forward-compat resolution", () => {
const provider = requireProviderContractProvider("anthropic");
@ -511,9 +541,7 @@ describe("provider runtime contract", () => {
describe("openai-codex", () => {
it("owns refresh fallback for accountId extraction failures", async () => {
vi.resetModules();
const openAIPlugin = (await import("../../../extensions/openai/index.js")).default;
const provider = requireProvider(registerProviders(openAIPlugin), "openai-codex");
const provider = requireProviderContractProvider("openai-codex");
const credential = {
type: "oauth" as const,
provider: "openai-codex",
@ -608,9 +636,7 @@ describe("provider runtime contract", () => {
describe("qwen-portal", () => {
it("owns OAuth refresh", async () => {
const qwenPortalPlugin = (await import("../../../extensions/qwen-portal-auth/index.js"))
.default;
const provider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
const provider = requireProviderContractProvider("qwen-portal");
const credential = {
type: "oauth" as const,
provider: "qwen-portal",

View File

@ -1,7 +1,13 @@
import { describe } from "vitest";
import { describe, expect, it } from "vitest";
import { webSearchProviderContractRegistry } from "./registry.js";
import { installWebSearchProviderContractSuite } from "./suites.js";
describe("web search provider contract registry load", () => {
it("loads bundled web search providers", () => {
expect(webSearchProviderContractRegistry.length).toBeGreaterThan(0);
});
});
for (const entry of webSearchProviderContractRegistry) {
describe(`${entry.pluginId}:${entry.provider.id} web search contract`, () => {
installWebSearchProviderContractSuite({

View File

@ -1,23 +1,19 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
buildProviderPluginMethodChoice,
resolveProviderModelPickerEntries,
resolveProviderPluginChoice,
resolveProviderWizardOptions,
} from "../provider-wizard.js";
import type { ProviderPlugin } from "../types.js";
import { providerContractPluginIds, uniqueProviderContractProviders } from "./registry.js";
const CONTRACT_SETUP_TIMEOUT_MS = 300_000;
type ResolvePluginProviders = typeof import("../providers.js").resolvePluginProviders;
const resolvePluginProvidersMock = vi.hoisted(() => vi.fn<ResolvePluginProviders>(() => []));
const resolvePluginProvidersMock = vi.fn();
vi.mock("../providers.js", () => ({
resolvePluginProviders: (params?: { onlyPluginIds?: string[] }) =>
resolvePluginProvidersMock(params as never),
resolvePluginProviders: (...args: unknown[]) => resolvePluginProvidersMock(...args),
}));
let buildProviderPluginMethodChoice: typeof import("../provider-wizard.js").buildProviderPluginMethodChoice;
let resolveProviderModelPickerEntries: typeof import("../provider-wizard.js").resolveProviderModelPickerEntries;
let resolveProviderPluginChoice: typeof import("../provider-wizard.js").resolveProviderPluginChoice;
let resolveProviderWizardOptions: typeof import("../provider-wizard.js").resolveProviderWizardOptions;
let providerContractPluginIds: typeof import("./registry.js").providerContractPluginIds;
let uniqueProviderContractProviders: typeof import("./registry.js").uniqueProviderContractProviders;
function resolveExpectedWizardChoiceValues(providers: ProviderPlugin[]) {
const values: string[] = [];
@ -75,25 +71,8 @@ function resolveExpectedModelPickerValues(providers: ProviderPlugin[]) {
}
describe("provider wizard contract", () => {
beforeAll(async () => {
const actualProviders =
await vi.importActual<typeof import("../providers.js")>("../providers.js");
resolvePluginProvidersMock.mockImplementation((params?: { onlyPluginIds?: string[] }) =>
actualProviders.resolvePluginProviders(params as never),
);
({ providerContractPluginIds, uniqueProviderContractProviders } =
await import("./registry.js"));
resolvePluginProvidersMock.mockReturnValue(uniqueProviderContractProviders);
({
buildProviderPluginMethodChoice,
resolveProviderModelPickerEntries,
resolveProviderPluginChoice,
resolveProviderWizardOptions,
} = await import("../provider-wizard.js"));
}, CONTRACT_SETUP_TIMEOUT_MS);
beforeEach(() => {
resolvePluginProvidersMock.mockClear();
resolvePluginProvidersMock.mockReset();
resolvePluginProvidersMock.mockReturnValue(uniqueProviderContractProviders);
});