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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy