This commit is contained in:
Ryan Ghadimi 2025-03-19 11:21:30 +00:00
parent 96a6f165f4
commit 9a869e9c49

View File

@ -1,19 +1,8 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as github from '@actions/github'
import * as os from 'os'
import artifact, {ArtifactNotFoundError} from '@actions/artifact' import artifact, {ArtifactNotFoundError} from '@actions/artifact'
import {run} from '../src/download-artifact' import {run} from '../src/download-artifact'
import {Inputs} from '../src/constants' import {Inputs} from '../src/constants'
const fixtures = {
artifactName: 'artifact-name',
rootDirectory: '/some/artifact/path',
filesToUpload: [
'/some/artifact/path/file1.txt',
'/some/artifact/path/file2.txt'
]
}
jest.mock('@actions/github', () => ({ jest.mock('@actions/github', () => ({
context: { context: {
repo: { repo: {
@ -27,7 +16,7 @@ jest.mock('@actions/github', () => ({
jest.mock('@actions/core') jest.mock('@actions/core')
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => { const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
const inputs = { const inputs = {
[Inputs.Name]: 'artifact-name', [Inputs.Name]: 'artifact-name',
@ -50,177 +39,177 @@ const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
} }
describe('download', () => { describe('download', () => {
beforeEach(async () => { beforeEach(async () => {
mockInputs() mockInputs()
jest.clearAllMocks() jest.clearAllMocks()
// Mock artifact client methods // Mock artifact client methods
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() => jest
Promise.resolve({ artifacts: [] }) .spyOn(artifact, 'listArtifacts')
) .mockImplementation(() => Promise.resolve({artifacts: []}))
jest.spyOn(artifact, 'getArtifact').mockImplementation((name) => { jest.spyOn(artifact, 'getArtifact').mockImplementation(name => {
throw new ArtifactNotFoundError(`Artifact '${name}' not found`) throw new ArtifactNotFoundError(`Artifact '${name}' not found`)
})
jest
.spyOn(artifact, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: false}))
})
test('downloads a single artifact by name', async () => {
const mockArtifact = {
id: 123,
name: 'artifact-name',
size: 1024,
digest: 'abc123'
}
jest
.spyOn(artifact, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
await run()
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
mockArtifact.id,
expect.objectContaining({
expectedHash: mockArtifact.digest
}) })
jest.spyOn(artifact, 'downloadArtifact').mockImplementation(() => )
Promise.resolve({ digestMismatch: false }) expect(core.info).toHaveBeenCalledWith('Total of 1 artifact(s) downloaded')
) })
test('downloads multiple artifacts when no name or pattern provided', async () => {
jest.clearAllMocks()
mockInputs({
[Inputs.Name]: '',
[Inputs.Pattern]: ''
}) })
test('downloads a single artifact by name', async () => { const mockArtifacts = [
const mockArtifact = { {id: 123, name: 'artifact1', size: 1024, digest: 'abc123'},
id: 123, {id: 456, name: 'artifact2', size: 2048, digest: 'def456'}
name: 'artifact-name', ]
size: 1024,
digest: 'abc123' // Set up artifact mock after clearing mocks
} jest
.spyOn(artifact, 'listArtifacts')
jest.spyOn(artifact, 'getArtifact').mockImplementation(() => .mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
Promise.resolve({ artifact: mockArtifact })
) // Reset downloadArtifact mock as well
jest
await run() .spyOn(artifact, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: false}))
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
mockArtifact.id, await run()
expect.objectContaining({
expectedHash: mockArtifact.digest expect(core.info).toHaveBeenCalledWith(
}) 'No input name or pattern filtered specified, downloading all artifacts'
) )
expect(core.info).toHaveBeenCalledWith('Total of 1 artifact(s) downloaded')
expect(core.info).toHaveBeenCalledWith('Total of 2 artifact(s) downloaded')
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(2)
})
test('sets download path output even when no artifacts are found', async () => {
mockInputs({[Inputs.Name]: ''})
await run()
expect(core.setOutput).toHaveBeenCalledWith(
'download-path',
expect.any(String)
)
expect(core.info).toHaveBeenCalledWith(
'Download artifact has finished successfully'
)
expect(core.info).toHaveBeenCalledWith('Total of 0 artifact(s) downloaded')
})
test('filters artifacts by pattern', async () => {
const mockArtifacts = [
{id: 123, name: 'test-artifact', size: 1024, digest: 'abc123'},
{id: 456, name: 'prod-artifact', size: 2048, digest: 'def456'}
]
jest
.spyOn(artifact, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
mockInputs({
[Inputs.Name]: '',
[Inputs.Pattern]: 'test-*'
}) })
test('downloads multiple artifacts when no name or pattern provided', async () => { await run()
jest.clearAllMocks()
mockInputs({ expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
[Inputs.Name]: '', expect(artifact.downloadArtifact).toHaveBeenCalledWith(
[Inputs.Pattern]: '' 123,
expect.anything()
)
})
test('uses token and repository information when provided', async () => {
const token = 'ghp_testtoken123'
mockInputs({
[Inputs.Name]: '',
[Inputs.GitHubToken]: token,
[Inputs.Repository]: 'myorg/myrepo',
[Inputs.RunID]: '789'
})
jest
.spyOn(artifact, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: []}))
await run()
expect(artifact.listArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
findBy: {
token,
workflowRunId: 789,
repositoryName: 'myrepo',
repositoryOwner: 'myorg'
}
}) })
)
const mockArtifacts = [ })
{ id: 123, name: 'artifact1', size: 1024, digest: 'abc123' },
{ id: 456, name: 'artifact2', size: 2048, digest: 'def456' }
]
// Set up artifact mock after clearing mocks
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
Promise.resolve({ artifacts: mockArtifacts })
)
// Reset downloadArtifact mock as well
jest.spyOn(artifact, 'downloadArtifact').mockImplementation(() =>
Promise.resolve({ digestMismatch: false })
)
await run()
expect(core.info).toHaveBeenCalledWith('No input name or pattern filtered specified, downloading all artifacts') test('throws error when repository format is invalid', async () => {
mockInputs({
expect(core.info).toHaveBeenCalledWith('Total of 2 artifact(s) downloaded') [Inputs.GitHubToken]: 'some-token',
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(2) [Inputs.Repository]: 'invalid-format' // Missing the owner/repo format
})
test('sets download path output even when no artifacts are found', async () => {
mockInputs({ [Inputs.Name]: '' })
await run()
expect(core.setOutput).toHaveBeenCalledWith(
'download-path',
expect.any(String)
)
expect(core.info).toHaveBeenCalledWith(
'Download artifact has finished successfully'
)
expect(core.info).toHaveBeenCalledWith(
'Total of 0 artifact(s) downloaded'
)
}) })
test('filters artifacts by pattern', async () => { await expect(run()).rejects.toThrow(
const mockArtifacts = [ "Invalid repository: 'invalid-format'. Must be in format owner/repo"
{ id: 123, name: 'test-artifact', size: 1024, digest: 'abc123' }, )
{ id: 456, name: 'prod-artifact', size: 2048, digest: 'def456' } })
]
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
Promise.resolve({ artifacts: mockArtifacts })
)
mockInputs({
[Inputs.Name]: '',
[Inputs.Pattern]: 'test-*'
})
await run()
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
123,
expect.anything()
)
})
test('uses token and repository information when provided', async () => { test('warns when digest validation fails', async () => {
const token = 'ghp_testtoken123' const mockArtifact = {
id: 123,
mockInputs({ name: 'corrupted-artifact',
[Inputs.Name]: '', size: 1024,
[Inputs.GitHubToken]: token, digest: 'abc123'
[Inputs.Repository]: 'myorg/myrepo', }
[Inputs.RunID]: '789'
})
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
Promise.resolve({ artifacts: [] })
)
await run()
expect(artifact.listArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
findBy: {
token,
workflowRunId: 789,
repositoryName: 'myrepo',
repositoryOwner: 'myorg'
}
})
)
})
test('throws error when repository format is invalid', async () => { jest
mockInputs({ .spyOn(artifact, 'getArtifact')
[Inputs.GitHubToken]: 'some-token', .mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
[Inputs.Repository]: 'invalid-format' // Missing the owner/repo format
}) jest
.spyOn(artifact, 'downloadArtifact')
await expect(run()).rejects.toThrow( .mockImplementation(() => Promise.resolve({digestMismatch: true}))
"Invalid repository: 'invalid-format'. Must be in format owner/repo"
) await run()
})
expect(core.warning).toHaveBeenCalledWith(
test('warns when digest validation fails', async () => { expect.stringContaining('digest validation failed')
const mockArtifact = { )
id: 123, })
name: 'corrupted-artifact', })
size: 1024,
digest: 'abc123'
}
jest.spyOn(artifact, 'getArtifact').mockImplementation(() =>
Promise.resolve({ artifact: mockArtifact })
)
jest.spyOn(artifact, 'downloadArtifact').mockImplementation(() =>
Promise.resolve({ digestMismatch: true })
)
await run()
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining('digest validation failed')
)
})
})