diff --git a/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.test.ts b/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.test.ts new file mode 100644 index 00000000000..50d89f78d86 --- /dev/null +++ b/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.test.ts @@ -0,0 +1,77 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../../../config/config.js"; +import { applySimpleNonInteractiveApiKeyChoice } from "./auth-choice.api-key-providers.js"; + +const applyAuthProfileConfig = vi.hoisted(() => vi.fn((cfg: OpenClawConfig) => cfg)); +vi.mock("../../../plugins/provider-auth-helpers.js", () => ({ + applyAuthProfileConfig, +})); + +const setGigachatApiKey = vi.hoisted(() => vi.fn(async () => {})); +const setLitellmApiKey = vi.hoisted(() => vi.fn(async () => {})); +vi.mock("../../../plugins/provider-auth-storage.js", () => ({ + setGigachatApiKey, + setLitellmApiKey, +})); + +const applyGigachatConfig = vi.hoisted(() => vi.fn((cfg: OpenClawConfig) => cfg)); +vi.mock("../../onboard-auth.config-core.js", () => ({ + applyGigachatConfig, +})); + +const applyLitellmConfig = vi.hoisted(() => vi.fn((cfg: OpenClawConfig) => cfg)); +vi.mock("../../onboard-auth.config-litellm.js", () => ({ + applyLitellmConfig, +})); + +describe("applySimpleNonInteractiveApiKeyChoice", () => { + beforeEach(() => { + vi.clearAllMocks(); + applyAuthProfileConfig.mockImplementation((cfg: OpenClawConfig) => cfg); + applyGigachatConfig.mockImplementation((cfg: OpenClawConfig) => cfg); + applyLitellmConfig.mockImplementation((cfg: OpenClawConfig) => cfg); + }); + + it("disables profile fallback for GigaChat personal OAuth onboarding", async () => { + const nextConfig = { agents: { defaults: {} } } as OpenClawConfig; + const resolveApiKey = vi.fn(async () => ({ + key: "gigachat-oauth-credentials", + source: "env" as const, + })); + const maybeSetResolvedApiKey = vi.fn(async (resolved, setter) => { + await setter(resolved.key); + return true; + }); + + await applySimpleNonInteractiveApiKeyChoice({ + authChoice: "gigachat-api-key", + nextConfig, + baseConfig: nextConfig, + opts: {} as never, + runtime: { error: vi.fn(), exit: vi.fn(), log: vi.fn() } as never, + apiKeyStorageOptions: undefined, + resolveApiKey, + maybeSetResolvedApiKey, + }); + + expect(resolveApiKey).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "gigachat", + flagName: "--gigachat-api-key", + envVar: "GIGACHAT_CREDENTIALS", + allowProfile: false, + }), + ); + expect(maybeSetResolvedApiKey).toHaveBeenCalledOnce(); + expect(setGigachatApiKey).toHaveBeenCalledWith( + "gigachat-oauth-credentials", + undefined, + undefined, + { + authMode: "oauth", + insecureTls: "false", + scope: "GIGACHAT_API_PERS", + }, + ); + }); +}); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts b/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts index e17a2af5a1c..57ab0588ea9 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts @@ -29,6 +29,7 @@ async function applyGigachatNonInteractiveApiKeyChoice(params: { flagName: `--${string}`; envVar: string; runtime: RuntimeEnv; + allowProfile?: boolean; }) => Promise; maybeSetResolvedApiKey: ( resolved: ResolvedNonInteractiveApiKey, @@ -42,6 +43,9 @@ async function applyGigachatNonInteractiveApiKeyChoice(params: { flagName: "--gigachat-api-key", envVar: "GIGACHAT_CREDENTIALS", runtime: params.runtime, + // Personal OAuth onboarding must not silently reuse an existing Basic + // username:password profile and then rewrite the provider to OAuth config. + allowProfile: false, }); if (!resolved) { return null; @@ -80,6 +84,7 @@ export async function applySimpleNonInteractiveApiKeyChoice(params: { flagName: `--${string}`; envVar: string; runtime: RuntimeEnv; + allowProfile?: boolean; }) => Promise; maybeSetResolvedApiKey: ( resolved: ResolvedNonInteractiveApiKey,