Skip to content

Commit

Permalink
Merge pull request #719 from actions/change-spdx-parser
Browse files Browse the repository at this point in the history
Update SPDX Expression Parsing
  • Loading branch information
juxtin authored Jul 10, 2024
2 parents d6f34c3 + b4ae47c commit 28743f8
Show file tree
Hide file tree
Showing 17 changed files with 871 additions and 298 deletions.
24 changes: 8 additions & 16 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import {expect, test, beforeEach} from '@jest/globals'
import {readConfig} from '../src/config'
import {getRefs} from '../src/git-refs'
import * as Utils from '../src/utils'
import * as spdx from '../src/spdx'
import {setInput, clearInputs} from './test-helpers'

beforeAll(() => {
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(true)
})

beforeEach(() => {
clearInputs()
})
Expand All @@ -19,11 +15,11 @@ test('it defaults to low severity', async () => {

test('it reads custom configs', async () => {
setInput('fail-on-severity', 'critical')
setInput('allow-licenses', ' BSD, GPL 2')
setInput('allow-licenses', 'ISC, GPL-2.0')

const config = await readConfig()
expect(config.fail_on_severity).toEqual('critical')
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
expect(config.allow_licenses).toEqual(['ISC', 'GPL-2.0'])
})

test('it defaults to false for warn-only', async () => {
Expand All @@ -40,7 +36,7 @@ test('it defaults to empty allow/deny lists ', async () => {

test('it raises an error if both an allow and denylist are specified', async () => {
setInput('allow-licenses', 'MIT')
setInput('deny-licenses', 'BSD')
setInput('deny-licenses', 'BSD-3-Clause')

await expect(readConfig()).rejects.toThrow(
'You cannot specify both allow-licenses and deny-licenses'
Expand Down Expand Up @@ -204,21 +200,17 @@ test('it is not possible to disable both checks', async () => {
})

describe('licenses that are not valid SPDX licenses', () => {
beforeAll(() => {
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(false)
})

test('it raises an error for invalid licenses in allow-licenses', async () => {
setInput('allow-licenses', ' BSD, GPL 2')
setInput('allow-licenses', ' BSD-YOLO, GPL-2.0')
await expect(readConfig()).rejects.toThrow(
'Invalid license(s) in allow-licenses: BSD,GPL 2'
'Invalid license(s) in allow-licenses: BSD-YOLO'
)
})

test('it raises an error for invalid licenses in deny-licenses', async () => {
setInput('deny-licenses', ' BSD, GPL 2')
setInput('deny-licenses', ' GPL-2.0, BSD-YOLO, Apache-2.0, ToIll')
await expect(readConfig()).rejects.toThrow(
'Invalid license(s) in deny-licenses: BSD,GPL 2'
'Invalid license(s) in deny-licenses: BSD-YOLO, ToIll'
)
})
})
Expand Down
5 changes: 0 additions & 5 deletions __tests__/deny.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ jest.mock('octokit', () => {

beforeEach(async () => {
jest.resetModules()
jest.doMock('spdx-satisfies', () => {
// mock spdx-satisfies return value
// true for BSD, false for all others
return jest.fn((license: string, _: string): boolean => license === 'BSD')
})

npmChange = createTestChange({ecosystem: 'npm'})
rubyChange = createTestChange({ecosystem: 'rubygems'})
Expand Down
8 changes: 2 additions & 6 deletions __tests__/external-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expect, test, beforeEach} from '@jest/globals'
import {readConfig} from '../src/config'
import * as Utils from '../src/utils'
import * as spdx from '../src/spdx'
import {setInput, clearInputs} from './test-helpers'

const externalConfig = `fail_on_severity: 'high'
Expand All @@ -25,10 +25,6 @@ jest.mock('octokit', () => {
}
})

beforeAll(() => {
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(true)
})

beforeEach(() => {
clearInputs()
})
Expand All @@ -38,7 +34,7 @@ test('it reads an external config file', async () => {

const config = await readConfig()
expect(config.fail_on_severity).toEqual('critical')
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
expect(config.allow_licenses).toEqual(['BSD-3-Clause', 'GPL-2.0'])
})

test('raises an error when the config file was not found', async () => {
Expand Down
4 changes: 2 additions & 2 deletions __tests__/fixtures/config-allow-sample.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fail_on_severity: critical
allow_licenses:
- 'BSD'
- 'GPL 2'
- 'BSD-3-Clause'
- 'GPL-2.0'
59 changes: 29 additions & 30 deletions __tests__/licenses.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {expect, jest, test} from '@jest/globals'
import {Change, Changes} from '../src/schemas'

let getInvalidLicenseChanges: Function
import {getInvalidLicenseChanges} from '../src/licenses'

const npmChange: Change = {
manifest: 'package.json',
Expand Down Expand Up @@ -30,7 +29,7 @@ const rubyChange: Change = {
name: 'actionsomething',
version: '3.2.0',
package_url: 'pkg:gem/[email protected]',
license: 'BSD',
license: 'BSD-3-Clause',
source_repository_url: 'github.com/some-repo',
scope: 'runtime',
vulnerabilities: [
Expand Down Expand Up @@ -100,29 +99,32 @@ jest.mock('octokit', () => {

beforeEach(async () => {
jest.resetModules()
jest.doMock('spdx-satisfies', () => {
// mock spdx-satisfies return value
// true for BSD, false for all others
return jest.fn((license: string, _: string): boolean => license === 'BSD')
})
// eslint-disable-next-line @typescript-eslint/no-require-imports
;({getInvalidLicenseChanges} = require('../src/licenses'))
})

test('it adds license outside the allow list to forbidden changes', async () => {
const changes: Changes = [npmChange, rubyChange]
const changes: Changes = [
npmChange, // MIT license
rubyChange // BSD license
]

const {forbidden} = await getInvalidLicenseChanges(changes, {
allow: ['BSD']
allow: ['BSD-3-Clause']
})

expect(forbidden[0]).toBe(npmChange)
expect(forbidden.length).toEqual(1)
})

test('it adds license inside the deny list to forbidden changes', async () => {
const changes: Changes = [npmChange, rubyChange]
const changes: Changes = [
npmChange, // MIT license
rubyChange // BSD license
]

const {forbidden} = await getInvalidLicenseChanges(changes, {
deny: ['BSD']
deny: ['BSD-3-Clause']
})

expect(forbidden[0]).toBe(rubyChange)
expect(forbidden.length).toEqual(1)
})
Expand All @@ -133,7 +135,7 @@ test('it does not add license outside the allow list to forbidden changes if it
{...rubyChange, change_type: 'removed'}
]
const {forbidden} = await getInvalidLicenseChanges(changes, {
allow: ['BSD']
allow: ['BSD-3-Clause']
})
expect(forbidden).toStrictEqual([])
})
Expand All @@ -144,7 +146,7 @@ test('it does not add license inside the deny list to forbidden changes if it is
{...rubyChange, change_type: 'removed'}
]
const {forbidden} = await getInvalidLicenseChanges(changes, {
deny: ['BSD']
deny: ['BSD-3-Clause']
})
expect(forbidden).toStrictEqual([])
})
Expand All @@ -156,23 +158,18 @@ test('it adds license outside the allow list to forbidden changes if it is in bo
{...rubyChange, change_type: 'removed'}
]
const {forbidden} = await getInvalidLicenseChanges(changes, {
allow: ['BSD']
allow: ['BSD-3-Clause']
})
expect(forbidden).toStrictEqual([npmChange])
})

test('it adds all licenses to unresolved if it is unable to determine the validity', async () => {
jest.resetModules() // reset module set in before
jest.doMock('spdx-satisfies', () => {
return jest.fn((_first: string, _second: string) => {
throw new Error('Some Error')
})
})
// eslint-disable-next-line @typescript-eslint/no-require-imports
;({getInvalidLicenseChanges} = require('../src/licenses'))
const changes: Changes = [npmChange, rubyChange]
const changes: Changes = [
{...npmChange, license: 'Foo'},
{...rubyChange, license: 'Bar'}
]
const invalidLicenses = await getInvalidLicenseChanges(changes, {
allow: ['BSD']
allow: ['Apache-2.0']
})
expect(invalidLicenses.forbidden.length).toEqual(0)
expect(invalidLicenses.unlicensed.length).toEqual(0)
Expand All @@ -182,7 +179,7 @@ test('it adds all licenses to unresolved if it is unable to determine the validi
test('it does not filter out changes that are on the exclusions list', async () => {
const changes: Changes = [pipChange, npmChange, rubyChange]
const licensesConfig = {
allow: ['BSD'],
allow: ['BSD-3-Clause'],
licenseExclusions: ['pkg:pypi/[email protected]', 'pkg:npm/[email protected]']
}
const invalidLicenses = await getInvalidLicenseChanges(
Expand All @@ -198,7 +195,7 @@ test('it does not fail when the packages dont have a valid PURL', async () => {

const changes: Changes = [emptyPurlChange, npmChange, rubyChange]
const licensesConfig = {
allow: ['BSD'],
allow: ['BSD-3-Clause'],
licenseExclusions: ['pkg:pypi/[email protected]', 'pkg:npm/[email protected]']
}

Expand All @@ -212,16 +209,18 @@ test('it does not fail when the packages dont have a valid PURL', async () => {
test('it does filters out changes if they are not on the exclusions list', async () => {
const changes: Changes = [pipChange, npmChange, rubyChange]
const licensesConfig = {
allow: ['BSD'],
allow: ['BSD-3-Clause'],
licenseExclusions: [
'pkg:pypi/[email protected]',
'pkg:npm/[email protected]'
]
}

const invalidLicenses = await getInvalidLicenseChanges(
changes,
licensesConfig
)

expect(invalidLicenses.forbidden.length).toEqual(2)
expect(invalidLicenses.forbidden[0]).toBe(pipChange)
expect(invalidLicenses.forbidden[1]).toBe(npmChange)
Expand Down
Loading

0 comments on commit 28743f8

Please sign in to comment.