











@@ -8,7 +8,23 @@ const modelCatalogMocks = vi.hoisted(() => ({
8899const modelAuthMocks = vi.hoisted(() => ({
1010hasRuntimeAvailableProviderAuth:
11-vi.fn<(params: { provider: string; cfg?: OpenClawConfig; workspaceDir?: string }) => boolean>(),
11+vi.fn<
12+(params: {
13+provider: string;
14+cfg?: OpenClawConfig;
15+workspaceDir?: string;
16+envAuthLookup?: unknown;
17+}) => boolean
18+>(),
19+}));
20+21+const providerAuthAliasMocks = vi.hoisted(() => ({
22+resolveProviderAuthAliasMap: vi.fn(() => ({ openai: "openai" })),
23+}));
24+25+const modelAuthEnvVarMocks = vi.hoisted(() => ({
26+resolveProviderEnvApiKeyCandidates: vi.fn(() => ({ openai: ["OPENAI_API_KEY"] })),
27+resolveProviderEnvAuthEvidence: vi.fn(() => ({})),
1228}));
13291430const authProfilesMocks = vi.hoisted(() => ({
@@ -27,6 +43,15 @@ vi.mock("./model-auth.js", () => ({
2743hasRuntimeAvailableProviderAuth: modelAuthMocks.hasRuntimeAvailableProviderAuth,
2844}));
294546+vi.mock("./provider-auth-aliases.js", () => ({
47+resolveProviderAuthAliasMap: providerAuthAliasMocks.resolveProviderAuthAliasMap,
48+}));
49+50+vi.mock("./model-auth-env-vars.js", () => ({
51+resolveProviderEnvApiKeyCandidates: modelAuthEnvVarMocks.resolveProviderEnvApiKeyCandidates,
52+resolveProviderEnvAuthEvidence: modelAuthEnvVarMocks.resolveProviderEnvAuthEvidence,
53+}));
54+3055vi.mock("./auth-profiles.js", () => ({
3156ensureAuthProfileStore: authProfilesMocks.ensureAuthProfileStore,
3257ensureAuthProfileStoreWithoutExternalProfiles:
@@ -56,6 +81,26 @@ describe("prepared provider auth state", () => {
5681vi.clearAllMocks();
5782});
588384+it("reuses prepared env auth lookup data while warming providers", async () => {
85+const cfg = {} as OpenClawConfig;
86+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
87+{ id: "gpt", name: "gpt", provider: "openai" },
88+{ id: "claude", name: "claude", provider: "anthropic" },
89+]);
90+modelAuthMocks.hasRuntimeAvailableProviderAuth.mockReturnValue(false);
91+92+await warmCurrentProviderAuthState(cfg);
93+94+expect(providerAuthAliasMocks.resolveProviderAuthAliasMap).toHaveBeenCalledTimes(1);
95+expect(modelAuthEnvVarMocks.resolveProviderEnvApiKeyCandidates).toHaveBeenCalledTimes(1);
96+expect(modelAuthEnvVarMocks.resolveProviderEnvAuthEvidence).toHaveBeenCalledTimes(1);
97+const firstLookup =
98+modelAuthMocks.hasRuntimeAvailableProviderAuth.mock.calls[0]?.[0].envAuthLookup;
99+const secondLookup =
100+modelAuthMocks.hasRuntimeAvailableProviderAuth.mock.calls[1]?.[0].envAuthLookup;
101+expect(firstLookup).toBe(secondLookup);
102+});
103+59104it("hasAuthForModelProvider returns the prepared answer after warm and falls through to compute after clear", async () => {
60105const cfg = {} as OpenClawConfig;
61106modelCatalogMocks.loadModelCatalog.mockResolvedValue([
@@ -192,17 +237,17 @@ describe("prepared provider auth state", () => {
192237await secondWarm;
193238resolveFirstCatalog?.([{ id: "gpt", name: "gpt", provider: "openai" }]);
194239await firstWarm;
195-expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2);
240+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(1);
196241197242modelAuthMocks.hasRuntimeAvailableProviderAuth.mockReturnValue(true);
198243await expect(hasAuthForModelProvider({ provider: "openai", cfg: secondCfg })).resolves.toBe(
199244false,
200245);
201-expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2);
246+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(1);
202247await expect(hasAuthForModelProvider({ provider: "openai", cfg: firstCfg })).resolves.toBe(
203248true,
204249);
205-expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(3);
250+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2);
206251});
207252208253it("does not publish a warm that is cancelled before completion", async () => {
@@ -224,6 +269,28 @@ describe("prepared provider auth state", () => {
224269225270modelAuthMocks.hasRuntimeAvailableProviderAuth.mockReturnValue(false);
226271await expect(hasAuthForModelProvider({ provider: "openai", cfg })).resolves.toBe(false);
227-expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(2);
272+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(1);
273+});
274+275+it("stops sweeping providers when a warm is cancelled mid-flight", async () => {
276+const cfg = {} as OpenClawConfig;
277+let cancelled = false;
278+modelCatalogMocks.loadModelCatalog.mockResolvedValue([
279+{ id: "gpt", name: "gpt", provider: "openai" },
280+{ id: "claude", name: "claude", provider: "anthropic" },
281+{ id: "gemini", name: "gemini", provider: "google" },
282+]);
283+modelAuthMocks.hasRuntimeAvailableProviderAuth.mockImplementation(() => {
284+cancelled = true;
285+return false;
286+});
287+288+await warmCurrentProviderAuthState(cfg, { isCancelled: () => cancelled });
289+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(1);
290+291+modelAuthMocks.hasRuntimeAvailableProviderAuth.mockClear();
292+modelAuthMocks.hasRuntimeAvailableProviderAuth.mockReturnValue(true);
293+await expect(hasAuthForModelProvider({ provider: "openai", cfg })).resolves.toBe(true);
294+expect(modelAuthMocks.hasRuntimeAvailableProviderAuth).toHaveBeenCalledTimes(1);
228295});
229296});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。