-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Artifacts] Name validation + zip specification creation (#1482)
* Artifact name validation + zip specification creation * Fix linting issues * Grammar fix * Update test description
- Loading branch information
1 parent
2461056
commit 7da3ac6
Showing
6 changed files
with
620 additions
and
8 deletions.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
packages/artifact/__tests__/path-and-artifact-name-validation.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { | ||
validateArtifactName, | ||
validateFilePath | ||
} from '../src/internal/upload/path-and-artifact-name-validation' | ||
|
||
import * as core from '@actions/core' | ||
|
||
describe('Path and artifact name validation', () => { | ||
beforeAll(() => { | ||
// mock all output so that there is less noise when running tests | ||
jest.spyOn(console, 'log').mockImplementation(() => {}) | ||
jest.spyOn(core, 'debug').mockImplementation(() => {}) | ||
jest.spyOn(core, 'info').mockImplementation(() => {}) | ||
jest.spyOn(core, 'warning').mockImplementation(() => {}) | ||
}) | ||
|
||
it('Check Artifact Name for any invalid characters', () => { | ||
const invalidNames = [ | ||
'my\\artifact', | ||
'my/artifact', | ||
'my"artifact', | ||
'my:artifact', | ||
'my<artifact', | ||
'my>artifact', | ||
'my|artifact', | ||
'my*artifact', | ||
'my?artifact', | ||
'' | ||
] | ||
for (const invalidName of invalidNames) { | ||
expect(() => { | ||
validateArtifactName(invalidName) | ||
}).toThrow() | ||
} | ||
|
||
const validNames = [ | ||
'my-normal-artifact', | ||
'myNormalArtifact', | ||
'm¥ñðrmålÄr†ï£å¢†' | ||
] | ||
for (const validName of validNames) { | ||
expect(() => { | ||
validateArtifactName(validName) | ||
}).not.toThrow() | ||
} | ||
}) | ||
|
||
it('Check Artifact File Path for any invalid characters', () => { | ||
const invalidNames = [ | ||
'some/invalid"artifact/path', | ||
'some/invalid:artifact/path', | ||
'some/invalid<artifact/path', | ||
'some/invalid>artifact/path', | ||
'some/invalid|artifact/path', | ||
'some/invalid*artifact/path', | ||
'some/invalid?artifact/path', | ||
'some/invalid\rartifact/path', | ||
'some/invalid\nartifact/path', | ||
'some/invalid\r\nartifact/path', | ||
'' | ||
] | ||
for (const invalidName of invalidNames) { | ||
expect(() => { | ||
validateFilePath(invalidName) | ||
}).toThrow() | ||
} | ||
|
||
const validNames = [ | ||
'my/perfectly-normal/artifact-path', | ||
'my/perfectly\\Normal/Artifact-path', | ||
'm¥/ñðrmål/Är†ï£å¢†' | ||
] | ||
for (const validName of validNames) { | ||
expect(() => { | ||
validateFilePath(validName) | ||
}).not.toThrow() | ||
} | ||
}) | ||
}) |
312 changes: 312 additions & 0 deletions
312
packages/artifact/__tests__/upload-zip-specification.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,312 @@ | ||
import * as io from '../../io/src/io' | ||
import * as path from 'path' | ||
import {promises as fs} from 'fs' | ||
import * as core from '@actions/core' | ||
import { | ||
getUploadZipSpecification, | ||
validateRootDirectory | ||
} from '../src/internal/upload/upload-zip-specification' | ||
|
||
const root = path.join(__dirname, '_temp', 'upload-specification') | ||
const goodItem1Path = path.join( | ||
root, | ||
'folder-a', | ||
'folder-b', | ||
'folder-c', | ||
'good-item1.txt' | ||
) | ||
const goodItem2Path = path.join(root, 'folder-d', 'good-item2.txt') | ||
const goodItem3Path = path.join(root, 'folder-d', 'good-item3.txt') | ||
const goodItem4Path = path.join(root, 'folder-d', 'good-item4.txt') | ||
const goodItem5Path = path.join(root, 'good-item5.txt') | ||
const badItem1Path = path.join( | ||
root, | ||
'folder-a', | ||
'folder-b', | ||
'folder-c', | ||
'bad-item1.txt' | ||
) | ||
const badItem2Path = path.join(root, 'folder-d', 'bad-item2.txt') | ||
const badItem3Path = path.join(root, 'folder-f', 'bad-item3.txt') | ||
const badItem4Path = path.join(root, 'folder-h', 'folder-i', 'bad-item4.txt') | ||
const badItem5Path = path.join(root, 'folder-h', 'folder-i', 'bad-item5.txt') | ||
const extraFileInFolderCPath = path.join( | ||
root, | ||
'folder-a', | ||
'folder-b', | ||
'folder-c', | ||
'extra-file-in-folder-c.txt' | ||
) | ||
const amazingFileInFolderHPath = path.join(root, 'folder-h', 'amazing-item.txt') | ||
|
||
const artifactFilesToUpload = [ | ||
goodItem1Path, | ||
goodItem2Path, | ||
goodItem3Path, | ||
goodItem4Path, | ||
goodItem5Path, | ||
extraFileInFolderCPath, | ||
amazingFileInFolderHPath | ||
] | ||
|
||
describe('Search', () => { | ||
beforeAll(async () => { | ||
// mock all output so that there is less noise when running tests | ||
jest.spyOn(console, 'log').mockImplementation(() => {}) | ||
jest.spyOn(core, 'debug').mockImplementation(() => {}) | ||
jest.spyOn(core, 'info').mockImplementation(() => {}) | ||
jest.spyOn(core, 'warning').mockImplementation(() => {}) | ||
|
||
// clear temp directory | ||
await io.rmRF(root) | ||
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-c'), { | ||
recursive: true | ||
}) | ||
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-e'), { | ||
recursive: true | ||
}) | ||
await fs.mkdir(path.join(root, 'folder-d'), { | ||
recursive: true | ||
}) | ||
await fs.mkdir(path.join(root, 'folder-f'), { | ||
recursive: true | ||
}) | ||
await fs.mkdir(path.join(root, 'folder-g'), { | ||
recursive: true | ||
}) | ||
await fs.mkdir(path.join(root, 'folder-h', 'folder-i'), { | ||
recursive: true | ||
}) | ||
|
||
await fs.writeFile(goodItem1Path, 'good item1 file') | ||
await fs.writeFile(goodItem2Path, 'good item2 file') | ||
await fs.writeFile(goodItem3Path, 'good item3 file') | ||
await fs.writeFile(goodItem4Path, 'good item4 file') | ||
await fs.writeFile(goodItem5Path, 'good item5 file') | ||
|
||
await fs.writeFile(badItem1Path, 'bad item1 file') | ||
await fs.writeFile(badItem2Path, 'bad item2 file') | ||
await fs.writeFile(badItem3Path, 'bad item3 file') | ||
await fs.writeFile(badItem4Path, 'bad item4 file') | ||
await fs.writeFile(badItem5Path, 'bad item5 file') | ||
|
||
await fs.writeFile(extraFileInFolderCPath, 'extra file') | ||
|
||
await fs.writeFile(amazingFileInFolderHPath, 'amazing file') | ||
/* | ||
Directory structure of files that get created: | ||
root/ | ||
folder-a/ | ||
folder-b/ | ||
folder-c/ | ||
good-item1.txt | ||
bad-item1.txt | ||
extra-file-in-folder-c.txt | ||
folder-e/ | ||
folder-d/ | ||
good-item2.txt | ||
good-item3.txt | ||
good-item4.txt | ||
bad-item2.txt | ||
folder-f/ | ||
bad-item3.txt | ||
folder-g/ | ||
folder-h/ | ||
amazing-item.txt | ||
folder-i/ | ||
bad-item4.txt | ||
bad-item5.txt | ||
good-item5.txt | ||
*/ | ||
}) | ||
|
||
it('Upload Specification - Fail non-existent rootDirectory', async () => { | ||
const invalidRootDirectory = path.join( | ||
__dirname, | ||
'_temp', | ||
'upload-specification-invalid' | ||
) | ||
expect(() => { | ||
validateRootDirectory(invalidRootDirectory) | ||
}).toThrow( | ||
`The provided rootDirectory ${invalidRootDirectory} does not exist` | ||
) | ||
}) | ||
|
||
it('Upload Specification - Fail invalid rootDirectory', async () => { | ||
expect(() => { | ||
validateRootDirectory(goodItem1Path) | ||
}).toThrow( | ||
`The provided rootDirectory ${goodItem1Path} is not a valid directory` | ||
) | ||
}) | ||
|
||
it('Upload Specification - File does not exist', async () => { | ||
const fakeFilePath = path.join( | ||
'folder-a', | ||
'folder-b', | ||
'non-existent-file.txt' | ||
) | ||
expect(() => { | ||
getUploadZipSpecification([fakeFilePath], root) | ||
}).toThrow(`File ${fakeFilePath} does not exist`) | ||
}) | ||
|
||
it('Upload Specification - Non parent directory', async () => { | ||
const folderADirectory = path.join(root, 'folder-a') | ||
const artifactFiles = [ | ||
goodItem1Path, | ||
badItem1Path, | ||
extraFileInFolderCPath, | ||
goodItem5Path | ||
] | ||
expect(() => { | ||
getUploadZipSpecification(artifactFiles, folderADirectory) | ||
}).toThrow( | ||
`The rootDirectory: ${folderADirectory} is not a parent directory of the file: ${goodItem5Path}` | ||
) | ||
}) | ||
|
||
it('Upload Specification - Success', async () => { | ||
const specifications = getUploadZipSpecification( | ||
artifactFilesToUpload, | ||
root | ||
) | ||
expect(specifications.length).toEqual(7) | ||
|
||
const absolutePaths = specifications.map(item => item.sourcePath) | ||
expect(absolutePaths).toContain(goodItem1Path) | ||
expect(absolutePaths).toContain(goodItem2Path) | ||
expect(absolutePaths).toContain(goodItem3Path) | ||
expect(absolutePaths).toContain(goodItem4Path) | ||
expect(absolutePaths).toContain(goodItem5Path) | ||
expect(absolutePaths).toContain(extraFileInFolderCPath) | ||
expect(absolutePaths).toContain(amazingFileInFolderHPath) | ||
|
||
for (const specification of specifications) { | ||
if (specification.sourcePath === goodItem1Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-a', 'folder-b', 'folder-c', 'good-item1.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem2Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-d', 'good-item2.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem3Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-d', 'good-item3.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem4Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-d', 'good-item4.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem5Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/good-item5.txt') | ||
) | ||
} else if (specification.sourcePath === extraFileInFolderCPath) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join( | ||
'/folder-a', | ||
'folder-b', | ||
'folder-c', | ||
'extra-file-in-folder-c.txt' | ||
) | ||
) | ||
} else if (specification.sourcePath === amazingFileInFolderHPath) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-h', 'amazing-item.txt') | ||
) | ||
} else { | ||
throw new Error( | ||
'Invalid specification found. This should never be reached' | ||
) | ||
} | ||
} | ||
}) | ||
|
||
it('Upload Specification - Success with extra slash', async () => { | ||
const rootWithSlash = `${root}/` | ||
const specifications = getUploadZipSpecification( | ||
artifactFilesToUpload, | ||
rootWithSlash | ||
) | ||
expect(specifications.length).toEqual(7) | ||
|
||
const absolutePaths = specifications.map(item => item.sourcePath) | ||
expect(absolutePaths).toContain(goodItem1Path) | ||
expect(absolutePaths).toContain(goodItem2Path) | ||
expect(absolutePaths).toContain(goodItem3Path) | ||
expect(absolutePaths).toContain(goodItem4Path) | ||
expect(absolutePaths).toContain(goodItem5Path) | ||
expect(absolutePaths).toContain(extraFileInFolderCPath) | ||
expect(absolutePaths).toContain(amazingFileInFolderHPath) | ||
|
||
for (const specification of specifications) { | ||
if (specification.sourcePath === goodItem1Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-a', 'folder-b', 'folder-c', 'good-item1.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem2Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-d', 'good-item2.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem3Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-d', 'good-item3.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem4Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-d', 'good-item4.txt') | ||
) | ||
} else if (specification.sourcePath === goodItem5Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/good-item5.txt') | ||
) | ||
} else if (specification.sourcePath === extraFileInFolderCPath) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join( | ||
'/folder-a', | ||
'folder-b', | ||
'folder-c', | ||
'extra-file-in-folder-c.txt' | ||
) | ||
) | ||
} else if (specification.sourcePath === amazingFileInFolderHPath) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-h', 'amazing-item.txt') | ||
) | ||
} else { | ||
throw new Error( | ||
'Invalid specification found. This should never be reached' | ||
) | ||
} | ||
} | ||
}) | ||
|
||
it('Upload Specification - Empty Directories are included', async () => { | ||
const folderEPath = path.join(root, 'folder-a', 'folder-b', 'folder-e') | ||
const filesWithDirectory = [goodItem1Path, folderEPath] | ||
const specifications = getUploadZipSpecification(filesWithDirectory, root) | ||
expect(specifications.length).toEqual(2) | ||
const absolutePaths = specifications.map(item => item.sourcePath) | ||
expect(absolutePaths).toContain(goodItem1Path) | ||
expect(absolutePaths).toContain(null) | ||
|
||
for (const specification of specifications) { | ||
if (specification.sourcePath === goodItem1Path) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-a', 'folder-b', 'folder-c', 'good-item1.txt') | ||
) | ||
} else if (specification.sourcePath === null) { | ||
expect(specification.destinationPath).toEqual( | ||
path.join('/folder-a', 'folder-b', 'folder-e') | ||
) | ||
} else { | ||
throw new Error( | ||
'Invalid specification found. This should never be reached' | ||
) | ||
} | ||
} | ||
}) | ||
}) |
Oops, something went wrong.