From 8a5b7cf573a3cbffa210a4dfa79e4f02772ca0cf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 10 Apr 2026 16:41:10 +0100 Subject: [PATCH] test: inject provider auth command fixtures --- src/commands/auth-choice.moonshot.test.ts | 72 +++++++++ ...fault-account-bindings.integration.test.ts | 139 ++++++------------ src/plugins/provider-auth-choice.ts | 23 ++- 3 files changed, 135 insertions(+), 99 deletions(-) diff --git a/src/commands/auth-choice.moonshot.test.ts b/src/commands/auth-choice.moonshot.test.ts index 5e44201293f..b985f985f78 100644 --- a/src/commands/auth-choice.moonshot.test.ts +++ b/src/commands/auth-choice.moonshot.test.ts @@ -1,5 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; +import { __testing as providerAuthChoiceTesting } from "../plugins/provider-auth-choice.js"; +import type { ProviderAuthContext, ProviderPlugin } from "../plugins/types.js"; import type { WizardPrompter } from "../wizard/prompts.js"; import { applyAuthChoice } from "./auth-choice.js"; import { @@ -27,6 +29,75 @@ describe("applyAuthChoice (moonshot)", () => { const env = await setupAuthTestEnv("openclaw-auth-"); lifecycle.setStateDir(env.stateDir); delete process.env.MOONSHOT_API_KEY; + providerAuthChoiceTesting.setDepsForTest({ + loadPluginProviderRuntime: async () => + ({ + resolvePluginProviders: () => + [ + { + id: "moonshot", + label: "Moonshot", + auth: [ + { + id: "api-key-cn", + label: "Moonshot API key (.cn)", + kind: "api_key", + run: async ({ prompter }: ProviderAuthContext) => { + const key = await prompter.text({ + message: "Enter Moonshot API key (.cn)", + }); + return { + profiles: [ + { + profileId: "moonshot:default", + credential: { + type: "api_key", + provider: "moonshot", + key, + }, + }, + ], + configPatch: { + models: { + providers: { + moonshot: { + api: "openai-completions", + baseUrl: "https://api.moonshot.cn/v1", + models: [ + { + id: "kimi-k2.5", + name: "kimi-k2.5", + input: ["text", "image"], + reasoning: true, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 128_000, + maxTokens: 8192, + }, + ], + }, + }, + }, + }, + defaultModel: "moonshot/kimi-k2.5", + }; + }, + }, + ], + }, + ] as ProviderPlugin[], + resolveProviderPluginChoice: ({ + choice, + providers, + }: { + choice: string; + providers: ProviderPlugin[]; + }) => + choice === "moonshot-api-key-cn" + ? { provider: providers[0], method: providers[0]?.auth[0] } + : null, + runProviderModelSelectedHook: async () => {}, + }) as never, + }); } async function readAuthProfiles() { @@ -53,6 +124,7 @@ describe("applyAuthChoice (moonshot)", () => { } afterEach(async () => { + providerAuthChoiceTesting.resetDepsForTest(); await lifecycle.cleanup(); }); diff --git a/src/commands/doctor-config-flow.missing-default-account-bindings.integration.test.ts b/src/commands/doctor-config-flow.missing-default-account-bindings.integration.test.ts index bbfe3063b23..a3d54ed361d 100644 --- a/src/commands/doctor-config-flow.missing-default-account-bindings.integration.test.ts +++ b/src/commands/doctor-config-flow.missing-default-account-bindings.integration.test.ts @@ -1,122 +1,65 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { note } from "../terminal/note.js"; -import { withEnvAsync } from "../test-utils/env.js"; -import { runDoctorConfigWithInput } from "./doctor-config-flow.test-utils.js"; - -vi.mock("../terminal/note.js", () => ({ - note: vi.fn(), -})); - -vi.mock("./doctor-legacy-config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - normalizeCompatibilityConfigValues: (cfg: unknown) => ({ - config: cfg, - changes: [], - }), - }; -}); - -import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js"; - -const noteSpy = vi.mocked(note); +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../config/config.js"; +import { + collectMissingDefaultAccountBindingWarnings, + collectMissingExplicitDefaultAccountWarnings, +} from "./doctor/shared/default-account-warnings.js"; describe("doctor missing default account binding warning", () => { - beforeEach(() => { - noteSpy.mockClear(); - }); - - it("emits a doctor warning when named accounts have no valid account-scoped bindings", async () => { - await withEnvAsync( - { - TELEGRAM_BOT_TOKEN: undefined, - TELEGRAM_BOT_TOKEN_FILE: undefined, - }, - async () => { - await runDoctorConfigWithInput({ - config: { - channels: { - telegram: { - accounts: { - alerts: {}, - work: {}, - }, - }, - }, - bindings: [{ agentId: "ops", match: { channel: "telegram" } }], + it("warns when named accounts have no valid account-scoped bindings", () => { + const warnings = collectMissingDefaultAccountBindingWarnings({ + channels: { + telegram: { + accounts: { + alerts: {}, + work: {}, }, - run: loadAndMaybeMigrateDoctorConfig, - }); + }, }, - ); + bindings: [{ agentId: "ops", match: { channel: "telegram" } }], + } as OpenClawConfig); - expect(noteSpy).toHaveBeenCalledWith( + expect(warnings).toEqual([ expect.stringContaining("channels.telegram: accounts.default is missing"), - "Doctor warnings", - ); + ]); }); - it("emits a warning when multiple accounts have no explicit default", async () => { - await withEnvAsync( - { - TELEGRAM_BOT_TOKEN: undefined, - TELEGRAM_BOT_TOKEN_FILE: undefined, - }, - async () => { - await runDoctorConfigWithInput({ - config: { - channels: { - telegram: { - accounts: { - alerts: {}, - work: {}, - }, - }, - }, + it("warns when multiple accounts have no explicit default", () => { + const warnings = collectMissingExplicitDefaultAccountWarnings({ + channels: { + telegram: { + accounts: { + alerts: {}, + work: {}, }, - run: loadAndMaybeMigrateDoctorConfig, - }); + }, }, - ); + } as OpenClawConfig); - expect(noteSpy).toHaveBeenCalledWith( + expect(warnings).toEqual([ expect.stringContaining( "channels.telegram: multiple accounts are configured but no explicit default is set", ), - "Doctor warnings", - ); + ]); }); - it("emits a warning when defaultAccount does not match configured accounts", async () => { - await withEnvAsync( - { - TELEGRAM_BOT_TOKEN: undefined, - TELEGRAM_BOT_TOKEN_FILE: undefined, - }, - async () => { - await runDoctorConfigWithInput({ - config: { - channels: { - telegram: { - defaultAccount: "missing", - accounts: { - alerts: {}, - work: {}, - }, - }, - }, + it("warns when defaultAccount does not match configured accounts", () => { + const warnings = collectMissingExplicitDefaultAccountWarnings({ + channels: { + telegram: { + defaultAccount: "missing", + accounts: { + alerts: {}, + work: {}, }, - run: loadAndMaybeMigrateDoctorConfig, - }); + }, }, - ); + } as OpenClawConfig); - expect(noteSpy).toHaveBeenCalledWith( + expect(warnings).toEqual([ expect.stringContaining( 'channels.telegram: defaultAccount is set to "missing" but does not match configured accounts', ), - "Doctor warnings", - ); + ]); }); }); diff --git a/src/plugins/provider-auth-choice.ts b/src/plugins/provider-auth-choice.ts index c79641c6877..b3907bf61e7 100644 --- a/src/plugins/provider-auth-choice.ts +++ b/src/plugins/provider-auth-choice.ts @@ -78,10 +78,31 @@ function restoreConfiguredPrimaryModel( }; } +type ProviderAuthChoiceRuntime = typeof import("./provider-auth-choice.runtime.js"); + +const defaultProviderAuthChoiceDeps = { + loadPluginProviderRuntime: async (): Promise => + import("./provider-auth-choice.runtime.js"), +}; + +let providerAuthChoiceDeps = defaultProviderAuthChoiceDeps; + async function loadPluginProviderRuntime() { - return import("./provider-auth-choice.runtime.js"); + return await providerAuthChoiceDeps.loadPluginProviderRuntime(); } +export const __testing = { + resetDepsForTest(): void { + providerAuthChoiceDeps = defaultProviderAuthChoiceDeps; + }, + setDepsForTest(deps: Partial): void { + providerAuthChoiceDeps = { + ...defaultProviderAuthChoiceDeps, + ...deps, + }; + }, +} as const; + export async function runProviderPluginAuthMethod(params: { config: OpenClawConfig; env?: NodeJS.ProcessEnv;