Files
openclaw/src/infra/provider-usage.auth.plugin.test.ts
2026-05-09 05:29:45 +01:00

342 lines
11 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const resolveProviderUsageAuthWithPluginMock = vi.fn(
async (..._args: unknown[]): Promise<unknown> => null,
);
const hasAnyAuthProfileStoreSourceMock = vi.fn(() => false);
const ensureAuthProfileStoreMock = vi.fn(() => ({
profiles: {},
}));
const ensureAuthProfileStoreWithoutExternalProfilesMock = vi.fn(() => ({
profiles: {},
}));
const resolveAuthProfileOrderMock = vi.fn((_params: unknown): string[] => []);
vi.mock("../agents/auth-profiles.js", () => ({
dedupeProfileIds: (profileIds: string[]) => [...new Set(profileIds)],
ensureAuthProfileStore: () => ensureAuthProfileStoreMock(),
ensureAuthProfileStoreWithoutExternalProfiles: () =>
ensureAuthProfileStoreWithoutExternalProfilesMock(),
hasAnyAuthProfileStoreSource: () => hasAnyAuthProfileStoreSourceMock(),
listProfilesForProvider: () => [],
resolveApiKeyForProfile: async () => null,
resolveAuthProfileOrder: (params: unknown) => resolveAuthProfileOrderMock(params),
}));
vi.mock("../plugins/provider-runtime.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/provider-runtime.js")>(
"../plugins/provider-runtime.js",
);
return {
...actual,
resolveProviderUsageAuthWithPlugin: resolveProviderUsageAuthWithPluginMock,
};
});
let resolveProviderAuths: typeof import("./provider-usage.auth.js").resolveProviderAuths;
async function withTempHome<T>(fn: (homeDir: string) => Promise<T>): Promise<T> {
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-provider-usage-"));
try {
return await fn(homeDir);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
}
describe("resolveProviderAuths plugin boundary", () => {
beforeAll(async () => {
({ resolveProviderAuths } = await import("./provider-usage.auth.js"));
});
beforeEach(() => {
hasAnyAuthProfileStoreSourceMock.mockReset();
hasAnyAuthProfileStoreSourceMock.mockReturnValue(false);
ensureAuthProfileStoreMock.mockClear();
ensureAuthProfileStoreMock.mockReturnValue({
profiles: {},
});
ensureAuthProfileStoreWithoutExternalProfilesMock.mockClear();
ensureAuthProfileStoreWithoutExternalProfilesMock.mockReturnValue({
profiles: {},
});
resolveAuthProfileOrderMock.mockReset();
resolveAuthProfileOrderMock.mockReturnValue([]);
resolveProviderUsageAuthWithPluginMock.mockReset();
resolveProviderUsageAuthWithPluginMock.mockResolvedValue(null);
});
it("prefers plugin-owned usage auth when available", async () => {
resolveProviderUsageAuthWithPluginMock.mockResolvedValueOnce({
token: "plugin-zai-token",
});
await expect(
resolveProviderAuths({
providers: ["zai"],
}),
).resolves.toEqual([
{
provider: "zai",
token: "plugin-zai-token",
},
]);
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
});
it("skips plugin usage auth when requested and no direct credential source exists", async () => {
await withTempHome(async (homeDir) => {
await expect(
resolveProviderAuths({
providers: ["zai"],
skipPluginAuthWithoutCredentialSource: true,
env: { HOME: homeDir },
}),
).resolves.toStrictEqual([]);
});
expect(resolveProviderUsageAuthWithPluginMock).not.toHaveBeenCalled();
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
});
it("keeps plugin usage auth when a shared legacy plugin credential source exists", async () => {
await withTempHome(async (homeDir) => {
fs.mkdirSync(path.join(homeDir, ".pi", "agent"), { recursive: true });
fs.writeFileSync(
path.join(homeDir, ".pi", "agent", "auth.json"),
`${JSON.stringify({ "z-ai": { access: "legacy-zai-token" } })}\n`,
);
resolveProviderUsageAuthWithPluginMock.mockResolvedValueOnce({
token: "legacy-zai-token",
});
await expect(
resolveProviderAuths({
providers: ["zai"],
skipPluginAuthWithoutCredentialSource: true,
env: { HOME: homeDir },
}),
).resolves.toEqual([
{
provider: "zai",
token: "legacy-zai-token",
},
]);
});
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "zai",
}),
);
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
});
it("keeps legacy plugin credential sources provider-specific", async () => {
await withTempHome(async (homeDir) => {
fs.mkdirSync(path.join(homeDir, ".pi", "agent"), { recursive: true });
fs.writeFileSync(
path.join(homeDir, ".pi", "agent", "auth.json"),
`${JSON.stringify({ "z-ai": { access: "legacy-zai-token" } })}\n`,
);
resolveProviderUsageAuthWithPluginMock.mockResolvedValueOnce({
token: "legacy-zai-token",
});
await expect(
resolveProviderAuths({
providers: ["anthropic", "zai"],
skipPluginAuthWithoutCredentialSource: true,
env: { HOME: homeDir },
}),
).resolves.toEqual([
{
provider: "zai",
token: "legacy-zai-token",
},
]);
});
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledTimes(1);
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "zai",
}),
);
});
it("keeps auth-profile credential sources provider-specific", async () => {
hasAnyAuthProfileStoreSourceMock.mockReturnValue(true);
ensureAuthProfileStoreWithoutExternalProfilesMock.mockReturnValue({
profiles: {
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: "sk-ant",
},
},
});
resolveAuthProfileOrderMock.mockImplementation((params: unknown) => {
const provider =
params && typeof params === "object" && "provider" in params
? (params as { provider?: unknown }).provider
: undefined;
return provider === "anthropic" ? ["anthropic:default"] : [];
});
resolveProviderUsageAuthWithPluginMock.mockResolvedValueOnce({
token: "plugin-anthropic-token",
});
await withTempHome(async (homeDir) => {
await expect(
resolveProviderAuths({
providers: ["anthropic", "zai"],
skipPluginAuthWithoutCredentialSource: true,
env: { HOME: homeDir },
}),
).resolves.toEqual([
{
provider: "anthropic",
token: "plugin-anthropic-token",
},
]);
});
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledTimes(1);
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "anthropic",
}),
);
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
});
it("keeps plugin usage auth when an owned alias provider has auth-profile credentials", async () => {
hasAnyAuthProfileStoreSourceMock.mockReturnValue(true);
ensureAuthProfileStoreWithoutExternalProfilesMock.mockReturnValue({
profiles: {
"minimax-portal:default": {
type: "oauth",
provider: "minimax-portal",
accessToken: "portal-oauth-token",
},
},
});
resolveAuthProfileOrderMock.mockImplementation((params: unknown) => {
const provider =
params && typeof params === "object" && "provider" in params
? (params as { provider?: unknown }).provider
: undefined;
return provider === "minimax-portal" ? ["minimax-portal:default"] : [];
});
resolveProviderUsageAuthWithPluginMock.mockResolvedValueOnce({
token: "plugin-minimax-token",
});
await withTempHome(async (homeDir) => {
await expect(
resolveProviderAuths({
providers: ["minimax"],
skipPluginAuthWithoutCredentialSource: true,
env: { HOME: homeDir },
}),
).resolves.toEqual([
{
provider: "minimax",
token: "plugin-minimax-token",
},
]);
});
expect(resolveAuthProfileOrderMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "minimax-portal",
}),
);
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "minimax",
}),
);
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
});
it("keeps plugin usage auth when provider-owned usage env credentials exist", async () => {
resolveProviderUsageAuthWithPluginMock.mockResolvedValueOnce({
token: "plugin-minimax-token",
});
await withTempHome(async (homeDir) => {
await expect(
resolveProviderAuths({
providers: ["minimax"],
skipPluginAuthWithoutCredentialSource: true,
env: {
HOME: homeDir,
MINIMAX_CODE_PLAN_KEY: "code-plan-key",
},
}),
).resolves.toEqual([
{
provider: "minimax",
token: "plugin-minimax-token",
},
]);
});
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "minimax",
}),
);
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
});
it("does not overlay external auth profiles while checking the skip gate", async () => {
hasAnyAuthProfileStoreSourceMock.mockReturnValue(true);
await withTempHome(async (homeDir) => {
await expect(
resolveProviderAuths({
providers: ["anthropic"],
skipPluginAuthWithoutCredentialSource: true,
env: { HOME: homeDir },
}),
).resolves.toStrictEqual([]);
});
expect(ensureAuthProfileStoreWithoutExternalProfilesMock).toHaveBeenCalledTimes(1);
expect(ensureAuthProfileStoreMock).not.toHaveBeenCalled();
expect(resolveProviderUsageAuthWithPluginMock).not.toHaveBeenCalled();
});
it("skips plugin usage auth per provider when only another provider has direct credentials", async () => {
await withTempHome(async (homeDir) => {
await expect(
resolveProviderAuths({
providers: ["anthropic", "zai"],
skipPluginAuthWithoutCredentialSource: true,
env: {
HOME: homeDir,
ANTHROPIC_API_KEY: "sk-ant",
},
}),
).resolves.toEqual([
{
provider: "anthropic",
token: "sk-ant",
},
]);
});
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledTimes(1);
expect(resolveProviderUsageAuthWithPluginMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "anthropic",
}),
);
});
});