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 9b7a03e920..b66af921b3 100644 --- a/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts +++ b/test/acceptance/cli-monitor/cli-monitor.all-projects.spec.ts @@ -664,8 +664,7 @@ export const AllProjectsTests: AcceptanceTests = { 'same body for --all-projects and --file=mono-repo-go/hello-vendor/vendor/vendor.json', ); }, - - '`monitor mono-repo-go with --all-projects and --detectin-depth=3`': ( + '`monitor mono-repo-go with --all-projects and --detection-depth=3`': ( params, utils, ) => async (t) => { @@ -955,5 +954,105 @@ export const AllProjectsTests: AcceptanceTests = { ); }); }, + 'monitor yarn-workspaces --all-projects --detection-depth=5 finds Yarn workspaces & Npm projects': ( + params, + utils, + ) => async (t) => { + t.teardown(() => { + loadPlugin.restore(); + }); + utils.chdirWorkspaces(); + const loadPlugin = sinon.spy(params.plugins, 'loadPlugin'); + + const result = await params.cli.monitor('yarn-workspaces', { + allProjects: true, + detectionDepth: 5, + }); + // the parser is used directly + t.ok( + loadPlugin.withArgs('yarn').notCalled, + 'skips load plugin for yarn as a parser is used directly', + ); + t.equal(loadPlugin.withArgs('npm').callCount, 1, 'calls npm plugin once'); + + t.match( + result, + 'Monitoring yarn-workspaces (package.json)', + 'yarn workspace root was monitored', + ); + t.match( + result, + 'Monitoring yarn-workspaces (apple-lib)', + 'yarn workspace was monitored', + ); + t.match( + result, + 'Monitoring yarn-workspaces (apples)', + 'yarn workspace was monitored', + ); + t.match( + result, + 'Monitoring yarn-workspaces (tomatoes)', + 'yarn workspace was monitored', + ); + t.match( + result, + 'Monitoring yarn-workspaces (not-in-a-workspace)', + 'npm project was monitored', + ); + + const requests = params.server + .getRequests() + .filter((req) => req.url.includes('/monitor/')); + t.equal(requests.length, 5, 'correct amount of monitor requests'); + let policyCount = 0; + const applesWorkspace = + process.platform === 'win32' + ? '\\apples\\package.json' + : 'apples/package.json'; + const tomatoesWorkspace = + process.platform === 'win32' + ? '\\tomatoes\\package.json' + : 'tomatoes/package.json'; + const rootWorkspace = + process.platform === 'win32' + ? '\\yarn-workspaces\\package.json' + : 'yarn-workspaces/package.json'; + requests.forEach((req) => { + t.match( + req.url, + /\/api\/v1\/monitor\/(yarn\/graph|npm\/graph)/, + 'puts at correct url', + ); + t.equal(req.method, 'PUT', 'makes PUT request'); + t.equal( + req.headers['x-snyk-cli-version'], + params.versionNumber, + 'sends version number', + ); + if (req.body.targetFileRelativePath.endsWith(applesWorkspace)) { + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'policy is as expected', + ); + t.ok(req.body.policy, 'body contains policy'); + policyCount += 1; + } else if ( + req.body.targetFileRelativePath.endsWith(tomatoesWorkspace) + ) { + t.notOk(req.body.policy, 'body does not contain policy'); + } else if (req.body.targetFileRelativePath.endsWith(rootWorkspace)) { + t.match( + req.body.policy, + 'npm:node-uuid:20111130', + 'policy is as expected', + ); + t.ok(req.body.policy, 'body contains policy'); + policyCount += 1; + } + }); + t.equal(policyCount, 2, '2 policies found in a workspace'); + }, }, }; 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 7dd1ddb5d8..14a1e70079 100644 --- a/test/acceptance/cli-test/cli-test.all-projects.spec.ts +++ b/test/acceptance/cli-test/cli-test.all-projects.spec.ts @@ -1,5 +1,5 @@ -import * as path from 'path'; import * as sinon from 'sinon'; +import * as path from 'path'; import * as depGraphLib from '@snyk/dep-graph'; import { CommandResult } from '../../../src/cli/commands/types'; import { AcceptanceTests } from './cli-test.acceptance.test'; @@ -577,5 +577,141 @@ export const AllProjectsTests: AcceptanceTests = { 'Go dep package manager', ); }, + 'test yarn-workspaces-v2-resolutions --all-projects --detection-depth=5 --strict-out-of-sync=false (yarn v2 with resolutions)': ( + params, + utils, + ) => async (t) => { + // Yarn workspaces for Yarn 2 is only supported on Node 10+ + utils.chdirWorkspaces(); + const result = await params.cli.test('yarn-workspaces-v2-resolutions', { + allProjects: true, + detectionDepth: 5, + strictOutOfSync: false, + printDeps: true, + }); + const loadPlugin = sinon.spy(params.plugins, 'loadPlugin'); + // the parser is used directly + t.ok(loadPlugin.withArgs('yarn').notCalled, 'skips load plugin'); + t.teardown(() => { + loadPlugin.restore(); + }); + t.match( + result.getDisplayResults(), + '✔ Tested 1 dependencies for known vulnerabilities, no vulnerable paths found.', + 'correctly showing dep number', + ); + t.match(result.getDisplayResults(), 'Package manager: yarn\n'); + t.match( + result.getDisplayResults(), + 'Project name: package.json', + 'yarn project in output', + ); + t.match( + result.getDisplayResults(), + 'Project name: tomatoes', + 'yarn project in output', + ); + t.match( + result.getDisplayResults(), + 'Project name: apples', + 'yarn project in output', + ); + t.match( + result.getDisplayResults(), + 'Tested 3 projects, no vulnerable paths were found.', + 'no vulnerable paths found as both policies detected and applied.', + ); + }, + 'test --all-projects --detection-depth=5 finds Yarn workspaces & Npm projects': ( + params, + utils, + ) => async (t) => { + utils.chdirWorkspaces(); + const result = await params.cli.test('yarn-workspaces', { + allProjects: true, + detectionDepth: 5, + }); + const loadPlugin = sinon.spy(params.plugins, 'loadPlugin'); + // the parser is used directly + t.ok(loadPlugin.withArgs('yarn').notCalled, 'skips load plugin'); + t.teardown(() => { + loadPlugin.restore(); + }); + const output = result.getDisplayResults(); + + t.match(output, 'Package manager: yarn\n'); + t.match(output, 'Package manager: npm\n'); + t.match( + output, + 'Project name: not-in-a-workspace', + 'npm project in output', + ); + t.match( + output, + 'Project name: package.json', + 'yarn project in output', + ); + t.match(output, 'Project name: tomatoes', 'yarn project in output'); + t.match(output, 'Project name: apples', 'yarn project in output'); + t.match(output, 'Project name: apple-lib', 'yarn project in output'); + + t.match( + output, + 'Tested 5 projects, no vulnerable paths were found.', + 'tested 4 workspace projects + 1 npm project', + ); + let policyCount = 0; + const applesWorkspace = + process.platform === 'win32' + ? '\\apples\\package.json' + : 'apples/package.json'; + const tomatoesWorkspace = + process.platform === 'win32' + ? '\\tomatoes\\package.json' + : 'tomatoes/package.json'; + const rootWorkspace = + process.platform === 'win32' + ? '\\yarn-workspaces\\package.json' + : 'yarn-workspaces/package.json'; + + params.server.popRequests(5).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'); + + if (req.body.targetFileRelativePath.endsWith(applesWorkspace)) { + t.match( + req.body.policy, + 'npm:node-uuid:20160328', + 'policy is as expected', + ); + t.ok(req.body.policy, 'body contains policy'); + policyCount += 1; + } else if ( + req.body.targetFileRelativePath.endsWith(tomatoesWorkspace) + ) { + t.notOk(req.body.policy, 'body does not contain policy'); + } else if (req.body.targetFileRelativePath.endsWith(rootWorkspace)) { + t.match( + req.body.policy, + 'npm:node-uuid:20111130', + 'policy is as expected', + ); + t.ok(req.body.policy, 'body contains policy'); + policyCount += 1; + } + t.match( + req.body.depGraph.pkgManager.name, + /(yarn|npm)/, + 'depGraph has package manager', + ); + }); + t.equal(policyCount, 2, '2 policies found in a workspace'); + }, }, }; diff --git a/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package-lock.json b/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package-lock.json new file mode 100644 index 0000000000..dd467df273 --- /dev/null +++ b/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package-lock.json @@ -0,0 +1,49 @@ +{ + "name": "not-in-a-workspace", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "not-in-a-workspace", + "dependencies": { + "debug": "4.3.2" + } + }, + "node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } +} diff --git a/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package.json b/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package.json index d257399515..9d4328c21b 100644 --- a/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package.json +++ b/test/acceptance/workspaces/yarn-workspaces/not-part-of-workspace/package.json @@ -1,6 +1,6 @@ { "name": "not-in-a-workspace", "dependencies": { - "debug": "3.6.0" + "debug": "4.3.2" } }