Tests: retry scoped contract registry loads

This commit is contained in:
Gustavo Madeira Santana 2026-03-28 19:53:21 -04:00
parent d3673fd53e
commit d0e0150129
No known key found for this signature in database
2 changed files with 240 additions and 12 deletions

View File

@ -0,0 +1,147 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ProviderPlugin, WebSearchProviderPlugin } from "../types.js";
type MockPluginRecord = {
id: string;
status: "loaded" | "error";
error?: string;
providerIds: string[];
webSearchProviderIds: string[];
};
type MockRuntimeRegistry = {
plugins: MockPluginRecord[];
diagnostics: Array<{ pluginId?: string; message: string }>;
providers: Array<{ pluginId: string; provider: ProviderPlugin }>;
webSearchProviders: Array<{ pluginId: string; provider: WebSearchProviderPlugin }>;
};
function createMockRuntimeRegistry(params: {
plugin: MockPluginRecord;
providers?: Array<{ pluginId: string; provider: ProviderPlugin }>;
webSearchProviders?: Array<{ pluginId: string; provider: WebSearchProviderPlugin }>;
diagnostics?: Array<{ pluginId?: string; message: string }>;
}): MockRuntimeRegistry {
return {
plugins: [params.plugin],
diagnostics: params.diagnostics ?? [],
providers: params.providers ?? [],
webSearchProviders: params.webSearchProviders ?? [],
};
}
afterEach(() => {
vi.resetModules();
vi.restoreAllMocks();
});
describe("plugin contract registry scoped retries", () => {
it("retries provider loads after a transient plugin-scoped runtime error", async () => {
const loadBundledCapabilityRuntimeRegistry = vi
.fn()
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
status: "error",
error: "transient xai load failure",
providerIds: [],
webSearchProviderIds: [],
},
diagnostics: [{ pluginId: "xai", message: "transient xai load failure" }],
}),
)
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
status: "loaded",
providerIds: ["xai"],
webSearchProviderIds: ["grok"],
},
providers: [
{
pluginId: "xai",
provider: {
id: "xai",
label: "xAI",
docsPath: "/providers/xai",
auth: [],
} as ProviderPlugin,
},
],
}),
);
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
const { resolveProviderContractProvidersForPluginIds } = await import("./registry.js");
expect(
resolveProviderContractProvidersForPluginIds(["xai"]).map((provider) => provider.id),
).toEqual(["xai"]);
expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(2);
});
it("retries web search provider loads after a transient plugin-scoped runtime error", async () => {
const loadBundledCapabilityRuntimeRegistry = vi
.fn()
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
status: "error",
error: "transient grok load failure",
providerIds: [],
webSearchProviderIds: [],
},
diagnostics: [{ pluginId: "xai", message: "transient grok load failure" }],
}),
)
.mockReturnValueOnce(
createMockRuntimeRegistry({
plugin: {
id: "xai",
status: "loaded",
providerIds: ["xai"],
webSearchProviderIds: ["grok"],
},
webSearchProviders: [
{
pluginId: "xai",
provider: {
id: "grok",
label: "Grok Search",
hint: "Search the web with Grok",
envVars: ["XAI_API_KEY"],
placeholder: "XAI_API_KEY",
signupUrl: "https://x.ai",
credentialPath: "plugins.entries.xai.config.webSearch.apiKey",
requiresCredential: true,
getCredentialValue: () => undefined,
setCredentialValue() {},
createTool: () => ({
description: "search",
parameters: {},
execute: async () => ({}),
}),
} as WebSearchProviderPlugin,
},
],
}),
);
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
const { resolveWebSearchProviderContractEntriesForPluginId } = await import("./registry.js");
expect(
resolveWebSearchProviderContractEntriesForPluginId("xai").map((entry) => entry.provider.id),
).toEqual(["grok"]);
expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(2);
});
});

View File

@ -20,6 +20,7 @@ import {
loadVitestSpeechProviderContractRegistry,
} from "./speech-vitest-registry.js";
type BundledCapabilityRuntimeRegistry = ReturnType<typeof loadBundledCapabilityRuntimeRegistry>;
type CapabilityContractEntry<T> = {
pluginId: string;
provider: T;
@ -91,6 +92,73 @@ const providerContractPluginIdsByProviderId = createProviderContractPluginIdsByP
export let providerContractLoadError: Error | undefined;
function formatBundledCapabilityPluginLoadError(params: {
pluginId: string;
capabilityLabel: string;
registry: BundledCapabilityRuntimeRegistry;
}): Error {
const plugin = params.registry.plugins.find((entry) => entry.id === params.pluginId);
const diagnostics = params.registry.diagnostics
.filter((entry) => entry.pluginId === params.pluginId)
.map((entry) => entry.message);
const detailParts = plugin
? [
`status=${plugin.status}`,
...(plugin.error ? [`error=${plugin.error}`] : []),
`providerIds=[${plugin.providerIds.join(", ")}]`,
`webSearchProviderIds=[${plugin.webSearchProviderIds.join(", ")}]`,
]
: ["plugin record missing"];
if (diagnostics.length > 0) {
detailParts.push(`diagnostics=${diagnostics.join(" | ")}`);
}
return new Error(
`bundled ${params.capabilityLabel} contract load failed for ${params.pluginId}: ${detailParts.join("; ")}`,
);
}
function loadScopedCapabilityRuntimeRegistryEntries<T>(params: {
pluginId: string;
capabilityLabel: string;
loadEntries: (registry: BundledCapabilityRuntimeRegistry) => T[];
loadDeclaredIds: (
plugin: BundledCapabilityRuntimeRegistry["plugins"][number],
) => readonly string[];
}): T[] {
let lastFailure: Error | undefined;
for (let attempt = 0; attempt < 2; attempt += 1) {
const registry = loadBundledCapabilityRuntimeRegistry({
pluginIds: [params.pluginId],
pluginSdkResolution: "dist",
});
const entries = params.loadEntries(registry);
if (entries.length > 0) {
return entries;
}
const plugin = registry.plugins.find((entry) => entry.id === params.pluginId);
lastFailure = formatBundledCapabilityPluginLoadError({
pluginId: params.pluginId,
capabilityLabel: params.capabilityLabel,
registry,
});
const shouldRetry =
attempt === 0 &&
(!plugin || plugin.status !== "loaded" || params.loadDeclaredIds(plugin).length === 0);
if (!shouldRetry) {
break;
}
}
throw (
lastFailure ??
new Error(
`bundled ${params.capabilityLabel} contract load failed for ${params.pluginId}: no entries`,
)
);
}
function loadProviderContractEntriesForPluginIds(
pluginIds: readonly string[],
): ProviderContractEntry[] {
@ -112,10 +180,18 @@ function loadProviderContractEntriesForPluginId(pluginId: string): ProviderContr
try {
providerContractLoadError = undefined;
const entries = loadBundledCapabilityRuntimeRegistry({
pluginIds: [pluginId],
pluginSdkResolution: "dist",
}).providers.map((entry) => ({
const entries = loadScopedCapabilityRuntimeRegistryEntries({
pluginId,
capabilityLabel: "provider",
loadEntries: (registry) =>
registry.providers
.filter((entry) => entry.pluginId === pluginId)
.map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
})),
loadDeclaredIds: (plugin) => plugin.providerIds,
}).map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
}));
@ -208,14 +284,19 @@ export function resolveWebSearchProviderContractEntriesForPluginId(
return cached;
}
const entries = loadBundledCapabilityRuntimeRegistry({
pluginIds: [pluginId],
pluginSdkResolution: "dist",
}).webSearchProviders.map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
credentialValue: resolveWebSearchCredentialValue(entry.provider),
}));
const entries = loadScopedCapabilityRuntimeRegistryEntries({
pluginId,
capabilityLabel: "web search provider",
loadEntries: (registry) =>
registry.webSearchProviders
.filter((entry) => entry.pluginId === pluginId)
.map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
credentialValue: resolveWebSearchCredentialValue(entry.provider),
})),
loadDeclaredIds: (plugin) => plugin.webSearchProviderIds,
});
cache.set(pluginId, entries);
return entries;
}