linting and unit testing

This commit is contained in:
Michael Waddell
2022-02-17 20:32:55 -06:00
parent d5d6d4da96
commit 4d5384fc6f
7 changed files with 229 additions and 90 deletions

View File

@@ -9,6 +9,20 @@ beforeEach(() => {
jest.spyOn(core, 'startGroup').mockImplementation(jest.fn())
})
const baseDependency = {
dependencyName: '',
dependencyType: '',
updateType: '',
directory: '',
packageEcosystem: '',
targetBranch: '',
prevVersion: '',
newVersion: '',
alertState: '',
ghsaId: '',
cvss: 0
}
test('when given a single dependency it sets its values', async () => {
const updatedDependencies = [
{
@@ -17,7 +31,12 @@ test('when given a single dependency it sets its values', async () => {
updateType: 'version-update:semver-minor',
directory: 'wwwroot',
packageEcosystem: 'nuget',
targetBranch: 'main'
targetBranch: 'main',
prevVersion: '1.0.2',
newVersion: '1.1.3-beta',
alertState: 'FIXED',
ghsaId: 'VERY_LONG_ID',
cvss: 4.6
}
]
@@ -40,36 +59,28 @@ test('when given a single dependency it sets its values', async () => {
test('when given a multiple dependencies, it uses the highest values for types', async () => {
const updatedDependencies = [
{
...baseDependency,
dependencyName: 'rspec',
dependencyType: 'direct:development',
updateType: 'version-update:semver-minor',
directory: '',
packageEcosystem: '',
targetBranch: ''
updateType: 'version-update:semver-minor'
},
{
...baseDependency,
dependencyName: 'coffee-rails',
dependencyType: 'indirect',
updateType: 'version-update:semver-minor',
directory: '',
packageEcosystem: '',
targetBranch: ''
updateType: 'version-update:semver-minor'
},
{
...baseDependency,
dependencyName: 'coffeescript',
dependencyType: 'indirect',
updateType: 'version-update:semver-major',
directory: '',
packageEcosystem: '',
targetBranch: ''
updateType: 'version-update:semver-major'
},
{
...baseDependency,
dependencyName: 'rspec-coffeescript',
dependencyType: 'indirect',
updateType: 'version-update:semver-patch',
directory: '',
packageEcosystem: '',
targetBranch: ''
updateType: 'version-update:semver-patch'
}
]
@@ -88,12 +99,9 @@ test('when given a multiple dependencies, it uses the highest values for types',
test('when the dependency has no update type', async () => {
const updatedDependencies = [
{
...baseDependency,
dependencyName: 'coffee-rails',
dependencyType: 'direct:production',
updateType: '',
directory: '',
packageEcosystem: '',
targetBranch: ''
dependencyType: 'direct:production'
}
]
@@ -116,36 +124,26 @@ test('when the dependency has no update type', async () => {
test('when given a multiple dependencies, and some do not have update types', async () => {
const updatedDependencies = [
{
...baseDependency,
dependencyName: 'rspec',
dependencyType: 'direct:development',
updateType: '',
directory: '',
packageEcosystem: '',
targetBranch: ''
dependencyType: 'direct:development'
},
{
...baseDependency,
dependencyName: 'coffee-rails',
dependencyType: 'indirect',
updateType: 'version-update:semver-minor',
directory: '',
packageEcosystem: '',
targetBranch: ''
updateType: 'version-update:semver-minor'
},
{
...baseDependency,
dependencyName: 'coffeescript',
dependencyType: 'indirect',
updateType: '',
directory: '',
packageEcosystem: '',
targetBranch: ''
dependencyType: 'indirect'
},
{
...baseDependency,
dependencyName: 'rspec-coffeescript',
dependencyType: 'indirect',
updateType: 'version-update:semver-patch',
directory: '',
packageEcosystem: '',
targetBranch: ''
updateType: 'version-update:semver-patch'
}
]

View File

@@ -24,6 +24,11 @@ export function set (updatedDependencies: Array<updatedDependency>): void {
const directory = firstDependency?.directory
const ecosystem = firstDependency?.packageEcosystem
const target = firstDependency?.targetBranch
const prevVersion = firstDependency?.prevVersion
const newVersion = firstDependency?.newVersion
const alertState = firstDependency?.alertState
const ghsaId = firstDependency?.ghsaId
const cvss = firstDependency?.cvss
core.startGroup(`Outputting metadata for ${Pluralize('updated dependency', updatedDependencies.length, true)}`)
core.info(`outputs.dependency-names: ${dependencyNames}`)
@@ -32,6 +37,11 @@ export function set (updatedDependencies: Array<updatedDependency>): void {
core.info(`outputs.directory: ${directory}`)
core.info(`outputs.package-ecosystem: ${ecosystem}`)
core.info(`outputs.target-branch: ${target}`)
core.info(`outputs.previous-version: ${prevVersion}`)
core.info(`outputs.new-version: ${newVersion}`)
core.info(`outputs.alert-state: ${alertState}`)
core.info(`outputs.ghsa-id: ${ghsaId}`)
core.info(`outputs.cvss: ${cvss}`)
core.endGroup()
core.setOutput('updated-dependencies-json', updatedDependencies)
@@ -41,6 +51,11 @@ export function set (updatedDependencies: Array<updatedDependency>): void {
core.setOutput('directory', directory)
core.setOutput('package-ecosystem', ecosystem)
core.setOutput('target-branch', target)
core.setOutput('previous-version', prevVersion)
core.setOutput('new-version', newVersion)
core.setOutput('alert-state', alertState)
core.setOutput('ghsa-id', ghsaId)
core.setOutput('cvss', cvss)
}
function maxDependencyTypes (updatedDependencies: Array<updatedDependency>): string {

View File

@@ -1,22 +1,26 @@
import * as updateMetadata from './update_metadata'
test('it returns an empty array for a blank string', async () => {
expect(updateMetadata.parse('', 'dependabot/nuget/feature1', 'main')).toEqual([])
const getAlert = async () => Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
expect(updateMetadata.parse('', 'dependabot/nuget/feature1', 'main', getAlert)).resolves.toEqual([])
})
test('it returns an empty array for commit message with no dependabot yaml fragment', async () => {
const commitMessage = `Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.
const commitMessage = `Bump coffee-rails from 4.0.1 to 4.2.2
Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.
- [Release notes](https://github.com/rails/coffee-rails/releases)
- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)
- [Commits](rails/coffee-rails@v4.0.1...v4.2.2)
Signed-off-by: dependabot[bot] <support@github.com>`
expect(updateMetadata.parse(commitMessage, 'dependabot/nuget/feature1', 'main')).toEqual([])
const getAlert = async () => Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
expect(updateMetadata.parse(commitMessage, 'dependabot/nuget/feature1', 'main', getAlert)).resolves.toEqual([])
})
test('it returns the updated dependency information when there is a yaml fragment', async () => {
const commitMessage =
'Bump coffee-rails from 4.0.1 to 4.2.2\n' +
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
@@ -31,7 +35,8 @@ test('it returns the updated dependency information when there is a yaml fragmen
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const updatedDependencies = updateMetadata.parse(commitMessage, 'dependabot/nuget/feature1', 'main')
const getAlert = async () => Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
const updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot/nuget/feature1', 'main', getAlert)
expect(updatedDependencies).toHaveLength(1)
@@ -41,10 +46,16 @@ test('it returns the updated dependency information when there is a yaml fragmen
expect(updatedDependencies[0].directory).toEqual('/')
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
expect(updatedDependencies[0].targetBranch).toEqual('main')
expect(updatedDependencies[0].prevVersion).toEqual('4.0.1')
expect(updatedDependencies[0].newVersion).toEqual('4.2.2')
expect(updatedDependencies[0].alertState).toEqual('DISMISSED')
expect(updatedDependencies[0].ghsaId).toEqual('GHSA-III-BBB')
expect(updatedDependencies[0].cvss).toEqual(4.6)
})
test('it supports multiple dependencies within a single fragment', async () => {
const commitMessage =
'Bump coffee-rails from 4.0.1 to 4.2.2 in /api/main\n' +
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
@@ -62,28 +73,45 @@ test('it supports multiple dependencies within a single fragment', async () => {
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const updatedDependencies = updateMetadata.parse(commitMessage, 'dependabot/nuget/api/main/feature1', 'main')
const getAlert = async (name: string) => {
if (name === 'coffee-rails') {
return Promise.resolve({ alertState: 'DISMISSED', ghsaId: 'GHSA-III-BBB', cvss: 4.6 })
}
return Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
}
const updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot/nuget/api/main/feature1', 'main', getAlert)
expect(updatedDependencies).toHaveLength(2)
expect(updatedDependencies[0].dependencyName).toEqual('coffee-rails')
expect(updatedDependencies[0].dependencyType).toEqual('direct:production')
expect(updatedDependencies[0].updateType).toEqual('version-update:semver-minor')
expect(updatedDependencies[0].directory).toEqual('api/main')
expect(updatedDependencies[0].directory).toEqual('/api/main')
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
expect(updatedDependencies[0].targetBranch).toEqual('main')
expect(updatedDependencies[0].prevVersion).toEqual('4.0.1')
expect(updatedDependencies[0].newVersion).toEqual('4.2.2')
expect(updatedDependencies[0].alertState).toEqual('DISMISSED')
expect(updatedDependencies[0].ghsaId).toEqual('GHSA-III-BBB')
expect(updatedDependencies[0].cvss).toEqual(4.6)
expect(updatedDependencies[1].dependencyName).toEqual('coffeescript')
expect(updatedDependencies[1].dependencyType).toEqual('indirect')
expect(updatedDependencies[1].updateType).toEqual('version-update:semver-patch')
expect(updatedDependencies[1].directory).toEqual('api/main')
expect(updatedDependencies[1].directory).toEqual('/api/main')
expect(updatedDependencies[1].packageEcosystem).toEqual('nuget')
expect(updatedDependencies[1].targetBranch).toEqual('main')
expect(updatedDependencies[1].prevVersion).toEqual('')
expect(updatedDependencies[1].newVersion).toEqual('')
expect(updatedDependencies[1].alertState).toEqual('')
expect(updatedDependencies[1].ghsaId).toEqual('')
expect(updatedDependencies[1].cvss).toEqual(0)
})
test('it only returns information within the first fragment if there are multiple yaml documents', async () => {
const commitMessage =
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
'- [Commits](rails/coffee-rails@v4.0.1...v4.2.2)\n' +
@@ -104,14 +132,20 @@ test('it only returns information within the first fragment if there are multipl
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const updatedDependencies = updateMetadata.parse(commitMessage, 'dependabot|nuget|api|feature1', 'main')
const getAlert = async () => Promise.resolve({ alertState: '', ghsaId: '', cvss: 0 })
const updatedDependencies = await updateMetadata.parse(commitMessage, 'dependabot|nuget|feature1', 'main', getAlert)
expect(updatedDependencies).toHaveLength(1)
expect(updatedDependencies[0].dependencyName).toEqual('coffee-rails')
expect(updatedDependencies[0].dependencyType).toEqual('direct:production')
expect(updatedDependencies[0].updateType).toEqual('version-update:semver-minor')
expect(updatedDependencies[0].directory).toEqual('api')
expect(updatedDependencies[0].directory).toEqual('/')
expect(updatedDependencies[0].packageEcosystem).toEqual('nuget')
expect(updatedDependencies[0].targetBranch).toEqual('main')
expect(updatedDependencies[0].prevVersion).toEqual('')
expect(updatedDependencies[0].newVersion).toEqual('')
expect(updatedDependencies[0].alertState).toEqual('')
expect(updatedDependencies[0].ghsaId).toEqual('')
expect(updatedDependencies[0].cvss).toEqual(0)
})

View File

@@ -23,7 +23,7 @@ export interface alertLookup {
export async function parse (commitMessage: string, branchName: string, mainBranch: string, lookup: alertLookup): Promise<Array<updatedDependency>> {
const firstLine = commitMessage.split('\n')[0]
const directory = firstLine.match(/ in (?<directory>\/[^ ]*)$/)
const directory = firstLine.match(/ in (?<directory>[^ ]+)$/)
const bumpFragment = commitMessage.match(/^Bumps .* from (?<from>\d[^ ]*) to (?<to>\d[^ ]*)\.$/m)
const yamlFragment = commitMessage.match(/^-{3}\n(?<dependencies>[\S|\s]*?)\n^\.{3}\n/m)
@@ -33,21 +33,21 @@ export async function parse (commitMessage: string, branchName: string, mainBran
// Since we are on the `dependabot` branch (9 letters), the 10th letter in the branch name is the delimiter
const delim = branchName[10]
const chunks = branchName.split(delim)
const dirname = directory?.groups?.directory ?? "/"
const prev = bumpFragment?.groups?.from ?? ""
const next = bumpFragment?.groups?.to ?? ""
const dirname = directory?.groups?.directory ?? '/'
const prev = bumpFragment?.groups?.from ?? ''
const next = bumpFragment?.groups?.to ?? ''
if (data['updated-dependencies']) {
return await Promise.all(data['updated-dependencies'].map(async (dependency) => ({
return await Promise.all(data['updated-dependencies'].map(async (dependency, index) => ({
dependencyName: dependency['dependency-name'],
dependencyType: dependency['dependency-type'],
updateType: dependency['update-type'],
directory: dirname,
packageEcosystem: chunks[1],
targetBranch: mainBranch,
prevVersion: prev,
newVersion: next,
...await lookup(dependency['dependency-name'], prev, dirname)
prevVersion: index === 0 ? prev : "",
newVersion: index === 0 ? next : "",
...await lookup(dependency['dependency-name'], index === 0 ? prev : "", dirname)
})))
}
}

View File

@@ -86,17 +86,17 @@ export async function getAlert (name: string, version: string, directory: string
}`)
const nodes = alerts?.repository?.vulnerabilityAlerts?.nodes
const found = nodes.find(a => a.vulnerableRequirements == `= ${version}`
&& trimSlashes(a.vulnerableManifestPath) == `${trimSlashes(directory)}/${a.vulnerableManifestFilename}`
&& a.securityVulnerability.package.name == name)
const found = nodes.find(a => (version === '' || a.vulnerableRequirements === `= ${version}`) &&
trimSlashes(a.vulnerableManifestPath) === `${trimSlashes(directory)}/${a.vulnerableManifestFilename}` &&
a.securityVulnerability.package.name === name)
return {
alertState: found?.state ?? "",
ghsaId: found?.securityAdvisory.ghsaId ?? "",
return {
alertState: found?.state ?? '',
ghsaId: found?.securityAdvisory.ghsaId ?? '',
cvss: found?.securityAdvisory.cvss.score ?? 0.0
}
}
export function trimSlashes(value: string): string {
export function trimSlashes (value: string): string {
return value.replace(/^\//, '').replace(/\/$/, '')
}

View File

@@ -23,6 +23,7 @@ test('it early exits with an error if github-token is not set', async () => {
)
/* eslint-disable no-unused-expressions */
expect(dependabotCommits.getMessage).not.toHaveBeenCalled
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
/* eslint-enable no-unused-expressions */
})
@@ -38,6 +39,7 @@ test('it does nothing if the PR is not verified as from Dependabot', async () =>
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('PR is not from Dependabot, nothing to do.')
)
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
})
test('it does nothing if there is no metadata in the commit', async () => {
@@ -52,6 +54,7 @@ test('it does nothing if there is no metadata in the commit', async () => {
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('PR does not contain metadata, nothing to do.')
)
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
})
test('it sets the updated dependency as an output for subsequent actions', async () => {
@@ -69,12 +72,16 @@ test('it sets the updated dependency as an output for subsequent actions', async
'...\n' +
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const mockAlert = { alertState: "FIXED", ghsaId: "GSHA", cvss: 3.4 }
jest.spyOn(core, 'getInput').mockReturnValue('mock-token')
jest.spyOn(util, 'getBranchNames').mockReturnValue({ headName: 'dependabot|nuget|feature1', baseName: 'main' })
jest.spyOn(dependabotCommits, 'getMessage').mockImplementation(jest.fn(
() => Promise.resolve(mockCommitMessage)
))
jest.spyOn(dependabotCommits, 'getAlert').mockImplementation(jest.fn(
() => Promise.resolve(mockAlert)
))
jest.spyOn(core, 'setOutput').mockImplementation(jest.fn())
await run()
@@ -92,7 +99,12 @@ test('it sets the updated dependency as an output for subsequent actions', async
updateType: 'version-update:semver-minor',
directory: '/',
packageEcosystem: 'nuget',
targetBranch: 'main'
targetBranch: 'main',
prevVersion: '4.0.1',
newVersion: '4.2.2',
alertState: 'FIXED',
ghsaId: 'GSHA',
cvss: 3.4
}
]
)
@@ -107,6 +119,7 @@ test('it sets the updated dependency as an output for subsequent actions', async
test('if there are multiple dependencies, it summarizes them', async () => {
const mockCommitMessage =
'Bump coffee-rails from 4.0.1 to 4.2.2 in api/main\n' +
'Bumps [coffee-rails](https://github.com/rails/coffee-rails) from 4.0.1 to 4.2.2.\n' +
'- [Release notes](https://github.com/rails/coffee-rails/releases)\n' +
'- [Changelog](https://github.com/rails/coffee-rails/blob/master/CHANGELOG.md)\n' +
@@ -123,12 +136,16 @@ test('if there are multiple dependencies, it summarizes them', async () => {
'...\n' +
'\n' +
'Signed-off-by: dependabot[bot] <support@github.com>'
const mockAlert = { alertState: "", ghsaId: "", cvss: 0 }
jest.spyOn(core, 'getInput').mockReturnValue('mock-token')
jest.spyOn(util, 'getBranchNames').mockReturnValue({ headName: 'dependabot/npm_and_yarn/api/main/feature1', baseName: 'trunk' })
jest.spyOn(dependabotCommits, 'getMessage').mockImplementation(jest.fn(
() => Promise.resolve(mockCommitMessage)
))
jest.spyOn(dependabotCommits, 'getAlert').mockImplementation(jest.fn(
() => Promise.resolve(mockAlert)
))
jest.spyOn(core, 'setOutput').mockImplementation(jest.fn())
await run()
@@ -146,7 +163,12 @@ test('if there are multiple dependencies, it summarizes them', async () => {
updateType: 'version-update:semver-minor',
directory: 'api/main',
packageEcosystem: 'npm_and_yarn',
targetBranch: 'trunk'
targetBranch: 'trunk',
prevVersion: '4.0.1',
newVersion: '4.2.2',
alertState: '',
ghsaId: '',
cvss: 0
},
{
dependencyName: 'coffeescript',
@@ -154,7 +176,12 @@ test('if there are multiple dependencies, it summarizes them', async () => {
updateType: 'version-update:semver-major',
directory: 'api/main',
packageEcosystem: 'npm_and_yarn',
targetBranch: 'trunk'
targetBranch: 'trunk',
prevVersion: '',
newVersion: '',
alertState: '',
ghsaId: '',
cvss: 0
}
]
)
@@ -176,6 +203,7 @@ test('it sets the action to failed if there is an unexpected exception', async (
await run()
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('Something bad happened!')
)
@@ -199,6 +227,7 @@ test('it sets the action to failed if there is a request error', async () => {
await run()
expect(dependabotCommits.getAlert).not.toHaveBeenCalled
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('(500) Something bad happened!')
)