From f22e879c7a42942acd5c95bebc9d22858dcba31d Mon Sep 17 00:00:00 2001 From: Agustin Groh Date: Tue, 10 Dec 2024 09:05:40 -0300 Subject: [PATCH] chore:SP-1924 Adds copyleft policy unit tests --- .github/linters/.eslintrc.yml | 4 +- __tests__/copyleft-argument-builder.test.ts | 129 ++++++++++ __tests__/copyleft-policy-check.test.ts | 112 ++++++--- __tests__/data/empty-results.json | 50 ++++ __tests__/data/package.json | 28 +++ __tests__/data/results.json | 225 ++++++++++++++++++ __tests__/data/test-results.json | 1 + __tests__/scan-service.test.ts | 126 ++++++++++ .../argument_builders/argument-builder.ts | 3 + .../copyleft-argument-builder.ts | 47 ++++ src/policies/copyleft-policy-check.ts | 53 +---- 11 files changed, 697 insertions(+), 81 deletions(-) create mode 100644 __tests__/copyleft-argument-builder.test.ts create mode 100644 __tests__/data/empty-results.json create mode 100644 __tests__/data/package.json create mode 100644 __tests__/data/results.json create mode 100644 __tests__/data/test-results.json create mode 100644 __tests__/scan-service.test.ts create mode 100644 src/policies/argument_builders/argument-builder.ts create mode 100644 src/policies/argument_builders/copyleft-argument-builder.ts diff --git a/.github/linters/.eslintrc.yml b/.github/linters/.eslintrc.yml index 3dcfe2f..983c1a3 100644 --- a/.github/linters/.eslintrc.yml +++ b/.github/linters/.eslintrc.yml @@ -57,7 +57,7 @@ rules: '@typescript-eslint/func-call-spacing': ['error', 'never'], '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-empty-interface': 'error', - '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-extraneous-class': 'error', '@typescript-eslint/no-for-in-array': 'error', '@typescript-eslint/no-inferrable-types': 'error', @@ -82,5 +82,5 @@ rules: '@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/unbound-method': 'error', 'github/array-foreach' : 'off', - 'eslint-comments/no-unlimited-disable': 'off' + 'eslint-comments/no-unlimited-disable': 'off', } diff --git a/__tests__/copyleft-argument-builder.test.ts b/__tests__/copyleft-argument-builder.test.ts new file mode 100644 index 0000000..aa7ad1c --- /dev/null +++ b/__tests__/copyleft-argument-builder.test.ts @@ -0,0 +1,129 @@ +import { + COPYLEFT_LICENSE_EXCLUDE, + COPYLEFT_LICENSE_EXPLICIT, + COPYLEFT_LICENSE_INCLUDE, + OUTPUT_FILEPATH, + REPO_DIR, + RUNTIME_CONTAINER +} from '../src/app.input'; +import { CopyLeftArgumentBuilder } from '../src/policies/argument_builders/copyleft-argument-builder'; + +describe('CopyleftArgumentBuilder', () => { + const defaultCopyleftLicenseExplicit = COPYLEFT_LICENSE_EXPLICIT; + const defaultCopyleftLicenseExclude = COPYLEFT_LICENSE_EXCLUDE; + const defaultCopyleftLicenseInclude = COPYLEFT_LICENSE_INCLUDE; + + afterEach(() => { + // Restore all mocks + jest.restoreAllMocks(); + (COPYLEFT_LICENSE_EXPLICIT as any) = defaultCopyleftLicenseExplicit; + (COPYLEFT_LICENSE_EXCLUDE as any) = defaultCopyleftLicenseExclude; + (COPYLEFT_LICENSE_INCLUDE as any) = defaultCopyleftLicenseInclude; + }); + + test('Copyleft explicit test', async () => { + (COPYLEFT_LICENSE_EXPLICIT as any) = 'MIT,Apache-2.0'; + (COPYLEFT_LICENSE_EXCLUDE as any) = 'MIT,Apache-2.0'; + (REPO_DIR as any) = 'scanoss'; + (OUTPUT_FILEPATH as any) = 'results.json'; + + const builder = new CopyLeftArgumentBuilder(); + const cmd = await builder.build(); + expect(cmd).toEqual([ + 'run', + '-v', + 'scanoss:/scanoss', + 'ghcr.io/scanoss/scanoss-py:v1.18.0', + 'inspect', + 'copyleft', + '--input', + 'results.json', + '--format', + 'md', + '--explicit', + 'MIT,Apache-2.0' + ]); + }); + + test('Copyleft exclude test', async () => { + (COPYLEFT_LICENSE_EXCLUDE as any) = 'MIT,Apache-2.0'; + (REPO_DIR as any) = 'scanoss'; + (OUTPUT_FILEPATH as any) = 'results.json'; + const builder = new CopyLeftArgumentBuilder(); + const cmd = await builder.build(); + expect(cmd).toEqual([ + 'run', + '-v', + 'scanoss:/scanoss', + 'ghcr.io/scanoss/scanoss-py:v1.18.0', + 'inspect', + 'copyleft', + '--input', + 'results.json', + '--format', + 'md', + '--exclude', + 'MIT,Apache-2.0' + ]); + }); + + test('Copyleft include test', async () => { + (COPYLEFT_LICENSE_INCLUDE as any) = 'MIT,Apache-2.0,LGPL-3.0-only'; + (REPO_DIR as any) = 'scanoss'; + (OUTPUT_FILEPATH as any) = 'results.json'; + const builder = new CopyLeftArgumentBuilder(); + const cmd = await builder.build(); + expect(cmd).toEqual([ + 'run', + '-v', + 'scanoss:/scanoss', + 'ghcr.io/scanoss/scanoss-py:v1.18.0', + 'inspect', + 'copyleft', + '--input', + 'results.json', + '--format', + 'md', + '--include', + 'MIT,Apache-2.0,LGPL-3.0-only' + ]); + }); + + test('Copyleft empty parameters test', async () => { + (REPO_DIR as any) = 'scanoss'; + (OUTPUT_FILEPATH as any) = 'results.json'; + const builder = new CopyLeftArgumentBuilder(); + const cmd = await builder.build(); + expect(cmd).toEqual([ + 'run', + '-v', + 'scanoss:/scanoss', + RUNTIME_CONTAINER, + 'inspect', + 'copyleft', + '--input', + 'results.json', + '--format', + 'md' + ]); + }); + + test('Build Command test', async () => { + (REPO_DIR as any) = 'scanoss'; + (OUTPUT_FILEPATH as any) = 'results.json'; + const builder = new CopyLeftArgumentBuilder(); + const cmd = await builder.build(); + expect(cmd).toEqual([ + 'run', + '-v', + 'scanoss:/scanoss', + RUNTIME_CONTAINER, + 'inspect', + 'copyleft', + '--input', + 'results.json', + '--format', + 'md' + ]); + }); +}); diff --git a/__tests__/copyleft-policy-check.test.ts b/__tests__/copyleft-policy-check.test.ts index a5916ab..fc356b0 100644 --- a/__tests__/copyleft-policy-check.test.ts +++ b/__tests__/copyleft-policy-check.test.ts @@ -1,7 +1,13 @@ +import path from 'path'; +import { + COPYLEFT_LICENSE_EXCLUDE, + COPYLEFT_LICENSE_EXPLICIT, + COPYLEFT_LICENSE_INCLUDE, + OUTPUT_FILEPATH, + REPO_DIR +} from '../src/app.input'; import { CopyleftPolicyCheck } from '../src/policies/copyleft-policy-check'; import { CONCLUSION, PolicyCheck } from '../src/policies/policy-check'; -import { ScannerResults } from '../src/services/result.interfaces'; -import { resultsMock } from './results.mock'; // Mock the @actions/github module jest.mock('@actions/github', () => ({ @@ -14,49 +20,89 @@ jest.mock('@actions/github', () => ({ getOctokit: jest.fn().mockReturnValue({ rest: { checks: { - update: jest.fn().mockResolvedValue({}) + update: jest.fn().mockResolvedValue({}), + create: jest.fn().mockReturnValue({ + data: { + id: 1 + } + }) } } }) })); describe('CopyleftPolicyCheck', () => { - let scannerResults: ScannerResults; - let policyCheck: CopyleftPolicyCheck; + const defaultCopyleftLicenseExplicit = COPYLEFT_LICENSE_EXPLICIT; + const defaultCopyleftLicenseExclude = COPYLEFT_LICENSE_EXCLUDE; + const defaultCopyleftLicenseInclude = COPYLEFT_LICENSE_INCLUDE; - beforeEach(() => { - jest.clearAllMocks(); + afterEach(() => { + // Restore all mocks + jest.restoreAllMocks(); + (COPYLEFT_LICENSE_EXPLICIT as any) = defaultCopyleftLicenseExplicit; + (COPYLEFT_LICENSE_EXCLUDE as any) = defaultCopyleftLicenseExclude; + (COPYLEFT_LICENSE_INCLUDE as any) = defaultCopyleftLicenseInclude; + }); + + test('Copyleft policy check fail', async () => { + const TEST_DIR = __dirname; + const TEST_REPO_DIR = path.join(TEST_DIR, 'data'); + const TEST_RESULTS_FILE = 'results.json'; - policyCheck = new CopyleftPolicyCheck(); - jest.spyOn(PolicyCheck.prototype, 'uploadArtifact').mockImplementation(async () => { + (REPO_DIR as any) = TEST_REPO_DIR; + (OUTPUT_FILEPATH as any) = TEST_RESULTS_FILE; + + jest.spyOn(CopyleftPolicyCheck.prototype, 'uploadArtifact').mockImplementation(async () => { return Promise.resolve({ id: 123456 }); }); - jest.spyOn(PolicyCheck.prototype, 'initStatus').mockImplementation(); - jest.spyOn(PolicyCheck.prototype, 'finish').mockImplementation(); - }); + jest.spyOn(CopyleftPolicyCheck.prototype, 'initStatus').mockImplementation(); + jest.spyOn(CopyleftPolicyCheck.prototype, 'updateCheck').mockImplementation(); + const copyleftPolicyCheck = new CopyleftPolicyCheck(); + await copyleftPolicyCheck.start(1); + await copyleftPolicyCheck.run(); + //neutral cause policy policy halt on failure is not set + expect(copyleftPolicyCheck.conclusion).toEqual(CONCLUSION.Neutral); + }, 30000); - it('should pass the policy check when no copyleft components are found', async () => { - scannerResults = JSON.parse(resultsMock[0].content); - await policyCheck.run(scannerResults); - expect(policyCheck.conclusion).toEqual(CONCLUSION.Success); - }); + test('Copyleft policy empty results', async () => { + const TEST_DIR = __dirname; + const TEST_REPO_DIR = path.join(TEST_DIR, 'data'); + const TEST_RESULTS_FILE = 'results.json'; - it('should fail the policy check when copyleft components are found', async () => { - scannerResults = JSON.parse(resultsMock[2].content); - await policyCheck.run(scannerResults); - expect(policyCheck.conclusion).toEqual(CONCLUSION.Neutral); - }); + (REPO_DIR as any) = TEST_REPO_DIR; + (OUTPUT_FILEPATH as any) = TEST_RESULTS_FILE; + (COPYLEFT_LICENSE_EXCLUDE as any) = 'GPL-2.0-only'; - it('should fail the policy check when copyleft dependencies are found', async () => { - scannerResults = JSON.parse(resultsMock[4].content); - await policyCheck.run(scannerResults); - // NEUTRAL is the same as failure in this context. See inputs.POLICIES_HALT_ON_FAILURE. (Default FALSE) - expect(policyCheck.conclusion).toEqual(CONCLUSION.Neutral); - }); + jest.spyOn(CopyleftPolicyCheck.prototype, 'uploadArtifact').mockImplementation(async () => { + return Promise.resolve({ id: 123456 }); + }); + jest.spyOn(CopyleftPolicyCheck.prototype, 'initStatus').mockImplementation(); + jest.spyOn(CopyleftPolicyCheck.prototype, 'updateCheck').mockImplementation(); + const copyleftPolicyCheck = new CopyleftPolicyCheck(); + await copyleftPolicyCheck.start(1); + await copyleftPolicyCheck.run(); + //neutral cause policy policy halt on failure is not set + expect(copyleftPolicyCheck.conclusion).toEqual(CONCLUSION.Success); + }, 30000); - it('should pass the copyleft policy check', async () => { - scannerResults = JSON.parse(resultsMock[5].content); - await policyCheck.run(scannerResults); - expect(policyCheck.conclusion).toEqual(CONCLUSION.Success); - }); + test('Copyleft policy explicit licenses', async () => { + const TEST_DIR = __dirname; + const TEST_REPO_DIR = path.join(TEST_DIR, 'data'); + const TEST_RESULTS_FILE = 'results.json'; + + (REPO_DIR as any) = TEST_REPO_DIR; + (OUTPUT_FILEPATH as any) = TEST_RESULTS_FILE; + (COPYLEFT_LICENSE_EXPLICIT as any) = 'MIT,Apache-2.0'; + + jest.spyOn(CopyleftPolicyCheck.prototype, 'uploadArtifact').mockImplementation(async () => { + return Promise.resolve({ id: 123456 }); + }); + jest.spyOn(CopyleftPolicyCheck.prototype, 'initStatus').mockImplementation(); + jest.spyOn(CopyleftPolicyCheck.prototype, 'updateCheck').mockImplementation(); + const copyleftPolicyCheck = new CopyleftPolicyCheck(); + await copyleftPolicyCheck.start(1); + await copyleftPolicyCheck.run(); + //neutral cause policy policy halt on failure is not set + expect(copyleftPolicyCheck.conclusion).toEqual(CONCLUSION.Neutral); + }, 30000); }); diff --git a/__tests__/data/empty-results.json b/__tests__/data/empty-results.json new file mode 100644 index 0000000..cd352a0 --- /dev/null +++ b/__tests__/data/empty-results.json @@ -0,0 +1,50 @@ +{ + "crc32c.c": [ + { + "id": "none", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + } + } + ], + "json.c": [ + { + "id": "none", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + } + } + ], + "log.c": [ + { + "id": "none", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + } + } + ], + "package.json": [ + { + "id": "none", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + } + } + ] +} diff --git a/__tests__/data/package.json b/__tests__/data/package.json new file mode 100644 index 0000000..77df647 --- /dev/null +++ b/__tests__/data/package.json @@ -0,0 +1,28 @@ +{ + "name": "scanoss", + "version": "0.15.3", + "description": "The SCANOSS JS package provides a simple, easy to consume module for interacting with SCANOSS APIs/Engine.", + "main": "build/main/index.js", + "typings": "build/main/index.d.ts", + "module": "build/module/index.js", + "repository": "https://github.com/scanoss/scanoss.js", + "license": "MIT", + "keywords": [], + "bin": { + "scanoss-js": "build/main/cli/bin/cli-bin.js" + }, + "scripts": { + "build": "run-p build:*", + "build:main": "tsc -p tsconfig.json", + "build:module": "tsc -p tsconfig.module.json", + "test": "nyc mocha -r ts-node/register 'tests/**/*.ts' 'src/**/*.spec.ts'", + "install-dev": "npm run build && npm run test && npm install -g ." + }, + "engines": { + "node": ">=10" + }, + "dependencies": { + "@grpc/grpc-js": "^1.5.5", + "abort-controller": "^3.0.0" + } +} diff --git a/__tests__/data/results.json b/__tests__/data/results.json new file mode 100644 index 0000000..a3da3a6 --- /dev/null +++ b/__tests__/data/results.json @@ -0,0 +1,225 @@ +{ + "crc32c.c": [ + { + "component": "wfp", + "file": "wfp-6afc1f6163d1d6c8d03ff5211a0571118e08da1f/src/external/crc32c/crc32c.c", + "file_hash": "0fe279946d388ef07d9c3f6e3ffb8ebe", + "file_url": "https://api.osskb.org/file_contents/0fe279946d388ef07d9c3f6e3ffb8ebe", + "id": "file", + "latest": "0ed473d", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/Zlib.txt", + "copyleft": "no", + "name": "Zlib", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "no", + "source": "scancode", + "url": "https://spdx.org/licenses/Zlib.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/Zlib.txt", + "copyleft": "no", + "name": "Zlib", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "no", + "source": "file_header", + "url": "https://spdx.org/licenses/Zlib.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "yes", + "source": "scancode", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "yes", + "source": "license_file", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "yes", + "source": "component_declared", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "all", + "matched": "100%", + "oss_lines": "all", + "purl": [ + "pkg:github/scanoss/wfp" + ], + "release_date": "2020-07-12", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + }, + "source_hash": "0fe279946d388ef07d9c3f6e3ffb8ebe", + "status": "pending", + "url": "https://github.com/scanoss/wfp", + "url_hash": "9b36f30d422d7f77854f298f63c55256", + "url_stats": {}, + "vendor": "scanoss", + "version": "6afc1f6" + } + ], + "json.c": [ + { + "component": "scanner.c", + "file": "scanner.c-1.3.3/external/src/json.c", + "file_hash": "8e4d433c1547b59681379e9fe9960546", + "file_url": "https://api.osskb.org/file_contents/8e4d433c1547b59681379e9fe9960546", + "id": "file", + "latest": "1.3.4", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/BSD-2-Clause.txt", + "copyleft": "no", + "name": "BSD-2-Clause", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "no", + "source": "file_header", + "url": "https://spdx.org/licenses/BSD-2-Clause.html" + }, + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "yes", + "source": "component_declared", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "all", + "matched": "100%", + "oss_lines": "all", + "purl": [ + "pkg:github/scanoss/scanner.c" + ], + "release_date": "2021-05-26", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + }, + "source_hash": "8e4d433c1547b59681379e9fe9960546", + "status": "pending", + "url": "https://github.com/scanoss/scanner.c", + "url_hash": "2d1700ba496453d779d4987255feb5f2", + "url_stats": {}, + "vendor": "scanoss", + "version": "1.3.3" + } + ], + "log.c": [ + { + "component": "scanner.c", + "file": "scanner.c-1.3.3/external/src/log.c", + "file_hash": "f00c8a010806ff1593b15c7cbff7e594", + "file_url": "https://api.osskb.org/file_contents/f00c8a010806ff1593b15c7cbff7e594", + "id": "file", + "latest": "1.3.4", + "licenses": [ + { + "checklist_url": "https://www.osadl.org/fileadmin/checklists/unreflicenses/GPL-2.0-only.txt", + "copyleft": "yes", + "incompatible_with": "Apache-1.0, Apache-1.1, Apache-2.0, BSD-4-Clause, BSD-4-Clause-UC, BSD-4.3TAHOE, ECL-2.0, FTL, IJG, LicenseRef-scancode-bsla-no-advert, Minpack, OpenSSL, PHP-3.01, Python-2.0, zlib-acknowledgement, XFree86-1.1", + "name": "GPL-2.0-only", + "osadl_updated": "2024-11-04T13:45:00+0000", + "patent_hints": "yes", + "source": "component_declared", + "url": "https://spdx.org/licenses/GPL-2.0-only.html" + } + ], + "lines": "all", + "matched": "100%", + "oss_lines": "all", + "purl": [ + "pkg:github/scanoss/scanner.c" + ], + "release_date": "2021-05-26", + "server": { + "kb_version": { + "daily": "24.11.12", + "monthly": "24.10" + }, + "version": "5.4.8" + }, + "source_hash": "f00c8a010806ff1593b15c7cbff7e594", + "status": "pending", + "url": "https://github.com/scanoss/scanner.c", + "url_hash": "2d1700ba496453d779d4987255feb5f2", + "url_stats": {}, + "vendor": "scanoss", + "version": "1.3.3" + } + ], + "package.json": [ + { + "dependencies": [ + { + "component": "@grpc/grpc-js", + "licenses": [ + { + "is_spdx_approved": true, + "name": "Apache-2.0", + "spdx_id": "Apache-2.0" + } + ], + "purl": "pkg:npm/%40grpc/grpc-js", + "url": "https://www.npmjs.com/package/%40grpc/grpc-js", + "version": "1.12.2" + }, + { + "component": "abort-controller", + "licenses": [ + { + "is_spdx_approved": true, + "name": "MIT", + "spdx_id": "MIT" + } + ], + "purl": "pkg:npm/abort-controller", + "url": "https://www.npmjs.com/package/abort-controller", + "version": "3.0.0" + }, + { + "component": "adm-zip", + "licenses": [ + { + "is_spdx_approved": true, + "name": "MIT", + "spdx_id": "MIT" + } + ], + "purl": "pkg:npm/adm-zip", + "url": "https://www.npmjs.com/package/adm-zip", + "version": "0.5.16" + } + ], + "id": "dependency", + "status": "pending" + } + ] +} diff --git a/__tests__/data/test-results.json b/__tests__/data/test-results.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/__tests__/data/test-results.json @@ -0,0 +1 @@ +{} diff --git a/__tests__/scan-service.test.ts b/__tests__/scan-service.test.ts new file mode 100644 index 0000000..7f3c3c9 --- /dev/null +++ b/__tests__/scan-service.test.ts @@ -0,0 +1,126 @@ +import { OUTPUT_FILEPATH, RUNTIME_CONTAINER } from '../src/app.input'; +import { ScanService } from '../src/services/scan.service'; +import fs from 'fs'; +import path from 'path'; + +describe('ScanService', () => { + it('should correctly return the dependency scope command', () => { + const service = new ScanService({ + outputFilepath: '', + inputFilepath: '', + runtimeContainer: RUNTIME_CONTAINER, + dependencyScope: 'prod', + dependencyScopeInclude: '', + dependencyScopeExclude: '', + scanFiles: true, + skipSnippets: false, + settingsFilePath: '', + scanossSettings: false + }); + + // Accessing the private method by bypassing TypeScript type checks + const command = (service as any).dependencyScopeArgs(); + console.log(command); + expect(command).toEqual(['--dep-scope', 'prod']); + }); + + it('Should return --dependencies-only parameter', () => { + const service = new ScanService({ + outputFilepath: '', + inputFilepath: '', + runtimeContainer: RUNTIME_CONTAINER, + dependencyScope: '', + dependencyScopeInclude: '', + dependencyScopeExclude: '', + dependenciesEnabled: true, + scanFiles: false, + skipSnippets: false, + settingsFilePath: '', + scanossSettings: false + }); + + const command = (service as any).buildDependenciesArgs(); + expect(command).toEqual(['--dependencies-only']); + }); + + it('Should return dependencies parameter', () => { + const service = new ScanService({ + outputFilepath: '', + inputFilepath: '', + runtimeContainer: RUNTIME_CONTAINER, + dependencyScope: '', + dependencyScopeInclude: '', + dependencyScopeExclude: '', + dependenciesEnabled: true, + scanFiles: true, + skipSnippets: false, + settingsFilePath: '', + scanossSettings: false + }); + + const command = (service as any).buildDependenciesArgs(); + expect(command).toEqual(['--dependencies']); + }); + + it('Should return skip snippet parameter', () => { + const service = new ScanService({ + outputFilepath: '', + inputFilepath: '', + runtimeContainer: RUNTIME_CONTAINER, + dependencyScope: '', + dependencyScopeInclude: '', + dependencyScopeExclude: '', + dependenciesEnabled: true, + scanFiles: true, + skipSnippets: true, + settingsFilePath: '', + scanossSettings: false + }); + + const command = (service as any).buildSnippetArgs(); + expect(command).toEqual(['-S']); + }); + + it('Should return a command with skip snippet and prod dependencies', async () => { + const service = new ScanService({ + outputFilepath: 'results.json', + inputFilepath: 'inputFilepath', + runtimeContainer: RUNTIME_CONTAINER, + dependencyScope: 'prod', + dependencyScopeInclude: '', + dependencyScopeExclude: '', + dependenciesEnabled: true, + scanFiles: true, + skipSnippets: true, + settingsFilePath: '', + scanossSettings: false + }); + + const command = await (service as any).buildArgs(); + console.log(command); + expect(command).not.toBe(''); + }); + + it('Should scan dependencies', async () => { + (OUTPUT_FILEPATH as any) = 'test-results.json'; + const TEST_DIR = __dirname; + const resultPath = path.join(TEST_DIR, 'data', 'test-results.json'); + const service = new ScanService({ + outputFilepath: resultPath, + inputFilepath: path.join(TEST_DIR, 'data'), + runtimeContainer: RUNTIME_CONTAINER, + dependencyScopeInclude: '', + dependencyScopeExclude: '', + dependenciesEnabled: true, + sbomEnabled: false, + scanFiles: true, + skipSnippets: false, + settingsFilePath: 'scanoss.json', + scanossSettings: false + }); + + const { scan } = await service.scan(); + expect(scan['package.json'][0].dependencies.length).toBeGreaterThan(0); + await fs.promises.rm(resultPath); + }, 30000); +}); diff --git a/src/policies/argument_builders/argument-builder.ts b/src/policies/argument_builders/argument-builder.ts new file mode 100644 index 0000000..fd4c8af --- /dev/null +++ b/src/policies/argument_builders/argument-builder.ts @@ -0,0 +1,3 @@ +export abstract class ArgumentBuilder { + abstract build(): Promise; +} diff --git a/src/policies/argument_builders/copyleft-argument-builder.ts b/src/policies/argument_builders/copyleft-argument-builder.ts new file mode 100644 index 0000000..32e6857 --- /dev/null +++ b/src/policies/argument_builders/copyleft-argument-builder.ts @@ -0,0 +1,47 @@ +import { ArgumentBuilder } from './argument-builder'; +import { + COPYLEFT_LICENSE_EXCLUDE, + COPYLEFT_LICENSE_EXPLICIT, + COPYLEFT_LICENSE_INCLUDE, + OUTPUT_FILEPATH, + REPO_DIR, + RUNTIME_CONTAINER +} from '../../app.input'; +import * as core from '@actions/core'; + +export class CopyLeftArgumentBuilder extends ArgumentBuilder { + private buildCopyleftArgs(): string[] { + if (COPYLEFT_LICENSE_EXPLICIT) { + core.info(`Explicit copyleft licenses: ${COPYLEFT_LICENSE_EXPLICIT}`); + return ['--explicit', COPYLEFT_LICENSE_EXPLICIT]; + } + + if (COPYLEFT_LICENSE_INCLUDE) { + core.info(`Included copyleft licenses: ${COPYLEFT_LICENSE_INCLUDE}`); + return ['--include', COPYLEFT_LICENSE_INCLUDE]; + } + + if (COPYLEFT_LICENSE_EXCLUDE) { + core.info(`Excluded copyleft licenses: ${COPYLEFT_LICENSE_EXCLUDE}`); + return ['--exclude', COPYLEFT_LICENSE_EXCLUDE]; + } + + return []; + } + + async build(): Promise { + return [ + 'run', + '-v', + `${REPO_DIR}:/scanoss`, + RUNTIME_CONTAINER, + 'inspect', + 'copyleft', + '--input', + OUTPUT_FILEPATH, + '--format', + 'md', + ...this.buildCopyleftArgs() + ]; + } +} diff --git a/src/policies/copyleft-policy-check.ts b/src/policies/copyleft-policy-check.ts index 0f13ba9..c431be9 100644 --- a/src/policies/copyleft-policy-check.ts +++ b/src/policies/copyleft-policy-check.ts @@ -24,16 +24,10 @@ import * as core from '@actions/core'; import { CHECK_NAME } from '../app.config'; import { PolicyCheck } from './policy-check'; -import { - COPYLEFT_LICENSE_EXCLUDE, - COPYLEFT_LICENSE_EXPLICIT, - COPYLEFT_LICENSE_INCLUDE, - EXECUTABLE, - OUTPUT_FILEPATH, - REPO_DIR, - RUNTIME_CONTAINER -} from '../app.input'; +import { EXECUTABLE } from '../app.input'; import * as exec from '@actions/exec'; +import { CopyLeftArgumentBuilder } from './argument_builders/copyleft-argument-builder'; +import { ArgumentBuilder } from './argument_builders/argument-builder'; /** * This class checks if any of the components identified in the scanner results are subject to copyleft licenses. @@ -42,50 +36,17 @@ import * as exec from '@actions/exec'; */ export class CopyleftPolicyCheck extends PolicyCheck { static policyName = 'Copyleft Policy'; + private argumentBuilder: ArgumentBuilder; - constructor() { + constructor(argumentBuilder: CopyLeftArgumentBuilder = new CopyLeftArgumentBuilder()) { super(`${CHECK_NAME}: ${CopyleftPolicyCheck.policyName}`); - } - - private buildCopyleftArgs(): string[] { - if (COPYLEFT_LICENSE_EXPLICIT) { - core.info(`Explicit copyleft licenses: ${COPYLEFT_LICENSE_EXPLICIT}`); - return ['--explicit', COPYLEFT_LICENSE_EXPLICIT]; - } - - if (COPYLEFT_LICENSE_INCLUDE) { - core.info(`Included copyleft licenses: ${COPYLEFT_LICENSE_INCLUDE}`); - return ['--include', COPYLEFT_LICENSE_INCLUDE]; - } - - if (COPYLEFT_LICENSE_EXCLUDE) { - core.info(`Excluded copyleft licenses: ${COPYLEFT_LICENSE_EXCLUDE}`); - return ['--exclude', COPYLEFT_LICENSE_EXCLUDE]; - } - - return []; - } - - private buildArgs(): string[] { - return [ - 'run', - '-v', - `${REPO_DIR}:/scanoss`, - RUNTIME_CONTAINER, - 'inspect', - 'copyleft', - '--input', - OUTPUT_FILEPATH, - '--format', - 'md', - ...this.buildCopyleftArgs() - ]; + this.argumentBuilder = argumentBuilder; } async run(): Promise { core.info(`Running Copyleft Policy Check...`); super.initStatus(); - const args = this.buildArgs(); + const args = await this.argumentBuilder.build(); const options = { failOnStdErr: false, ignoreReturnCode: true