checkout/__test__/github-api-helper.test.ts
askalf dbd3a6024b report the resolved commit when a ref is downloaded without a SHA
On the REST API fallback, a ref given without a SHA (a branch or tag of
another repository, or the default branch) left the commit output empty: the
archive was downloaded by ref and nothing recorded which commit that was.

downloadRepository now resolves the ref to its commit first
(GET /repos/{owner}/{repo}/commits/{ref} with the sha media type), downloads
that commit, and returns its SHA, which getSource sets as the output. The
downloaded content and the reported SHA are the same commit even if the ref
moves meanwhile. If the ref cannot be resolved, the download falls back to
the ref as before, a warning is logged, and the output stays empty.
2026-09-28 15:38:49 -04:00

190 lines
5.3 KiB
TypeScript

import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals'
// Mock @actions/core
const mockDebug = jest.fn()
const mockWarning = jest.fn()
jest.unstable_mockModule('@actions/core', () => ({
debug: mockDebug,
info: jest.fn(),
warning: mockWarning,
error: jest.fn()
}))
// Mock @actions/github
const mockGetOctokit = jest.fn()
jest.unstable_mockModule('@actions/github', () => ({
getOctokit: mockGetOctokit
}))
// Run retried actions once, so a failing lookup does not wait out the backoff
jest.unstable_mockModule('../src/retry-helper.js', () => ({
execute: async (action: () => Promise<unknown>) => await action()
}))
// Dynamic imports after mocking
const githubApiHelper = await import('../src/github-api-helper.js')
describe('github-api-helper object format', () => {
let request: jest.Mock<any>
function mockHashAlgorithmApi(hashAlgorithm: string): void {
request = jest.fn(async () => ({
data: {
hash_algorithm: hashAlgorithm
}
}))
mockGetOctokit.mockReturnValue({
request
} as any)
}
beforeEach(() => {
mockDebug.mockClear()
mockGetOctokit.mockClear()
})
afterEach(() => {
jest.clearAllMocks()
})
it('detects SHA-256 from the repository hash algorithm endpoint', async () => {
mockHashAlgorithmApi('sha256')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: 'sha256', succeeded: true})
expect(mockGetOctokit).toHaveBeenCalledWith(
'token',
expect.objectContaining({baseUrl: 'https://api.github.com'})
)
expect(request).toHaveBeenCalledWith(
'GET /repos/{owner}/{repo}/hash-algorithm',
{owner: 'owner', repo: 'repo'}
)
})
it('detects SHA-1 from the repository hash algorithm endpoint', async () => {
mockHashAlgorithmApi('sha1')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: 'sha1', succeeded: true})
})
it('detects object format from an existing commit without API calls', async () => {
const commitSha =
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92'
await expect(
githubApiHelper.tryGetRepositoryObjectFormat(
'token',
'owner',
'repo',
undefined,
commitSha
)
).resolves.toEqual({format: 'sha256', succeeded: true})
expect(mockGetOctokit).not.toHaveBeenCalled()
})
it('returns unsuccessful when the hash algorithm endpoint value is not recognized', async () => {
mockHashAlgorithmApi('unknown')
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: '', succeeded: false})
expect(mockDebug).toHaveBeenCalledWith(
'Unable to determine repository object format from hash-algorithm endpoint'
)
})
it('returns unsuccessful when the hash algorithm API lookup fails', async () => {
request = jest.fn(async () => {
throw new Error('not found')
})
mockGetOctokit.mockReturnValue({
request
} as any)
await expect(
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
).resolves.toEqual({format: '', succeeded: false})
expect(mockDebug).toHaveBeenCalledWith(
'Unable to determine repository object format from hash-algorithm endpoint: not found'
)
})
})
describe('github-api-helper commit resolution', () => {
const sha1 = '0123456789abcdef0123456789abcdef01234567'
const sha256 =
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
let request: jest.Mock<any>
function mockCommitApi(data: unknown): void {
request = jest.fn(async () => ({data}))
mockGetOctokit.mockReturnValue({request} as any)
}
afterEach(() => {
jest.clearAllMocks()
})
it('resolves a ref to the SHA of its commit', async () => {
mockCommitApi(`${sha1}\n`)
await expect(
githubApiHelper.tryGetCommitSha(
'token',
'owner',
'repo',
'refs/heads/dev'
)
).resolves.toBe(sha1)
expect(request).toHaveBeenCalledWith(
'GET /repos/{owner}/{repo}/commits/{ref}',
{
owner: 'owner',
repo: 'repo',
ref: 'refs/heads/dev',
headers: {accept: 'application/vnd.github.sha'}
}
)
})
it('resolves a ref in a SHA-256 repository', async () => {
mockCommitApi(sha256)
await expect(
githubApiHelper.tryGetCommitSha('token', 'owner', 'repo', 'v1.0.0')
).resolves.toBe(sha256)
})
it('returns an empty string and warns when the response is not a SHA', async () => {
mockCommitApi('<html>not a sha</html>')
await expect(
githubApiHelper.tryGetCommitSha('token', 'owner', 'repo', 'main')
).resolves.toBe('')
expect(mockWarning).toHaveBeenCalledWith(
expect.stringContaining("Unable to resolve the commit for 'main'")
)
})
it('returns an empty string and warns when the lookup fails', async () => {
request = jest.fn(async () => {
throw new Error('Not Found')
})
mockGetOctokit.mockReturnValue({request} as any)
await expect(
githubApiHelper.tryGetCommitSha('token', 'owner', 'repo', 'gone')
).resolves.toBe('')
expect(mockWarning).toHaveBeenCalledWith(
"Unable to resolve the commit for 'gone': Not Found"
)
})
})