From 7c56e2539bfbc4c6217e48f71e9d4b136fa7772d Mon Sep 17 00:00:00 2001 From: ghe Date: Wed, 17 Jun 2020 22:23:26 +0100 Subject: [PATCH 1/3] test: show non root scans ignore policy --- .../cli-monitor.all-projects.spec.ts | 31 + .../cli-test/cli-test.all-projects.spec.ts | 1561 +++++++++-------- .../acceptance/cli-test/cli-test.yarn.spec.ts | 37 +- .../mono-repo-project/bundler-app/.snyk | 0 .../mono-repo-with-ignores/package-lock.json | 13 + .../mono-repo-with-ignores/package.json | 14 + .../mono-repo-with-ignores/vulnerable/.snyk | 13 + .../vulnerable/package-lock.json | 13 + .../vulnerable/package.json | 14 + test/acceptance/workspaces/yarn-package/.snyk | 9 + test/package-no-name.test.ts | 2 - 11 files changed, 953 insertions(+), 754 deletions(-) create mode 100644 test/acceptance/workspaces/mono-repo-project/bundler-app/.snyk create mode 100644 test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json create mode 100644 test/acceptance/workspaces/mono-repo-with-ignores/package.json create mode 100644 test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk create mode 100644 test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json create mode 100644 test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json create mode 100644 test/acceptance/workspaces/yarn-package/.snyk diff --git a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts index dd95ba5931d..95a1f921a1d 100644 --- a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts +++ b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts @@ -12,6 +12,37 @@ interface AcceptanceTests { export const AllProjectsTests: AcceptanceTests = { language: 'Mixed', tests: { + '`monitor mono-repo-with-ignores --all-projects` respects .snyk policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + await params.cli.monitor('mono-repo-with-ignores ', { + allProjects: true, + detectionDepth: 1, + }); + + params.server.popRequests(2).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/monitor', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'body contains policy', + ); + t.match( + req.body.depGraph.pkgManager.name, + /(npm)/, + 'depGraph has package manager', + ); + }); + }, '`monitor mono-repo-project --all-projects --detection-depth=1`': ( params, utils, diff --git a/test/acceptance/cli-test/cli-test.all-projects.spec.ts b/test/acceptance/cli-test/cli-test.all-projects.spec.ts index e7493c8ff88..c160608f9ee 100644 --- a/test/acceptance/cli-test/cli-test.all-projects.spec.ts +++ b/test/acceptance/cli-test/cli-test.all-projects.spec.ts @@ -7,42 +7,24 @@ import { CommandResult } from '../../../src/cli/commands/types'; export const AllProjectsTests: AcceptanceTests = { language: 'Mixed', tests: { - '`test mono-repo-project with lockfiles --all-projects`': ( + '`test mono-repo-with-ignores --all-projects` respects .snyk policy': ( params, utils, ) => async (t) => { utils.chdirWorkspaces(); - - // mock python plugin becuase CI tooling doesn't have pipenv installed - const mockPlugin = { - async inspect() { - return { - plugin: { - targetFile: 'Pipfile', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + const loadPlugin = sinon.spy(params.plugins, 'loadPlugin'); t.teardown(loadPlugin.restore); - loadPlugin.withArgs('pip').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock other plugins - const result: CommandResult = await params.cli.test('mono-repo-project', { - allProjects: true, - detectionDepth: 1, - skipUnresolved: true, - }); - t.ok(loadPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); - t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); - t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); - t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); - t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); - t.ok(loadPlugin.withArgs('pip').calledOnce, 'calls pip plugin'); - - params.server.popRequests(6).forEach((req) => { + const result: CommandResult = await params.cli.test( + 'mono-repo-with-ignores', + { + allProjects: true, + detectionDepth: 3, + }, + ); + t.ok(loadPlugin.withArgs('npm').calledTwice, 'calls npm plugin'); + let policyCount = 0; + params.server.popRequests(2).forEach((req) => { t.equal(req.method, 'POST', 'makes POST request'); t.equal( req.headers['x-snyk-cli-version'], @@ -51,29 +33,30 @@ export const AllProjectsTests: AcceptanceTests = { ); t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); t.ok(req.body.depGraph, 'body contains depGraph'); + if ( + req.body.targetFileRelativePath.endsWith( + 'vulnerable/package-lock.json', + ) + ) { + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'body contains policy', + ); + policyCount += 1; + } t.match( req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven|nuget|paket|pip)/, + /(npm)/, 'depGraph has package manager', ); }); + + t.match(policyCount, 1, 'one policy should have been found'); // results should contain test results from both package managers + // and show only 1/2 vulnerable paths for nested one since we ignore + // it in the .snyk file - t.match( - result.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - result.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - t.match( - result.getDisplayResults(), - 'Project name: shallow-goof', - 'contains correct project name for npm', - ); t.match( result.getDisplayResults(), 'Package manager: npm', @@ -84,671 +67,863 @@ export const AllProjectsTests: AcceptanceTests = { 'Target file: package-lock.json', 'contains target file package-lock.json', ); - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', - ); - t.match( - result.getDisplayResults(), - 'Target file: Pipfile', - 'contains target file Pipfile', - ); }, + '`test mono-repo-project with lockfiles --all-projects`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + + // mock python plugin becuase CI tooling doesn't have pipenv installed + const mockPlugin = { + async inspect() { + return { + plugin: { + targetFile: 'Pipfile', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin.withArgs('pip').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock other plugins - '`test mono-repo-project --all-projects --detection-depth=3`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - - // mock python plugin becuase CI tooling doesn't have pipenv installed - const mockPipfile = { - async inspect() { - return { - plugin: { - targetFile: 'Pipfile', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const mockRequirements = { - async inspect() { - return { - plugin: { - targetFile: 'python-app-with-req-file/requirements.txt', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - // detect pip plugin called only with Pipfile (and not requirement.txt) - loadPlugin - .withArgs('pip', sinon.match.has('file', 'Pipfile')) - .returns(mockPipfile) - .withArgs( - 'pip', - sinon.match.has('file', 'python-app-with-req-file/requirements.txt'), - ) - .returns(mockRequirements); - loadPlugin.callThrough(); // don't mock other plugins - - const result: CommandResult = await params.cli.test('mono-repo-project', { - allProjects: true, - detectionDepth: 3, - allowMissing: true, // allow requirements.txt to pass when deps not installed - }); - - t.equals( - loadPlugin.withArgs('rubygems').callCount, - 2, - 'calls rubygems plugin', - ); - t.equals(loadPlugin.withArgs('npm').callCount, 2, 'calls npm plugin'); - t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); - t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); - t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); - t.equals(loadPlugin.withArgs('pip').callCount, 2, 'calls pip plugin'); + const result: CommandResult = await params.cli.test('mono-repo-project', { + allProjects: true, + detectionDepth: 1, + skipUnresolved: true, + }); + t.ok(loadPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); + t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); + t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); + t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('pip').calledOnce, 'calls pip plugin'); + + params.server.popRequests(6).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + t.match( + req.body.depGraph.pkgManager.name, + /(npm|rubygems|maven|nuget|paket|pip)/, + 'depGraph has package manager', + ); + }); + // results should contain test results from both package managers - params.server.popRequests(9).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', + t.match( + result.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); t.match( - req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven|nuget|paket|pip)/, - 'depGraph has package manager', + result.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', ); - }); - - // ruby - t.match( - result.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - result.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - t.match( - result.getDisplayResults(), - `Target file: bundler-app${path.sep}Gemfile.lock`, - `contains target file bundler-app${path.sep}Gemfile.lock`, - ); + t.match( + result.getDisplayResults(), + 'Project name: shallow-goof', + 'contains correct project name for npm', + ); + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', + ); + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); + t.match( + result.getDisplayResults(), + 'Target file: Pipfile', + 'contains target file Pipfile', + ); + }, + + '`test mono-repo-project --all-projects --detection-depth=3`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + + // mock python plugin becuase CI tooling doesn't have pipenv installed + const mockPipfile = { + async inspect() { + return { + plugin: { + targetFile: 'Pipfile', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const mockRequirements = { + async inspect() { + return { + plugin: { + targetFile: 'python-app-with-req-file/requirements.txt', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + // detect pip plugin called only with Pipfile (and not requirement.txt) + loadPlugin + .withArgs('pip', sinon.match.has('file', 'Pipfile')) + .returns(mockPipfile) + .withArgs( + 'pip', + sinon.match.has('file', 'python-app-with-req-file/requirements.txt'), + ) + .returns(mockRequirements); + loadPlugin.callThrough(); // don't mock other plugins + + const result: CommandResult = await params.cli.test('mono-repo-project', { + allProjects: true, + detectionDepth: 3, + allowMissing: true, // allow requirements.txt to pass when deps not installed + }); - // npm - t.match( - result.getDisplayResults(), - 'Project name: shallow-goof', - 'contains correct project name for npm', - ); - t.match( - result.getDisplayResults(), - 'Project name: goof', - 'contains correct project name for npm', - ); - t.match( - result.getDisplayResults(), - 'Package manager: npm', - 'contains package manager npm', - ); - t.match( - result.getDisplayResults(), - 'Target file: package-lock.json', - 'contains target file package-lock.json', - ); - t.match( - result.getDisplayResults(), - `Target file: npm-project${path.sep}package.json`, - `contains target file npm-project${path.sep}package.json`, - ); + t.equals( + loadPlugin.withArgs('rubygems').callCount, + 2, + 'calls rubygems plugin', + ); + t.equals(loadPlugin.withArgs('npm').callCount, 2, 'calls npm plugin'); + t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); + t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.equals(loadPlugin.withArgs('pip').callCount, 2, 'calls pip plugin'); + + params.server.popRequests(9).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + t.match( + req.body.depGraph.pkgManager.name, + /(npm|rubygems|maven|nuget|paket|pip)/, + 'depGraph has package manager', + ); + }); - // maven - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', - ); + // ruby + t.match( + result.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', + ); + t.match( + result.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', + ); + t.match( + result.getDisplayResults(), + `Target file: bundler-app${path.sep}Gemfile.lock`, + `contains target file bundler-app${path.sep}Gemfile.lock`, + ); - // nuget - t.match( - result.getDisplayResults(), - 'Package manager: nuget', - 'contains package manager nuget', - ); - t.match( - result.getDisplayResults(), - 'Target file: packages.config', - 'contains target file packages.config', - ); + // npm + t.match( + result.getDisplayResults(), + 'Project name: shallow-goof', + 'contains correct project name for npm', + ); + t.match( + result.getDisplayResults(), + 'Project name: goof', + 'contains correct project name for npm', + ); + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', + ); + t.match( + result.getDisplayResults(), + `Target file: npm-project${path.sep}package.json`, + `contains target file npm-project${path.sep}package.json`, + ); - // paket - t.match( - result.getDisplayResults(), - 'Package manager: paket', - 'contains package manager paket', - ); - t.match( - result.getDisplayResults(), - 'Target file: paket.dependencies', - 'contains target file paket.dependencies', - ); + // maven + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); - // pip - t.match( - result.getDisplayResults(), - 'Package manager: pip', - 'contains package manager pip', - ); - t.match( - result.getDisplayResults(), - 'Target file: Pipfile', - 'contains target file Pipfile', - ); - t.match( - result.getDisplayResults(), - `Target file: python-app-with-req-file${path.sep}requirements.txt`, - `contains target file python-app-with-req-file${path.sep}requirements.txt`, - ); - }, + // nuget + t.match( + result.getDisplayResults(), + 'Package manager: nuget', + 'contains package manager nuget', + ); + t.match( + result.getDisplayResults(), + 'Target file: packages.config', + 'contains target file packages.config', + ); - '`test mono-repo-project --all-projects and --file payloads are the same`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); + // paket + t.match( + result.getDisplayResults(), + 'Package manager: paket', + 'contains package manager paket', + ); + t.match( + result.getDisplayResults(), + 'Target file: paket.dependencies', + 'contains target file paket.dependencies', + ); - // mock python plugin becuase CI tooling doesn't have pipenv installed - const mockPlugin = { - async inspect() { - return { - plugin: { - targetFile: 'Pipfile', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - loadPlugin.withArgs('pip').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock other plugins + // pip + t.match( + result.getDisplayResults(), + 'Package manager: pip', + 'contains package manager pip', + ); + t.match( + result.getDisplayResults(), + 'Target file: Pipfile', + 'contains target file Pipfile', + ); + t.match( + result.getDisplayResults(), + `Target file: python-app-with-req-file${path.sep}requirements.txt`, + `contains target file python-app-with-req-file${path.sep}requirements.txt`, + ); + }, + + '`test mono-repo-project --all-projects and --file payloads are the same`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + + // mock python plugin becuase CI tooling doesn't have pipenv installed + const mockPlugin = { + async inspect() { + return { + plugin: { + targetFile: 'Pipfile', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin.withArgs('pip').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock other plugins - await params.cli.test('mono-repo-project', { - allProjects: true, - detectionDepth: 1, - }); + await params.cli.test('mono-repo-project', { + allProjects: true, + detectionDepth: 1, + }); - const requests = params.server.popRequests(6); + const requests = params.server.popRequests(6); - // find each type of request - const rubyAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'rubygems', - ); - const pipAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'pip', - ); - const npmAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'npm', - ); - const nugetAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'nuget', - ); - const paketAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'paket', - ); - const mavenAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'maven', - ); + // find each type of request + const rubyAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'rubygems', + ); + const pipAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'pip', + ); + const npmAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'npm', + ); + const nugetAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'nuget', + ); + const paketAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'paket', + ); + const mavenAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'maven', + ); - await params.cli.test('mono-repo-project', { - file: 'Gemfile.lock', - }); - const rubyFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'Gemfile.lock', + }); + const rubyFile = params.server.popRequest(); - await params.cli.test('mono-repo-project', { - file: 'Pipfile', - }); - const pipFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'Pipfile', + }); + const pipFile = params.server.popRequest(); - await params.cli.test('mono-repo-project', { - file: 'paket.dependencies', - }); - const paketFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'paket.dependencies', + }); + const paketFile = params.server.popRequest(); - await params.cli.test('mono-repo-project', { - file: 'packages.config', - }); - const nugetFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'packages.config', + }); + const nugetFile = params.server.popRequest(); - await params.cli.test('mono-repo-project', { - file: 'package-lock.json', - }); - const npmFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'package-lock.json', + }); + const npmFile = params.server.popRequest(); - await params.cli.test('mono-repo-project', { - file: 'pom.xml', - }); - const mavenFile = params.server.popRequest(); + await params.cli.test('mono-repo-project', { + file: 'pom.xml', + }); + const mavenFile = params.server.popRequest(); - t.same( - pipAll.body, - pipFile.body, - 'Same body for --all-projects and --file=Pipfile', - ); + t.same( + pipAll.body, + pipFile.body, + 'Same body for --all-projects and --file=Pipfile', + ); - t.same( - rubyAll.body, - rubyFile.body, - 'Same body for --all-projects and --file=Gemfile.lock', - ); + t.same( + rubyAll.body, + rubyFile.body, + 'Same body for --all-projects and --file=Gemfile.lock', + ); - t.same( - npmAll.body, - npmFile.body, - 'Same body for --all-projects and --file=package-lock.json', - ); + t.same( + npmAll.body, + npmFile.body, + 'Same body for --all-projects and --file=package-lock.json', + ); - t.same( - paketAll.body, - paketFile.body, - 'Same body for --all-projects and --file=package-lock.json', - ); + t.same( + paketAll.body, + paketFile.body, + 'Same body for --all-projects and --file=package-lock.json', + ); - t.same( - nugetAll.body, - nugetFile.body, - 'Same body for --all-projects and --file=package-lock.json', - ); - t.same( - mavenAll.body, - mavenFile.body, - 'Same body for --all-projects and --file=pom.xml', - ); - }, + t.same( + nugetAll.body, + nugetFile.body, + 'Same body for --all-projects and --file=package-lock.json', + ); + t.same( + mavenAll.body, + mavenFile.body, + 'Same body for --all-projects and --file=pom.xml', + ); + }, - '`test maven-multi-app --all-projects --detection-depth=2`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); + '`test maven-multi-app --all-projects --detection-depth=2`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); - const result: CommandResult = await params.cli.test('maven-multi-app', { - allProjects: true, - detectionDepth: 2, - }); + const result: CommandResult = await params.cli.test('maven-multi-app', { + allProjects: true, + detectionDepth: 2, + }); - t.ok(spyPlugin.withArgs('maven').calledTwice, 'calls maven plugin'); - t.ok( - spyPlugin.withArgs('rubygems').notCalled, - 'did not call rubygems plugin', - ); - t.ok(spyPlugin.withArgs('npm').notCalled, 'did not call npm plugin'); - params.server.popRequests(2).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', + t.ok(spyPlugin.withArgs('maven').calledTwice, 'calls maven plugin'); + t.ok( + spyPlugin.withArgs('rubygems').notCalled, + 'did not call rubygems plugin', ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); + t.ok(spyPlugin.withArgs('npm').notCalled, 'did not call npm plugin'); + params.server.popRequests(2).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + t.match( + req.body.depGraph.pkgManager.name, + /maven/, + 'depGraph has package manager', + ); + }); t.match( - req.body.depGraph.pkgManager.name, - /maven/, - 'depGraph has package manager', + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', ); - }); - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', - ); - t.match( - result.getDisplayResults(), - `Target file: simple-child${path.sep}pom.xml`, - `contains target file simple-child${path.sep}pom.xml`, - ); - }, - - '`test large-mono-repo with --all-projects and --detection-depth=2`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - await params.cli.test('large-mono-repo', { - allProjects: true, - detectionDepth: 2, - }); - t.equals( - spyPlugin.withArgs('rubygems').callCount, - 1, - 'calls rubygems plugin once', - ); - t.equals( - spyPlugin.withArgs('npm').callCount, - 19, - 'calls npm plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('maven').callCount, - 1, - 'calls maven plugin once', - ); - }, - - '`test large-mono-repo with --all-projects and --detection-depth=7`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - await params.cli.test('large-mono-repo', { - allProjects: true, - detectionDepth: 7, - }); - t.equals( - spyPlugin.withArgs('rubygems').callCount, - 19, - 'calls rubygems plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('npm').callCount, - 19, - 'calls npm plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('maven').callCount, - 6, - 'calls maven plugin 6 times', - ); - }, - - '`test mono-repo-project-manifests-only --all-projects`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const result: CommandResult = await params.cli.test( - 'mono-repo-project-manifests-only', - { - allProjects: true, - }, - ); - params.server.popRequests(3).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); t.match( - req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven)/, - 'depGraph has package manager', + result.getDisplayResults(), + `Target file: simple-child${path.sep}pom.xml`, + `contains target file simple-child${path.sep}pom.xml`, ); - }); - - // results should contain test results from all package managers - t.match( - result.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - result.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - t.match( - result.getDisplayResults(), - 'Package manager: npm', - 'contains package manager npm', - ); - t.match( - result.getDisplayResults(), - 'Target file: package-lock.json', - 'contains target file package-lock.json', - ); - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', - ); - }, - - '`test ruby-app --all-projects`': (params, utils) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - - const res: CommandResult = await params.cli.test('ruby-app', { - allProjects: true, - }); - - t.ok(spyPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); - t.notOk(spyPlugin.withArgs('npm').calledOnce, "doesn't call npm plugin"); - t.notOk( - spyPlugin.withArgs('maven').calledOnce, - "doesn't call maven plugin", - ); - - t.match( - res.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - res.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - }, - - '`test ruby-app-thresholds --all-projects --ignore-policy`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces('ruby-app-thresholds'); - params.server.setNextResponse( - getWorkspaceJSON( - 'ruby-app-thresholds', - 'test-graph-result-medium-severity.json', - ), - ); - try { - await params.cli.test('./', { - 'ignore-policy': true, + }, + + '`test large-mono-repo with --all-projects and --detection-depth=2`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + await params.cli.test('large-mono-repo', { allProjects: true, + detectionDepth: 2, }); - t.fail('should have thrown'); - } catch (err) { - const req = params.server.popRequest(); - t.equal(req.query.ignorePolicy, 'true', 'should request ignore policy'); - const res = err.message; - t.match( - res, - 'Tested 7 dependencies for known vulnerabilities, found 5 vulnerabilities, 6 vulnerable paths.', - 'should display expected message', + t.equals( + spyPlugin.withArgs('rubygems').callCount, + 1, + 'calls rubygems plugin once', ); - } - }, - - '`test large-mono-repo with --all-projects, --detection-depth=7 and --exclude=bundler-app,maven-project-1`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - await params.cli.test('large-mono-repo', { - allProjects: true, - detectionDepth: 7, - exclude: 'bundler-app,maven-project-1', - }); - t.equals( - spyPlugin.withArgs('rubygems').callCount, - 0, - 'does not call rubygems', - ); - t.equals( - spyPlugin.withArgs('npm').callCount, - 19, - 'calls npm plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('maven').callCount, - 1, - 'calls maven plugin once, excluding the rest', - ); - }, - - '`test empty --all-projects`': (params, utils) => async (t) => { - utils.chdirWorkspaces(); - try { - await params.cli.test('empty', { + t.equals( + spyPlugin.withArgs('npm').callCount, + 19, + 'calls npm plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('maven').callCount, + 1, + 'calls maven plugin once', + ); + }, + + '`test large-mono-repo with --all-projects and --detection-depth=7`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + await params.cli.test('large-mono-repo', { allProjects: true, + detectionDepth: 7, }); - t.fail('expected an error to be thrown'); - } catch (err) { - const res = err.message; - t.match( - res, - 'Could not detect supported target files', - 'should display expected message', + t.equals( + spyPlugin.withArgs('rubygems').callCount, + 19, + 'calls rubygems plugin 19 times', ); - } - }, - - '`test monorepo-with-nuget --all-projects with Nuget, Python, Go, Npm, Cocoapods`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const mockPlugin = { - async inspect() { - return { - package: {}, - plugin: { - name: 'mock', - }, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - // prevent plugin inspect from actually running (requires go to be installed) - loadPlugin.withArgs('golangdep').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock other plugins - - try { - const res: CommandResult = await params.cli.test( - 'monorepo-with-nuget', + t.equals( + spyPlugin.withArgs('npm').callCount, + 19, + 'calls npm plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('maven').callCount, + 6, + 'calls maven plugin 6 times', + ); + }, + + '`test mono-repo-project-manifests-only --all-projects`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const result: CommandResult = await params.cli.test( + 'mono-repo-project-manifests-only', { allProjects: true, - detectionDepth: 4, }, ); - t.equal( - loadPlugin.withArgs('nuget').callCount, - 2, - 'calls nuget plugin twice', + params.server.popRequests(3).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + t.match( + req.body.depGraph.pkgManager.name, + /(npm|rubygems|maven)/, + 'depGraph has package manager', + ); + }); + + // results should contain test results from all package managers + t.match( + result.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', ); - t.ok( - loadPlugin.withArgs('cocoapods').calledOnce, - 'calls cocoapods plugin', + t.match( + result.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', ); - t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); - t.ok( - loadPlugin.withArgs('golangdep').calledOnce, - 'calls golangdep plugin', + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', ); - t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); + }, + + '`test ruby-app --all-projects`': (params, utils) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + + const res: CommandResult = await params.cli.test('ruby-app', { + allProjects: true, + }); + + t.ok(spyPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); + t.notOk(spyPlugin.withArgs('npm').calledOnce, "doesn't call npm plugin"); + t.notOk( + spyPlugin.withArgs('maven').calledOnce, + "doesn't call maven plugin", + ); + t.match( res.getDisplayResults(), - /Tested 6 projects, no vulnerable paths were found./, - 'Six projects tested', + 'Package manager: rubygems', + 'contains package manager rubygems', ); t.match( res.getDisplayResults(), - `Target file: src${path.sep}paymentservice${path.sep}package-lock.json`, - 'Npm project targetFile is as expected', + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', + ); + }, + + '`test ruby-app-thresholds --all-projects --ignore-policy`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces('ruby-app-thresholds'); + params.server.setNextResponse( + getWorkspaceJSON( + 'ruby-app-thresholds', + 'test-graph-result-medium-severity.json', + ), + ); + try { + await params.cli.test('./', { + 'ignore-policy': true, + allProjects: true, + }); + t.fail('should have thrown'); + } catch (err) { + const req = params.server.popRequest(); + t.equal(req.query.ignorePolicy, 'true', 'should request ignore policy'); + const res = err.message; + t.match( + res, + 'Tested 7 dependencies for known vulnerabilities, found 5 vulnerabilities, 6 vulnerable paths.', + 'should display expected message', + ); + } + }, + + '`test large-mono-repo with --all-projects, --detection-depth=7 and --exclude=bundler-app,maven-project-1`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + await params.cli.test('large-mono-repo', { + allProjects: true, + detectionDepth: 7, + exclude: 'bundler-app,maven-project-1', + }); + t.equals( + spyPlugin.withArgs('rubygems').callCount, + 0, + 'does not call rubygems', + ); + t.equals( + spyPlugin.withArgs('npm').callCount, + 19, + 'calls npm plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('maven').callCount, + 1, + 'calls maven plugin once, excluding the rest', + ); + }, + + '`test empty --all-projects`': (params, utils) => async (t) => { + utils.chdirWorkspaces(); + try { + await params.cli.test('empty', { + allProjects: true, + }); + t.fail('expected an error to be thrown'); + } catch (err) { + const res = err.message; + t.match( + res, + 'Could not detect supported target files', + 'should display expected message', + ); + } + }, + + '`test monorepo-with-nuget --all-projects with Nuget, Python, Go, Npm, Cocoapods`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const mockPlugin = { + async inspect() { + return { + package: {}, + plugin: { + name: 'mock', + }, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + // prevent plugin inspect from actually running (requires go to be installed) + loadPlugin.withArgs('golangdep').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock other plugins + + try { + const res: CommandResult = await params.cli.test( + 'monorepo-with-nuget', + { + allProjects: true, + detectionDepth: 4, + }, + ); + t.equal( + loadPlugin.withArgs('nuget').callCount, + 2, + 'calls nuget plugin twice', + ); + t.ok( + loadPlugin.withArgs('cocoapods').calledOnce, + 'calls cocoapods plugin', + ); + t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); + t.ok( + loadPlugin.withArgs('golangdep').calledOnce, + 'calls golangdep plugin', + ); + t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.match( + res.getDisplayResults(), + /Tested 6 projects, no vulnerable paths were found./, + 'Six projects tested', + ); + t.match( + res.getDisplayResults(), + `Target file: src${path.sep}paymentservice${path.sep}package-lock.json`, + 'Npm project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: src${path.sep}cocoapods-app${path.sep}Podfile`, + 'Cocoapods project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: src${path.sep}frontend${path.sep}Gopkg.lock`, + 'Go dep project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: src${path.sep}cartservice-nuget${path.sep}obj${path.sep}project.assets.json`, + 'Nuget project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: test${path.sep}nuget-app-4${path.sep}packages.config`, + 'Nuget project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: test${path.sep}paket-app${path.sep}paket.dependencies`, + 'Paket project targetFile is as expected', + ); + + t.match( + res.getDisplayResults(), + 'Package manager: nuget', + 'Nuget package manager', + ); + t.match( + res.getDisplayResults(), + 'Package manager: cocoapods', + 'Cocoapods package manager', + ); + t.match( + res.getDisplayResults(), + 'Package manager: npm', + 'Npm package manager', + ); + t.match( + res.getDisplayResults(), + 'Package manager: golangdep', + 'Go dep package manager', + ); + } catch (err) { + t.fail('expected to pass'); + } + }, + '`test composer-app --all-projects`': (params, utils) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + + const result: CommandResult = await params.cli.test('composer-app', { + allProjects: true, + }); + + t.ok(spyPlugin.withArgs('composer').calledOnce, 'calls composer plugin'); + + params.server.popRequests(2).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test', 'posts to correct url'); + }); + t.match( + result.getDisplayResults(), + 'Package manager: composer', + 'contains package manager composer', + ); + t.match( + result.getDisplayResults(), + 'Target file: composer.lock', + 'contains target file composer.lock', + ); + }, + '`test mono-repo-go --all-projects --detection-depth=2`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const mockPlugin = { + async inspect() { + return { + package: {}, + plugin: { + name: 'mock', + }, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + // prevent plugin inspect from actually running (requires go to be installed) + loadPlugin.withArgs('golangdep').returns(mockPlugin); + loadPlugin.withArgs('gomodules').returns(mockPlugin); + loadPlugin.withArgs('govendor').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock npm plugin + + const res: CommandResult = await params.cli.test('mono-repo-go', { + allProjects: true, + detectionDepth: 3, + }); + t.ok(loadPlugin.withArgs('golangdep').calledOnce, 'calls go dep plugin'); + t.ok(loadPlugin.withArgs('gomodules').calledOnce, 'calls go mod plugin'); + t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); + t.ok( + loadPlugin.withArgs('govendor').calledOnce, + 'calls go vendor plugin', ); t.match( res.getDisplayResults(), - `Target file: src${path.sep}cocoapods-app${path.sep}Podfile`, - 'Cocoapods project targetFile is as expected', + /Tested 4 projects, no vulnerable paths were found./, + 'Four projects tested', ); t.match( res.getDisplayResults(), - `Target file: src${path.sep}frontend${path.sep}Gopkg.lock`, + `Target file: hello-dep${path.sep}Gopkg.lock`, 'Go dep project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: src${path.sep}cartservice-nuget${path.sep}obj${path.sep}project.assets.json`, - 'Nuget project targetFile is as expected', + `Target file: hello-mod${path.sep}go.mod`, + 'Go mod project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: test${path.sep}nuget-app-4${path.sep}packages.config`, - 'Nuget project targetFile is as expected', + `Target file: hello-node${path.sep}package-lock.json`, + 'Npm project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: test${path.sep}paket-app${path.sep}paket.dependencies`, - 'Paket project targetFile is as expected', + `Target file: hello-vendor${path.sep}vendor${path.sep}vendor.json`, + 'Go vendor project targetFile is as expected', ); - t.match( res.getDisplayResults(), - 'Package manager: nuget', + 'Package manager: golangdep', 'Nuget package manager', ); t.match( res.getDisplayResults(), - 'Package manager: cocoapods', - 'Cocoapods package manager', + 'Package manager: gomodules', + 'Nuget package manager', ); t.match( res.getDisplayResults(), @@ -757,123 +932,9 @@ export const AllProjectsTests: AcceptanceTests = { ); t.match( res.getDisplayResults(), - 'Package manager: golangdep', + 'Package manager: govendor', 'Go dep package manager', ); - } catch (err) { - t.fail('expected to pass'); - } - }, - '`test composer-app --all-projects`': (params, utils) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - - const result: CommandResult = await params.cli.test('composer-app', { - allProjects: true, - }); - - t.ok(spyPlugin.withArgs('composer').calledOnce, 'calls composer plugin'); - - params.server.popRequests(2).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/test', 'posts to correct url'); - }); - t.match( - result.getDisplayResults(), - 'Package manager: composer', - 'contains package manager composer', - ); - t.match( - result.getDisplayResults(), - 'Target file: composer.lock', - 'contains target file composer.lock', - ); - }, - '`test mono-repo-go --all-projects --detection-depth=2`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const mockPlugin = { - async inspect() { - return { - package: {}, - plugin: { - name: 'mock', - }, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - // prevent plugin inspect from actually running (requires go to be installed) - loadPlugin.withArgs('golangdep').returns(mockPlugin); - loadPlugin.withArgs('gomodules').returns(mockPlugin); - loadPlugin.withArgs('govendor').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock npm plugin - - const res: CommandResult = await params.cli.test('mono-repo-go', { - allProjects: true, - detectionDepth: 3, - }); - t.ok(loadPlugin.withArgs('golangdep').calledOnce, 'calls go dep plugin'); - t.ok(loadPlugin.withArgs('gomodules').calledOnce, 'calls go mod plugin'); - t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); - t.ok( - loadPlugin.withArgs('govendor').calledOnce, - 'calls go vendor plugin', - ); - t.match( - res.getDisplayResults(), - /Tested 4 projects, no vulnerable paths were found./, - 'Four projects tested', - ); - t.match( - res.getDisplayResults(), - `Target file: hello-dep${path.sep}Gopkg.lock`, - 'Go dep project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: hello-mod${path.sep}go.mod`, - 'Go mod project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: hello-node${path.sep}package-lock.json`, - 'Npm project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: hello-vendor${path.sep}vendor${path.sep}vendor.json`, - 'Go vendor project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - 'Package manager: golangdep', - 'Nuget package manager', - ); - t.match( - res.getDisplayResults(), - 'Package manager: gomodules', - 'Nuget package manager', - ); - t.match( - res.getDisplayResults(), - 'Package manager: npm', - 'Npm package manager', - ); - t.match( - res.getDisplayResults(), - 'Package manager: govendor', - 'Go dep package manager', - ); - }, + }, }, }; diff --git a/test/acceptance/cli-test/cli-test.yarn.spec.ts b/test/acceptance/cli-test/cli-test.yarn.spec.ts index 624b93a126b..e6f131edaa7 100644 --- a/test/acceptance/cli-test/cli-test.yarn.spec.ts +++ b/test/acceptance/cli-test/cli-test.yarn.spec.ts @@ -187,8 +187,24 @@ export const YarnTests: AcceptanceTests = { 'depGraph looks fine', ); }, - - '`test yarn-package --file=yarn.lock ` sends pkg info': ( + '`test yarn-package --file=yarn-package/yarn.lock ` sends pkg info & policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + await params.cli.test({ file: 'yarn-package/yarn.lock' }); + const req = params.server.popRequest(); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.match(req.body.policy, 'npm:debug:20170905', 'policy is found & sent'); + t.match(req.body.targetFile, undefined, 'target is undefined'); + const depGraph = req.body.depGraph; + t.same( + depGraph.pkgs.map((p) => p.id).sort(), + ['npm-package@1.0.0', 'ms@0.7.1', 'debug@2.2.0'].sort(), + 'depGraph looks fine', + ); + }, + '`test yarn-package --file=yarn.lock ` sends pkg info & policy': ( params, utils, ) => async (t) => { @@ -196,6 +212,23 @@ export const YarnTests: AcceptanceTests = { await params.cli.test('yarn-package', { file: 'yarn.lock' }); const req = params.server.popRequest(); t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.match(req.body.policy, 'npm:debug:20170905', 'policy is found & sent'); + t.match(req.body.targetFile, undefined, 'target is undefined'); + const depGraph = req.body.depGraph; + t.same( + depGraph.pkgs.map((p) => p.id).sort(), + ['npm-package@1.0.0', 'ms@0.7.1', 'debug@2.2.0'].sort(), + 'depGraph looks fine', + ); + }, + '`test yarn-package` sends pkg info & policy': (params, utils) => async ( + t, + ) => { + utils.chdirWorkspaces('yarn-package'); + await params.cli.test(); + const req = params.server.popRequest(); + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + t.match(req.body.policy, 'npm:debug:20170905', 'policy is found & sent'); t.match(req.body.targetFile, undefined, 'target is undefined'); const depGraph = req.body.depGraph; t.same( diff --git a/test/acceptance/workspaces/mono-repo-project/bundler-app/.snyk b/test/acceptance/workspaces/mono-repo-project/bundler-app/.snyk new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json b/test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json new file mode 100644 index 00000000000..fb619ede5e0 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "mono-repo-with-ignores", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + } + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/package.json b/test/acceptance/workspaces/mono-repo-with-ignores/package.json new file mode 100644 index 00000000000..b87367a529a --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/package.json @@ -0,0 +1,14 @@ +{ + "name": "mono-repo-with-ignores", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "qs": "6.9.4" + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk new file mode 100644 index 00000000000..bf755cc8f32 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/.snyk @@ -0,0 +1,13 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + 'npm:node-uuid:20160328': + - '*': + reason: None Given + expires: 2020-07-17T17:50:18.603Z + 'npm:node-uuid:20111130': + - '*': + reason: None Given + expires: 2020-07-17T17:50:18.603Z +patch: {} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json new file mode 100644 index 00000000000..fd73bd92c75 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "vulnerable", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "node-uuid": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.3.0.tgz", + "integrity": "sha1-rnqqhsKUGizjr00H3k5jb3A5oFE=" + } + } +} diff --git a/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json new file mode 100644 index 00000000000..aa31b590077 --- /dev/null +++ b/test/acceptance/workspaces/mono-repo-with-ignores/vulnerable/package.json @@ -0,0 +1,14 @@ +{ + "name": "vulnerable", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "node-uuid": "1.3.0" + } +} diff --git a/test/acceptance/workspaces/yarn-package/.snyk b/test/acceptance/workspaces/yarn-package/.snyk new file mode 100644 index 00000000000..1cf55a4e446 --- /dev/null +++ b/test/acceptance/workspaces/yarn-package/.snyk @@ -0,0 +1,9 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + 'npm:debug:20170905': + - '*': + reason: None Given + expires: 2020-07-19T08:17:11.449Z +patch: {} diff --git a/test/package-no-name.test.ts b/test/package-no-name.test.ts index 3102af40024..38752482613 100644 --- a/test/package-no-name.test.ts +++ b/test/package-no-name.test.ts @@ -4,11 +4,9 @@ import * as snyk from '../src/lib'; test('packages with no name read dir', async (t) => { await snyk.test(__dirname + '/fixtures/package-sans-name'); t.pass('succeed'); - t.end(); }); test('packages with no name read dir with a lockfile', async (t) => { await snyk.test(__dirname + '/fixtures/package-sans-name-lockfile'); t.pass('succeed'); - t.end(); }); From e75db659fed2df0b450c0c42970372928997a88a Mon Sep 17 00:00:00 2001 From: ghe Date: Fri, 19 Jun 2020 16:00:51 +0100 Subject: [PATCH 2/3] fix: calculate the policy folder from targetFile --- src/lib/monitor/index.ts | 27 +++++++++++++++++++ src/lib/policy/find-and-load-policy.ts | 4 ++- src/lib/snyk-test/run-test.ts | 27 ++++++++++++------- .../cli-monitor.acceptance.test.ts | 1 - .../mono-repo-project/bundler-app/.snyk | 0 5 files changed, 47 insertions(+), 12 deletions(-) delete mode 100644 test/acceptance/workspaces/mono-repo-project/bundler-app/.snyk diff --git a/src/lib/monitor/index.ts b/src/lib/monitor/index.ts index 051dc4d4926..cb9a78e4a76 100644 --- a/src/lib/monitor/index.ts +++ b/src/lib/monitor/index.ts @@ -1,4 +1,6 @@ import * as Debug from 'debug'; +import * as path from 'path'; + import * as depGraphLib from '@snyk/dep-graph'; import * as snyk from '..'; import { apiTokenExists } from '../api-token'; @@ -185,6 +187,13 @@ async function monitorDepTree( treeMissingDeps = missingDeps; } + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, meta.isDocker ? 'docker' : packageManager!, @@ -192,6 +201,7 @@ async function monitorDepTree( // TODO: fix this and send only send when we used resolve-deps for node // it should be a ExpandedPkgTree type instead depTree, + targetFileDir, ); const target = await projectMetadata.getInfo(scannedProject, meta, depTree); @@ -328,10 +338,19 @@ export async function monitorDepGraph( ); } + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, meta.isDocker ? 'docker' : packageManager!, options, + undefined, + targetFileDir, ); const target = await projectMetadata.getInfo(scannedProject, meta); @@ -443,6 +462,13 @@ async function experimentalMonitorDepGraphFromDepTree( ); } + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, meta.isDocker ? 'docker' : packageManager!, @@ -450,6 +476,7 @@ async function experimentalMonitorDepGraphFromDepTree( // TODO: fix this and send only send when we used resolve-deps for node // it should be a ExpandedPkgTree type instead depTree, + targetFileDir, ); if (['npm', 'yarn'].includes(meta.packageManager)) { diff --git a/src/lib/policy/find-and-load-policy.ts b/src/lib/policy/find-and-load-policy.ts index 59dfe5f3f87..9b896853d5a 100644 --- a/src/lib/policy/find-and-load-policy.ts +++ b/src/lib/policy/find-and-load-policy.ts @@ -14,11 +14,12 @@ export async function findAndLoadPolicy( scanType: SupportedPackageManagers | 'docker', options: PolicyOptions, pkg?: PackageExpanded, + scannedProjectFolder?: string, ): Promise { const isDocker = scanType === 'docker'; const isNodeProject = ['npm', 'yarn'].includes(scanType); // monitor - let policyLocations: string[] = [options['policy-path'] || root]; + let policyLocations: string[] = [options['policy-path'] || scannedProjectFolder || root]; if (isDocker) { policyLocations = policyLocations.filter((loc) => loc !== root); } else if (isNodeProject) { @@ -26,6 +27,7 @@ export async function findAndLoadPolicy( // find and apply policies in node_modules policyLocations = policyLocations.concat(pluckPolicies(pkg as PackageJson)); } + debug('Potential policy locations found:', policyLocations); analytics.add('policies', policyLocations.length); analytics.add('policyLocations', policyLocations); diff --git a/src/lib/snyk-test/run-test.ts b/src/lib/snyk-test/run-test.ts index 05214264361..ffc2f823f3a 100644 --- a/src/lib/snyk-test/run-test.ts +++ b/src/lib/snyk-test/run-test.ts @@ -109,7 +109,6 @@ async function runTest( _.get(payload, 'body.originalProjectName'); const foundProjectCount = _.get(payload, 'body.foundProjectCount'); const displayTargetFile = _.get(payload, 'body.displayTargetFile'); - let dockerfilePackages; if ( payload.body && @@ -403,6 +402,22 @@ async function assembleLocalPayloads( } } + // todo: normalize what target file gets used across plugins and functions + const targetFile = + scannedProject.targetFile || deps.plugin.targetFile || options.file; + + // Forcing options.path to be a string as pathUtil requires is to be stringified + const targetFileRelativePath = targetFile + ? pathUtil.join(pathUtil.resolve(`${options.path || root}`), targetFile) + : ''; + + let targetFileDir; + + if (targetFileRelativePath) { + const { dir } = path.parse(targetFileRelativePath); + targetFileDir = dir; + } + const policy = await findAndLoadPolicy( root, options.docker ? 'docker' : packageManager!, @@ -410,6 +425,7 @@ async function assembleLocalPayloads( // TODO: fix this and send only send when we used resolve-deps for node // it should be a ExpandedPkgTree type instead pkg, + targetFileDir, ); analytics.add('packageManager', packageManager); @@ -422,15 +438,6 @@ async function assembleLocalPayloads( addPackageAnalytics(depTree.name!, depTree.version!); } - // todo: normalize what target file gets used across plugins and functions - const targetFile = - scannedProject.targetFile || deps.plugin.targetFile || options.file; - - // Forcing options.path to be a string as pathUtil requires is to be stringified - const targetFileRelativePath = targetFile - ? pathUtil.join(pathUtil.resolve(`${options.path}`), targetFile) - : ''; - let target: GitTarget | ContainerTarget | null; if (scannedProject.depGraph) { target = await projectMetadata.getInfo(scannedProject, options); diff --git a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts index c65b5b570e7..7d13f7ff11a 100644 --- a/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts +++ b/test/acceptance/cli-monitor/cli-monitor.acceptance.test.ts @@ -13,7 +13,6 @@ import * as _ from '@snyk/lodash'; // ensure this is required *after* the demo server, since this will // configure our fake configuration too -import * as snykPolicy from 'snyk-policy'; import { AllProjectsTests } from './cli-monitor.all-projects.spec'; const { test, only } = tap; diff --git a/test/acceptance/workspaces/mono-repo-project/bundler-app/.snyk b/test/acceptance/workspaces/mono-repo-project/bundler-app/.snyk deleted file mode 100644 index e69de29bb2d..00000000000 From 9f50581c643ca4550c2ffb804e8e1b4889801c6f Mon Sep 17 00:00:00 2001 From: ghe Date: Fri, 19 Jun 2020 16:03:08 +0100 Subject: [PATCH 3/3] test: gradle --all-sub-projects policy is not applying Also fix some formatting --- src/lib/policy/find-and-load-policy.ts | 4 +- .../cli-monitor.all-projects.spec.ts | 44 +- .../cli-test/cli-test.all-projects.spec.ts | 1582 ++++++++--------- .../cli-test/cli-test.gradle.spec.ts | 44 + .../gradle-multi-project/build.gradle | 0 .../gradle-multi-project/settings.gradle | 5 + .../gradle-multi-project/subproj/.snyk | 9 + .../gradle-multi-project/subproj/build.gradle | 51 + 8 files changed, 925 insertions(+), 814 deletions(-) create mode 100644 test/acceptance/workspaces/gradle-multi-project/build.gradle create mode 100644 test/acceptance/workspaces/gradle-multi-project/settings.gradle create mode 100644 test/acceptance/workspaces/gradle-multi-project/subproj/.snyk create mode 100644 test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle diff --git a/src/lib/policy/find-and-load-policy.ts b/src/lib/policy/find-and-load-policy.ts index 9b896853d5a..4e8727769c1 100644 --- a/src/lib/policy/find-and-load-policy.ts +++ b/src/lib/policy/find-and-load-policy.ts @@ -19,7 +19,9 @@ export async function findAndLoadPolicy( const isDocker = scanType === 'docker'; const isNodeProject = ['npm', 'yarn'].includes(scanType); // monitor - let policyLocations: string[] = [options['policy-path'] || scannedProjectFolder || root]; + let policyLocations: string[] = [ + options['policy-path'] || scannedProjectFolder || root, + ]; if (isDocker) { policyLocations = policyLocations.filter((loc) => loc !== root); } else if (isNodeProject) { diff --git a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts index 95a1f921a1d..4bb744822b1 100644 --- a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts +++ b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts @@ -17,31 +17,31 @@ export const AllProjectsTests: AcceptanceTests = { utils, ) => async (t) => { utils.chdirWorkspaces(); - await params.cli.monitor('mono-repo-with-ignores ', { + await params.cli.monitor('mono-repo-with-ignores', { allProjects: true, - detectionDepth: 1, + detectionDepth: 2, }); - - params.server.popRequests(2).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/monitor', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); - t.match( - req.body.policy, - 'npm:node-uuid:20160328', - 'body contains policy', - ); - t.match( - req.body.depGraph.pkgManager.name, - /(npm)/, - 'depGraph has package manager', - ); + // Pop all calls to server and filter out calls to `featureFlag` endpoint + const requests = params.server + .popRequests(4) + .filter((req) => req.url.includes('/monitor/')); + let policyCount = 0; + requests.forEach((req) => { + const vulnerableFolderPath = + process.platform === 'win32' + ? 'vulnerable\\package-lock.json' + : 'vulnerable/package-lock.json'; + + if (req.body.targetFileRelativePath.endsWith(vulnerableFolderPath)) { + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'body contains policy', + ); + policyCount += 1; + } }); + t.equal(policyCount, 1, 'one policy found'); }, '`monitor mono-repo-project --all-projects --detection-depth=1`': ( params, diff --git a/test/acceptance/cli-test/cli-test.all-projects.spec.ts b/test/acceptance/cli-test/cli-test.all-projects.spec.ts index c160608f9ee..cdaa3022ce2 100644 --- a/test/acceptance/cli-test/cli-test.all-projects.spec.ts +++ b/test/acceptance/cli-test/cli-test.all-projects.spec.ts @@ -33,11 +33,11 @@ export const AllProjectsTests: AcceptanceTests = { ); t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); t.ok(req.body.depGraph, 'body contains depGraph'); - if ( - req.body.targetFileRelativePath.endsWith( - 'vulnerable/package-lock.json', - ) - ) { + const vulnerableFolderPath = + process.platform === 'win32' + ? 'vulnerable\\package-lock.json' + : 'vulnerable/package-lock.json'; + if (req.body.targetFileRelativePath.endsWith(vulnerableFolderPath)) { t.match( req.body.policy, 'npm:node-uuid:20160328', @@ -68,862 +68,748 @@ export const AllProjectsTests: AcceptanceTests = { 'contains target file package-lock.json', ); }, - '`test mono-repo-project with lockfiles --all-projects`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - - // mock python plugin becuase CI tooling doesn't have pipenv installed - const mockPlugin = { - async inspect() { - return { - plugin: { - targetFile: 'Pipfile', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - loadPlugin.withArgs('pip').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock other plugins + '`test mono-repo-project with lockfiles --all-projects`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); - const result: CommandResult = await params.cli.test('mono-repo-project', { - allProjects: true, - detectionDepth: 1, - skipUnresolved: true, - }); - t.ok(loadPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); - t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); - t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); - t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); - t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); - t.ok(loadPlugin.withArgs('pip').calledOnce, 'calls pip plugin'); - - params.server.popRequests(6).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); - t.match( - req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven|nuget|paket|pip)/, - 'depGraph has package manager', - ); - }); - // results should contain test results from both package managers + // mock python plugin becuase CI tooling doesn't have pipenv installed + const mockPlugin = { + async inspect() { + return { + plugin: { + targetFile: 'Pipfile', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin.withArgs('pip').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock other plugins - t.match( - result.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - result.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - t.match( - result.getDisplayResults(), - 'Project name: shallow-goof', - 'contains correct project name for npm', - ); - t.match( - result.getDisplayResults(), - 'Package manager: npm', - 'contains package manager npm', - ); - t.match( - result.getDisplayResults(), - 'Target file: package-lock.json', - 'contains target file package-lock.json', - ); - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', + const result: CommandResult = await params.cli.test('mono-repo-project', { + allProjects: true, + detectionDepth: 1, + skipUnresolved: true, + }); + t.ok(loadPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); + t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); + t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); + t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('pip').calledOnce, 'calls pip plugin'); + + params.server.popRequests(6).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); t.match( - result.getDisplayResults(), - 'Target file: Pipfile', - 'contains target file Pipfile', + req.body.depGraph.pkgManager.name, + /(npm|rubygems|maven|nuget|paket|pip)/, + 'depGraph has package manager', ); - }, - - '`test mono-repo-project --all-projects --detection-depth=3`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - - // mock python plugin becuase CI tooling doesn't have pipenv installed - const mockPipfile = { - async inspect() { - return { - plugin: { - targetFile: 'Pipfile', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const mockRequirements = { - async inspect() { - return { - plugin: { - targetFile: 'python-app-with-req-file/requirements.txt', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - // detect pip plugin called only with Pipfile (and not requirement.txt) - loadPlugin - .withArgs('pip', sinon.match.has('file', 'Pipfile')) - .returns(mockPipfile) - .withArgs( - 'pip', - sinon.match.has('file', 'python-app-with-req-file/requirements.txt'), - ) - .returns(mockRequirements); - loadPlugin.callThrough(); // don't mock other plugins - - const result: CommandResult = await params.cli.test('mono-repo-project', { - allProjects: true, - detectionDepth: 3, - allowMissing: true, // allow requirements.txt to pass when deps not installed - }); + }); + // results should contain test results from both package managers - t.equals( - loadPlugin.withArgs('rubygems').callCount, - 2, - 'calls rubygems plugin', - ); - t.equals(loadPlugin.withArgs('npm').callCount, 2, 'calls npm plugin'); - t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); - t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); - t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); - t.equals(loadPlugin.withArgs('pip').callCount, 2, 'calls pip plugin'); - - params.server.popRequests(9).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); - t.match( - req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven|nuget|paket|pip)/, - 'depGraph has package manager', - ); - }); + t.match( + result.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', + ); + t.match( + result.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', + ); + t.match( + result.getDisplayResults(), + 'Project name: shallow-goof', + 'contains correct project name for npm', + ); + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', + ); + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); + t.match( + result.getDisplayResults(), + 'Target file: Pipfile', + 'contains target file Pipfile', + ); + }, - // ruby - t.match( - result.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - result.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - t.match( - result.getDisplayResults(), - `Target file: bundler-app${path.sep}Gemfile.lock`, - `contains target file bundler-app${path.sep}Gemfile.lock`, - ); + '`test mono-repo-project --all-projects --detection-depth=3`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); - // npm - t.match( - result.getDisplayResults(), - 'Project name: shallow-goof', - 'contains correct project name for npm', - ); - t.match( - result.getDisplayResults(), - 'Project name: goof', - 'contains correct project name for npm', - ); - t.match( - result.getDisplayResults(), - 'Package manager: npm', - 'contains package manager npm', - ); - t.match( - result.getDisplayResults(), - 'Target file: package-lock.json', - 'contains target file package-lock.json', - ); - t.match( - result.getDisplayResults(), - `Target file: npm-project${path.sep}package.json`, - `contains target file npm-project${path.sep}package.json`, - ); + // mock python plugin becuase CI tooling doesn't have pipenv installed + const mockPipfile = { + async inspect() { + return { + plugin: { + targetFile: 'Pipfile', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const mockRequirements = { + async inspect() { + return { + plugin: { + targetFile: 'python-app-with-req-file/requirements.txt', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + // detect pip plugin called only with Pipfile (and not requirement.txt) + loadPlugin + .withArgs('pip', sinon.match.has('file', 'Pipfile')) + .returns(mockPipfile) + .withArgs( + 'pip', + sinon.match.has('file', 'python-app-with-req-file/requirements.txt'), + ) + .returns(mockRequirements); + loadPlugin.callThrough(); // don't mock other plugins + + const result: CommandResult = await params.cli.test('mono-repo-project', { + allProjects: true, + detectionDepth: 3, + allowMissing: true, // allow requirements.txt to pass when deps not installed + }); - // maven - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', - ); + t.equals( + loadPlugin.withArgs('rubygems').callCount, + 2, + 'calls rubygems plugin', + ); + t.equals(loadPlugin.withArgs('npm').callCount, 2, 'calls npm plugin'); + t.ok(loadPlugin.withArgs('maven').calledOnce, 'calls maven plugin'); + t.ok(loadPlugin.withArgs('nuget').calledOnce, 'calls nuget plugin'); + t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); + t.equals(loadPlugin.withArgs('pip').callCount, 2, 'calls pip plugin'); - // nuget - t.match( - result.getDisplayResults(), - 'Package manager: nuget', - 'contains package manager nuget', + params.server.popRequests(9).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); t.match( - result.getDisplayResults(), - 'Target file: packages.config', - 'contains target file packages.config', + req.body.depGraph.pkgManager.name, + /(npm|rubygems|maven|nuget|paket|pip)/, + 'depGraph has package manager', ); + }); - // paket - t.match( - result.getDisplayResults(), - 'Package manager: paket', - 'contains package manager paket', - ); - t.match( - result.getDisplayResults(), - 'Target file: paket.dependencies', - 'contains target file paket.dependencies', - ); + // ruby + t.match( + result.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', + ); + t.match( + result.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', + ); + t.match( + result.getDisplayResults(), + `Target file: bundler-app${path.sep}Gemfile.lock`, + `contains target file bundler-app${path.sep}Gemfile.lock`, + ); - // pip - t.match( - result.getDisplayResults(), - 'Package manager: pip', - 'contains package manager pip', - ); - t.match( - result.getDisplayResults(), - 'Target file: Pipfile', - 'contains target file Pipfile', + // npm + t.match( + result.getDisplayResults(), + 'Project name: shallow-goof', + 'contains correct project name for npm', + ); + t.match( + result.getDisplayResults(), + 'Project name: goof', + 'contains correct project name for npm', + ); + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', + ); + t.match( + result.getDisplayResults(), + `Target file: npm-project${path.sep}package.json`, + `contains target file npm-project${path.sep}package.json`, + ); + + // maven + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); + + // nuget + t.match( + result.getDisplayResults(), + 'Package manager: nuget', + 'contains package manager nuget', + ); + t.match( + result.getDisplayResults(), + 'Target file: packages.config', + 'contains target file packages.config', + ); + + // paket + t.match( + result.getDisplayResults(), + 'Package manager: paket', + 'contains package manager paket', + ); + t.match( + result.getDisplayResults(), + 'Target file: paket.dependencies', + 'contains target file paket.dependencies', + ); + + // pip + t.match( + result.getDisplayResults(), + 'Package manager: pip', + 'contains package manager pip', + ); + t.match( + result.getDisplayResults(), + 'Target file: Pipfile', + 'contains target file Pipfile', + ); + t.match( + result.getDisplayResults(), + `Target file: python-app-with-req-file${path.sep}requirements.txt`, + `contains target file python-app-with-req-file${path.sep}requirements.txt`, + ); + }, + + '`test mono-repo-project --all-projects and --file payloads are the same`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + + // mock python plugin becuase CI tooling doesn't have pipenv installed + const mockPlugin = { + async inspect() { + return { + plugin: { + targetFile: 'Pipfile', + name: 'snyk-python-plugin', + }, + package: {}, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin.withArgs('pip').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock other plugins + + await params.cli.test('mono-repo-project', { + allProjects: true, + detectionDepth: 1, + }); + + const requests = params.server.popRequests(6); + + // find each type of request + const rubyAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'rubygems', + ); + const pipAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'pip', + ); + const npmAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'npm', + ); + const nugetAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'nuget', + ); + const paketAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'paket', + ); + const mavenAll = requests.find( + (req) => req.body.depGraph.pkgManager.name === 'maven', + ); + + await params.cli.test('mono-repo-project', { + file: 'Gemfile.lock', + }); + const rubyFile = params.server.popRequest(); + + await params.cli.test('mono-repo-project', { + file: 'Pipfile', + }); + const pipFile = params.server.popRequest(); + + await params.cli.test('mono-repo-project', { + file: 'paket.dependencies', + }); + const paketFile = params.server.popRequest(); + + await params.cli.test('mono-repo-project', { + file: 'packages.config', + }); + const nugetFile = params.server.popRequest(); + + await params.cli.test('mono-repo-project', { + file: 'package-lock.json', + }); + const npmFile = params.server.popRequest(); + + await params.cli.test('mono-repo-project', { + file: 'pom.xml', + }); + const mavenFile = params.server.popRequest(); + + t.same( + pipAll.body, + pipFile.body, + 'Same body for --all-projects and --file=Pipfile', + ); + + t.same( + rubyAll.body, + rubyFile.body, + 'Same body for --all-projects and --file=Gemfile.lock', + ); + + t.same( + npmAll.body, + npmFile.body, + 'Same body for --all-projects and --file=package-lock.json', + ); + + t.same( + paketAll.body, + paketFile.body, + 'Same body for --all-projects and --file=package-lock.json', + ); + + t.same( + nugetAll.body, + nugetFile.body, + 'Same body for --all-projects and --file=package-lock.json', + ); + t.same( + mavenAll.body, + mavenFile.body, + 'Same body for --all-projects and --file=pom.xml', + ); + }, + + '`test maven-multi-app --all-projects --detection-depth=2`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + + const result: CommandResult = await params.cli.test('maven-multi-app', { + allProjects: true, + detectionDepth: 2, + }); + + t.ok(spyPlugin.withArgs('maven').calledTwice, 'calls maven plugin'); + t.ok( + spyPlugin.withArgs('rubygems').notCalled, + 'did not call rubygems plugin', + ); + t.ok(spyPlugin.withArgs('npm').notCalled, 'did not call npm plugin'); + params.server.popRequests(2).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', ); + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); t.match( - result.getDisplayResults(), - `Target file: python-app-with-req-file${path.sep}requirements.txt`, - `contains target file python-app-with-req-file${path.sep}requirements.txt`, + req.body.depGraph.pkgManager.name, + /maven/, + 'depGraph has package manager', ); - }, - - '`test mono-repo-project --all-projects and --file payloads are the same`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - - // mock python plugin becuase CI tooling doesn't have pipenv installed - const mockPlugin = { - async inspect() { - return { - plugin: { - targetFile: 'Pipfile', - name: 'snyk-python-plugin', - }, - package: {}, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - loadPlugin.withArgs('pip').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock other plugins + }); + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); + t.match( + result.getDisplayResults(), + `Target file: simple-child${path.sep}pom.xml`, + `contains target file simple-child${path.sep}pom.xml`, + ); + }, - await params.cli.test('mono-repo-project', { - allProjects: true, - detectionDepth: 1, - }); + '`test large-mono-repo with --all-projects and --detection-depth=2`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + await params.cli.test('large-mono-repo', { + allProjects: true, + detectionDepth: 2, + }); + t.equals( + spyPlugin.withArgs('rubygems').callCount, + 1, + 'calls rubygems plugin once', + ); + t.equals( + spyPlugin.withArgs('npm').callCount, + 19, + 'calls npm plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('maven').callCount, + 1, + 'calls maven plugin once', + ); + }, - const requests = params.server.popRequests(6); + '`test large-mono-repo with --all-projects and --detection-depth=7`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + await params.cli.test('large-mono-repo', { + allProjects: true, + detectionDepth: 7, + }); + t.equals( + spyPlugin.withArgs('rubygems').callCount, + 19, + 'calls rubygems plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('npm').callCount, + 19, + 'calls npm plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('maven').callCount, + 6, + 'calls maven plugin 6 times', + ); + }, - // find each type of request - const rubyAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'rubygems', - ); - const pipAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'pip', - ); - const npmAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'npm', - ); - const nugetAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'nuget', - ); - const paketAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'paket', + '`test mono-repo-project-manifests-only --all-projects`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const result: CommandResult = await params.cli.test( + 'mono-repo-project-manifests-only', + { + allProjects: true, + }, + ); + params.server.popRequests(3).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', ); - const mavenAll = requests.find( - (req) => req.body.depGraph.pkgManager.name === 'maven', + t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); + t.ok(req.body.depGraph, 'body contains depGraph'); + t.match( + req.body.depGraph.pkgManager.name, + /(npm|rubygems|maven)/, + 'depGraph has package manager', ); + }); - await params.cli.test('mono-repo-project', { - file: 'Gemfile.lock', - }); - const rubyFile = params.server.popRequest(); + // results should contain test results from all package managers + t.match( + result.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', + ); + t.match( + result.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', + ); + t.match( + result.getDisplayResults(), + 'Package manager: npm', + 'contains package manager npm', + ); + t.match( + result.getDisplayResults(), + 'Target file: package-lock.json', + 'contains target file package-lock.json', + ); + t.match( + result.getDisplayResults(), + 'Package manager: maven', + 'contains package manager maven', + ); + t.match( + result.getDisplayResults(), + 'Target file: pom.xml', + 'contains target file pom.xml', + ); + }, - await params.cli.test('mono-repo-project', { - file: 'Pipfile', - }); - const pipFile = params.server.popRequest(); + '`test ruby-app --all-projects`': (params, utils) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); - await params.cli.test('mono-repo-project', { - file: 'paket.dependencies', - }); - const paketFile = params.server.popRequest(); + const res: CommandResult = await params.cli.test('ruby-app', { + allProjects: true, + }); - await params.cli.test('mono-repo-project', { - file: 'packages.config', - }); - const nugetFile = params.server.popRequest(); + t.ok(spyPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); + t.notOk(spyPlugin.withArgs('npm').calledOnce, "doesn't call npm plugin"); + t.notOk( + spyPlugin.withArgs('maven').calledOnce, + "doesn't call maven plugin", + ); - await params.cli.test('mono-repo-project', { - file: 'package-lock.json', - }); - const npmFile = params.server.popRequest(); + t.match( + res.getDisplayResults(), + 'Package manager: rubygems', + 'contains package manager rubygems', + ); + t.match( + res.getDisplayResults(), + 'Target file: Gemfile.lock', + 'contains target file Gemfile.lock', + ); + }, - await params.cli.test('mono-repo-project', { - file: 'pom.xml', + '`test ruby-app-thresholds --all-projects --ignore-policy`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces('ruby-app-thresholds'); + params.server.setNextResponse( + getWorkspaceJSON( + 'ruby-app-thresholds', + 'test-graph-result-medium-severity.json', + ), + ); + try { + await params.cli.test('./', { + 'ignore-policy': true, + allProjects: true, }); - const mavenFile = params.server.popRequest(); - - t.same( - pipAll.body, - pipFile.body, - 'Same body for --all-projects and --file=Pipfile', - ); - - t.same( - rubyAll.body, - rubyFile.body, - 'Same body for --all-projects and --file=Gemfile.lock', - ); - - t.same( - npmAll.body, - npmFile.body, - 'Same body for --all-projects and --file=package-lock.json', - ); - - t.same( - paketAll.body, - paketFile.body, - 'Same body for --all-projects and --file=package-lock.json', - ); - - t.same( - nugetAll.body, - nugetFile.body, - 'Same body for --all-projects and --file=package-lock.json', - ); - t.same( - mavenAll.body, - mavenFile.body, - 'Same body for --all-projects and --file=pom.xml', + t.fail('should have thrown'); + } catch (err) { + const req = params.server.popRequest(); + t.equal(req.query.ignorePolicy, 'true', 'should request ignore policy'); + const res = err.message; + t.match( + res, + 'Tested 7 dependencies for known vulnerabilities, found 5 vulnerabilities, 6 vulnerable paths.', + 'should display expected message', ); - }, + } + }, - '`test maven-multi-app --all-projects --detection-depth=2`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); + '`test large-mono-repo with --all-projects, --detection-depth=7 and --exclude=bundler-app,maven-project-1`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + await params.cli.test('large-mono-repo', { + allProjects: true, + detectionDepth: 7, + exclude: 'bundler-app,maven-project-1', + }); + t.equals( + spyPlugin.withArgs('rubygems').callCount, + 0, + 'does not call rubygems', + ); + t.equals( + spyPlugin.withArgs('npm').callCount, + 19, + 'calls npm plugin 19 times', + ); + t.equals( + spyPlugin.withArgs('maven').callCount, + 1, + 'calls maven plugin once, excluding the rest', + ); + }, - const result: CommandResult = await params.cli.test('maven-multi-app', { + '`test empty --all-projects`': (params, utils) => async (t) => { + utils.chdirWorkspaces(); + try { + await params.cli.test('empty', { allProjects: true, - detectionDepth: 2, - }); - - t.ok(spyPlugin.withArgs('maven').calledTwice, 'calls maven plugin'); - t.ok( - spyPlugin.withArgs('rubygems').notCalled, - 'did not call rubygems plugin', - ); - t.ok(spyPlugin.withArgs('npm').notCalled, 'did not call npm plugin'); - params.server.popRequests(2).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); - t.match( - req.body.depGraph.pkgManager.name, - /maven/, - 'depGraph has package manager', - ); }); + t.fail('expected an error to be thrown'); + } catch (err) { + const res = err.message; t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', - ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', - ); - t.match( - result.getDisplayResults(), - `Target file: simple-child${path.sep}pom.xml`, - `contains target file simple-child${path.sep}pom.xml`, - ); - }, - - '`test large-mono-repo with --all-projects and --detection-depth=2`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - await params.cli.test('large-mono-repo', { - allProjects: true, - detectionDepth: 2, - }); - t.equals( - spyPlugin.withArgs('rubygems').callCount, - 1, - 'calls rubygems plugin once', - ); - t.equals( - spyPlugin.withArgs('npm').callCount, - 19, - 'calls npm plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('maven').callCount, - 1, - 'calls maven plugin once', - ); - }, - - '`test large-mono-repo with --all-projects and --detection-depth=7`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - await params.cli.test('large-mono-repo', { - allProjects: true, - detectionDepth: 7, - }); - t.equals( - spyPlugin.withArgs('rubygems').callCount, - 19, - 'calls rubygems plugin 19 times', + res, + 'Could not detect supported target files', + 'should display expected message', ); - t.equals( - spyPlugin.withArgs('npm').callCount, - 19, - 'calls npm plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('maven').callCount, - 6, - 'calls maven plugin 6 times', - ); - }, - - '`test mono-repo-project-manifests-only --all-projects`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const result: CommandResult = await params.cli.test( - 'mono-repo-project-manifests-only', + } + }, + + '`test monorepo-with-nuget --all-projects with Nuget, Python, Go, Npm, Cocoapods`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const mockPlugin = { + async inspect() { + return { + package: {}, + plugin: { + name: 'mock', + }, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + // prevent plugin inspect from actually running (requires go to be installed) + loadPlugin.withArgs('golangdep').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock other plugins + + try { + const res: CommandResult = await params.cli.test( + 'monorepo-with-nuget', { allProjects: true, + detectionDepth: 4, }, ); - params.server.popRequests(3).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/test-dep-graph', 'posts to correct url'); - t.ok(req.body.depGraph, 'body contains depGraph'); - t.match( - req.body.depGraph.pkgManager.name, - /(npm|rubygems|maven)/, - 'depGraph has package manager', - ); - }); - - // results should contain test results from all package managers - t.match( - result.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', - ); - t.match( - result.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - t.match( - result.getDisplayResults(), - 'Package manager: npm', - 'contains package manager npm', - ); - t.match( - result.getDisplayResults(), - 'Target file: package-lock.json', - 'contains target file package-lock.json', - ); - t.match( - result.getDisplayResults(), - 'Package manager: maven', - 'contains package manager maven', + t.equal( + loadPlugin.withArgs('nuget').callCount, + 2, + 'calls nuget plugin twice', ); - t.match( - result.getDisplayResults(), - 'Target file: pom.xml', - 'contains target file pom.xml', + t.ok( + loadPlugin.withArgs('cocoapods').calledOnce, + 'calls cocoapods plugin', ); - }, - - '`test ruby-app --all-projects`': (params, utils) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - - const res: CommandResult = await params.cli.test('ruby-app', { - allProjects: true, - }); - - t.ok(spyPlugin.withArgs('rubygems').calledOnce, 'calls rubygems plugin'); - t.notOk(spyPlugin.withArgs('npm').calledOnce, "doesn't call npm plugin"); - t.notOk( - spyPlugin.withArgs('maven').calledOnce, - "doesn't call maven plugin", + t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); + t.ok( + loadPlugin.withArgs('golangdep').calledOnce, + 'calls golangdep plugin', ); - + t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); t.match( res.getDisplayResults(), - 'Package manager: rubygems', - 'contains package manager rubygems', + /Tested 6 projects, no vulnerable paths were found./, + 'Six projects tested', ); t.match( res.getDisplayResults(), - 'Target file: Gemfile.lock', - 'contains target file Gemfile.lock', - ); - }, - - '`test ruby-app-thresholds --all-projects --ignore-policy`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces('ruby-app-thresholds'); - params.server.setNextResponse( - getWorkspaceJSON( - 'ruby-app-thresholds', - 'test-graph-result-medium-severity.json', - ), - ); - try { - await params.cli.test('./', { - 'ignore-policy': true, - allProjects: true, - }); - t.fail('should have thrown'); - } catch (err) { - const req = params.server.popRequest(); - t.equal(req.query.ignorePolicy, 'true', 'should request ignore policy'); - const res = err.message; - t.match( - res, - 'Tested 7 dependencies for known vulnerabilities, found 5 vulnerabilities, 6 vulnerable paths.', - 'should display expected message', - ); - } - }, - - '`test large-mono-repo with --all-projects, --detection-depth=7 and --exclude=bundler-app,maven-project-1`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - await params.cli.test('large-mono-repo', { - allProjects: true, - detectionDepth: 7, - exclude: 'bundler-app,maven-project-1', - }); - t.equals( - spyPlugin.withArgs('rubygems').callCount, - 0, - 'does not call rubygems', - ); - t.equals( - spyPlugin.withArgs('npm').callCount, - 19, - 'calls npm plugin 19 times', - ); - t.equals( - spyPlugin.withArgs('maven').callCount, - 1, - 'calls maven plugin once, excluding the rest', - ); - }, - - '`test empty --all-projects`': (params, utils) => async (t) => { - utils.chdirWorkspaces(); - try { - await params.cli.test('empty', { - allProjects: true, - }); - t.fail('expected an error to be thrown'); - } catch (err) { - const res = err.message; - t.match( - res, - 'Could not detect supported target files', - 'should display expected message', - ); - } - }, - - '`test monorepo-with-nuget --all-projects with Nuget, Python, Go, Npm, Cocoapods`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const mockPlugin = { - async inspect() { - return { - package: {}, - plugin: { - name: 'mock', - }, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - // prevent plugin inspect from actually running (requires go to be installed) - loadPlugin.withArgs('golangdep').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock other plugins - - try { - const res: CommandResult = await params.cli.test( - 'monorepo-with-nuget', - { - allProjects: true, - detectionDepth: 4, - }, - ); - t.equal( - loadPlugin.withArgs('nuget').callCount, - 2, - 'calls nuget plugin twice', - ); - t.ok( - loadPlugin.withArgs('cocoapods').calledOnce, - 'calls cocoapods plugin', - ); - t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); - t.ok( - loadPlugin.withArgs('golangdep').calledOnce, - 'calls golangdep plugin', - ); - t.ok(loadPlugin.withArgs('paket').calledOnce, 'calls nuget plugin'); - t.match( - res.getDisplayResults(), - /Tested 6 projects, no vulnerable paths were found./, - 'Six projects tested', - ); - t.match( - res.getDisplayResults(), - `Target file: src${path.sep}paymentservice${path.sep}package-lock.json`, - 'Npm project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: src${path.sep}cocoapods-app${path.sep}Podfile`, - 'Cocoapods project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: src${path.sep}frontend${path.sep}Gopkg.lock`, - 'Go dep project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: src${path.sep}cartservice-nuget${path.sep}obj${path.sep}project.assets.json`, - 'Nuget project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: test${path.sep}nuget-app-4${path.sep}packages.config`, - 'Nuget project targetFile is as expected', - ); - t.match( - res.getDisplayResults(), - `Target file: test${path.sep}paket-app${path.sep}paket.dependencies`, - 'Paket project targetFile is as expected', - ); - - t.match( - res.getDisplayResults(), - 'Package manager: nuget', - 'Nuget package manager', - ); - t.match( - res.getDisplayResults(), - 'Package manager: cocoapods', - 'Cocoapods package manager', - ); - t.match( - res.getDisplayResults(), - 'Package manager: npm', - 'Npm package manager', - ); - t.match( - res.getDisplayResults(), - 'Package manager: golangdep', - 'Go dep package manager', - ); - } catch (err) { - t.fail('expected to pass'); - } - }, - '`test composer-app --all-projects`': (params, utils) => async (t) => { - utils.chdirWorkspaces(); - const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); - t.teardown(spyPlugin.restore); - - const result: CommandResult = await params.cli.test('composer-app', { - allProjects: true, - }); - - t.ok(spyPlugin.withArgs('composer').calledOnce, 'calls composer plugin'); - - params.server.popRequests(2).forEach((req) => { - t.equal(req.method, 'POST', 'makes POST request'); - t.equal( - req.headers['x-snyk-cli-version'], - params.versionNumber, - 'sends version number', - ); - t.match(req.url, '/api/v1/test', 'posts to correct url'); - }); - t.match( - result.getDisplayResults(), - 'Package manager: composer', - 'contains package manager composer', - ); - t.match( - result.getDisplayResults(), - 'Target file: composer.lock', - 'contains target file composer.lock', - ); - }, - '`test mono-repo-go --all-projects --detection-depth=2`': ( - params, - utils, - ) => async (t) => { - utils.chdirWorkspaces(); - const mockPlugin = { - async inspect() { - return { - package: {}, - plugin: { - name: 'mock', - }, - }; - }, - }; - const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); - t.teardown(loadPlugin.restore); - // prevent plugin inspect from actually running (requires go to be installed) - loadPlugin.withArgs('golangdep').returns(mockPlugin); - loadPlugin.withArgs('gomodules').returns(mockPlugin); - loadPlugin.withArgs('govendor').returns(mockPlugin); - loadPlugin.callThrough(); // don't mock npm plugin - - const res: CommandResult = await params.cli.test('mono-repo-go', { - allProjects: true, - detectionDepth: 3, - }); - t.ok(loadPlugin.withArgs('golangdep').calledOnce, 'calls go dep plugin'); - t.ok(loadPlugin.withArgs('gomodules').calledOnce, 'calls go mod plugin'); - t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); - t.ok( - loadPlugin.withArgs('govendor').calledOnce, - 'calls go vendor plugin', + `Target file: src${path.sep}paymentservice${path.sep}package-lock.json`, + 'Npm project targetFile is as expected', ); t.match( res.getDisplayResults(), - /Tested 4 projects, no vulnerable paths were found./, - 'Four projects tested', + `Target file: src${path.sep}cocoapods-app${path.sep}Podfile`, + 'Cocoapods project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: hello-dep${path.sep}Gopkg.lock`, + `Target file: src${path.sep}frontend${path.sep}Gopkg.lock`, 'Go dep project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: hello-mod${path.sep}go.mod`, - 'Go mod project targetFile is as expected', + `Target file: src${path.sep}cartservice-nuget${path.sep}obj${path.sep}project.assets.json`, + 'Nuget project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: hello-node${path.sep}package-lock.json`, - 'Npm project targetFile is as expected', + `Target file: test${path.sep}nuget-app-4${path.sep}packages.config`, + 'Nuget project targetFile is as expected', ); t.match( res.getDisplayResults(), - `Target file: hello-vendor${path.sep}vendor${path.sep}vendor.json`, - 'Go vendor project targetFile is as expected', + `Target file: test${path.sep}paket-app${path.sep}paket.dependencies`, + 'Paket project targetFile is as expected', ); + t.match( res.getDisplayResults(), - 'Package manager: golangdep', + 'Package manager: nuget', 'Nuget package manager', ); t.match( res.getDisplayResults(), - 'Package manager: gomodules', - 'Nuget package manager', + 'Package manager: cocoapods', + 'Cocoapods package manager', ); t.match( res.getDisplayResults(), @@ -932,9 +818,123 @@ export const AllProjectsTests: AcceptanceTests = { ); t.match( res.getDisplayResults(), - 'Package manager: govendor', + 'Package manager: golangdep', 'Go dep package manager', ); - }, + } catch (err) { + t.fail('expected to pass'); + } + }, + '`test composer-app --all-projects`': (params, utils) => async (t) => { + utils.chdirWorkspaces(); + const spyPlugin = sinon.spy(params.plugins, 'loadPlugin'); + t.teardown(spyPlugin.restore); + + const result: CommandResult = await params.cli.test('composer-app', { + allProjects: true, + }); + + t.ok(spyPlugin.withArgs('composer').calledOnce, 'calls composer plugin'); + + params.server.popRequests(2).forEach((req) => { + t.equal(req.method, 'POST', 'makes POST request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + t.match(req.url, '/api/v1/test', 'posts to correct url'); + }); + t.match( + result.getDisplayResults(), + 'Package manager: composer', + 'contains package manager composer', + ); + t.match( + result.getDisplayResults(), + 'Target file: composer.lock', + 'contains target file composer.lock', + ); + }, + '`test mono-repo-go --all-projects --detection-depth=2`': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const mockPlugin = { + async inspect() { + return { + package: {}, + plugin: { + name: 'mock', + }, + }; + }, + }; + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + // prevent plugin inspect from actually running (requires go to be installed) + loadPlugin.withArgs('golangdep').returns(mockPlugin); + loadPlugin.withArgs('gomodules').returns(mockPlugin); + loadPlugin.withArgs('govendor').returns(mockPlugin); + loadPlugin.callThrough(); // don't mock npm plugin + + const res: CommandResult = await params.cli.test('mono-repo-go', { + allProjects: true, + detectionDepth: 3, + }); + t.ok(loadPlugin.withArgs('golangdep').calledOnce, 'calls go dep plugin'); + t.ok(loadPlugin.withArgs('gomodules').calledOnce, 'calls go mod plugin'); + t.ok(loadPlugin.withArgs('npm').calledOnce, 'calls npm plugin'); + t.ok( + loadPlugin.withArgs('govendor').calledOnce, + 'calls go vendor plugin', + ); + t.match( + res.getDisplayResults(), + /Tested 4 projects, no vulnerable paths were found./, + 'Four projects tested', + ); + t.match( + res.getDisplayResults(), + `Target file: hello-dep${path.sep}Gopkg.lock`, + 'Go dep project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: hello-mod${path.sep}go.mod`, + 'Go mod project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: hello-node${path.sep}package-lock.json`, + 'Npm project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + `Target file: hello-vendor${path.sep}vendor${path.sep}vendor.json`, + 'Go vendor project targetFile is as expected', + ); + t.match( + res.getDisplayResults(), + 'Package manager: golangdep', + 'Nuget package manager', + ); + t.match( + res.getDisplayResults(), + 'Package manager: gomodules', + 'Nuget package manager', + ); + t.match( + res.getDisplayResults(), + 'Package manager: npm', + 'Npm package manager', + ); + t.match( + res.getDisplayResults(), + 'Package manager: govendor', + 'Go dep package manager', + ); + }, }, }; diff --git a/test/acceptance/cli-test/cli-test.gradle.spec.ts b/test/acceptance/cli-test/cli-test.gradle.spec.ts index 323e3db499e..7713d8e38a0 100644 --- a/test/acceptance/cli-test/cli-test.gradle.spec.ts +++ b/test/acceptance/cli-test/cli-test.gradle.spec.ts @@ -112,6 +112,50 @@ export const GradleTests: AcceptanceTests = { }); t.true(((spyPlugin.args[0] as any)[2] as any).allSubProjects); }, + '`test gradle-app --all-sub-projects` with policy': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const plugin = { + async inspect() { + return { plugin: { name: 'gradle' }, package: {} }; + }, + }; + const spyPlugin = sinon.spy(plugin, 'inspect'); + const loadPlugin = sinon.stub(params.plugins, 'loadPlugin'); + t.teardown(loadPlugin.restore); + loadPlugin.withArgs('gradle').returns(plugin); + + await params.cli.test('gradle-app', { + allSubProjects: true, + }); + t.true(((spyPlugin.args[0] as any)[2] as any).allSubProjects); + const requests = params.server.popRequests(2); + let policyCount = 0; + requests.forEach((req) => { + if ( + req.body.displayTargetFile.endsWith('gradle-multi-project/subproj') + ) { + // TODO: this should return 1 policy when fixed + // uncomment then + // t.match( + // req.body.policy, + // 'SNYK-JAVA-ORGBOUNCYCASTLE-32364', + // 'policy is found & sent', + // ); + t.ok( + req.body.policy, + undefined, + 'policy is not found even though it should be', + ); + policyCount += 1; + } + t.match(req.url, '/test-dep-graph', 'posts to correct url'); + }); + // TODO: this should return 1 policy when fixed + t.equal(policyCount, 0, 'one sub-project policy found & sent'); + }, '`test gradle-app` plugin fails to return package or scannedProjects': ( params, diff --git a/test/acceptance/workspaces/gradle-multi-project/build.gradle b/test/acceptance/workspaces/gradle-multi-project/build.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/acceptance/workspaces/gradle-multi-project/settings.gradle b/test/acceptance/workspaces/gradle-multi-project/settings.gradle new file mode 100644 index 00000000000..90289e8a32b --- /dev/null +++ b/test/acceptance/workspaces/gradle-multi-project/settings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'root-proj' + +include 'subproj' + + diff --git a/test/acceptance/workspaces/gradle-multi-project/subproj/.snyk b/test/acceptance/workspaces/gradle-multi-project/subproj/.snyk new file mode 100644 index 00000000000..d46ea24efac --- /dev/null +++ b/test/acceptance/workspaces/gradle-multi-project/subproj/.snyk @@ -0,0 +1,9 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JAVA-ORGBOUNCYCASTLE-32364: + - '*': + reason: None Given + expires: 2020-07-19T11:33:19.028Z +patch: {} diff --git a/test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle b/test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle new file mode 100644 index 00000000000..301e7be03bc --- /dev/null +++ b/test/acceptance/workspaces/gradle-multi-project/subproj/build.gradle @@ -0,0 +1,51 @@ +apply plugin: 'java' +apply plugin: 'maven' + +group = 'com.github.jitpack' + +sourceCompatibility = 1.8 // java 8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + // Gradle 3+ will not pick up "compile" dependencies for "compileOnly" + // Gradle 2 will, so for configuration-matching tests we use "runtime" + runtime 'com.google.guava:guava:18.0' + runtime 'batik:batik-dom:1.6' + runtime 'commons-discovery:commons-discovery:0.2' + compileOnly 'axis:axis:1.3' + runtime 'com.android.tools.build:builder:2.3.0' +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +// To specify a license in the pom: +install { + repositories.mavenInstaller { + pom.project { + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + } + } +}