























@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
22import { ErrorCodes } from "../../../packages/gateway-protocol/src/index.js";
33import type { OpenClawConfig } from "../../config/types.openclaw.js";
44import { modelsHandlers } from "./models.js";
5+import type { RespondFn } from "./types.js";
5667type Deferred<T> = {
78promise: Promise<T>;
@@ -22,44 +23,58 @@ function createDeferred<T>(): Deferred<T> {
2223return { promise, resolve, reject };
2324}
242526+function requestModelsList(params: {
27+view: "configured" | "all";
28+respond?: ReturnType<typeof vi.fn>;
29+runtimeConfig?: OpenClawConfig;
30+loadGatewayModelCatalog: () => Promise<Array<Record<string, unknown>>>;
31+reqId?: string;
32+}) {
33+const respond = params.respond ?? vi.fn();
34+const request = modelsHandlers["models.list"]({
35+req: {
36+type: "req",
37+id: params.reqId ?? `req-models-list-${params.view}`,
38+method: "models.list",
39+params: { view: params.view },
40+},
41+params: { view: params.view },
42+respond: respond as RespondFn,
43+client: null,
44+isWebchatConnect: () => false,
45+context: {
46+getRuntimeConfig: () => params.runtimeConfig ?? ({} as OpenClawConfig),
47+loadGatewayModelCatalog: params.loadGatewayModelCatalog,
48+logGateway: {
49+debug: vi.fn(),
50+},
51+} as never,
52+});
53+return { request, respond };
54+}
55+2556describe("models.list", () => {
2657it("does not block the configured view on slow model catalog discovery", async () => {
2758const catalog = createDeferred<never>();
28-const respond = vi.fn();
2959const loadGatewayModelCatalog = vi.fn(() => catalog.promise);
60+const runtimeConfig = {
61+models: {
62+providers: {
63+openai: {
64+baseUrl: "https://openai.example.com",
65+models: [{ id: "gpt-test", name: "GPT Test" }],
66+},
67+},
68+},
69+} as unknown as OpenClawConfig;
30703171vi.useFakeTimers();
3272try {
33-const request = modelsHandlers["models.list"]({
34-req: {
35-type: "req",
36-id: "req-models-list-slow-catalog",
37-method: "models.list",
38-params: { view: "configured" },
39-},
40-params: { view: "configured" },
41- respond,
42-client: null,
43-isWebchatConnect: () => false,
44-context: {
45-getRuntimeConfig: () => {
46-const config = {
47-models: {
48-providers: {
49-openai: {
50-baseUrl: "https://openai.example.com",
51-models: [{ id: "gpt-test", name: "GPT Test" }],
52-},
53-},
54-},
55-};
56-return config as unknown as OpenClawConfig;
57-},
58- loadGatewayModelCatalog,
59-logGateway: {
60-debug: vi.fn(),
61-},
62-} as never,
73+const { request, respond } = requestModelsList({
74+view: "configured",
75+ runtimeConfig,
76+ loadGatewayModelCatalog,
77+reqId: "req-models-list-slow-catalog",
6378});
64796580await vi.advanceTimersByTimeAsync(800);
@@ -86,29 +101,14 @@ describe("models.list", () => {
8610187102it("keeps the all view exact instead of timing out to a partial catalog", async () => {
88103const catalog = createDeferred<[{ id: string; name: string; provider: string }]>();
89-const respond = vi.fn();
90104const loadGatewayModelCatalog = vi.fn(() => catalog.promise);
9110592106vi.useFakeTimers();
93107try {
94-const request = modelsHandlers["models.list"]({
95-req: {
96-type: "req",
97-id: "req-models-list-all-slow-catalog",
98-method: "models.list",
99-params: { view: "all" },
100-},
101-params: { view: "all" },
102- respond,
103-client: null,
104-isWebchatConnect: () => false,
105-context: {
106-getRuntimeConfig: () => ({}) as OpenClawConfig,
107- loadGatewayModelCatalog,
108-logGateway: {
109-debug: vi.fn(),
110-},
111-} as never,
108+const { request, respond } = requestModelsList({
109+view: "all",
110+ loadGatewayModelCatalog,
111+reqId: "req-models-list-all-slow-catalog",
112112});
113113114114await vi.advanceTimersByTimeAsync(800);
@@ -129,35 +129,21 @@ describe("models.list", () => {
129129});
130130131131it("does not expose runtime params from catalog rows", async () => {
132-const respond = vi.fn();
133-await modelsHandlers["models.list"]({
134-req: {
135-type: "req",
136-id: "req-models-list-redact-params",
137-method: "models.list",
138-params: { view: "all" },
139-},
140-params: { view: "all" },
141- respond,
142-client: null,
143-isWebchatConnect: () => false,
144-context: {
145-getRuntimeConfig: () => ({}) as OpenClawConfig,
146-loadGatewayModelCatalog: vi.fn(() =>
147-Promise.resolve([
148-{
149-id: "qwen-local",
150-name: "Qwen Local",
151-provider: "vllm",
152-params: { qwenThinkingFormat: "chat-template" },
153-},
154-]),
155-),
156-logGateway: {
157-debug: vi.fn(),
158-},
159-} as never,
132+const { request, respond } = requestModelsList({
133+view: "all",
134+loadGatewayModelCatalog: vi.fn(() =>
135+Promise.resolve([
136+{
137+id: "qwen-local",
138+name: "Qwen Local",
139+provider: "vllm",
140+params: { qwenThinkingFormat: "chat-template" },
141+},
142+]),
143+),
144+reqId: "req-models-list-redact-params",
160145});
146+await request;
161147162148expect(respond).toHaveBeenCalledWith(
163149true,
@@ -191,27 +177,14 @@ describe("models.list", () => {
191177},
192178} as unknown as OpenClawConfig;
193179194-const configuredRespond = vi.fn();
195180const loadConfiguredCatalog = vi.fn(() => Promise.resolve(catalog));
196-await modelsHandlers["models.list"]({
197-req: {
198-type: "req",
199-id: "req-models-list-provider-allowlist",
200-method: "models.list",
201-params: { view: "configured" },
202-},
203-params: { view: "configured" },
204-respond: configuredRespond,
205-client: null,
206-isWebchatConnect: () => false,
207-context: {
208-getRuntimeConfig: () => cfg,
209-loadGatewayModelCatalog: loadConfiguredCatalog,
210-logGateway: {
211-debug: vi.fn(),
212-},
213-} as never,
181+const { request: configuredRequest, respond: configuredRespond } = requestModelsList({
182+view: "configured",
183+runtimeConfig: cfg,
184+loadGatewayModelCatalog: loadConfiguredCatalog,
185+reqId: "req-models-list-provider-allowlist",
214186});
187+await configuredRequest;
215188216189expect(configuredRespond).toHaveBeenCalledWith(
217190true,
@@ -227,52 +200,24 @@ describe("models.list", () => {
227200);
228201expect(loadConfiguredCatalog).toHaveBeenCalledWith({ readOnly: false });
229202230-const allRespond = vi.fn();
231-await modelsHandlers["models.list"]({
232-req: {
233-type: "req",
234-id: "req-models-list-provider-allowlist-all",
235-method: "models.list",
236-params: { view: "all" },
237-},
238-params: { view: "all" },
239-respond: allRespond,
240-client: null,
241-isWebchatConnect: () => false,
242-context: {
243-getRuntimeConfig: () => cfg,
244-loadGatewayModelCatalog: vi.fn(() => Promise.resolve(catalog)),
245-logGateway: {
246-debug: vi.fn(),
247-},
248-} as never,
203+const { request: allRequest, respond: allRespond } = requestModelsList({
204+view: "all",
205+runtimeConfig: cfg,
206+loadGatewayModelCatalog: vi.fn(() => Promise.resolve(catalog)),
207+reqId: "req-models-list-provider-allowlist-all",
249208});
209+await allRequest;
250210251211expect(allRespond).toHaveBeenCalledWith(true, { models: catalog }, undefined);
252212});
253213254214it("preserves catalog load errors before the timeout fallback wins", async () => {
255-const respond = vi.fn();
256-257-await modelsHandlers["models.list"]({
258-req: {
259-type: "req",
260-id: "req-models-list-catalog-error",
261-method: "models.list",
262-params: { view: "configured" },
263-},
264-params: { view: "configured" },
265- respond,
266-client: null,
267-isWebchatConnect: () => false,
268-context: {
269-getRuntimeConfig: () => ({}) as OpenClawConfig,
270-loadGatewayModelCatalog: vi.fn(() => Promise.reject(new Error("catalog failed"))),
271-logGateway: {
272-debug: vi.fn(),
273-},
274-} as never,
215+const { request, respond } = requestModelsList({
216+view: "configured",
217+loadGatewayModelCatalog: vi.fn(() => Promise.reject(new Error("catalog failed"))),
218+reqId: "req-models-list-catalog-error",
275219});
220+await request;
276221277222const call = respond.mock.calls.at(0) as
278223| [boolean, unknown, { code?: number; message?: string }]
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。