Skip to content

Commit 7da3ac6

Browse files
authored
[Artifacts] Name validation + zip specification creation (actions#1482)
* Artifact name validation + zip specification creation * Fix linting issues * Grammar fix * Update test description
1 parent 2461056 commit 7da3ac6

File tree

6 files changed

+620
-8
lines changed

6 files changed

+620
-8
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
validateArtifactName,
3+
validateFilePath
4+
} from '../src/internal/upload/path-and-artifact-name-validation'
5+
6+
import * as core from '@actions/core'
7+
8+
describe('Path and artifact name validation', () => {
9+
beforeAll(() => {
10+
// mock all output so that there is less noise when running tests
11+
jest.spyOn(console, 'log').mockImplementation(() => {})
12+
jest.spyOn(core, 'debug').mockImplementation(() => {})
13+
jest.spyOn(core, 'info').mockImplementation(() => {})
14+
jest.spyOn(core, 'warning').mockImplementation(() => {})
15+
})
16+
17+
it('Check Artifact Name for any invalid characters', () => {
18+
const invalidNames = [
19+
'my\\artifact',
20+
'my/artifact',
21+
'my"artifact',
22+
'my:artifact',
23+
'my<artifact',
24+
'my>artifact',
25+
'my|artifact',
26+
'my*artifact',
27+
'my?artifact',
28+
''
29+
]
30+
for (const invalidName of invalidNames) {
31+
expect(() => {
32+
validateArtifactName(invalidName)
33+
}).toThrow()
34+
}
35+
36+
const validNames = [
37+
'my-normal-artifact',
38+
'myNormalArtifact',
39+
'm¥ñðrmålÄr†ï£å¢†'
40+
]
41+
for (const validName of validNames) {
42+
expect(() => {
43+
validateArtifactName(validName)
44+
}).not.toThrow()
45+
}
46+
})
47+
48+
it('Check Artifact File Path for any invalid characters', () => {
49+
const invalidNames = [
50+
'some/invalid"artifact/path',
51+
'some/invalid:artifact/path',
52+
'some/invalid<artifact/path',
53+
'some/invalid>artifact/path',
54+
'some/invalid|artifact/path',
55+
'some/invalid*artifact/path',
56+
'some/invalid?artifact/path',
57+
'some/invalid\rartifact/path',
58+
'some/invalid\nartifact/path',
59+
'some/invalid\r\nartifact/path',
60+
''
61+
]
62+
for (const invalidName of invalidNames) {
63+
expect(() => {
64+
validateFilePath(invalidName)
65+
}).toThrow()
66+
}
67+
68+
const validNames = [
69+
'my/perfectly-normal/artifact-path',
70+
'my/perfectly\\Normal/Artifact-path',
71+
'm¥/ñðrmål/Är†ï£å¢†'
72+
]
73+
for (const validName of validNames) {
74+
expect(() => {
75+
validateFilePath(validName)
76+
}).not.toThrow()
77+
}
78+
})
79+
})
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
import * as io from '../../io/src/io'
2+
import * as path from 'path'
3+
import {promises as fs} from 'fs'
4+
import * as core from '@actions/core'
5+
import {
6+
getUploadZipSpecification,
7+
validateRootDirectory
8+
} from '../src/internal/upload/upload-zip-specification'
9+
10+
const root = path.join(__dirname, '_temp', 'upload-specification')
11+
const goodItem1Path = path.join(
12+
root,
13+
'folder-a',
14+
'folder-b',
15+
'folder-c',
16+
'good-item1.txt'
17+
)
18+
const goodItem2Path = path.join(root, 'folder-d', 'good-item2.txt')
19+
const goodItem3Path = path.join(root, 'folder-d', 'good-item3.txt')
20+
const goodItem4Path = path.join(root, 'folder-d', 'good-item4.txt')
21+
const goodItem5Path = path.join(root, 'good-item5.txt')
22+
const badItem1Path = path.join(
23+
root,
24+
'folder-a',
25+
'folder-b',
26+
'folder-c',
27+
'bad-item1.txt'
28+
)
29+
const badItem2Path = path.join(root, 'folder-d', 'bad-item2.txt')
30+
const badItem3Path = path.join(root, 'folder-f', 'bad-item3.txt')
31+
const badItem4Path = path.join(root, 'folder-h', 'folder-i', 'bad-item4.txt')
32+
const badItem5Path = path.join(root, 'folder-h', 'folder-i', 'bad-item5.txt')
33+
const extraFileInFolderCPath = path.join(
34+
root,
35+
'folder-a',
36+
'folder-b',
37+
'folder-c',
38+
'extra-file-in-folder-c.txt'
39+
)
40+
const amazingFileInFolderHPath = path.join(root, 'folder-h', 'amazing-item.txt')
41+
42+
const artifactFilesToUpload = [
43+
goodItem1Path,
44+
goodItem2Path,
45+
goodItem3Path,
46+
goodItem4Path,
47+
goodItem5Path,
48+
extraFileInFolderCPath,
49+
amazingFileInFolderHPath
50+
]
51+
52+
describe('Search', () => {
53+
beforeAll(async () => {
54+
// mock all output so that there is less noise when running tests
55+
jest.spyOn(console, 'log').mockImplementation(() => {})
56+
jest.spyOn(core, 'debug').mockImplementation(() => {})
57+
jest.spyOn(core, 'info').mockImplementation(() => {})
58+
jest.spyOn(core, 'warning').mockImplementation(() => {})
59+
60+
// clear temp directory
61+
await io.rmRF(root)
62+
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-c'), {
63+
recursive: true
64+
})
65+
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-e'), {
66+
recursive: true
67+
})
68+
await fs.mkdir(path.join(root, 'folder-d'), {
69+
recursive: true
70+
})
71+
await fs.mkdir(path.join(root, 'folder-f'), {
72+
recursive: true
73+
})
74+
await fs.mkdir(path.join(root, 'folder-g'), {
75+
recursive: true
76+
})
77+
await fs.mkdir(path.join(root, 'folder-h', 'folder-i'), {
78+
recursive: true
79+
})
80+
81+
await fs.writeFile(goodItem1Path, 'good item1 file')
82+
await fs.writeFile(goodItem2Path, 'good item2 file')
83+
await fs.writeFile(goodItem3Path, 'good item3 file')
84+
await fs.writeFile(goodItem4Path, 'good item4 file')
85+
await fs.writeFile(goodItem5Path, 'good item5 file')
86+
87+
await fs.writeFile(badItem1Path, 'bad item1 file')
88+
await fs.writeFile(badItem2Path, 'bad item2 file')
89+
await fs.writeFile(badItem3Path, 'bad item3 file')
90+
await fs.writeFile(badItem4Path, 'bad item4 file')
91+
await fs.writeFile(badItem5Path, 'bad item5 file')
92+
93+
await fs.writeFile(extraFileInFolderCPath, 'extra file')
94+
95+
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')
96+
/*
97+
Directory structure of files that get created:
98+
root/
99+
folder-a/
100+
folder-b/
101+
folder-c/
102+
good-item1.txt
103+
bad-item1.txt
104+
extra-file-in-folder-c.txt
105+
folder-e/
106+
folder-d/
107+
good-item2.txt
108+
good-item3.txt
109+
good-item4.txt
110+
bad-item2.txt
111+
folder-f/
112+
bad-item3.txt
113+
folder-g/
114+
folder-h/
115+
amazing-item.txt
116+
folder-i/
117+
bad-item4.txt
118+
bad-item5.txt
119+
good-item5.txt
120+
*/
121+
})
122+
123+
it('Upload Specification - Fail non-existent rootDirectory', async () => {
124+
const invalidRootDirectory = path.join(
125+
__dirname,
126+
'_temp',
127+
'upload-specification-invalid'
128+
)
129+
expect(() => {
130+
validateRootDirectory(invalidRootDirectory)
131+
}).toThrow(
132+
`The provided rootDirectory ${invalidRootDirectory} does not exist`
133+
)
134+
})
135+
136+
it('Upload Specification - Fail invalid rootDirectory', async () => {
137+
expect(() => {
138+
validateRootDirectory(goodItem1Path)
139+
}).toThrow(
140+
`The provided rootDirectory ${goodItem1Path} is not a valid directory`
141+
)
142+
})
143+
144+
it('Upload Specification - File does not exist', async () => {
145+
const fakeFilePath = path.join(
146+
'folder-a',
147+
'folder-b',
148+
'non-existent-file.txt'
149+
)
150+
expect(() => {
151+
getUploadZipSpecification([fakeFilePath], root)
152+
}).toThrow(`File ${fakeFilePath} does not exist`)
153+
})
154+
155+
it('Upload Specification - Non parent directory', async () => {
156+
const folderADirectory = path.join(root, 'folder-a')
157+
const artifactFiles = [
158+
goodItem1Path,
159+
badItem1Path,
160+
extraFileInFolderCPath,
161+
goodItem5Path
162+
]
163+
expect(() => {
164+
getUploadZipSpecification(artifactFiles, folderADirectory)
165+
}).toThrow(
166+
`The rootDirectory: ${folderADirectory} is not a parent directory of the file: ${goodItem5Path}`
167+
)
168+
})
169+
170+
it('Upload Specification - Success', async () => {
171+
const specifications = getUploadZipSpecification(
172+
artifactFilesToUpload,
173+
root
174+
)
175+
expect(specifications.length).toEqual(7)
176+
177+
const absolutePaths = specifications.map(item => item.sourcePath)
178+
expect(absolutePaths).toContain(goodItem1Path)
179+
expect(absolutePaths).toContain(goodItem2Path)
180+
expect(absolutePaths).toContain(goodItem3Path)
181+
expect(absolutePaths).toContain(goodItem4Path)
182+
expect(absolutePaths).toContain(goodItem5Path)
183+
expect(absolutePaths).toContain(extraFileInFolderCPath)
184+
expect(absolutePaths).toContain(amazingFileInFolderHPath)
185+
186+
for (const specification of specifications) {
187+
if (specification.sourcePath === goodItem1Path) {
188+
expect(specification.destinationPath).toEqual(
189+
path.join('/folder-a', 'folder-b', 'folder-c', 'good-item1.txt')
190+
)
191+
} else if (specification.sourcePath === goodItem2Path) {
192+
expect(specification.destinationPath).toEqual(
193+
path.join('/folder-d', 'good-item2.txt')
194+
)
195+
} else if (specification.sourcePath === goodItem3Path) {
196+
expect(specification.destinationPath).toEqual(
197+
path.join('/folder-d', 'good-item3.txt')
198+
)
199+
} else if (specification.sourcePath === goodItem4Path) {
200+
expect(specification.destinationPath).toEqual(
201+
path.join('/folder-d', 'good-item4.txt')
202+
)
203+
} else if (specification.sourcePath === goodItem5Path) {
204+
expect(specification.destinationPath).toEqual(
205+
path.join('/good-item5.txt')
206+
)
207+
} else if (specification.sourcePath === extraFileInFolderCPath) {
208+
expect(specification.destinationPath).toEqual(
209+
path.join(
210+
'/folder-a',
211+
'folder-b',
212+
'folder-c',
213+
'extra-file-in-folder-c.txt'
214+
)
215+
)
216+
} else if (specification.sourcePath === amazingFileInFolderHPath) {
217+
expect(specification.destinationPath).toEqual(
218+
path.join('/folder-h', 'amazing-item.txt')
219+
)
220+
} else {
221+
throw new Error(
222+
'Invalid specification found. This should never be reached'
223+
)
224+
}
225+
}
226+
})
227+
228+
it('Upload Specification - Success with extra slash', async () => {
229+
const rootWithSlash = `${root}/`
230+
const specifications = getUploadZipSpecification(
231+
artifactFilesToUpload,
232+
rootWithSlash
233+
)
234+
expect(specifications.length).toEqual(7)
235+
236+
const absolutePaths = specifications.map(item => item.sourcePath)
237+
expect(absolutePaths).toContain(goodItem1Path)
238+
expect(absolutePaths).toContain(goodItem2Path)
239+
expect(absolutePaths).toContain(goodItem3Path)
240+
expect(absolutePaths).toContain(goodItem4Path)
241+
expect(absolutePaths).toContain(goodItem5Path)
242+
expect(absolutePaths).toContain(extraFileInFolderCPath)
243+
expect(absolutePaths).toContain(amazingFileInFolderHPath)
244+
245+
for (const specification of specifications) {
246+
if (specification.sourcePath === goodItem1Path) {
247+
expect(specification.destinationPath).toEqual(
248+
path.join('/folder-a', 'folder-b', 'folder-c', 'good-item1.txt')
249+
)
250+
} else if (specification.sourcePath === goodItem2Path) {
251+
expect(specification.destinationPath).toEqual(
252+
path.join('/folder-d', 'good-item2.txt')
253+
)
254+
} else if (specification.sourcePath === goodItem3Path) {
255+
expect(specification.destinationPath).toEqual(
256+
path.join('/folder-d', 'good-item3.txt')
257+
)
258+
} else if (specification.sourcePath === goodItem4Path) {
259+
expect(specification.destinationPath).toEqual(
260+
path.join('/folder-d', 'good-item4.txt')
261+
)
262+
} else if (specification.sourcePath === goodItem5Path) {
263+
expect(specification.destinationPath).toEqual(
264+
path.join('/good-item5.txt')
265+
)
266+
} else if (specification.sourcePath === extraFileInFolderCPath) {
267+
expect(specification.destinationPath).toEqual(
268+
path.join(
269+
'/folder-a',
270+
'folder-b',
271+
'folder-c',
272+
'extra-file-in-folder-c.txt'
273+
)
274+
)
275+
} else if (specification.sourcePath === amazingFileInFolderHPath) {
276+
expect(specification.destinationPath).toEqual(
277+
path.join('/folder-h', 'amazing-item.txt')
278+
)
279+
} else {
280+
throw new Error(
281+
'Invalid specification found. This should never be reached'
282+
)
283+
}
284+
}
285+
})
286+
287+
it('Upload Specification - Empty Directories are included', async () => {
288+
const folderEPath = path.join(root, 'folder-a', 'folder-b', 'folder-e')
289+
const filesWithDirectory = [goodItem1Path, folderEPath]
290+
const specifications = getUploadZipSpecification(filesWithDirectory, root)
291+
expect(specifications.length).toEqual(2)
292+
const absolutePaths = specifications.map(item => item.sourcePath)
293+
expect(absolutePaths).toContain(goodItem1Path)
294+
expect(absolutePaths).toContain(null)
295+
296+
for (const specification of specifications) {
297+
if (specification.sourcePath === goodItem1Path) {
298+
expect(specification.destinationPath).toEqual(
299+
path.join('/folder-a', 'folder-b', 'folder-c', 'good-item1.txt')
300+
)
301+
} else if (specification.sourcePath === null) {
302+
expect(specification.destinationPath).toEqual(
303+
path.join('/folder-a', 'folder-b', 'folder-e')
304+
)
305+
} else {
306+
throw new Error(
307+
'Invalid specification found. This should never be reached'
308+
)
309+
}
310+
}
311+
})
312+
})

0 commit comments

Comments
 (0)