update deps, linting, test cases, etc.

This commit is contained in:
Conor Sloan
2023-11-17 21:31:12 +00:00
committed by Edwin Sirko
parent 651b1739d1
commit 4ac7dfc3cb
19 changed files with 4111 additions and 1995 deletions
+51 -7
View File
@@ -41,8 +41,8 @@ describe('createArchives', () => {
expect(tarFile.sha256.startsWith('sha256:')).toEqual(true)
// Validate the hashes by comparing to the output of the system's hashing utility
let zipSHA = zipFile.sha256.substring(7) // remove "sha256:" prefix
let tarSHA = tarFile.sha256.substring(7) // remove "sha256:" prefix
const zipSHA = zipFile.sha256.substring(7) // remove "sha256:" prefix
const tarSHA = tarFile.sha256.substring(7) // remove "sha256:" prefix
// sha256 hash is 64 characters long
expect(zipSHA).toHaveLength(64)
@@ -86,13 +86,13 @@ describe('createTempDir', () => {
})
afterEach(() => {
dirs.forEach(dir => {
for (const dir of dirs) {
fs.rmSync(dir, { recursive: true })
})
}
})
it('creates a temporary directory in the OS temporary dir', () => {
let tmpDir = fsHelper.createTempDir()
const tmpDir = fsHelper.createTempDir()
dirs.push(tmpDir)
expect(fs.existsSync(tmpDir)).toEqual(true)
@@ -101,10 +101,10 @@ describe('createTempDir', () => {
})
it('creates a unique temporary directory', () => {
let dir1 = fsHelper.createTempDir()
const dir1 = fsHelper.createTempDir()
dirs.push(dir1)
let dir2 = fsHelper.createTempDir()
const dir2 = fsHelper.createTempDir()
dirs.push(dir2)
expect(dir1).not.toEqual(dir2)
@@ -170,3 +170,47 @@ describe('removeDir', () => {
expect(fs.existsSync(dir)).toEqual(false)
})
})
describe('bundleFilesintoDirectory', () => {
let sourceDir: string
let targetDir: string
beforeEach(() => {
sourceDir = fsHelper.createTempDir()
targetDir = fsHelper.createTempDir()
})
afterEach(() => {
fs.rmSync(sourceDir, { recursive: true })
fs.rmSync(targetDir, { recursive: true })
})
it('bundles files and folders into a directory', () => {
// Create some test files and folders in the sourceDir
const file1 = `${sourceDir}/file1.txt`
const folder1 = `${sourceDir}/folder1`
const file2 = `${folder1}/file3.txt`
fs.mkdirSync(folder1)
fs.writeFileSync(file1, fileContent)
fs.writeFileSync(file2, fileContent)
// Bundle the files and folders into the targetDir
fsHelper.bundleFilesintoDirectory([file1, folder1], targetDir)
// Check that the files and folders were copied
expect(fs.existsSync(file1)).toEqual(true)
expect(fsHelper.readFileContents(file1).toString()).toEqual(fileContent)
expect(fs.existsSync(`${targetDir}/folder1`)).toEqual(true)
expect(fs.existsSync(file2)).toEqual(true)
expect(fsHelper.readFileContents(file2).toString()).toEqual(fileContent)
})
it('throws an error if a file or directory does not exist', () => {
expect(() => {
fsHelper.bundleFilesintoDirectory(['/does/not/exist'], targetDir)
}).toThrow('File /does/not/exist does not exist')
})
})
+21 -27
View File
@@ -1,6 +1,5 @@
import { publishOCIArtifact } from '../src/ghcr-client'
import axios, { AxiosRequestConfig } from 'axios'
import * as fs from 'fs'
import axios from 'axios'
import * as fsHelper from '../src/fs-helper'
import * as ociContainer from '../src/oci-container'
@@ -89,14 +88,12 @@ describe('publishOCIArtifact', () => {
it('publishes layer blobs & then a manifest to the provided registry', async () => {
// Simulate none of the blobs existing currently
axiosHeadMock.mockImplementation(
async (url: string, config: AxiosRequestConfig) => {
validateRequestConfig(404, url, config)
return {
status: 404
}
axiosHeadMock.mockImplementation(async (url, config) => {
validateRequestConfig(404, url, config)
return {
status: 404
}
)
})
// Simulate successful initiation of uploads for all blobs & return location
axiosPostMock.mockImplementation(async (url, data, config) => {
@@ -110,7 +107,7 @@ describe('publishOCIArtifact', () => {
})
// Simulate successful reading of all the files
fsReadFileSyncMock.mockImplementation(async path => {
fsReadFileSyncMock.mockImplementation(() => {
return Buffer.from('test')
})
@@ -161,7 +158,7 @@ describe('publishOCIArtifact', () => {
})
// Simulate successful reading of all the files
fsReadFileSyncMock.mockImplementation(async path => {
fsReadFileSyncMock.mockImplementation(() => {
return Buffer.from('test')
})
@@ -199,7 +196,7 @@ describe('publishOCIArtifact', () => {
}
})
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -230,7 +227,7 @@ describe('publishOCIArtifact', () => {
}
})
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -262,7 +259,7 @@ describe('publishOCIArtifact', () => {
}
})
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -297,7 +294,7 @@ describe('publishOCIArtifact', () => {
})
// Simulate successful reading of all the files
fsReadFileSyncMock.mockImplementation(async path => {
fsReadFileSyncMock.mockImplementation(() => {
return Buffer.from('test')
})
@@ -309,7 +306,7 @@ describe('publishOCIArtifact', () => {
}
})
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -344,7 +341,7 @@ describe('publishOCIArtifact', () => {
})
// Simulate successful reading of all the files
fsReadFileSyncMock.mockImplementation(async path => {
fsReadFileSyncMock.mockImplementation(() => {
return Buffer.from('test')
})
@@ -363,7 +360,7 @@ describe('publishOCIArtifact', () => {
}
})
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -398,7 +395,7 @@ describe('publishOCIArtifact', () => {
})
// Simulate successful reading of all the files
fsReadFileSyncMock.mockImplementation(path => {
fsReadFileSyncMock.mockImplementation(() => {
throw new Error('failed to read a file: test')
})
@@ -410,7 +407,7 @@ describe('publishOCIArtifact', () => {
}
})
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -425,10 +422,10 @@ describe('publishOCIArtifact', () => {
})
it('throws an error if one of the layers has the wrong media type', async () => {
let modifiedTestManifest = testManifest
const modifiedTestManifest = testManifest
modifiedTestManifest.layers[0].mediaType = 'application/json'
expect(
await expect(
publishOCIArtifact(
token,
registry,
@@ -445,11 +442,8 @@ describe('publishOCIArtifact', () => {
// We expect all axios calls to have auth headers set and to not intercept any status codes so we can handle them.
// This function verifies that given an axios request config.
function validateRequestConfig(
status: number,
url: string,
config: AxiosRequestConfig
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function validateRequestConfig(status: number, url: string, config: any): void {
// Basic URL checks
expect(url).toBeDefined()
+102 -85
View File
@@ -11,15 +11,9 @@ import * as main from '../src/main'
import * as github from '@actions/github'
import * as fsHelper from '../src/fs-helper'
import * as ociContainer from '../src/oci-container'
import * as ghcr from '../src/ghcr-client'
// Mock the action's main function
const runMock = jest.spyOn(main, 'run')
// Mock the GitHub Actions core library
let debugMock: jest.SpyInstance
let errorMock: jest.SpyInstance
let getInputMock: jest.SpyInstance
let setFailedMock: jest.SpyInstance
let setOutputMock: jest.SpyInstance
@@ -29,6 +23,7 @@ let createTempDirMock: jest.SpyInstance
let isDirectoryMock: jest.SpyInstance
let createArchivesMock: jest.SpyInstance
let removeDirMock: jest.SpyInstance
let bundleFilesintoDirectoryMock: jest.SpyInstance
// Mock the GHCR Client
let publishOCIArtifactMock: jest.SpyInstance
@@ -38,8 +33,6 @@ describe('action', () => {
jest.clearAllMocks()
// Core mocks
debugMock = jest.spyOn(core, 'debug').mockImplementation()
errorMock = jest.spyOn(core, 'error').mockImplementation()
getInputMock = jest.spyOn(core, 'getInput').mockImplementation()
setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation()
setOutputMock = jest.spyOn(core, 'setOutput').mockImplementation()
@@ -53,6 +46,9 @@ describe('action', () => {
.spyOn(fsHelper, 'createArchives')
.mockImplementation()
removeDirMock = jest.spyOn(fsHelper, 'removeDir').mockImplementation()
bundleFilesintoDirectoryMock = jest
.spyOn(fsHelper, 'bundleFilesintoDirectory')
.mockImplementation()
// GHCR Client mocks
publishOCIArtifactMock = jest
@@ -105,7 +101,7 @@ describe('action', () => {
)
})
it('fails if path is not a directory', async () => {
it('fails if multiple paths are provided and staging files fails', async () => {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
github.context.eventName = 'release'
@@ -117,23 +113,24 @@ describe('action', () => {
}
getInputMock.mockImplementation((name: string) => {
if (name === 'path') {
return 'not-a-directory'
return 'directory1 directory2'
} else if (name === 'registry') {
return 'https://ghcr.io'
}
return ''
})
isDirectoryMock.mockImplementation(() => false)
isDirectoryMock.mockImplementation(() => true)
bundleFilesintoDirectoryMock.mockImplementation(() => {
throw new Error('Something went wrong')
})
// Run the action
await main.run()
// Check the results
expect(isDirectoryMock).toHaveBeenCalledWith('not-a-directory')
expect(setFailedMock).toHaveBeenCalledWith(
'The path not-a-directory is not a directory. Please provide a path to a valid directory.'
)
expect(setFailedMock).toHaveBeenCalledWith('Something went wrong')
})
it('fails if an error is thrown from dependent code', async () => {
@@ -174,77 +171,97 @@ describe('action', () => {
expect(removeDirMock).toHaveBeenCalledWith('/tmp/test')
})
it('uploads and returns the manifest & package URL if all succeeds', async () => {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
github.context.eventName = 'release'
github.context.payload = {
release: {
id: '123',
tag_name: 'v1.0.0'
}
}
getInputMock.mockImplementation((name: string) => {
if (name === 'path') {
return 'test'
} else if (name === 'registry') {
return 'https://ghcr.io'
}
return ''
})
it('successfully uploads if the release tag is a semver without v prefix', async () => {
await testHappyPath('1.0.0', 'test')
})
isDirectoryMock.mockImplementation(() => true)
it('successfully uploads if the release tag is a semver with v prefix', async () => {
await testHappyPath('v1.0.0', 'test')
})
createTempDirMock.mockImplementation(() => '/tmp/test')
createArchivesMock.mockImplementation(() => {
return {
zipFile: {
path: 'test',
size: 5,
sha256: '123'
},
tarFile: {
path: 'test2',
size: 52,
sha256: '1234'
}
}
})
publishOCIArtifactMock.mockImplementation(() => {
return new URL('https://ghcr.io/v2/test/test:1.0.0')
})
// Run the action
await main.run()
expect(publishOCIArtifactMock).toHaveBeenCalledTimes(1)
// Check manifest is in output
expect(setOutputMock).toHaveBeenCalledWith(
'package-url',
'https://ghcr.io/v2/test/test:1.0.0'
)
expect(setOutputMock).toHaveBeenCalledWith(
'package-manifest',
expect.any(String)
)
// Validate the manifest
const manifest = JSON.parse(setOutputMock.mock.calls[1][1])
expect(manifest.mediaType).toEqual(
'application/vnd.oci.image.manifest.v1+json'
)
expect(manifest.config.mediaType).toEqual(
'application/vnd.github.actions.package.config.v1+json'
)
expect(manifest.layers.length).toEqual(3)
expect(manifest.annotations['com.github.package.type']).toEqual(
'actions_oci_pkg'
)
// Expect the files to be cleaned up
expect(removeDirMock).toHaveBeenCalledWith('/tmp/test')
it('successfully uploads if multiple paths are provided', async () => {
await testHappyPath('v1.0.0', 'test test2')
})
})
// Test that main successfully uploads and returns the manifest & package URL
async function testHappyPath(version: string, path: string): Promise<void> {
// Mock the environment
process.env.GITHUB_REPOSITORY = 'test/test'
github.context.eventName = 'release'
github.context.payload = {
release: {
id: '123',
tag_name: version
}
}
getInputMock.mockImplementation((name: string) => {
if (name === 'path') {
return path
} else if (name === 'registry') {
return 'https://ghcr.io'
}
return ''
})
isDirectoryMock.mockImplementation(() => true)
bundleFilesintoDirectoryMock.mockImplementation(() => {
return '/tmp/test'
})
createTempDirMock.mockImplementation(() => '/tmp/test')
createArchivesMock.mockImplementation(() => {
return {
zipFile: {
path: 'test',
size: 5,
sha256: '123'
},
tarFile: {
path: 'test2',
size: 52,
sha256: '1234'
}
}
})
publishOCIArtifactMock.mockImplementation(() => {
return new URL('https://ghcr.io/v2/test/test:1.0.0')
})
// Run the action
await main.run()
expect(publishOCIArtifactMock).toHaveBeenCalledTimes(1)
// Check manifest is in output
expect(setOutputMock).toHaveBeenCalledWith(
'package-url',
'https://ghcr.io/v2/test/test:1.0.0'
)
expect(setOutputMock).toHaveBeenCalledWith(
'package-manifest',
expect.any(String)
)
// Validate the manifest
const manifest = JSON.parse(setOutputMock.mock.calls[1][1])
expect(manifest.mediaType).toEqual(
'application/vnd.oci.image.manifest.v1+json'
)
expect(manifest.config.mediaType).toEqual(
'application/vnd.github.actions.package.config.v1+json'
)
expect(manifest.layers.length).toEqual(3)
expect(manifest.annotations['com.github.package.type']).toEqual(
'actions_oci_pkg'
)
// Expect all the temp files to be cleaned up
expect(removeDirMock).toHaveBeenCalledWith('/tmp/test')
expect(removeDirMock).toHaveBeenCalledTimes(
createTempDirMock.mock.calls.length
)
}
+13 -12
View File
@@ -3,21 +3,22 @@ import { FileMetadata } from '../src/fs-helper'
describe('createActionPackageManigest', () => {
it('creates a manifest containing the provided information', () => {
let date = new Date()
let repo = 'test-repo'
let version = '1.0.0'
let tarFile: FileMetadata = {
const date = new Date()
const repo = 'test-org/test-repo'
const sanitizedRepo = 'test-org-test-repo'
const version = '1.0.0'
const tarFile: FileMetadata = {
path: '/test/test/test',
sha256: '1234567890',
size: 100
}
let zipFile: FileMetadata = {
const zipFile: FileMetadata = {
path: '/test/test/test',
sha256: '1234567890',
size: 100
}
let expectedJSON: String = `{
const expectedJSON = `{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"artifactType": "application/vnd.oci.image.manifest.v1+json",
@@ -43,7 +44,7 @@ describe('createActionPackageManigest', () => {
"size":${tarFile.size},
"digest":"${tarFile.sha256}",
"annotations":{
"org.opencontainers.image.title":"${repo}-${version}.tar.gz"
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.tar.gz"
}
},
{
@@ -51,7 +52,7 @@ describe('createActionPackageManigest', () => {
"size":${tarFile.size},
"digest":"${tarFile.sha256}",
"annotations":{
"org.opencontainers.image.title":"${repo}-${version}.zip"
"org.opencontainers.image.title":"${sanitizedRepo}_${version}.zip"
}
}
],
@@ -63,7 +64,7 @@ describe('createActionPackageManigest', () => {
}
}`
let manifest = createActionPackageManifest(
const manifest = createActionPackageManifest(
{
path: 'test.tar.gz',
size: 100,
@@ -74,12 +75,12 @@ describe('createActionPackageManigest', () => {
size: 100,
sha256: '1234567890'
},
'test-repo',
'1.0.0',
repo,
version,
date
)
let manifestJSON = JSON.stringify(manifest)
const manifestJSON = JSON.stringify(manifest)
expect(manifestJSON).toEqual(expectedJSON.replace(/\s/g, ''))
})
})