diff --git a/src/license/index.js b/src/license/index.js index 112ae842..1b934fbe 100644 --- a/src/license/index.js +++ b/src/license/index.js @@ -95,11 +95,14 @@ export async function runLicenseCheck(sbomContent, manifestPath, url, opts = {}, const status = getCompatibility(projectCategory, entry.category); if (status === 'incompatible') { + const reason = entry.category?.toUpperCase() === 'UNKNOWN' + ? 'License not recognized as a standard SPDX identifier. Manual review recommended to verify compatibility.' + : 'Dependency license(s) are incompatible with the project license.'; incompatibleDependencies.push({ purl, licenses: entry.licenses, category: entry.category, - reason: 'Dependency license(s) are incompatible with the project license.' + reason }); } } diff --git a/src/license/license_utils.js b/src/license/license_utils.js index 4e845a00..86bedf3a 100644 --- a/src/license/license_utils.js +++ b/src/license/license_utils.js @@ -102,10 +102,14 @@ export function getCompatibility(projectCategory, dependencyCategory) { const proj = projectCategory.toUpperCase(); const dep = dependencyCategory.toUpperCase(); - if (proj === 'UNKNOWN' || dep === 'UNKNOWN') { + if (proj === 'UNKNOWN') { return 'unknown'; } + if (dep === 'UNKNOWN') { + return 'incompatible'; + } + const restrictiveness = { 'PERMISSIVE': 1, 'WEAK_COPYLEFT': 2, diff --git a/test/providers/license.test.js b/test/providers/license.test.js index 9a075c69..c8390403 100644 --- a/test/providers/license.test.js +++ b/test/providers/license.test.js @@ -11,6 +11,7 @@ import Javascript_pnpm from '../../src/providers/javascript_pnpm.js' import Javascript_yarn from '../../src/providers/javascript_yarn.js' import pythonPipProvider from '../../src/providers/python_pip.js' import rustCargoProvider from '../../src/providers/rust_cargo.js' +import { getCompatibility } from '../../src/license/license_utils.js' import { normalizeLicensesResponse } from '../../src/license/licenses_api.js' suite('normalizeLicensesResponse', () => { @@ -62,6 +63,39 @@ suite('normalizeLicensesResponse', () => { }) }) +suite('getCompatibility with UNKNOWN category', () => { + /** @type {Array<{proj: string, dep: string, expected: string}>} */ + const cases = [ + { proj: 'PERMISSIVE', dep: 'UNKNOWN', expected: 'incompatible' }, + { proj: 'WEAK_COPYLEFT', dep: 'UNKNOWN', expected: 'incompatible' }, + { proj: 'STRONG_COPYLEFT', dep: 'UNKNOWN', expected: 'incompatible' }, + { proj: 'UNKNOWN', dep: 'PERMISSIVE', expected: 'unknown' }, + { proj: 'UNKNOWN', dep: 'UNKNOWN', expected: 'unknown' }, + ]; + + cases.forEach(({ proj, dep, expected }) => { + /// Verifies getCompatibility returns the expected result for the given project/dependency category pair. + test(`getCompatibility('${proj}', '${dep}') returns '${expected}'`, () => { + expect(getCompatibility(proj, dep)).to.equal(expected); + }); + }); + + /// Verifies that a null project category with UNKNOWN dependency returns 'unknown'. + test("getCompatibility(null, 'UNKNOWN') returns 'unknown'", () => { + expect(getCompatibility(null, 'UNKNOWN')).to.equal('unknown'); + }); + + /// Verifies that existing known-category incompatibility checks still work correctly. + test("existing incompatibility check: STRONG_COPYLEFT dep vs PERMISSIVE proj returns 'incompatible'", () => { + expect(getCompatibility('PERMISSIVE', 'STRONG_COPYLEFT')).to.equal('incompatible'); + }); + + /// Verifies that compatible known-category checks are unaffected. + test("existing compatibility check: PERMISSIVE dep vs STRONG_COPYLEFT proj returns 'compatible'", () => { + expect(getCompatibility('STRONG_COPYLEFT', 'PERMISSIVE')).to.equal('compatible'); + }); +}); + suite('testing readLicenseFromManifest with existing test manifests', () => { suite('Java Maven provider', () => {