openclaw/extensions/github-copilot/auth.test.ts
Galin Iliev 93bad8de2a
fix: restore GitHub Copilot turns and account context limits (#106198)
* fix: restore GitHub Copilot runtime routing

Refs #106170

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6053811e-f081-436b-8741-54c5cab6c016

* fix: bind Copilot metadata to resolved credentials

Refs #106170

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6053811e-f081-436b-8741-54c5cab6c016

* refactor: split Copilot runtime helpers

* fix: satisfy Copilot runtime static gates

* fix: satisfy Copilot runtime lint

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Galin Iliev <galin.iliev@microsoft.com>
2026-07-13 16:04:34 -07:00

233 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", () => ({
coerceSecretRef: coerceSecretRefMock,
ensureAuthProfileStore: ensureAuthProfileStoreMock,
listProfilesForProvider: listProfilesForProviderMock,
normalizeOptionalSecretInput: (value: unknown) =>
typeof value === "string" && value.trim() ? value.trim() : undefined,
}));
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",
});
});
});