mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-18 05:20:48 +00:00
Plugins: add auth choice contracts
This commit is contained in:
230
src/plugins/contracts/auth-choice.contract.test.ts
Normal file
230
src/plugins/contracts/auth-choice.contract.test.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { clearRuntimeAuthProfileStoreSnapshots } from "../../agents/auth-profiles/store.js";
|
||||
import { applyAuthChoiceLoadedPluginProvider } from "../../commands/auth-choice.apply.plugin-provider.js";
|
||||
import { resolvePreferredProviderForAuthChoice } from "../../commands/auth-choice.preferred-provider.js";
|
||||
import type { AuthChoice } from "../../commands/onboard-types.js";
|
||||
import {
|
||||
createAuthTestLifecycle,
|
||||
createExitThrowingRuntime,
|
||||
createWizardPrompter,
|
||||
readAuthProfilesForAgent,
|
||||
requireOpenClawAgentDir,
|
||||
setupAuthTestEnv,
|
||||
} from "../../commands/test-wizard-helpers.js";
|
||||
import { createCapturedPluginRegistration } from "../../test-utils/plugin-registration.js";
|
||||
import type { OpenClawPluginApi, ProviderPlugin } from "../types.js";
|
||||
|
||||
const loginQwenPortalOAuthMock = vi.hoisted(() => vi.fn());
|
||||
const githubCopilotLoginCommandMock = vi.hoisted(() => vi.fn());
|
||||
const resolvePluginProvidersMock = vi.hoisted(() => vi.fn<() => ProviderPlugin[]>(() => []));
|
||||
const resolveProviderPluginChoiceMock = vi.hoisted(() =>
|
||||
vi.fn<() => { provider: ProviderPlugin; method: ProviderPlugin["auth"][number] } | null>(),
|
||||
);
|
||||
const runProviderModelSelectedHookMock = vi.hoisted(() => vi.fn(async () => {}));
|
||||
|
||||
vi.mock("../../../extensions/qwen-portal-auth/oauth.js", () => ({
|
||||
loginQwenPortalOAuth: loginQwenPortalOAuthMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../providers/github-copilot-auth.js", () => ({
|
||||
githubCopilotLoginCommand: githubCopilotLoginCommandMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/auth-choice.apply.plugin-provider.runtime.js", () => ({
|
||||
resolvePluginProviders: (...args: unknown[]) => resolvePluginProvidersMock(...args),
|
||||
resolveProviderPluginChoice: (...args: unknown[]) => resolveProviderPluginChoiceMock(...args),
|
||||
runProviderModelSelectedHook: (...args: unknown[]) => runProviderModelSelectedHookMock(...args),
|
||||
}));
|
||||
|
||||
type StoredAuthProfile = {
|
||||
type?: string;
|
||||
provider?: string;
|
||||
access?: string;
|
||||
refresh?: string;
|
||||
key?: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
const qwenPortalPlugin = (await import("../../../extensions/qwen-portal-auth/index.js")).default;
|
||||
|
||||
function registerProviders(...plugins: Array<{ register(api: OpenClawPluginApi): void }>) {
|
||||
const captured = createCapturedPluginRegistration();
|
||||
for (const plugin of plugins) {
|
||||
plugin.register(captured.api);
|
||||
}
|
||||
return captured.providers;
|
||||
}
|
||||
|
||||
function requireProvider(providers: ProviderPlugin[], providerId: string) {
|
||||
const provider = providers.find((entry) => entry.id === providerId);
|
||||
if (!provider) {
|
||||
throw new Error(`provider ${providerId} missing`);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
describe("provider auth-choice contract", () => {
|
||||
const lifecycle = createAuthTestLifecycle([
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
]);
|
||||
let activeStateDir: string | null = null;
|
||||
|
||||
async function setupTempState() {
|
||||
if (activeStateDir) {
|
||||
await lifecycle.cleanup();
|
||||
}
|
||||
const env = await setupAuthTestEnv("openclaw-provider-auth-choice-");
|
||||
activeStateDir = env.stateDir;
|
||||
lifecycle.setStateDir(env.stateDir);
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
loginQwenPortalOAuthMock.mockReset();
|
||||
githubCopilotLoginCommandMock.mockReset();
|
||||
resolvePluginProvidersMock.mockReset();
|
||||
resolvePluginProvidersMock.mockReturnValue([]);
|
||||
resolveProviderPluginChoiceMock.mockReset();
|
||||
resolveProviderPluginChoiceMock.mockReturnValue(null);
|
||||
runProviderModelSelectedHookMock.mockReset();
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await lifecycle.cleanup();
|
||||
activeStateDir = null;
|
||||
});
|
||||
|
||||
it("maps plugin-backed auth choices through the shared preferred-provider resolver", async () => {
|
||||
const scenarios = [
|
||||
{ authChoice: "github-copilot" as const, expectedProvider: "github-copilot" },
|
||||
{ authChoice: "qwen-portal" as const, expectedProvider: "qwen-portal" },
|
||||
{ authChoice: "minimax-global-oauth" as const, expectedProvider: "minimax-portal" },
|
||||
{ authChoice: "modelstudio-api-key" as const, expectedProvider: "modelstudio" },
|
||||
{ authChoice: "ollama" as const, expectedProvider: "ollama" },
|
||||
{ authChoice: "unknown" as AuthChoice, expectedProvider: undefined },
|
||||
] as const;
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
await expect(
|
||||
resolvePreferredProviderForAuthChoice({ choice: scenario.authChoice }),
|
||||
).resolves.toBe(scenario.expectedProvider);
|
||||
}
|
||||
});
|
||||
|
||||
it("applies qwen portal auth choices through the shared plugin-provider path", async () => {
|
||||
await setupTempState();
|
||||
const qwenProvider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
|
||||
resolvePluginProvidersMock.mockReturnValue([qwenProvider]);
|
||||
resolveProviderPluginChoiceMock.mockReturnValue({
|
||||
provider: qwenProvider,
|
||||
method: qwenProvider.auth[0],
|
||||
});
|
||||
loginQwenPortalOAuthMock.mockResolvedValueOnce({
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: 1_700_000_000_000,
|
||||
resourceUrl: "portal.qwen.ai",
|
||||
});
|
||||
|
||||
const note = vi.fn(async () => {});
|
||||
const result = await applyAuthChoiceLoadedPluginProvider({
|
||||
authChoice: "qwen-portal",
|
||||
config: {},
|
||||
prompter: createWizardPrompter({ note }),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(result?.config.agents?.defaults?.model).toEqual({
|
||||
primary: "qwen-portal/coder-model",
|
||||
});
|
||||
expect(result?.config.auth?.profiles?.["qwen-portal:default"]).toMatchObject({
|
||||
provider: "qwen-portal",
|
||||
mode: "oauth",
|
||||
});
|
||||
expect(result?.config.models?.providers?.["qwen-portal"]).toMatchObject({
|
||||
baseUrl: "https://portal.qwen.ai/v1",
|
||||
models: [],
|
||||
});
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
"Default model set to qwen-portal/coder-model",
|
||||
"Model configured",
|
||||
);
|
||||
|
||||
const stored = await readAuthProfilesForAgent<{ profiles?: Record<string, StoredAuthProfile> }>(
|
||||
requireOpenClawAgentDir(),
|
||||
);
|
||||
expect(stored.profiles?.["qwen-portal:default"]).toMatchObject({
|
||||
type: "oauth",
|
||||
provider: "qwen-portal",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns provider agent overrides when default-model application is deferred", async () => {
|
||||
await setupTempState();
|
||||
const qwenProvider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
|
||||
resolvePluginProvidersMock.mockReturnValue([qwenProvider]);
|
||||
resolveProviderPluginChoiceMock.mockReturnValue({
|
||||
provider: qwenProvider,
|
||||
method: qwenProvider.auth[0],
|
||||
});
|
||||
loginQwenPortalOAuthMock.mockResolvedValueOnce({
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: 1_700_000_000_000,
|
||||
resourceUrl: "portal.qwen.ai",
|
||||
});
|
||||
|
||||
const result = await applyAuthChoiceLoadedPluginProvider({
|
||||
authChoice: "qwen-portal",
|
||||
config: {},
|
||||
prompter: createWizardPrompter({}),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
setDefaultModel: false,
|
||||
});
|
||||
|
||||
expect(githubCopilotLoginCommandMock).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"qwen-portal/coder-model": {
|
||||
alias: "qwen",
|
||||
},
|
||||
"qwen-portal/vision-model": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
profiles: {
|
||||
"qwen-portal:default": {
|
||||
provider: "qwen-portal",
|
||||
mode: "oauth",
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
"qwen-portal": {
|
||||
baseUrl: "https://portal.qwen.ai/v1",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
agentModelOverride: "qwen-portal/coder-model",
|
||||
});
|
||||
|
||||
const stored = await readAuthProfilesForAgent<{
|
||||
profiles?: Record<string, StoredAuthProfile>;
|
||||
}>(requireOpenClawAgentDir());
|
||||
expect(stored.profiles?.["qwen-portal:default"]).toMatchObject({
|
||||
type: "oauth",
|
||||
provider: "qwen-portal",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user