refactor: share models list test helper

This commit is contained in:
Vincent Koc
2026-06-01 04:52:39 +02:00
parent dc1cfcc28d
commit 6e985931de

View File

@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
import { ErrorCodes } from "../../../packages/gateway-protocol/src/index.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { modelsHandlers } from "./models.js";
import type { RespondFn } from "./types.js";
type Deferred<T> = {
promise: Promise<T>;
@@ -22,44 +23,58 @@ function createDeferred<T>(): Deferred<T> {
return { promise, resolve, reject };
}
function requestModelsList(params: {
view: "configured" | "all";
respond?: ReturnType<typeof vi.fn>;
runtimeConfig?: OpenClawConfig;
loadGatewayModelCatalog: () => Promise<Array<Record<string, unknown>>>;
reqId?: string;
}) {
const respond = params.respond ?? vi.fn();
const request = modelsHandlers["models.list"]({
req: {
type: "req",
id: params.reqId ?? `req-models-list-${params.view}`,
method: "models.list",
params: { view: params.view },
},
params: { view: params.view },
respond: respond as RespondFn,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => params.runtimeConfig ?? ({} as OpenClawConfig),
loadGatewayModelCatalog: params.loadGatewayModelCatalog,
logGateway: {
debug: vi.fn(),
},
} as never,
});
return { request, respond };
}
describe("models.list", () => {
it("does not block the configured view on slow model catalog discovery", async () => {
const catalog = createDeferred<never>();
const respond = vi.fn();
const loadGatewayModelCatalog = vi.fn(() => catalog.promise);
const runtimeConfig = {
models: {
providers: {
openai: {
baseUrl: "https://openai.example.com",
models: [{ id: "gpt-test", name: "GPT Test" }],
},
},
},
} as unknown as OpenClawConfig;
vi.useFakeTimers();
try {
const request = modelsHandlers["models.list"]({
req: {
type: "req",
id: "req-models-list-slow-catalog",
method: "models.list",
params: { view: "configured" },
},
params: { view: "configured" },
respond,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => {
const config = {
models: {
providers: {
openai: {
baseUrl: "https://openai.example.com",
models: [{ id: "gpt-test", name: "GPT Test" }],
},
},
},
};
return config as unknown as OpenClawConfig;
},
loadGatewayModelCatalog,
logGateway: {
debug: vi.fn(),
},
} as never,
const { request, respond } = requestModelsList({
view: "configured",
runtimeConfig,
loadGatewayModelCatalog,
reqId: "req-models-list-slow-catalog",
});
await vi.advanceTimersByTimeAsync(800);
@@ -86,29 +101,14 @@ describe("models.list", () => {
it("keeps the all view exact instead of timing out to a partial catalog", async () => {
const catalog = createDeferred<[{ id: string; name: string; provider: string }]>();
const respond = vi.fn();
const loadGatewayModelCatalog = vi.fn(() => catalog.promise);
vi.useFakeTimers();
try {
const request = modelsHandlers["models.list"]({
req: {
type: "req",
id: "req-models-list-all-slow-catalog",
method: "models.list",
params: { view: "all" },
},
params: { view: "all" },
respond,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => ({}) as OpenClawConfig,
loadGatewayModelCatalog,
logGateway: {
debug: vi.fn(),
},
} as never,
const { request, respond } = requestModelsList({
view: "all",
loadGatewayModelCatalog,
reqId: "req-models-list-all-slow-catalog",
});
await vi.advanceTimersByTimeAsync(800);
@@ -129,35 +129,21 @@ describe("models.list", () => {
});
it("does not expose runtime params from catalog rows", async () => {
const respond = vi.fn();
await modelsHandlers["models.list"]({
req: {
type: "req",
id: "req-models-list-redact-params",
method: "models.list",
params: { view: "all" },
},
params: { view: "all" },
respond,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => ({}) as OpenClawConfig,
loadGatewayModelCatalog: vi.fn(() =>
Promise.resolve([
{
id: "qwen-local",
name: "Qwen Local",
provider: "vllm",
params: { qwenThinkingFormat: "chat-template" },
},
]),
),
logGateway: {
debug: vi.fn(),
},
} as never,
const { request, respond } = requestModelsList({
view: "all",
loadGatewayModelCatalog: vi.fn(() =>
Promise.resolve([
{
id: "qwen-local",
name: "Qwen Local",
provider: "vllm",
params: { qwenThinkingFormat: "chat-template" },
},
]),
),
reqId: "req-models-list-redact-params",
});
await request;
expect(respond).toHaveBeenCalledWith(
true,
@@ -191,27 +177,14 @@ describe("models.list", () => {
},
} as unknown as OpenClawConfig;
const configuredRespond = vi.fn();
const loadConfiguredCatalog = vi.fn(() => Promise.resolve(catalog));
await modelsHandlers["models.list"]({
req: {
type: "req",
id: "req-models-list-provider-allowlist",
method: "models.list",
params: { view: "configured" },
},
params: { view: "configured" },
respond: configuredRespond,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => cfg,
loadGatewayModelCatalog: loadConfiguredCatalog,
logGateway: {
debug: vi.fn(),
},
} as never,
const { request: configuredRequest, respond: configuredRespond } = requestModelsList({
view: "configured",
runtimeConfig: cfg,
loadGatewayModelCatalog: loadConfiguredCatalog,
reqId: "req-models-list-provider-allowlist",
});
await configuredRequest;
expect(configuredRespond).toHaveBeenCalledWith(
true,
@@ -227,52 +200,24 @@ describe("models.list", () => {
);
expect(loadConfiguredCatalog).toHaveBeenCalledWith({ readOnly: false });
const allRespond = vi.fn();
await modelsHandlers["models.list"]({
req: {
type: "req",
id: "req-models-list-provider-allowlist-all",
method: "models.list",
params: { view: "all" },
},
params: { view: "all" },
respond: allRespond,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => cfg,
loadGatewayModelCatalog: vi.fn(() => Promise.resolve(catalog)),
logGateway: {
debug: vi.fn(),
},
} as never,
const { request: allRequest, respond: allRespond } = requestModelsList({
view: "all",
runtimeConfig: cfg,
loadGatewayModelCatalog: vi.fn(() => Promise.resolve(catalog)),
reqId: "req-models-list-provider-allowlist-all",
});
await allRequest;
expect(allRespond).toHaveBeenCalledWith(true, { models: catalog }, undefined);
});
it("preserves catalog load errors before the timeout fallback wins", async () => {
const respond = vi.fn();
await modelsHandlers["models.list"]({
req: {
type: "req",
id: "req-models-list-catalog-error",
method: "models.list",
params: { view: "configured" },
},
params: { view: "configured" },
respond,
client: null,
isWebchatConnect: () => false,
context: {
getRuntimeConfig: () => ({}) as OpenClawConfig,
loadGatewayModelCatalog: vi.fn(() => Promise.reject(new Error("catalog failed"))),
logGateway: {
debug: vi.fn(),
},
} as never,
const { request, respond } = requestModelsList({
view: "configured",
loadGatewayModelCatalog: vi.fn(() => Promise.reject(new Error("catalog failed"))),
reqId: "req-models-list-catalog-error",
});
await request;
const call = respond.mock.calls.at(0) as
| [boolean, unknown, { code?: number; message?: string }]