





















@@ -0,0 +1,77 @@
1+import { afterEach, describe, expect, it, vi } from "vitest";
2+import type { OpenClawConfig } from "../config/types.openclaw.js";
3+import type { ModelCatalogEntry } from "./model-catalog.types.js";
4+5+const modelCatalogMocks = vi.hoisted(() => ({
6+loadModelCatalog: vi.fn<() => Promise<ModelCatalogEntry[]>>(),
7+}));
8+9+const modelAuthMocks = vi.hoisted(() => ({
10+hasRuntimeAvailableProviderAuth: vi.fn<(params: { provider: string }) => boolean>(),
11+}));
12+13+const authProfilesMocks = vi.hoisted(() => ({
14+ensureAuthProfileStore: vi.fn(() => ({ profiles: {} })),
15+ensureAuthProfileStoreWithoutExternalProfiles: vi.fn(() => ({ profiles: {} })),
16+externalCliDiscoveryForProviders: vi.fn(() => ({}) as never),
17+externalCliDiscoveryForProviderAuth: vi.fn(() => ({}) as never),
18+listProfilesForProvider: vi.fn(() => []),
19+}));
20+21+vi.mock("./model-catalog.js", () => ({
22+loadModelCatalog: modelCatalogMocks.loadModelCatalog,
23+}));
24+25+vi.mock("./model-auth.js", () => ({
26+hasRuntimeAvailableProviderAuth: modelAuthMocks.hasRuntimeAvailableProviderAuth,
27+}));
28+29+vi.mock("./auth-profiles.js", () => ({
30+ensureAuthProfileStore: authProfilesMocks.ensureAuthProfileStore,
31+ensureAuthProfileStoreWithoutExternalProfiles:
32+authProfilesMocks.ensureAuthProfileStoreWithoutExternalProfiles,
33+externalCliDiscoveryForProviders: authProfilesMocks.externalCliDiscoveryForProviders,
34+externalCliDiscoveryForProviderAuth: authProfilesMocks.externalCliDiscoveryForProviderAuth,
35+listProfilesForProvider: authProfilesMocks.listProfilesForProvider,
36+}));
37+38+vi.mock("./workspace.js", () => ({
39+resolveDefaultAgentWorkspaceDir: () => undefined,
40+}));
41+42+const { clearCurrentProviderAuthState, hasAuthForModelProvider, warmCurrentProviderAuthState } =
43+await import("./model-provider-auth.js");
44+45+describe("prepared provider auth state", () => {
46+afterEach(() => {
47+clearCurrentProviderAuthState();
48+vi.clearAllMocks();
49+});
50+51+it("hasAuthForModelProvider returns the prepared answer after warm and falls through to compute after clear", async () => {
52+const cfg = {} as OpenClawConfig;
53+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
54+{ id: "gpt", name: "gpt", provider: "openai" },
55+{ id: "claude", name: "claude", provider: "anthropic" },
56+]);
57+modelAuthMocks.hasRuntimeAvailableProviderAuth.mockImplementation(
58+({ provider }) => provider === "openai",
59+);
60+61+await warmCurrentProviderAuthState(cfg);
62+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2);
63+64+// Flip the underlying answer; if the prepared map is consulted first,
65+// hasAuthForModelProvider returns the cached answers without re-running
66+// the compute path.
67+modelAuthMocks.hasRuntimeAvailableProviderAuth.mockReturnValue(true);
68+expect(hasAuthForModelProvider({ provider: "openai", cfg })).toBe(true);
69+expect(hasAuthForModelProvider({ provider: "anthropic", cfg })).toBe(false);
70+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2);
71+72+// Clearing the prepared state forces the compute path on the next read.
73+clearCurrentProviderAuthState();
74+expect(hasAuthForModelProvider({ provider: "anthropic", cfg })).toBe(true);
75+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(3);
76+});
77+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。