diff --git a/README.md b/README.md index 21e02c0..d27ea44 100644 --- a/README.md +++ b/README.md @@ -71,15 +71,19 @@ node_modules/.bin/license-checker scan --failOn #### 🚩 Options -| Option | Description | Requiered | Type | Default | -|---|---|---|---|---| -| --start | Path of the initial json to look for | false | string | `process.cwd()` | -| --failOn | Fail (exit with code 1) if any package license does not satisfies any license in the provided list | true | string[] | | -| --outputFileName | Name of the report file generated | false | string | `license-report-.md` | -| --errorReportFileName | Name of the error report file generated when a license in the `failOn` option is found | false | string | `license-error-.md` | -| --disableErrorReport | Flag to disable the error report file generation | false | boolean | `false` | -| --disableReport | Flag to disable the report file generation, whether there is an error or not | false | boolean | `false` | -| --customHeader | Name of a text file containing the custom header to add at the start of the generated report | false | string | This application makes use of the following open source packages: | +| Option | Description | Requiered | Type | Default | +|-----------------------|-----------------------------------------------------------------------------------------------------------------------|---|---|---| +| --start | Path of the initial json to look for | false | string | `process.cwd()` | +| --failOn | Fail (exit with code 1) if at least one package license **satisfies** one of the licenses in the provided list | true | string[] | | +| --allowOnly | Fail (exit with code 1) if at least one package license **does not satisfy** one of the licenses in the provided list | true | string[] | | +| --outputFileName | Name of the report file generated | false | string | `license-report-.md` | +| --errorReportFileName | Name of the error report file generated when a license in the `failOn` option is found | false | string | `license-error-.md` | +| --disableErrorReport | Flag to disable the error report file generation | false | boolean | `false` | +| --disableReport | Flag to disable the report file generation, whether there is an error or not | false | boolean | `false` | +| --customHeader | Name of a text file containing the custom header to add at the start of the generated report | false | string | This application makes use of the following open source packages: | + + +> ❗The options `--failOn` and `--allowOnly` are mutually exclusive. You must use one of them. ## πŸ§‘β€πŸ’» Examples @@ -95,7 +99,7 @@ If the value provided is not SPDX compliant, the process fails (exit error 1). ### scan command -All the values provided in the `failOn` list must be [SPDX](https://spdx.dev/specifications/) compliant. Otherwise, an error will be thrown (exit error 1). +All the values provided in the `failOn` or `allowOnly` list must be [SPDX](https://spdx.dev/specifications/) compliant. Otherwise, an error will be thrown (exit error 1). Check the [SPDX license list](https://spdx.org/licenses/). ```sh @@ -105,6 +109,22 @@ npx @onebeyond/license-checker scan --failOn MIT GPL-1.0+ The input list is transformed into a SPDX expression with the `OR` logical operator. In the example, that is `MIT OR GPL-1.0+`. If any of the packages' licenses satisfies that expression, the process fails (exit error 1). +SPDX compliance and `OR` input concatenation also apply for the `allowOnly` option: + +```sh +npx @onebeyond/license-checker scan --allowOnly MIT GPL-1.0+ +``` + +In this case, all the packages' licenses must be either `MIT` or `GPL-1.0+`. + +Arguments to `failOn` and `allowOnly` are not limited to one license. Expressions with logical operators are also accepted: + +```sh +npx @onebeyond/license-checker scan --allowOnly "MIT AND Apache-2.0" GPL-1.0+ +``` + +In this example, all the packages' licenses must be either `MIT AND Apache-2.0` **or** `GPL-1.0+`. + ## πŸ”— Useful links - [Licensing a repository](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/licensing-a-repository) @@ -112,7 +132,7 @@ If any of the packages' licenses satisfies that expression, the process fails (e ## ⚠️ Temporal issue -An issue in `spdx-satisfies` has been found and it's pending resolution. Until then, GFDL 1x licenses are not supported and an error will be thrown if either packages or failOn arguments contain it. +An issue in `spdx-satisfies` has been found, and it's pending resolution. Until then, GFDL 1x licenses are not supported and an error will be thrown if either packages or failOn arguments contain it. ## Contributors ✨ diff --git a/cli.js b/cli.js index af4e773..7bbd735 100644 --- a/cli.js +++ b/cli.js @@ -1,10 +1,12 @@ const yargs = require('yargs')(process.argv.slice(2)); +const { checkArgs } = require('./src/utils'); // https://github.com/yargs/yargs/blob/main/docs/advanced.md#commanddirdirectory-opts module.exports = yargs .parserConfiguration({ 'camel-case-expansion': false }) .commandDir('commands') .demandCommand() + .check(checkArgs) .help() .alias('help', 'h') .argv; diff --git a/commands/scan.js b/commands/scan.js index cdb2db4..048b835 100644 --- a/commands/scan.js +++ b/commands/scan.js @@ -19,9 +19,14 @@ exports.builder = { default: process.cwd() }, failOn: { - description: 'fail (exit with code 1) if any package license does not satisfies any license in the provided list', + description: 'fail (exit with code 1) if at least one package license satisfies one of the licenses in the provided list ', type: 'array', - demandOption: true + conflicts: 'allowOnly' + }, + allowOnly: { + description: 'fail (exit with code 1) if at least one package license does not satisfy one of the licenses in the provided list', + type: 'array', + conflicts: 'failOn' }, outputFileName: { description: 'name of the output file generated', diff --git a/src/runner.js b/src/runner.js index f41764e..c96d0f0 100644 --- a/src/runner.js +++ b/src/runner.js @@ -25,18 +25,22 @@ const check = (license) => { }; const scan = async (options) => { - const { failOn } = options; + const { failOn, allowOnly } = options; - if (!failOn) throw new Error('Error: You must provide a list of licenses to fail on in the "failOn" option.'); + const allowSelected = !!allowOnly; + const licensesList = allowSelected ? allowOnly : failOn; - checkLicenseError(failOn); // @TODO Remove after issue has been solved - checkSPDXCompliance(failOn); - const bannedLicenses = generateSPDXExpression(failOn); + checkLicenseError(licensesList); // @TODO Remove after issue has been solved + checkSPDXCompliance(licensesList); + const spdxLicensesExpression = generateSPDXExpression(licensesList); const packages = await checker.parsePackages(options.start); const packageList = getPackageInfoList(packages); - const { forbidden: forbiddenPackages, nonCompliant: invalidPackages } = checkPackagesLicenses(bannedLicenses, packageList); + const { + forbidden: forbiddenPackages, + nonCompliant: invalidPackages + } = checkPackagesLicenses(spdxLicensesExpression, packageList, allowSelected); if (invalidPackages.length) { logger.warn(`The following package licenses are not SPDX compliant and cannot be validated:\n${invalidPackages.map(pkg => ` > ${pkg.package} | ${pkg.licenses}`).join('\n')}`); } diff --git a/src/utils.js b/src/utils.js index 8ad5db6..dade538 100644 --- a/src/utils.js +++ b/src/utils.js @@ -41,7 +41,7 @@ const formatForbiddenLicenseError = licenses => { [licenses]: !stats[licenses] ? 1 : stats[licenses] + 1 }), {}); - const header = `Found ${licenses.length} packages with licenses defined by the --failOn flag:`; + const header = `Found ${licenses.length} packages with licenses defined by the provided option:`; const lines = Object .entries(forbiddenLicenseStats) .map(([license, value]) => ` > ${value} packages with license ${license}`) @@ -66,7 +66,7 @@ const checkSPDXCompliance = (licenses = []) => { const invalidLicenses = licenses.filter(arg => !isSPDXCompliant(arg)); if (invalidLicenses.length) { throw new Error( - `The following licenses are not SPDX compliant. Please, use the --checkLicense option to validate your input:\n${invalidLicenses.join(' | ')}` + `The following licenses are not SPDX compliant. Please, use the "check" command to validate your input:\n${invalidLicenses.join(' | ')}` ); } }; @@ -80,7 +80,7 @@ const checkLicenseError = (licenses = []) => { const errorLicenses = licenses.some(isLicenseError); if (errorLicenses) { throw new Error( - 'Your failOn list contains a GFDL-1.x licenses and they are temporary unallowed. There\'s an issue pending to solve.' + 'Your licenses list contains a GFDL-1.x licenses and they are temporary unallowed. There\'s an issue pending to solve.' ); } }; @@ -93,31 +93,55 @@ const checkLicenseError = (licenses = []) => { */ const isLicenseError = (license = '') => licensesExceptions.includes(license); +/** + * Filter out licenses from the SPDX complete list depending on the value of the shouldSatisfy flag. + * @param {string} expression - A valid SPDX expression + * @param {boolean} shouldSatisfy - If true, the result will yield all the licenses that satisfies the incoming expression. If false, + * the result will yield any other license that do not match the expression + * @return {string[]} - List of resulting licenses + */ +const getValidLicenses = (expression, shouldSatisfy) => spdxIds.filter(id => { + if (isLicenseError(id)) return false;// @TODO Refactor after issue has been solved + const isSatisfied = satisfiesSPDXLicense(id, expression); + return shouldSatisfy ? isSatisfied : !isSatisfied; +}); + /** * Subtracts the expression from the full list of SPDX ids and check the result (the allowed licenses) against the list of packages. * If the license of the package itself is not SPDX compliant, the package will be included on the "nonCompliant" list. * If a package license does not satisfy the allowed SPDX id list, the package will be included on the "forbidden" list. * @param {string} expression - A SPDX expression * @param {object[]} packages - A list of packages to be checked against the SPDX expression + * @param {boolean} allow - Determines the license check. If true, forbidden will contain all the packages that + * do not comply with the expression. If false, forbidden will contain all the packages that comply with the expression * @return {{forbidden: object[], nonCompliant: object[]}} - A couple of lists including the packages that satisfy the SPDX expression * and the packages with a non SPDX compliant license */ -const checkPackagesLicenses = (expression, packages) => { - const validSpdxIds = expression && spdxIds.filter(id => !isLicenseError(id) && !satisfiesSPDXLicense(id, expression)); // @TODO Refactor after issue has been solved - const allowedLicensesExp = expression && generateSPDXExpression(validSpdxIds); +const checkPackagesLicenses = (expression, packages, allow = false) => { + const validSpdxIds = getValidLicenses(expression, allow); + + const allowedLicensesExp = generateSPDXExpression(validSpdxIds); return packages.reduce((total, pkg) => { const { licenses } = pkg; if (!isSPDXCompliant(licenses)) return { ...total, nonCompliant: [...total.nonCompliant, pkg] }; - const isSatisfiedLicense = expression && !satisfiesSPDXLicense(licenses, allowedLicensesExp); - if (isSatisfiedLicense) return { ...total, forbidden: [...total.forbidden, pkg] }; + if (!satisfiesSPDXLicense(licenses, allowedLicensesExp)) return { ...total, forbidden: [...total.forbidden, pkg] }; return total; }, { forbidden: [], nonCompliant: [] }); }; +const checkArgs = (args) => { + if (args._[0] === 'scan') { + const { failOn, allowOnly } = args; + if (!failOn && !allowOnly) throw new Error('You need to provide the "failOn" or "allowOnly" option.'); + if ((failOn && !failOn.length) || (allowOnly && !allowOnly.length)) throw new Error('You need to provide at least one license.'); + } + return true; +}; + module.exports = { getPackageInfoList, formatForbiddenLicenseError, @@ -125,5 +149,6 @@ module.exports = { checkSPDXCompliance, checkPackagesLicenses, isLicenseError, - checkLicenseError + checkLicenseError, + checkArgs }; diff --git a/test/scan.test.js b/test/scan.test.js index ef0ed50..414fbde 100644 --- a/test/scan.test.js +++ b/test/scan.test.js @@ -23,7 +23,7 @@ describe('scan command', () => { } catch (e) { error = e; } finally { - expect(error.message).toBe('The following licenses are not SPDX compliant. Please, use the --checkLicense option to validate your input:\nGPL | BSD'); + expect(error.message).toBe('The following licenses are not SPDX compliant. Please, use the "check" command to validate your input:\nGPL | BSD'); } }); @@ -38,166 +38,332 @@ describe('scan command', () => { } catch (e) { error = e; } finally { - expect(error.message).toBe("Your failOn list contains a GFDL-1.x licenses and they are temporary unallowed. There's an issue pending to solve."); + expect(error.message).toBe("Your licenses list contains a GFDL-1.x licenses and they are temporary unallowed. There's an issue pending to solve."); } }); }); describe('command execution output', () => { - it('should call the checker passing the path to the working directory', async () => { - const options = { - start: '/path/to/cwd', - failOn: ['MIT'] - }; + describe('"failOn" option', () => { + it('should call the checker passing the path to the working directory', async () => { + const options = { + start: '/path/to/cwd', + failOn: ['MIT'] + }; + + const packages = { + 'package-1': { + licenses: 'GPL-1.0+', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); - const packages = { - 'package-1': { - licenses: 'GPL-1.0+', - repository: 'https://git.com/repo/repo', - path: '/path/to/package', - licenseFile: '/path/to/package/LICENSE' - } - }; - - parsePackages.mockResolvedValueOnce(packages); - - await scan(options); + await scan(options); - expect(parsePackages).toHaveBeenCalledWith(options.start); - }); + expect(parsePackages).toHaveBeenCalledWith(options.start); + }); - it('should print a message if any of the packages\' licenses are not SPDX compliant', async () => { - const options = { - start: '/path/to/cwd', - failOn: ['MIT'] - }; + it('should print a message if any of the packages\' licenses are not SPDX compliant', async () => { + const options = { + start: '/path/to/cwd', + failOn: ['MIT'] + }; - const packages = { - 'package-1': { - licenses: 'GPL', - repository: 'https://git.com/repo/repo', - path: '/path/to/package', - licenseFile: '/path/to/package/LICENSE' - } - }; + const packages = { + 'package-1': { + licenses: 'GPL', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; - parsePackages.mockResolvedValueOnce(packages); + parsePackages.mockResolvedValueOnce(packages); - const errorSpy = jest.spyOn(logger, 'warn'); + const errorSpy = jest.spyOn(logger, 'warn'); - await scan(options); + await scan(options); - expect(errorSpy).toHaveBeenCalledWith( - `The following package licenses are not SPDX compliant and cannot be validated:\n > ${Object.keys(packages)[0]} | ${packages['package-1'].licenses}` - ); + expect(errorSpy).toHaveBeenCalledWith( + `The following package licenses are not SPDX compliant and cannot be validated:\n > ${Object.keys(packages)[0]} | ${packages['package-1'].licenses}` + ); + }); + + it('should throw an error if any packages\' licenses satisfies the "failOn" argument', async () => { + const options = { + start: '/path/to/cwd', + failOn: ['MIT', 'GPL-1.0+'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); + + let error; + try { + await scan(options); + } catch (e) { + error = e; + } finally { + expect(error.message).toBe('Found 1 packages with licenses defined by the provided option:\n > 1 packages with license MIT'); + } + }); + + it('should not throw an error if one of the licences in a package joined by the operator OR contains one of the licences in the "failOn" argument', async () => { + const options = { + start: '/path/to/cwd', + failOn: ['MIT', 'GPL-1.0+'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT OR Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + let error; + + parsePackages.mockResolvedValueOnce(packages); + try { + await scan(options); + } catch (err) { + error = err; + } finally { + expect(error).toBeUndefined(); + } + }); + + it('should not throw an error if one of the licences in a package joined by the operator AND does not contain any licences in the "failOn" argument', async () => { + const options = { + start: '/path/to/cwd', + failOn: ['GPL-1.0+'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT AND Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); + + let error; + try { + await scan(options); + } catch (err) { + error = err; + } finally { + expect(error).toBeUndefined(); + } + }); + + it('should throw an error if any packages\' licenses joined by the AND operator satisfies the "failOn" argument', async () => { + const options = { + start: '/path/to/cwd', + failOn: ['GPL-1.0+', 'MIT'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT AND Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); + + let error; + try { + await scan(options); + } catch (err) { + error = err; + } finally { + expect(error.message).toBe('Found 1 packages with licenses defined by the provided option:\n > 1 packages with license MIT AND Apache-2.0'); + } + }); }); - it('should throw an error if any packages\' licenses satisfies the "failOn" argument', async () => { - const options = { - start: '/path/to/cwd', - failOn: ['MIT', 'GPL-1.0+'] - }; + describe('"allowOnly" option', () => { + it('should call the checker passing the path to the working directory', async () => { + const options = { + start: '/path/to/cwd', + allowOnly: ['MIT'] + }; - const packages = { - 'package-1': { - licenses: 'MIT', - repository: 'https://git.com/repo/repo', - path: '/path/to/package', - licenseFile: '/path/to/package/LICENSE' - } - }; + const packages = { + 'package-1': { + licenses: 'MIT', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; - parsePackages.mockResolvedValueOnce(packages); + parsePackages.mockResolvedValueOnce(packages); - let error; - try { await scan(options); - } catch (e) { - error = e; - } finally { - expect(error.message).toBe('Found 1 packages with licenses defined by the --failOn flag:\n > 1 packages with license MIT'); - } - }); - it('should not throw an error if one of the licences in a package joined by the operator OR contains one of the licences in the failOn arguments', async () => { - const options = { - start: '/path/to/cwd', - failOn: ['MIT', 'GPL-1.0+'] - }; + expect(parsePackages).toHaveBeenCalledWith(options.start); + }); - const packages = { - 'package-1': { - licenses: 'MIT OR Apache-2.0', - repository: 'https://git.com/repo/repo', - path: '/path/to/package', - licenseFile: '/path/to/package/LICENSE' - } - }; - - let error; - - parsePackages.mockResolvedValueOnce(packages); - try { - await scan(options); - } catch (err) { - error = err; - } finally { - expect(error).toBeUndefined(); - } - }); + it('should print a message if any of the packages\' licenses are not SPDX compliant', async () => { + const options = { + start: '/path/to/cwd', + allowOnly: ['MIT'] + }; - it('should not throw an error if one of the licences in a package joined by the operator AND does not contain any licences in the failOn arguments', async () => { - const options = { - start: '/path/to/cwd', - failOn: ['GPL-1.0+'] - }; + const packages = { + 'package-1': { + licenses: 'GPL', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; - const packages = { - 'package-1': { - licenses: 'MIT AND Apache-2.0', - repository: 'https://git.com/repo/repo', - path: '/path/to/package', - licenseFile: '/path/to/package/LICENSE' - } - }; + parsePackages.mockResolvedValueOnce(packages); - parsePackages.mockResolvedValueOnce(packages); + const errorSpy = jest.spyOn(logger, 'warn'); - let error; - try { await scan(options); - } catch (err) { - error = err; - } finally { - expect(error).toBeUndefined(); - } - }); - it('should throw an error if any packages\' licenses joined by the AND operator satisfies the "failOn" argument', async () => { - const options = { - start: '/path/to/cwd', - failOn: ['GPL-1.0+', 'MIT'] - }; - - const packages = { - 'package-1': { - licenses: 'MIT AND Apache-2.0', - repository: 'https://git.com/repo/repo', - path: '/path/to/package', - licenseFile: '/path/to/package/LICENSE' + expect(errorSpy).toHaveBeenCalledWith( + `The following package licenses are not SPDX compliant and cannot be validated:\n > ${Object.keys(packages)[0]} | ${packages['package-1'].licenses}` + ); + }); + + it('should throw an error if none packages\' licenses satisfy the "allowOnly" argument', async () => { + const options = { + start: '/path/to/cwd', + allowOnly: ['MIT', 'GPL-1.0+'] + }; + + const packages = { + 'package-1': { + licenses: 'OpenSSL', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); + + let error; + try { + await scan(options); + } catch (e) { + error = e; + } finally { + expect(error.message).toBe('Found 1 packages with licenses defined by the provided option:\n > 1 packages with license OpenSSL'); } - }; - - parsePackages.mockResolvedValueOnce(packages); - - let error; - try { - await scan(options); - } catch (err) { - error = err; - } finally { - expect(error.message).toBe('Found 1 packages with licenses defined by the --failOn flag:\n > 1 packages with license MIT AND Apache-2.0'); - } + }); + + it('should not throw an error if one of the licences in a package joined by the operator OR contains one of the licences in the "allowOnly" arguments', async () => { + const options = { + start: '/path/to/cwd', + allowOnly: ['MIT', 'Apache-2.0'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT OR Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + }, + 'package-2': { + licenses: 'GPL-1.0+ OR Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + let error; + + parsePackages.mockResolvedValueOnce(packages); + try { + await scan(options); + } catch (err) { + error = err; + } finally { + expect(error).toBeUndefined(); + } + }); + + it('should not throw an error if one of the licences in a package joined by the operator AND contains all licences in the "allowOnly" arguments', async () => { + const options = { + start: '/path/to/cwd', + allowOnly: ['MIT AND Apache-2.0'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT AND Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); + + let error; + try { + await scan(options); + } catch (err) { + error = err; + } finally { + expect(error).toBeUndefined(); + } + }); + + it('should throw an error if any packages\' licenses joined by the AND operator does not satisfy the "allowOnly" argument', async () => { + const options = { + start: '/path/to/cwd', + allowOnly: ['MIT'] + }; + + const packages = { + 'package-1': { + licenses: 'MIT AND Apache-2.0', + repository: 'https://git.com/repo/repo', + path: '/path/to/package', + licenseFile: '/path/to/package/LICENSE' + } + }; + + parsePackages.mockResolvedValueOnce(packages); + + let error; + try { + await scan(options); + } catch (err) { + error = err; + } finally { + expect(error.message).toBe('Found 1 packages with licenses defined by the provided option:\n > 1 packages with license MIT AND Apache-2.0'); + } + }); }); }); @@ -278,7 +444,7 @@ describe('scan command', () => { } catch (err) { error = err; } finally { - expect(error.message).toBe('Found 1 packages with licenses defined by the --failOn flag:\n > 1 packages with license GPL-1.0'); + expect(error.message).toBe('Found 1 packages with licenses defined by the provided option:\n > 1 packages with license GPL-1.0'); expect(writeErrorReportFile).not.toHaveBeenCalled(); } }); diff --git a/test/utils.test.js b/test/utils.test.js new file mode 100644 index 0000000..7e35c60 --- /dev/null +++ b/test/utils.test.js @@ -0,0 +1,59 @@ +const { checkArgs } = require('../src/utils'); + +describe('Utilities', () => { + describe('checkArgs', () => { + describe('when the command is "scan"', () => { + it('should return true when the "_" property is not "scan"', () => { + const args = { _: ['foo'] }; + + let error; + try { + checkArgs(args); + } catch (e) { + error = e; + } finally { + expect(error).toBeUndefined(); + } + }); + + it('should throw an error if both "failOn" and "allowOnly" are missing when the "_" property is "scan"', () => { + const args = { _: ['scan'], foo: 'bar' }; + + let error; + try { + checkArgs(args); + } catch (e) { + error = e; + } finally { + expect(error.message).toBe('You need to provide the "failOn" or "allowOnly" option.'); + } + }); + + it('should throw an error if the "failOn" is an empty array when the "_" property is "scan"', () => { + const args = { _: ['scan'], failOn: [] }; + + let error; + try { + checkArgs(args); + } catch (e) { + error = e; + } finally { + expect(error.message).toBe('You need to provide at least one license.'); + } + }); + + it('should throw an error if the "allowOnly" is an empty array when the "_" property is "scan"', () => { + const args = { _: ['scan'], allowOnly: [] }; + + let error; + try { + checkArgs(args); + } catch (e) { + error = e; + } finally { + expect(error.message).toBe('You need to provide at least one license.'); + } + }); + }); + }); +});