mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 19:04:45 +00:00
test: clear provider runtime broad matchers
This commit is contained in:
@@ -158,17 +158,44 @@ function createOpenAiCatalogProviderPlugin(
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!isRecord(value)) {
|
||||
throw new Error(`Expected ${label} to be an object`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function expectRecordFields(record: Record<string, unknown>, fields: Record<string, unknown>) {
|
||||
for (const [key, value] of Object.entries(fields)) {
|
||||
expect(record[key]).toEqual(value);
|
||||
}
|
||||
}
|
||||
|
||||
function expectObjectOrArrayFields(value: unknown, fields: Record<string, unknown>) {
|
||||
if (!isRecord(value) && !Array.isArray(value)) {
|
||||
throw new Error("Expected value to be an object or array");
|
||||
}
|
||||
const record = value as Record<string, unknown>;
|
||||
expectRecordFields(record, fields);
|
||||
}
|
||||
|
||||
function getLastResolvePluginProvidersParams() {
|
||||
return requireRecord(resolvePluginProvidersMock.mock.calls.at(-1)?.[0], "provider load params");
|
||||
}
|
||||
|
||||
function expectProviderRuntimePluginLoad(params: { provider: string; expectedPluginId?: string }) {
|
||||
const plugin = resolveProviderRuntimePlugin({ provider: params.provider });
|
||||
|
||||
expect(plugin?.id).toBe(params.expectedPluginId);
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerRefs: [params.provider],
|
||||
bundledProviderAllowlistCompat: true,
|
||||
bundledProviderVitestCompat: true,
|
||||
}),
|
||||
);
|
||||
expectRecordFields(getLastResolvePluginProvidersParams(), {
|
||||
providerRefs: [params.provider],
|
||||
bundledProviderAllowlistCompat: true,
|
||||
bundledProviderVitestCompat: true,
|
||||
});
|
||||
}
|
||||
|
||||
function createDemoRuntimeContext<TContext extends Record<string, unknown>>(
|
||||
@@ -224,7 +251,7 @@ async function expectResolvedMatches(
|
||||
) {
|
||||
await Promise.all(
|
||||
cases.map(async ({ actual, expected }) => {
|
||||
await expect(actual()).resolves.toMatchObject(expected);
|
||||
expectObjectOrArrayFields(await actual(), expected);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -440,7 +467,8 @@ describe("provider-runtime", () => {
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(plugin).toMatchObject({ id: "ollama", pluginId: "ollama" });
|
||||
expect(plugin?.id).toBe("ollama");
|
||||
expect(plugin?.pluginId).toBe("ollama");
|
||||
expect(resolvePluginProvidersMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -630,19 +658,14 @@ describe("provider-runtime", () => {
|
||||
]);
|
||||
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
expect(
|
||||
resolveExternalAuthProfilesWithPlugins({
|
||||
const [profile] = resolveExternalAuthProfilesWithPlugins({
|
||||
env: process.env,
|
||||
context: {
|
||||
env: process.env,
|
||||
context: {
|
||||
env: process.env,
|
||||
store: { version: 1, profiles: {} },
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
profileId: "legacy-provider:external",
|
||||
}),
|
||||
]);
|
||||
store: { version: 1, profiles: {} },
|
||||
},
|
||||
});
|
||||
expect(profile?.profileId).toBe("legacy-provider:external");
|
||||
}
|
||||
|
||||
expect(providerRuntimeWarnMock).toHaveBeenCalledTimes(1);
|
||||
@@ -674,19 +697,14 @@ describe("provider-runtime", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveExternalAuthProfilesWithPlugins({
|
||||
const [profile] = resolveExternalAuthProfilesWithPlugins({
|
||||
env: process.env,
|
||||
context: {
|
||||
env: process.env,
|
||||
context: {
|
||||
env: process.env,
|
||||
store: { version: 1, profiles: {} },
|
||||
},
|
||||
}),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
profileId: "demo-provider:external",
|
||||
}),
|
||||
]);
|
||||
store: { version: 1, profiles: {} },
|
||||
},
|
||||
});
|
||||
expect(profile?.profileId).toBe("demo-provider:external");
|
||||
expect(providerRuntimeWarnMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -809,13 +827,12 @@ describe("provider-runtime", () => {
|
||||
baseUrl: "https://runtime.example.com/v1",
|
||||
expiresAt: 123,
|
||||
});
|
||||
expect(prepareRuntimeAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: "raw-token",
|
||||
modelId: MODEL.id,
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
}),
|
||||
);
|
||||
const prepareRuntimeAuthCalls = prepareRuntimeAuth.mock.calls as unknown[][];
|
||||
expectRecordFields(requireRecord(prepareRuntimeAuthCalls[0]?.[0], "runtime auth context"), {
|
||||
apiKey: "raw-token",
|
||||
modelId: MODEL.id,
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns no runtime plugin when the provider has no owning plugin", () => {
|
||||
@@ -852,13 +869,14 @@ describe("provider-runtime", () => {
|
||||
providerTransportPatch: true,
|
||||
},
|
||||
});
|
||||
expect(extraParamsForTransport).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
expectRecordFields(
|
||||
requireRecord(extraParamsForTransport.mock.calls[0]?.[0], "transport params context"),
|
||||
{
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
modelId: MODEL.id,
|
||||
model: MODEL,
|
||||
transport: "websocket",
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -988,15 +1006,15 @@ describe("provider-runtime", () => {
|
||||
expect(contribution?.stablePrefix).toContain("<persona_latch>");
|
||||
expect(contribution?.stablePrefix).toContain("provider overlay");
|
||||
expect(contribution?.sectionOverrides?.execution_bias).toBe("saw built-in overlay");
|
||||
expect(resolvePromptOverlay).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "openrouter",
|
||||
modelId: "openai/gpt-5.4",
|
||||
baseOverlay: expect.objectContaining({
|
||||
stablePrefix: expect.stringContaining("<persona_latch>"),
|
||||
}),
|
||||
}),
|
||||
const overlayContext = requireRecord(
|
||||
resolvePromptOverlay.mock.calls[0]?.[0],
|
||||
"overlay context",
|
||||
);
|
||||
expect(overlayContext.provider).toBe("openrouter");
|
||||
expect(overlayContext.modelId).toBe("openai/gpt-5.4");
|
||||
expect(
|
||||
String(requireRecord(overlayContext.baseOverlay, "base overlay").stablePrefix),
|
||||
).toContain("<persona_latch>");
|
||||
});
|
||||
|
||||
it("ignores OpenAI plugin personality fallback for non-OpenAI GPT-5 providers", () => {
|
||||
@@ -1125,10 +1143,8 @@ describe("provider-runtime", () => {
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toMatchObject({
|
||||
baseUrl: "https://normalized.example.com/v1",
|
||||
});
|
||||
})?.baseUrl,
|
||||
).toBe("https://normalized.example.com/v1");
|
||||
});
|
||||
|
||||
it("does not scan provider plugins after bundled policy surface handles config", () => {
|
||||
@@ -1206,7 +1222,7 @@ describe("provider-runtime", () => {
|
||||
config: {},
|
||||
},
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
agents: {
|
||||
defaults: {
|
||||
heartbeat: {
|
||||
@@ -1403,10 +1419,8 @@ describe("provider-runtime", () => {
|
||||
resolveProviderRuntimePlugin({
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
config: config as never,
|
||||
}),
|
||||
).toMatchObject({
|
||||
id: DEMO_PROVIDER_ID,
|
||||
});
|
||||
})?.id,
|
||||
).toBe(DEMO_PROVIDER_ID);
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -1507,7 +1521,7 @@ describe("provider-runtime", () => {
|
||||
}),
|
||||
createStreamFn,
|
||||
wrapStreamFn: ({ streamFn, model }) => {
|
||||
expect(model).toMatchObject(MODEL);
|
||||
expect(model).toEqual(MODEL);
|
||||
return streamFn;
|
||||
},
|
||||
createEmbeddingProvider,
|
||||
@@ -1565,7 +1579,7 @@ describe("provider-runtime", () => {
|
||||
modelRegistry: EMPTY_MODEL_REGISTRY,
|
||||
}),
|
||||
}),
|
||||
).toMatchObject(MODEL);
|
||||
).toEqual(MODEL);
|
||||
|
||||
expect(
|
||||
normalizeProviderModelIdWithPlugin({
|
||||
@@ -1602,25 +1616,22 @@ describe("provider-runtime", () => {
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toMatchObject({
|
||||
baseUrl: "https://normalized.example.com/v1",
|
||||
});
|
||||
})?.baseUrl,
|
||||
).toBe("https://normalized.example.com/v1");
|
||||
|
||||
expect(
|
||||
applyProviderNativeStreamingUsageCompatWithPlugin({
|
||||
const streamingCompatConfig = applyProviderNativeStreamingUsageCompatWithPlugin({
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
context: {
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
context: {
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
providerConfig: {
|
||||
baseUrl: "https://demo.example.com",
|
||||
api: "openai-completions",
|
||||
models: [],
|
||||
},
|
||||
providerConfig: {
|
||||
baseUrl: "https://demo.example.com",
|
||||
api: "openai-completions",
|
||||
models: [],
|
||||
},
|
||||
}),
|
||||
).toMatchObject({
|
||||
compat: { supportsUsageInStreaming: true },
|
||||
},
|
||||
});
|
||||
expect(requireRecord(streamingCompatConfig, "streaming compat config").compat).toEqual({
|
||||
supportsUsageInStreaming: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -1647,7 +1658,7 @@ describe("provider-runtime", () => {
|
||||
modelApi: MODEL.api,
|
||||
}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
sanitizeMode: "full",
|
||||
toolCallIdMode: "strict9",
|
||||
allowSyntheticToolResults: true,
|
||||
@@ -1669,7 +1680,7 @@ describe("provider-runtime", () => {
|
||||
extraParams: { temperature: 0.3 },
|
||||
}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
temperature: 0.3,
|
||||
transport: "auto",
|
||||
});
|
||||
@@ -1828,7 +1839,7 @@ describe("provider-runtime", () => {
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
context: createDemoResolvedModelContext({}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
...MODEL,
|
||||
api: "openai-codex-responses",
|
||||
});
|
||||
@@ -1919,29 +1930,6 @@ describe("provider-runtime", () => {
|
||||
}),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
actual: () =>
|
||||
resolveExternalAuthProfilesWithPlugins({
|
||||
env: process.env,
|
||||
context: {
|
||||
env: process.env,
|
||||
store: { version: 1, profiles: {} },
|
||||
},
|
||||
}),
|
||||
expected: [
|
||||
{
|
||||
persistence: "runtime-only",
|
||||
profileId: "demo:managed",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
access: "external-access",
|
||||
refresh: "external-refresh",
|
||||
expires: expect.any(Number),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actual: () =>
|
||||
resolveProviderSyntheticAuthWithPlugin({
|
||||
@@ -1986,6 +1974,26 @@ describe("provider-runtime", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const [externalProfile] = resolveExternalAuthProfilesWithPlugins({
|
||||
env: process.env,
|
||||
context: {
|
||||
env: process.env,
|
||||
store: { version: 1, profiles: {} },
|
||||
},
|
||||
});
|
||||
expectRecordFields(requireRecord(externalProfile, "external auth profile"), {
|
||||
persistence: "runtime-only",
|
||||
profileId: "demo:managed",
|
||||
});
|
||||
const credential = requireRecord(externalProfile?.credential, "external auth credential");
|
||||
expectRecordFields(credential, {
|
||||
type: "oauth",
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
access: "external-access",
|
||||
refresh: "external-refresh",
|
||||
});
|
||||
expect(typeof credential.expires).toBe("number");
|
||||
|
||||
expectCodexMissingAuthHint(buildProviderMissingAuthMessageWithPlugin);
|
||||
await expectAugmentedCodexCatalog(augmentModelCatalogWithProviderPlugins);
|
||||
|
||||
@@ -2032,11 +2040,7 @@ describe("provider-runtime", () => {
|
||||
});
|
||||
|
||||
expect(plugin).toBe(ollamaPlugin);
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerRefs: ["ollama-spark", "ollama"],
|
||||
}),
|
||||
);
|
||||
expect(getLastResolvePluginProvidersParams().providerRefs).toEqual(["ollama-spark", "ollama"]);
|
||||
});
|
||||
|
||||
it("does not match alias hooks when an exact custom provider declares a foreign api owner", () => {
|
||||
@@ -2065,11 +2069,10 @@ describe("provider-runtime", () => {
|
||||
});
|
||||
|
||||
expect(plugin).toBeUndefined();
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerRefs: ["modelstudio", "openai-completions"],
|
||||
}),
|
||||
);
|
||||
expect(getLastResolvePluginProvidersParams().providerRefs).toEqual([
|
||||
"modelstudio",
|
||||
"openai-completions",
|
||||
]);
|
||||
});
|
||||
|
||||
it("merges compat contributions from owner and foreign provider plugins", () => {
|
||||
@@ -2108,13 +2111,11 @@ describe("provider-runtime", () => {
|
||||
compat: { supportsDeveloperRole: false },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
compat: {
|
||||
supportsDeveloperRole: false,
|
||||
supportsStrictMode: true,
|
||||
supportsStore: false,
|
||||
},
|
||||
})?.compat,
|
||||
).toEqual({
|
||||
supportsDeveloperRole: false,
|
||||
supportsStrictMode: true,
|
||||
supportsStore: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2154,7 +2155,8 @@ describe("provider-runtime", () => {
|
||||
},
|
||||
}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
).toEqual({
|
||||
...MODEL,
|
||||
provider: "custom-openai",
|
||||
id: "gpt-5.4",
|
||||
api: "openai-responses",
|
||||
@@ -2188,12 +2190,10 @@ describe("provider-runtime", () => {
|
||||
}),
|
||||
).resolves.toEqual(expectedAugmentedOpenaiCodexCatalogEntries);
|
||||
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["openai"],
|
||||
activate: false,
|
||||
}),
|
||||
);
|
||||
expectRecordFields(getLastResolvePluginProvidersParams(), {
|
||||
onlyPluginIds: ["openai"],
|
||||
activate: false,
|
||||
});
|
||||
expect(resolveCatalogHookProviderPluginIdsMock).toHaveBeenCalledTimes(1);
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user