Skip to content

Commit 76a6eb5

Browse files
authored
Merge pull request #392 from GhadimiR/add_unit_tests
Add unit tests
2 parents b14cf4c + a2426d7 commit 76a6eb5

File tree

7 files changed

+14714
-6346
lines changed

7 files changed

+14714
-6346
lines changed

__tests__/download.test.ts

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import * as core from '@actions/core'
2+
import artifact, {ArtifactNotFoundError} from '@actions/artifact'
3+
import {run} from '../src/download-artifact'
4+
import {Inputs} from '../src/constants'
5+
6+
jest.mock('@actions/github', () => ({
7+
context: {
8+
repo: {
9+
owner: 'actions',
10+
repo: 'toolkit'
11+
},
12+
runId: 123,
13+
serverUrl: 'https://github.com'
14+
}
15+
}))
16+
17+
jest.mock('@actions/core')
18+
19+
/* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */
20+
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
21+
const inputs = {
22+
[Inputs.Name]: 'artifact-name',
23+
[Inputs.Path]: '/some/artifact/path',
24+
[Inputs.GitHubToken]: 'warn',
25+
[Inputs.Repository]: 'owner/some-repository',
26+
[Inputs.RunID]: 'some-run-id',
27+
[Inputs.Pattern]: 'some-pattern',
28+
...overrides
29+
}
30+
31+
;(core.getInput as jest.Mock).mockImplementation((name: string) => {
32+
return inputs[name]
33+
})
34+
;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
35+
return inputs[name]
36+
})
37+
38+
return inputs
39+
}
40+
41+
describe('download', () => {
42+
beforeEach(async () => {
43+
mockInputs()
44+
jest.clearAllMocks()
45+
46+
// Mock artifact client methods
47+
jest
48+
.spyOn(artifact, 'listArtifacts')
49+
.mockImplementation(() => Promise.resolve({artifacts: []}))
50+
jest.spyOn(artifact, 'getArtifact').mockImplementation(name => {
51+
throw new ArtifactNotFoundError(`Artifact '${name}' not found`)
52+
})
53+
jest
54+
.spyOn(artifact, 'downloadArtifact')
55+
.mockImplementation(() => Promise.resolve({digestMismatch: false}))
56+
})
57+
58+
test('downloads a single artifact by name', async () => {
59+
const mockArtifact = {
60+
id: 123,
61+
name: 'artifact-name',
62+
size: 1024,
63+
digest: 'abc123'
64+
}
65+
66+
jest
67+
.spyOn(artifact, 'getArtifact')
68+
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
69+
70+
await run()
71+
72+
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
73+
mockArtifact.id,
74+
expect.objectContaining({
75+
expectedHash: mockArtifact.digest
76+
})
77+
)
78+
expect(core.info).toHaveBeenCalledWith('Total of 1 artifact(s) downloaded')
79+
80+
expect(core.setOutput).toHaveBeenCalledWith(
81+
'download-path',
82+
expect.any(String)
83+
)
84+
85+
expect(core.info).toHaveBeenCalledWith(
86+
'Download artifact has finished successfully'
87+
)
88+
})
89+
90+
test('downloads multiple artifacts when no name or pattern provided', async () => {
91+
jest.clearAllMocks()
92+
mockInputs({
93+
[Inputs.Name]: '',
94+
[Inputs.Pattern]: ''
95+
})
96+
97+
const mockArtifacts = [
98+
{id: 123, name: 'artifact1', size: 1024, digest: 'abc123'},
99+
{id: 456, name: 'artifact2', size: 2048, digest: 'def456'}
100+
]
101+
102+
// Set up artifact mock after clearing mocks
103+
jest
104+
.spyOn(artifact, 'listArtifacts')
105+
.mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
106+
107+
// Reset downloadArtifact mock as well
108+
jest
109+
.spyOn(artifact, 'downloadArtifact')
110+
.mockImplementation(() => Promise.resolve({digestMismatch: false}))
111+
112+
await run()
113+
114+
expect(core.info).toHaveBeenCalledWith(
115+
'No input name or pattern filtered specified, downloading all artifacts'
116+
)
117+
118+
expect(core.info).toHaveBeenCalledWith('Total of 2 artifact(s) downloaded')
119+
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(2)
120+
})
121+
122+
test('sets download path output even when no artifacts are found', async () => {
123+
mockInputs({[Inputs.Name]: ''})
124+
125+
await run()
126+
127+
expect(core.setOutput).toHaveBeenCalledWith(
128+
'download-path',
129+
expect.any(String)
130+
)
131+
132+
expect(core.info).toHaveBeenCalledWith(
133+
'Download artifact has finished successfully'
134+
)
135+
136+
expect(core.info).toHaveBeenCalledWith('Total of 0 artifact(s) downloaded')
137+
})
138+
139+
test('filters artifacts by pattern', async () => {
140+
const mockArtifacts = [
141+
{id: 123, name: 'test-artifact', size: 1024, digest: 'abc123'},
142+
{id: 456, name: 'prod-artifact', size: 2048, digest: 'def456'}
143+
]
144+
145+
jest
146+
.spyOn(artifact, 'listArtifacts')
147+
.mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
148+
149+
mockInputs({
150+
[Inputs.Name]: '',
151+
[Inputs.Pattern]: 'test-*'
152+
})
153+
154+
await run()
155+
156+
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
157+
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
158+
123,
159+
expect.anything()
160+
)
161+
})
162+
163+
test('uses token and repository information when provided', async () => {
164+
const token = 'ghp_testtoken123'
165+
166+
mockInputs({
167+
[Inputs.Name]: '',
168+
[Inputs.GitHubToken]: token,
169+
[Inputs.Repository]: 'myorg/myrepo',
170+
[Inputs.RunID]: '789'
171+
})
172+
173+
jest
174+
.spyOn(artifact, 'listArtifacts')
175+
.mockImplementation(() => Promise.resolve({artifacts: []}))
176+
177+
await run()
178+
179+
expect(artifact.listArtifacts).toHaveBeenCalledWith(
180+
expect.objectContaining({
181+
findBy: {
182+
token,
183+
workflowRunId: 789,
184+
repositoryName: 'myrepo',
185+
repositoryOwner: 'myorg'
186+
}
187+
})
188+
)
189+
})
190+
191+
test('throws error when repository format is invalid', async () => {
192+
mockInputs({
193+
[Inputs.GitHubToken]: 'some-token',
194+
[Inputs.Repository]: 'invalid-format' // Missing the owner/repo format
195+
})
196+
197+
await expect(run()).rejects.toThrow(
198+
"Invalid repository: 'invalid-format'. Must be in format owner/repo"
199+
)
200+
})
201+
202+
test('warns when digest validation fails', async () => {
203+
const mockArtifact = {
204+
id: 123,
205+
name: 'corrupted-artifact',
206+
size: 1024,
207+
digest: 'abc123'
208+
}
209+
210+
jest
211+
.spyOn(artifact, 'getArtifact')
212+
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
213+
214+
jest
215+
.spyOn(artifact, 'downloadArtifact')
216+
.mockImplementation(() => Promise.resolve({digestMismatch: true}))
217+
218+
await run()
219+
220+
expect(core.warning).toHaveBeenCalledWith(
221+
expect.stringContaining('digest validation failed')
222+
)
223+
})
224+
})

dist/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118760,7 +118760,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
118760118760
return (mod && mod.__esModule) ? mod : { "default": mod };
118761118761
};
118762118762
Object.defineProperty(exports, "__esModule", ({ value: true }));
118763-
exports.chunk = void 0;
118763+
exports.run = exports.chunk = void 0;
118764118764
const os = __importStar(__nccwpck_require__(22037));
118765118765
const path = __importStar(__nccwpck_require__(71017));
118766118766
const core = __importStar(__nccwpck_require__(42186));
@@ -118863,6 +118863,7 @@ function run() {
118863118863
}
118864118864
});
118865118865
}
118866+
exports.run = run;
118866118867
run().catch(err => core.setFailed(`Unable to download artifact(s): ${err.message}`));
118867118868

118868118869

jest.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
clearMocks: true,
3+
moduleFileExtensions: ['js', 'ts'],
4+
roots: ['<rootDir>'],
5+
testEnvironment: 'node',
6+
testMatch: ['**/*.test.ts'],
7+
testRunner: 'jest-circus/runner',
8+
transform: {
9+
'^.+\\.ts$': 'ts-jest'
10+
},
11+
verbose: true
12+
}

0 commit comments

Comments
 (0)