-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from bflad/td-github-actions-template
Initial jest unit testing and ncc preparation
- Loading branch information
Showing
12 changed files
with
8,429 additions
and
1,306 deletions.
There are no files selected for viewing
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
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,15 @@ | ||
name: "Testing" | ||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- main | ||
- 'releases/*' | ||
|
||
jobs: | ||
unit: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- run: npm ci | ||
- run: npm test |
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,112 @@ | ||
'use strict' | ||
|
||
const fs = require('fs').promises | ||
const os = require('os') | ||
const path = require('path') | ||
const crypto = require('crypto') | ||
|
||
const core = require('@actions/core') | ||
const tc = require('@actions/tool-cache') | ||
|
||
const octokit = require('./octokit') | ||
|
||
const owner = 'hashicorp' | ||
const repo = 'signore' | ||
|
||
// adapted from setup-terraform | ||
// arch in [arm, x32, x64...] (https://nodejs.org/api/os.html#os_os_arch) | ||
// return value in [x86_64, 386, arm] | ||
function mapArch(arch) { | ||
const mappings = { | ||
x32: '386', | ||
x64: 'x86_64' | ||
} | ||
return mappings[arch] || arch | ||
} | ||
|
||
// adapted from setup-terraform | ||
// os in [darwin, linux, win32...] (https://nodejs.org/api/os.html#os_os_platform) | ||
// return value in [darwin, linux, windows] | ||
function mapOS(os) { | ||
const mappings = { | ||
win32: 'windows' | ||
} | ||
return mappings[os] || os | ||
} | ||
|
||
async function run() { | ||
try { | ||
const clientID = core.getInput('client-id') | ||
const clientSecret = core.getInput('client-secret') | ||
const expectedArchiveChecksum = core.getInput('archive-checksum') | ||
const githubToken = core.getInput('github-token') | ||
const version = core.getInput('version') | ||
|
||
const platform = mapOS(os.platform()) | ||
const arch = mapArch(os.arch()) | ||
|
||
// windows binaries are zipped | ||
const archiveSuffix = platform === 'windows' ? '.zip' : '.tar.gz' | ||
|
||
const client = await octokit(githubToken || process.env.GITHUB_TOKEN) | ||
|
||
// if we don't have specific version, get latest release | ||
let releaseToDownload | ||
if (version === 'latest' || !version) { | ||
releaseToDownload = (await client.rest.repos.getLatestRelease({ owner, repo })).data | ||
} else { | ||
releaseToDownload = (await client.rest.repos.getReleaseByTag({ owner, repo, tag: version })).data | ||
} | ||
const tag = releaseToDownload.tag_name | ||
core.setOutput('version', tag) | ||
|
||
// i.e. signore_0.1.2_darwin_x86_64.tar.gz | ||
const expectedAssetName = `${repo}_${tag.replace('v', '')}_${platform}_${arch}${archiveSuffix}` | ||
const assetToDownload = releaseToDownload.assets.find(asset => asset.name === expectedAssetName) | ||
if (assetToDownload === undefined) { | ||
throw new Error(`Unable to find asset matching ${expectedAssetName} in the ${tag} release`) | ||
} | ||
|
||
const url = assetToDownload.url | ||
const auth = 'token ' + (githubToken || process.env.GITHUB_TOKEN) | ||
|
||
core.debug(`Downloading ${repo} release from ${url}`) | ||
const downloadedArchive = await tc.downloadTool(url, undefined, auth, { accept: 'application/octet-stream' }) | ||
|
||
if (expectedArchiveChecksum !== '') { | ||
const downloadedArchiveChecksum = crypto.createHash('sha256').update(await fs.readFile(downloadedArchive)).digest('hex') | ||
if (expectedArchiveChecksum !== downloadedArchiveChecksum) { | ||
throw new Error(`Checksum mismatch: ${downloadedArchiveChecksum} does not match expected checksum ${expectedArchiveChecksum}`) | ||
} | ||
} | ||
|
||
core.debug(`Extracting ${repo} release`) | ||
let pathToCLI | ||
if (archiveSuffix === '.tar.gz') { | ||
pathToCLI = await tc.extractTar(downloadedArchive) | ||
} else { | ||
pathToCLI = await tc.extractZip(downloadedArchive) | ||
} | ||
core.debug(`${repo} CLI path is ${pathToCLI}.`) | ||
|
||
if (!downloadedArchive || !pathToCLI) { | ||
throw new Error(`Unable to download ${repo} from ${url}`) | ||
} | ||
|
||
core.addPath(pathToCLI) | ||
|
||
if (clientID || clientSecret) { | ||
core.debug('writing signore config file') | ||
let configContent = '' | ||
configContent += clientID ? `clientID: ${clientID}\n` : '' | ||
configContent += clientSecret ? `clientSecret: ${clientSecret}\n` : '' | ||
fs.writeFile(path.join(os.homedir(), '.signore', 'config.yaml'), configContent) | ||
} | ||
|
||
core.debug('success: signore has been set up!') | ||
} catch (error) { | ||
core.setFailed(error.message) | ||
} | ||
} | ||
|
||
module.exports = run |
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,177 @@ | ||
const fs = require('fs') | ||
const nock = require('nock') | ||
const path = require('path') | ||
const os = require('os') | ||
|
||
const core = require('@actions/core') | ||
|
||
const mockRelease = { | ||
assets: [ | ||
{ | ||
id: 1, | ||
name: 'signore_0.1.3_darwin_x86_64.tar.gz', | ||
url: 'https://api.github.com/repos/hashicorp/signore/releases/assets/1' | ||
}, | ||
{ | ||
id: 2, | ||
name: 'signore_0.1.3_linux_x86_64.tar.gz', | ||
url: 'https://api.github.com/repos/hashicorp/signore/releases/assets/2' | ||
}, | ||
{ | ||
id: 3, | ||
name: 'signore_0.1.3_windows_x86_64.zip', | ||
url: 'https://api.github.com/repos/hashicorp/signore/releases/assets/3' | ||
} | ||
], | ||
id: '1', | ||
name: 'v0.1.3', | ||
tag_name: 'v0.1.3' | ||
} | ||
|
||
beforeAll(() => { | ||
nock.disableNetConnect() | ||
}) | ||
|
||
beforeEach(() => { | ||
process.env['INPUT_GITHUB-TOKEN'] = 'testtoken' | ||
process.env.INPUT_VERSION = 'latest' | ||
process.env['INPUT_VERSION-CHECKSUM'] = '5663389ef1a8ec48af6ca622e66bf0f54ba8f22c127f14cb8a3f429e40868582' | ||
|
||
const spyOsArch = jest.spyOn(os, 'arch') | ||
spyOsArch.mockReturnValue('x64') | ||
const spyOsPlatform = jest.spyOn(os, 'platform') | ||
spyOsPlatform.mockReturnValue('win32') | ||
}) | ||
|
||
describe('action', () => { | ||
test('installs latest version', (done) => { | ||
const scope = nock('https://api.github.com') | ||
.get('/repos/hashicorp/signore/releases/latest') | ||
.reply(200, mockRelease) | ||
.get('/repos/hashicorp/signore/releases/assets/3') | ||
.replyWithFile(200, path.resolve(__dirname, 'test.zip'), { 'content-type': 'application/octet-stream' }) | ||
const spyCoreAddPath = jest.spyOn(core, 'addPath') | ||
const spyCoreSetOutput = jest.spyOn(core, 'setOutput') | ||
|
||
fs.mkdtemp(path.join(os.tmpdir(), 'setup-signore-'), async (err, directory) => { | ||
if (err) throw err | ||
|
||
process.env.RUNNER_TEMP = directory | ||
|
||
const spyOsHomedir = jest.spyOn(os, 'homedir') | ||
spyOsHomedir.mockReturnValue(directory) | ||
|
||
const action = require('./action') | ||
await expect(await action()).resolves | ||
expect(scope.isDone()).toBeTruthy() | ||
expect(spyCoreAddPath).toHaveBeenCalled() | ||
expect(spyCoreSetOutput).toHaveBeenCalledWith('version', 'v0.1.3') | ||
done() | ||
}) | ||
}) | ||
|
||
test('installs configured version', (done) => { | ||
const scope = nock('https://api.github.com') | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(200, mockRelease) | ||
.get('/repos/hashicorp/signore/releases/assets/3') | ||
.replyWithFile(200, path.resolve(__dirname, 'test.zip'), { 'content-type': 'application/octet-stream' }) | ||
const spyCoreAddPath = jest.spyOn(core, 'addPath') | ||
const spyCoreSetOutput = jest.spyOn(core, 'setOutput') | ||
|
||
fs.mkdtemp(path.join(os.tmpdir(), 'setup-signore-'), async (err, directory) => { | ||
if (err) throw err | ||
|
||
process.env.INPUT_VERSION = 'v0.1.3' | ||
process.env.RUNNER_TEMP = directory | ||
|
||
const spyOsHomedir = jest.spyOn(os, 'homedir') | ||
spyOsHomedir.mockReturnValue(directory) | ||
|
||
const action = require('./action') | ||
await expect(await action()).resolves | ||
expect(scope.isDone()).toBeTruthy() | ||
expect(spyCoreAddPath).toHaveBeenCalled() | ||
expect(spyCoreSetOutput).toHaveBeenCalledWith('version', 'v0.1.3') | ||
done() | ||
}) | ||
}) | ||
|
||
test('retries transient errors', (done) => { | ||
const scope = nock('https://api.github.com') | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(500, 'expected transient error') | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(200, mockRelease) | ||
.get('/repos/hashicorp/signore/releases/assets/3') | ||
.replyWithFile(200, path.resolve(__dirname, 'test.zip'), { 'content-type': 'application/octet-stream' }) | ||
|
||
fs.mkdtemp(path.join(os.tmpdir(), 'setup-signore-'), async (err, directory) => { | ||
if (err) throw err | ||
|
||
process.env.INPUT_VERSION = 'v0.1.3' | ||
process.env.RUNNER_TEMP = directory | ||
|
||
const spyOsHomedir = jest.spyOn(os, 'homedir') | ||
spyOsHomedir.mockReturnValue(directory) | ||
|
||
const action = require('./action') | ||
await expect(await action()).resolves | ||
expect(scope.isDone()).toBeTruthy() | ||
done() | ||
}) | ||
}) | ||
|
||
test('retries abuse limit errors', (done) => { | ||
const scope = nock('https://api.github.com') | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(403, { | ||
message: 'You have triggered an abuse detection mechanism and have been temporarily blocked from content creation. Please retry your request again later.', | ||
documentation_url: 'https://docs.github.com/rest/overview/resources-in-the-rest-api#abuse-rate-limits' | ||
}) | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(200, mockRelease) | ||
.get('/repos/hashicorp/signore/releases/assets/3') | ||
.replyWithFile(200, path.resolve(__dirname, 'test.zip'), { 'content-type': 'application/octet-stream' }) | ||
|
||
fs.mkdtemp(path.join(os.tmpdir(), 'setup-signore-'), async (err, directory) => { | ||
if (err) throw err | ||
|
||
process.env.INPUT_VERSION = 'v0.1.3' | ||
process.env.RUNNER_TEMP = directory | ||
|
||
const spyOsHomedir = jest.spyOn(os, 'homedir') | ||
spyOsHomedir.mockReturnValue(directory) | ||
|
||
const action = require('./action') | ||
await expect(await action()).resolves | ||
expect(scope.isDone()).toBeTruthy() | ||
done() | ||
}) | ||
}) | ||
|
||
test('retries rate limit errors', (done) => { | ||
const scope = nock('https://api.github.com') | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(429, 'expected rate limit error') | ||
.get('/repos/hashicorp/signore/releases/tags/v0.1.3') | ||
.reply(200, mockRelease) | ||
.get('/repos/hashicorp/signore/releases/assets/3') | ||
.replyWithFile(200, path.resolve(__dirname, 'test.zip'), { 'content-type': 'application/octet-stream' }) | ||
|
||
fs.mkdtemp(path.join(os.tmpdir(), 'setup-signore-'), async (err, directory) => { | ||
if (err) throw err | ||
|
||
process.env.INPUT_VERSION = 'v0.1.3' | ||
process.env.RUNNER_TEMP = directory | ||
|
||
const spyOsHomedir = jest.spyOn(os, 'homedir') | ||
spyOsHomedir.mockReturnValue(directory) | ||
|
||
const action = require('./action') | ||
await expect(await action()).resolves | ||
expect(scope.isDone()).toBeTruthy() | ||
done() | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.