mirror of
https://code.forgejo.org/actions/upload-artifact.git
synced 2026-03-21 07:25:54 +03:00
When archive is set to false and overwrite is true, it now deletes existing artifact by filename before uploading new one.
352 lines
8.9 KiB
TypeScript
352 lines
8.9 KiB
TypeScript
import {jest, describe, test, expect, beforeEach} from '@jest/globals'
|
|
|
|
// Mock @actions/github before importing modules that use it
|
|
jest.unstable_mockModule('@actions/github', () => ({
|
|
context: {
|
|
repo: {
|
|
owner: 'actions',
|
|
repo: 'toolkit'
|
|
},
|
|
runId: 123,
|
|
serverUrl: 'https://github.com'
|
|
},
|
|
getOctokit: jest.fn()
|
|
}))
|
|
|
|
// Mock @actions/core
|
|
jest.unstable_mockModule('@actions/core', () => ({
|
|
getInput: jest.fn(),
|
|
getBooleanInput: jest.fn(),
|
|
setOutput: jest.fn(),
|
|
setFailed: jest.fn(),
|
|
setSecret: jest.fn(),
|
|
info: jest.fn(),
|
|
warning: jest.fn(),
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
notice: jest.fn(),
|
|
startGroup: jest.fn(),
|
|
endGroup: jest.fn(),
|
|
isDebug: jest.fn(() => false),
|
|
getState: jest.fn(),
|
|
saveState: jest.fn(),
|
|
exportVariable: jest.fn(),
|
|
addPath: jest.fn(),
|
|
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
|
toPlatformPath: jest.fn((p: string) => p),
|
|
toWin32Path: jest.fn((p: string) => p),
|
|
toPosixPath: jest.fn((p: string) => p)
|
|
}))
|
|
|
|
// Mock shared search module
|
|
const mockFindFilesToUpload =
|
|
jest.fn<() => Promise<{filesToUpload: string[]; rootDirectory: string}>>()
|
|
jest.unstable_mockModule('../src/shared/search.js', () => ({
|
|
findFilesToUpload: mockFindFilesToUpload
|
|
}))
|
|
|
|
// Dynamic imports after mocking
|
|
const core = await import('@actions/core')
|
|
const github = await import('@actions/github')
|
|
const artifact = await import('@actions/artifact')
|
|
const {run} = await import('../src/upload/upload-artifact.js')
|
|
const {Inputs} = await import('../src/upload/constants.js')
|
|
const {ArtifactNotFoundError} = artifact
|
|
|
|
const fixtures = {
|
|
artifactName: 'artifact-name',
|
|
rootDirectory: '/some/artifact/path',
|
|
filesToUpload: [
|
|
'/some/artifact/path/file1.txt',
|
|
'/some/artifact/path/file2.txt'
|
|
]
|
|
}
|
|
|
|
const mockInputs = (
|
|
overrides?: Partial<{[K in (typeof Inputs)[keyof typeof Inputs]]?: any}>
|
|
) => {
|
|
const inputs: Record<string, any> = {
|
|
[Inputs.Name]: 'artifact-name',
|
|
[Inputs.Path]: '/some/artifact/path',
|
|
[Inputs.IfNoFilesFound]: 'warn',
|
|
[Inputs.RetentionDays]: 0,
|
|
[Inputs.CompressionLevel]: 6,
|
|
[Inputs.Overwrite]: false,
|
|
[Inputs.Archive]: true,
|
|
...overrides
|
|
}
|
|
|
|
;(core.getInput as jest.Mock<typeof core.getInput>).mockImplementation(
|
|
(name: string) => {
|
|
return inputs[name]
|
|
}
|
|
)
|
|
;(
|
|
core.getBooleanInput as jest.Mock<typeof core.getBooleanInput>
|
|
).mockImplementation((name: string) => {
|
|
return inputs[name]
|
|
})
|
|
|
|
return inputs
|
|
}
|
|
|
|
describe('upload', () => {
|
|
beforeEach(async () => {
|
|
mockInputs()
|
|
jest.clearAllMocks()
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: fixtures.filesToUpload,
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
jest.spyOn(artifact.default, 'uploadArtifact').mockResolvedValue({
|
|
size: 123,
|
|
id: 1337,
|
|
digest: 'facefeed'
|
|
})
|
|
})
|
|
|
|
test('uploads a single file', async () => {
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [fixtures.filesToUpload[0]],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
[fixtures.filesToUpload[0]],
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 6}
|
|
)
|
|
})
|
|
|
|
test('uploads multiple files', async () => {
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
fixtures.filesToUpload,
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 6}
|
|
)
|
|
})
|
|
|
|
test('sets outputs', async () => {
|
|
await run()
|
|
|
|
expect(core.setOutput).toHaveBeenCalledWith('artifact-id', 1337)
|
|
expect(core.setOutput).toHaveBeenCalledWith('artifact-digest', 'facefeed')
|
|
expect(core.setOutput).toHaveBeenCalledWith(
|
|
'artifact-url',
|
|
`${github.context.serverUrl}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}/artifacts/${1337}`
|
|
)
|
|
})
|
|
|
|
test('supports custom compression level', async () => {
|
|
mockInputs({
|
|
[Inputs.CompressionLevel]: 2
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
fixtures.filesToUpload,
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 2}
|
|
)
|
|
})
|
|
|
|
test('supports custom retention days', async () => {
|
|
mockInputs({
|
|
[Inputs.RetentionDays]: 7
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
fixtures.filesToUpload,
|
|
fixtures.rootDirectory,
|
|
{retentionDays: 7, compressionLevel: 6}
|
|
)
|
|
})
|
|
|
|
test('supports warn if-no-files-found', async () => {
|
|
mockInputs({
|
|
[Inputs.IfNoFilesFound]: 'warn'
|
|
})
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(core.warning).toHaveBeenCalledWith(
|
|
`No files were found with the provided path: ${fixtures.rootDirectory}. No artifacts will be uploaded.`
|
|
)
|
|
})
|
|
|
|
test('supports error if-no-files-found', async () => {
|
|
mockInputs({
|
|
[Inputs.IfNoFilesFound]: 'error'
|
|
})
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
`No files were found with the provided path: ${fixtures.rootDirectory}. No artifacts will be uploaded.`
|
|
)
|
|
})
|
|
|
|
test('supports ignore if-no-files-found', async () => {
|
|
mockInputs({
|
|
[Inputs.IfNoFilesFound]: 'ignore'
|
|
})
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(core.info).toHaveBeenCalledWith(
|
|
`No files were found with the provided path: ${fixtures.rootDirectory}. No artifacts will be uploaded.`
|
|
)
|
|
})
|
|
|
|
test('supports overwrite', async () => {
|
|
mockInputs({
|
|
[Inputs.Overwrite]: true
|
|
})
|
|
|
|
jest.spyOn(artifact.default, 'deleteArtifact').mockResolvedValue({
|
|
id: 1337
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
fixtures.filesToUpload,
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 6}
|
|
)
|
|
|
|
expect(artifact.default.deleteArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName
|
|
)
|
|
})
|
|
|
|
test('supports overwrite and continues if not found', async () => {
|
|
mockInputs({
|
|
[Inputs.Overwrite]: true
|
|
})
|
|
|
|
jest
|
|
.spyOn(artifact.default, 'deleteArtifact')
|
|
.mockRejectedValue(new ArtifactNotFoundError('not found'))
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
fixtures.filesToUpload,
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 6}
|
|
)
|
|
|
|
expect(artifact.default.deleteArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName
|
|
)
|
|
expect(core.debug).toHaveBeenCalledWith(
|
|
`Skipping deletion of '${fixtures.artifactName}', it does not exist`
|
|
)
|
|
})
|
|
|
|
test('passes skipArchive when archive is false', async () => {
|
|
mockInputs({
|
|
[Inputs.Archive]: false
|
|
})
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [fixtures.filesToUpload[0]],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
[fixtures.filesToUpload[0]],
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 6, skipArchive: true}
|
|
)
|
|
})
|
|
|
|
test('does not pass skipArchive when archive is true', async () => {
|
|
mockInputs({
|
|
[Inputs.Archive]: true
|
|
})
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [fixtures.filesToUpload[0]],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
|
fixtures.artifactName,
|
|
[fixtures.filesToUpload[0]],
|
|
fixtures.rootDirectory,
|
|
{compressionLevel: 6}
|
|
)
|
|
})
|
|
|
|
test('fails when archive is false and multiple files are provided', async () => {
|
|
mockInputs({
|
|
[Inputs.Archive]: false
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
`When 'archive' is set to false, only a single file can be uploaded. Found ${fixtures.filesToUpload.length} files to upload.`
|
|
)
|
|
expect(artifact.default.uploadArtifact).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('overwrite artifact by filename when archive is false', async () => {
|
|
mockInputs({
|
|
[Inputs.Archive]: false,
|
|
[Inputs.Overwrite]: true
|
|
})
|
|
|
|
mockFindFilesToUpload.mockResolvedValue({
|
|
filesToUpload: [fixtures.filesToUpload[0]],
|
|
rootDirectory: fixtures.rootDirectory
|
|
})
|
|
|
|
jest.spyOn(artifact.default, 'deleteArtifact').mockResolvedValue({
|
|
id: 1337
|
|
})
|
|
|
|
await run()
|
|
|
|
expect(artifact.default.deleteArtifact).toHaveBeenCalledWith(
|
|
fixtures.filesToUpload[0].split('/').pop() || ''
|
|
)
|
|
})
|
|
})
|