login-action/__tests__/chainguard.test.ts
Augustus Nguyen 4bcfaae325 Add Chainguard registry (cgr.dev) login support
Implement native OIDC-based authentication for Chainguard's container
registry, following the same pattern as the existing AWS ECR integration.

When registry is set to cgr.dev, the action automatically exchanges a
GitHub Actions OIDC token with Chainguard's STS endpoint for a short-lived
registry credential, removing the need for chainctl or long-lived pull tokens.

New inputs: chainguard (auto/true/false), chainguard-identity.

Signed-off-by: Augustus Nguyen <theflash28012002@gmail.com>
2026-04-21 11:20:09 +07:00

100 lines
3.1 KiB
TypeScript

import {beforeEach, describe, expect, test, vi} from 'vitest';
import * as chainguard from '../src/chainguard.js';
describe('isChainguard', () => {
test.each([
['cgr.dev', true],
['registry.gitlab.com', false],
['gcr.io', false],
['docker.io', false],
['ghcr.io', false],
['public.ecr.aws', false],
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', false],
['not-cgr.dev', false],
['cgr.dev.example.com', false]
])('given registry %p returns %p', (registry, expected) => {
expect(chainguard.isChainguard(registry)).toEqual(expected);
});
});
const mockGetIDToken = vi.fn();
vi.mock('@actions/core', () => ({
info: vi.fn(),
setSecret: vi.fn(),
getIDToken: (...args: unknown[]) => mockGetIDToken(...args)
}));
const mockGetJson = vi.fn();
vi.mock('@actions/http-client', () => {
return {
HttpClient: class {
getJson = mockGetJson;
}
};
});
describe('getRegistryToken', () => {
beforeEach(() => {
vi.clearAllMocks();
});
test('exchanges OIDC token for Chainguard token', async () => {
const fakeOIDCToken = 'oidc-token-123';
const fakeChainguardToken = 'chainguard-token-456';
const identity = 'abc123/def456';
mockGetIDToken.mockResolvedValue(fakeOIDCToken);
mockGetJson.mockResolvedValue({
statusCode: 200,
result: {token: fakeChainguardToken}
});
const result = await chainguard.getRegistryToken(identity);
expect(mockGetIDToken).toHaveBeenCalledWith('cgr.dev');
expect(mockGetJson).toHaveBeenCalledWith(`https://issuer.enforce.dev/sts/exchange?aud=cgr.dev&identity=${encodeURIComponent(identity)}`, {Authorization: `Bearer ${fakeOIDCToken}`});
expect(result).toEqual({
username: 'user',
password: fakeChainguardToken
});
});
test('uses custom issuer URL when provided', async () => {
const fakeOIDCToken = 'oidc-token-123';
const fakeChainguardToken = 'chainguard-token-456';
const identity = 'abc123/def456';
const customIssuer = 'https://custom-issuer.example.dev';
mockGetIDToken.mockResolvedValue(fakeOIDCToken);
mockGetJson.mockResolvedValue({
statusCode: 200,
result: {token: fakeChainguardToken}
});
await chainguard.getRegistryToken(identity, customIssuer);
expect(mockGetJson).toHaveBeenCalledWith(`${customIssuer}/sts/exchange?aud=cgr.dev&identity=${encodeURIComponent(identity)}`, {Authorization: `Bearer ${fakeOIDCToken}`});
});
test('throws on non-200 response', async () => {
mockGetIDToken.mockResolvedValue('oidc-token');
mockGetJson.mockResolvedValue({
statusCode: 401,
result: null
});
await expect(chainguard.getRegistryToken('identity-id')).rejects.toThrow('Failed to exchange OIDC token with Chainguard (HTTP 401)');
});
test('throws when response has no token', async () => {
mockGetIDToken.mockResolvedValue('oidc-token');
mockGetJson.mockResolvedValue({
statusCode: 200,
result: {}
});
await expect(chainguard.getRegistryToken('identity-id')).rejects.toThrow('Failed to exchange OIDC token with Chainguard (HTTP 200)');
});
});