From 180cecda8546dd124ef829a7d5eaefc48c0c8147 Mon Sep 17 00:00:00 2001 From: Sarah Fortune Date: Wed, 20 May 2026 22:22:11 -0700 Subject: [PATCH] test(model-provider-auth): cover prepared-state short-circuit and clear Asserts hasAuthForModelProvider returns the warmed answer for providers in the prepared map and skips the compute path, and that clearCurrentProviderAuthState restores fall-through to compute. --- src/agents/model-provider-auth.test.ts | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/agents/model-provider-auth.test.ts diff --git a/src/agents/model-provider-auth.test.ts b/src/agents/model-provider-auth.test.ts new file mode 100644 index 00000000000..d69a7aa18cd --- /dev/null +++ b/src/agents/model-provider-auth.test.ts @@ -0,0 +1,77 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { ModelCatalogEntry } from "./model-catalog.types.js"; + +const modelCatalogMocks = vi.hoisted(() => ({ + loadModelCatalog: vi.fn<() => Promise>(), +})); + +const modelAuthMocks = vi.hoisted(() => ({ + hasRuntimeAvailableProviderAuth: vi.fn<(params: { provider: string }) => boolean>(), +})); + +const authProfilesMocks = vi.hoisted(() => ({ + ensureAuthProfileStore: vi.fn(() => ({ profiles: {} })), + ensureAuthProfileStoreWithoutExternalProfiles: vi.fn(() => ({ profiles: {} })), + externalCliDiscoveryForProviders: vi.fn(() => ({}) as never), + externalCliDiscoveryForProviderAuth: vi.fn(() => ({}) as never), + listProfilesForProvider: vi.fn(() => []), +})); + +vi.mock("./model-catalog.js", () => ({ + loadModelCatalog: modelCatalogMocks.loadModelCatalog, +})); + +vi.mock("./model-auth.js", () => ({ + hasRuntimeAvailableProviderAuth: modelAuthMocks.hasRuntimeAvailableProviderAuth, +})); + +vi.mock("./auth-profiles.js", () => ({ + ensureAuthProfileStore: authProfilesMocks.ensureAuthProfileStore, + ensureAuthProfileStoreWithoutExternalProfiles: + authProfilesMocks.ensureAuthProfileStoreWithoutExternalProfiles, + externalCliDiscoveryForProviders: authProfilesMocks.externalCliDiscoveryForProviders, + externalCliDiscoveryForProviderAuth: authProfilesMocks.externalCliDiscoveryForProviderAuth, + listProfilesForProvider: authProfilesMocks.listProfilesForProvider, +})); + +vi.mock("./workspace.js", () => ({ + resolveDefaultAgentWorkspaceDir: () => undefined, +})); + +const { clearCurrentProviderAuthState, hasAuthForModelProvider, warmCurrentProviderAuthState } = + await import("./model-provider-auth.js"); + +describe("prepared provider auth state", () => { + afterEach(() => { + clearCurrentProviderAuthState(); + vi.clearAllMocks(); + }); + + it("hasAuthForModelProvider returns the prepared answer after warm and falls through to compute after clear", async () => { + const cfg = {} as OpenClawConfig; + modelCatalogMocks.loadModelCatalog.mockResolvedValue([ + { id: "gpt", name: "gpt", provider: "openai" }, + { id: "claude", name: "claude", provider: "anthropic" }, + ]); + modelAuthMocks.hasRuntimeAvailableProviderAuth.mockImplementation( + ({ provider }) => provider === "openai", + ); + + await warmCurrentProviderAuthState(cfg); + expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2); + + // Flip the underlying answer; if the prepared map is consulted first, + // hasAuthForModelProvider returns the cached answers without re-running + // the compute path. + modelAuthMocks.hasRuntimeAvailableProviderAuth.mockReturnValue(true); + expect(hasAuthForModelProvider({ provider: "openai", cfg })).toBe(true); + expect(hasAuthForModelProvider({ provider: "anthropic", cfg })).toBe(false); + expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2); + + // Clearing the prepared state forces the compute path on the next read. + clearCurrentProviderAuthState(); + expect(hasAuthForModelProvider({ provider: "anthropic", cfg })).toBe(true); + expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(3); + }); +});