

























@@ -0,0 +1,156 @@
1+import { describe, expect, it, vi } from "vitest";
2+import type { OpenClawConfig } from "../../config/types.openclaw.js";
3+import { ErrorCodes } from "../protocol/index.js";
4+import { modelsHandlers } from "./models.js";
5+6+type Deferred<T> = {
7+promise: Promise<T>;
8+resolve: (value: T) => void;
9+reject: (error: unknown) => void;
10+};
11+12+function createDeferred<T>(): Deferred<T> {
13+let resolve!: (value: T) => void;
14+let reject!: (error: unknown) => void;
15+const promise = new Promise<T>((resolvePromise, rejectPromise) => {
16+resolve = resolvePromise;
17+reject = rejectPromise;
18+});
19+return { promise, resolve, reject };
20+}
21+22+describe("models.list", () => {
23+it("does not block the configured view on slow model catalog discovery", async () => {
24+const catalog = createDeferred<never>();
25+const respond = vi.fn();
26+27+vi.useFakeTimers();
28+try {
29+const request = modelsHandlers["models.list"]({
30+req: {
31+type: "req",
32+id: "req-models-list-slow-catalog",
33+method: "models.list",
34+params: { view: "configured" },
35+},
36+params: { view: "configured" },
37+ respond,
38+client: null,
39+isWebchatConnect: () => false,
40+context: {
41+getRuntimeConfig: () => {
42+const config = {
43+models: {
44+providers: {
45+openai: {
46+baseUrl: "https://openai.example.com",
47+models: [{ id: "gpt-test", name: "GPT Test" }],
48+},
49+},
50+},
51+};
52+return config as unknown as OpenClawConfig;
53+},
54+loadGatewayModelCatalog: vi.fn(() => catalog.promise),
55+logGateway: {
56+debug: vi.fn(),
57+},
58+} as never,
59+});
60+61+await vi.advanceTimersByTimeAsync(800);
62+await request;
63+64+expect(respond).toHaveBeenCalledWith(
65+true,
66+{
67+models: [
68+{
69+id: "gpt-test",
70+name: "GPT Test",
71+provider: "openai",
72+},
73+],
74+},
75+undefined,
76+);
77+} finally {
78+vi.useRealTimers();
79+}
80+});
81+82+it("keeps the all view exact instead of timing out to a partial catalog", async () => {
83+const catalog = createDeferred<[{ id: string; name: string; provider: string }]>();
84+const respond = vi.fn();
85+86+vi.useFakeTimers();
87+try {
88+const request = modelsHandlers["models.list"]({
89+req: {
90+type: "req",
91+id: "req-models-list-all-slow-catalog",
92+method: "models.list",
93+params: { view: "all" },
94+},
95+params: { view: "all" },
96+ respond,
97+client: null,
98+isWebchatConnect: () => false,
99+context: {
100+getRuntimeConfig: () => ({}) as OpenClawConfig,
101+loadGatewayModelCatalog: vi.fn(() => catalog.promise),
102+logGateway: {
103+debug: vi.fn(),
104+},
105+} as never,
106+});
107+108+await vi.advanceTimersByTimeAsync(800);
109+expect(respond).not.toHaveBeenCalled();
110+111+catalog.resolve([{ id: "gpt-test", name: "GPT Test", provider: "openai" }]);
112+await request;
113+114+expect(respond).toHaveBeenCalledWith(
115+true,
116+{ models: [{ id: "gpt-test", name: "GPT Test", provider: "openai" }] },
117+undefined,
118+);
119+} finally {
120+vi.useRealTimers();
121+}
122+});
123+124+it("preserves catalog load errors before the timeout fallback wins", async () => {
125+const respond = vi.fn();
126+127+await modelsHandlers["models.list"]({
128+req: {
129+type: "req",
130+id: "req-models-list-catalog-error",
131+method: "models.list",
132+params: { view: "configured" },
133+},
134+params: { view: "configured" },
135+ respond,
136+client: null,
137+isWebchatConnect: () => false,
138+context: {
139+getRuntimeConfig: () => ({}) as OpenClawConfig,
140+loadGatewayModelCatalog: vi.fn(() => Promise.reject(new Error("catalog failed"))),
141+logGateway: {
142+debug: vi.fn(),
143+},
144+} as never,
145+});
146+147+expect(respond).toHaveBeenCalledWith(
148+false,
149+undefined,
150+expect.objectContaining({
151+code: ErrorCodes.UNAVAILABLE,
152+message: "Error: catalog failed",
153+}),
154+);
155+});
156+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。