Files
openclaw/extensions/github-copilot/auth.test.ts
T
Peter Steinberger b080dd1e76 refactor: consolidate coercion contracts (#122458)
* refactor: consolidate coercion contracts

Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics.

Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit.

* fix: preserve standalone script coercions

Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
2026-08-11 23:26:37 -07:00

236 lines
7.1 KiB
TypeScript

// Github Copilot tests cover auth plugin behavior.
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const ensureAuthProfileStoreMock = vi.hoisted(() => vi.fn());
const listProfilesForProviderMock = vi.hoisted(() => vi.fn());
const coerceSecretRefMock = vi.hoisted(() => vi.fn());
const resolveConfiguredSecretInputWithFallbackMock = vi.hoisted(() => vi.fn());
const resolveRequiredConfiguredSecretRefInputStringMock = vi.hoisted(() => vi.fn());
vi.mock("openclaw/plugin-sdk/provider-auth", async () => {
const { normalizeOptionalString } = await import("openclaw/plugin-sdk/string-coerce-runtime");
return {
coerceSecretRef: coerceSecretRefMock,
ensureAuthProfileStore: ensureAuthProfileStoreMock,
listProfilesForProvider: listProfilesForProviderMock,
normalizeOptionalSecretInput: normalizeOptionalString,
};
});
vi.mock("openclaw/plugin-sdk/secret-input-runtime", () => ({
resolveConfiguredSecretInputWithFallback: resolveConfiguredSecretInputWithFallbackMock,
resolveRequiredConfiguredSecretRefInputString: resolveRequiredConfiguredSecretRefInputStringMock,
}));
import { resolveFirstGithubToken } from "./auth.js";
afterAll(() => {
vi.doUnmock("openclaw/plugin-sdk/provider-auth");
vi.doUnmock("openclaw/plugin-sdk/secret-input-runtime");
vi.resetModules();
});
describe("resolveFirstGithubToken", () => {
beforeEach(() => {
ensureAuthProfileStoreMock.mockReturnValue({
profiles: {
"github-copilot:github": {
type: "token",
tokenRef: { source: "file", provider: "default", id: "/providers/github-copilot/token" },
},
},
});
listProfilesForProviderMock.mockReturnValue(["github-copilot:github"]);
coerceSecretRefMock.mockReturnValue({
source: "file",
provider: "default",
id: "/providers/github-copilot/token",
});
resolveRequiredConfiguredSecretRefInputStringMock.mockResolvedValue("resolved-profile-token");
resolveConfiguredSecretInputWithFallbackMock.mockResolvedValue({
value: "test-token-placeholder",
source: "config",
secretRefConfigured: false,
});
});
afterEach(() => {
vi.restoreAllMocks();
ensureAuthProfileStoreMock.mockReset();
listProfilesForProviderMock.mockReset();
coerceSecretRefMock.mockReset();
resolveConfiguredSecretInputWithFallbackMock.mockReset();
resolveRequiredConfiguredSecretRefInputStringMock.mockReset();
});
it("prefers env tokens when available", async () => {
const result = await resolveFirstGithubToken({
env: { GH_TOKEN: "env-token" } as NodeJS.ProcessEnv,
});
expect(result).toEqual({
githubToken: "env-token",
hasProfile: true,
});
expect(resolveRequiredConfiguredSecretRefInputStringMock).not.toHaveBeenCalled();
});
it("returns direct profile tokens before resolving SecretRefs", async () => {
ensureAuthProfileStoreMock.mockReturnValue({
profiles: {
"github-copilot:github": {
type: "token",
token: "profile-token",
},
},
});
coerceSecretRefMock.mockReturnValue(null);
const result = await resolveFirstGithubToken({
env: {} as NodeJS.ProcessEnv,
});
expect(result).toEqual({
githubToken: "profile-token",
hasProfile: true,
});
});
it("uses environment direct auth without falling back to config or the first profile", async () => {
const config = {
models: {
providers: {
"github-copilot": { apiKey: "test-token-placeholder" },
},
},
} as never;
const env = { GH_TOKEN: "test-auth-token" } as NodeJS.ProcessEnv;
const result = await resolveFirstGithubToken({
config,
env,
authProfileMode: "api_key",
});
expect(result).toEqual({
githubToken: "test-auth-token",
hasProfile: false,
});
expect(resolveConfiguredSecretInputWithFallbackMock).not.toHaveBeenCalled();
expect(resolveRequiredConfiguredSecretRefInputStringMock).not.toHaveBeenCalled();
});
it("lets explicit api-key config outrank environment direct auth", async () => {
const config = {
models: {
providers: {
"github-copilot": {
auth: "api-key",
apiKey: "test-token-placeholder",
},
},
},
} as never;
const env = { GH_TOKEN: "test-auth-token" } as NodeJS.ProcessEnv;
const result = await resolveFirstGithubToken({
config,
env,
authProfileMode: "api_key",
});
expect(result).toEqual({
githubToken: "test-token-placeholder",
hasProfile: false,
});
expect(resolveConfiguredSecretInputWithFallbackMock).toHaveBeenCalledWith({
config,
env,
value: "test-token-placeholder",
path: "models.providers.github-copilot.apiKey",
readFallback: expect.any(Function),
});
expect(resolveRequiredConfiguredSecretRefInputStringMock).not.toHaveBeenCalled();
});
it("skips empty higher-priority environment variables", async () => {
const result = await resolveFirstGithubToken({
env: {
COPILOT_GITHUB_TOKEN: "",
GH_TOKEN: "test-auth-token",
} as NodeJS.ProcessEnv,
authProfileMode: "api_key",
});
expect(result).toEqual({
githubToken: "test-auth-token",
hasProfile: false,
});
});
it("resolves config-only direct auth for unscoped model discovery", async () => {
ensureAuthProfileStoreMock.mockReturnValue({ profiles: {} });
listProfilesForProviderMock.mockReturnValue([]);
const config = {
models: {
providers: {
"github-copilot": { apiKey: "test-token-placeholder" },
},
},
} as never;
const result = await resolveFirstGithubToken({
config,
env: {} as NodeJS.ProcessEnv,
});
expect(result).toEqual({
githubToken: "test-token-placeholder",
hasProfile: false,
});
expect(resolveConfiguredSecretInputWithFallbackMock).toHaveBeenCalledOnce();
});
it("does not report stored profiles for a missing direct credential", async () => {
resolveConfiguredSecretInputWithFallbackMock.mockResolvedValue({
secretRefConfigured: false,
});
const result = await resolveFirstGithubToken({
config: {},
env: {} as NodeJS.ProcessEnv,
authProfileMode: "api_key",
});
expect(result).toEqual({
githubToken: "",
hasProfile: false,
});
expect(resolveRequiredConfiguredSecretRefInputStringMock).not.toHaveBeenCalled();
});
it("resolves non-env SecretRefs when config is available", async () => {
const config = { secrets: { defaults: { provider: "default" } } } as never;
const env = {} as NodeJS.ProcessEnv;
const result = await resolveFirstGithubToken({
config,
env,
});
expect(result).toEqual({
githubToken: "resolved-profile-token",
hasProfile: true,
});
expect(resolveRequiredConfiguredSecretRefInputStringMock).toHaveBeenCalledWith({
config,
env,
value: {
source: "file",
provider: "default",
id: "/providers/github-copilot/token",
},
path: "providers.github-copilot.authProfiles.github-copilot:github.tokenRef",
});
});
});