


























@@ -69,6 +69,18 @@ function mockSingleOpenAiCatalogModel() {
6969mockPiDiscoveryModels([{ id: "gpt-4.1", provider: "openai", name: "GPT-4.1" }]);
7070}
717172+function emptyPluginMetadataSnapshot() {
73+return {
74+policyHash: "test-policy",
75+configFingerprint: "test-config",
76+index: {
77+policyHash: "test-policy",
78+plugins: [],
79+},
80+plugins: [],
81+};
82+}
83+7284describe("loadModelCatalog", () => {
7385beforeAll(async () => {
7486readFileMock = vi.fn();
@@ -117,7 +129,9 @@ describe("loadModelCatalog", () => {
117129ensureOpenClawModelsJsonMock.mockClear();
118130augmentCatalogMock.mockClear();
119131currentPluginMetadataSnapshotMock.mockReset();
132+currentPluginMetadataSnapshotMock.mockReturnValue(emptyPluginMetadataSnapshot());
120133loadPluginMetadataSnapshotMock.mockReset();
134+loadPluginMetadataSnapshotMock.mockReturnValue(emptyPluginMetadataSnapshot());
121135});
122136123137afterEach(() => {
@@ -206,26 +220,46 @@ describe("loadModelCatalog", () => {
206220}
207221});
208222209-it("does not prepare models.json when loading catalog in read-only mode", async () => {
210-const discoverAuthStorage = vi.fn(() => ({}));
211-__setModelCatalogImportForTest(
212-async () =>
213-({
214- discoverAuthStorage,
215-AuthStorage: function AuthStorage() {},
216-ModelRegistry: class {
217-getAll() {
218-return [{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }];
219-}
220-},
221-}) as unknown as PiSdkModule,
222-);
223+it("does not prepare models.json or import provider discovery when loading fallback catalog in read-only mode", async () => {
224+const importPiSdk = vi.fn(async () => {
225+throw new Error("provider discovery should not load");
226+});
227+__setModelCatalogImportForTest(importPiSdk as unknown as () => Promise<PiSdkModule>);
228+currentPluginMetadataSnapshotMock.mockReturnValueOnce(undefined);
229+loadPluginMetadataSnapshotMock.mockImplementationOnce(() => {
230+throw new Error("metadata scan should not run");
231+});
223232224-const result = await loadModelCatalog({ config: {} as OpenClawConfig, readOnly: true });
233+const result = await loadModelCatalog({
234+config: {
235+models: {
236+providers: {
237+openai: {
238+baseUrl: "https://openai.example.com/v1",
239+models: [
240+{
241+id: "gpt-test",
242+name: "GPT Test",
243+reasoning: false,
244+input: ["text"],
245+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
246+contextWindow: 200_000,
247+maxTokens: 8192,
248+},
249+],
250+},
251+},
252+},
253+} as OpenClawConfig,
254+readOnly: true,
255+});
225256226-expect(result).toEqual([{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }]);
257+expect(result).toContainEqual(
258+expect.objectContaining({ id: "gpt-test", name: "GPT Test", provider: "openai" }),
259+);
227260expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled();
228-expect(discoverAuthStorage).toHaveBeenCalledWith("/tmp/openclaw", { readOnly: true });
261+expect(importPiSdk).not.toHaveBeenCalled();
262+expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
229263});
230264231265it("filters suppressed built-ins from persisted read-only catalog rows", async () => {
@@ -279,7 +313,7 @@ describe("loadModelCatalog", () => {
279313expect(augmentCatalogMock).not.toHaveBeenCalled();
280314});
281315282-it("falls back to the registry when persisted read-only catalog has no model rows", async () => {
316+it("falls back to manifest catalog rows when persisted read-only catalog has no model rows", async () => {
283317readFileMock.mockResolvedValueOnce(
284318JSON.stringify({
285319providers: {
@@ -293,27 +327,50 @@ describe("loadModelCatalog", () => {
293327},
294328}),
295329);
296-const discoverAuthStorage = vi.fn(() => ({
297-getOAuthProviders: () => [],
298-}));
299-__setModelCatalogImportForTest(
300-async () =>
301-({
302- discoverAuthStorage,
303-AuthStorage: function AuthStorage() {},
304-ModelRegistry: class {
305-getAll() {
306-return [{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }];
307-}
330+currentPluginMetadataSnapshotMock.mockReturnValueOnce({
331+policyHash: "policy",
332+index: {
333+policyHash: "policy",
334+plugins: [
335+{
336+pluginId: "external-provider",
337+enabled: true,
338+origin: "global",
308339},
309-}) as unknown as PiSdkModule,
310-);
340+],
341+},
342+plugins: [
343+{
344+id: "external-provider",
345+origin: "global",
346+modelCatalog: {
347+providers: {
348+external: {
349+models: [{ id: "external-fast", name: "External Fast" }],
350+},
351+},
352+},
353+},
354+],
355+});
356+const importPiSdk = vi.fn(async () => {
357+throw new Error("provider discovery should not load");
358+});
359+__setModelCatalogImportForTest(importPiSdk as unknown as () => Promise<PiSdkModule>);
311360312361const result = await loadModelCatalog({ config: {} as OpenClawConfig, readOnly: true });
313362314-expect(result).toEqual([{ id: "gpt-4.1", name: "GPT-4.1", provider: "openai" }]);
363+expect(result).toEqual([
364+{
365+provider: "external",
366+id: "external-fast",
367+name: "External Fast",
368+input: ["text"],
369+reasoning: false,
370+},
371+]);
315372expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled();
316-expect(discoverAuthStorage).toHaveBeenCalledWith("/tmp/openclaw", { readOnly: true });
373+expect(importPiSdk).not.toHaveBeenCalled();
317374});
318375319376it("preserves registry defaults for minimal persisted read-only catalog rows", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。