Skip to content

Commit

Permalink
test: yarn workspaces tests & fixtures
Browse files Browse the repository at this point in the history
  • Loading branch information
lili2311 committed Jun 29, 2020
1 parent 51c75d4 commit d942b3c
Show file tree
Hide file tree
Showing 12 changed files with 740 additions and 154 deletions.
36 changes: 27 additions & 9 deletions src/lib/plugins/nodejs-plugin/yarn-workspaces-parser.ts
@@ -1,5 +1,8 @@
import * as baseDebug from 'debug';
const debug = baseDebug('yarn-workspaces');
import * as pathUtil from 'path';
import * as _ from 'lodash';

const debug = baseDebug('snyk:yarn-workspaces');
import * as fs from 'fs';
import * as lockFileParser from 'snyk-nodejs-lockfile-parser';
import * as path from 'path';
Expand All @@ -17,9 +20,23 @@ export async function processYarnWorkspaces(
},
targetFiles: string[],
): Promise<MultiProjectResultCustom> {
const yarnTargetFiles = targetFiles.filter((f) => f.endsWith('package.json'));
// the order of folders is important
// must have the root level most folders at the top
const yarnTargetFiles: {
[dir: string]: Array<{
path: string;
base: string;
dir: string;
}>;
} = _(targetFiles)
.map((p) => ({ path: p, ...pathUtil.parse(p) }))
.filter((res) => ['package.json'].includes(res.base))
.sortBy('dir')
.groupBy('dir')
.value();

debug(`processing Yarn workspaces (${targetFiles.length})`);
if (yarnTargetFiles.length === 0) {
if (Object.keys(yarnTargetFiles).length === 0) {
throw NoSupportedManifestsFoundError([root]);
}
let yarnWorkspacesMap = {};
Expand All @@ -32,19 +49,20 @@ export async function processYarnWorkspaces(
},
scannedProjects: [],
};
for (const packageJsonFileName of yarnTargetFiles) {
// the folders must be ordered highest first
for (const directory of Object.keys(yarnTargetFiles)) {
const packageJsonFileName = pathUtil.join(directory, 'package.json');
const packageJson = getFileContents(root, packageJsonFileName);
yarnWorkspacesMap = {
...yarnWorkspacesMap,
...getWorkspacesMap(packageJson),
};

for (const workspaceRoot of Object.keys(yarnWorkspacesMap)) {
const workspaces = yarnWorkspacesMap[workspaceRoot].workspaces || [];
const match = workspaces
.map((pattern) =>
packageJsonFileName.includes(pattern.replace(/\*/, '')),
)
.map((pattern) => {
return packageJsonFileName.includes(pattern.replace(/\*/, ''));
})
.filter(Boolean);

if (match) {
Expand All @@ -71,7 +89,7 @@ export async function processYarnWorkspaces(
);
const project: ScannedProjectCustom = {
packageManager: 'yarn',
targetFile: path.parse(packageJson.name).base,
targetFile: path.relative(root, packageJson.name),
depTree: res as any,
plugin: {
name: 'snyk-nodejs-lockfile-parser',
Expand Down
4 changes: 2 additions & 2 deletions src/lib/snyk-test/run-test.ts
Expand Up @@ -190,10 +190,10 @@ async function parseRes(
pkgManager,
options.severityThreshold,
);

// For Node.js: inject additional information (for remediation etc.) into the response.
if (payload.modules) {
res.dependencyCount =
payload.modules.numDependencies || depGraph.getPkgs().length - 1;
res.dependencyCount = payload.modules.numDependencies;
if (res.vulnerabilities) {
res.vulnerabilities.forEach((vuln) => {
if (payload.modules && payload.modules.pluck) {
Expand Down
2 changes: 2 additions & 0 deletions test/acceptance/cli-test/cli-test.acceptance.test.ts
Expand Up @@ -26,6 +26,7 @@ import { RubyTests } from './cli-test.ruby.spec';
import { SbtTests } from './cli-test.sbt.spec';
import { YarnTests } from './cli-test.yarn.spec';
import { IacK8sTests } from './cli-test.iac-k8s.spec';
import { YarnWorkspacesTests } from './cli-test.yarn-workspaces.spec';
import { AllProjectsTests } from './cli-test.all-projects.spec';

const languageTests: AcceptanceTests[] = [
Expand All @@ -42,6 +43,7 @@ const languageTests: AcceptanceTests[] = [
SbtTests,
YarnTests,
IacK8sTests,
YarnWorkspacesTests,
];

const { test, only } = tap;
Expand Down
185 changes: 138 additions & 47 deletions test/acceptance/cli-test/cli-test.yarn-workspaces.spec.ts
@@ -1,34 +1,40 @@
import * as sinon from 'sinon';

import { AcceptanceTests } from './cli-test.acceptance.test';

export const YarnTests: AcceptanceTests = {
export const YarnWorkspacesTests: AcceptanceTests = {
language: 'Yarn',
tests: {
// yarn lockfile based testing is only supported for node 4+
'`test yarn-workspaces-out-of-sync --yarn-workspaces` out of sync fails': (
'`test yarn-workspace-out-of-sync --yarn-workspaces` out of sync fails': (
params,
utils,
) => async (t) => {
utils.chdirWorkspaces();
try {
await params.cli.test('yarn-out-of-sync', { dev: true });
await params.cli.test('yarn-workspace-out-of-sync', {
dev: true,
yarnWorkspaces: true,
detectionDepth: 3,
});
t.fail('Should fail');
} catch (e) {
t.equal(
e.message,
'\nTesting yarn-out-of-sync...\n\n' +
'\nTesting yarn-workspace-out-of-sync...\n\n' +
'Dependency snyk was not found in yarn.lock.' +
' Your package.json and yarn.lock are probably out of sync.' +
' Please run "yarn install" and try again.',
'Contains enough info about err',
);
}
},
'`test yarn-workspaces-workspace-out-of-sync --yarn-workspaces --strict-out-of-sync=false` passes': (
'`test yarn-workspace-out-of-sync --yarn-workspaces --strict-out-of-sync=false --dev` passes': (
params,
utils,
) => async (t) => {
utils.chdirWorkspaces();
await params.cli.test('yarn-workspaces-out-of-sync', {
await params.cli.test('yarn-workspace-out-of-sync', {
dev: true,
strictOutOfSync: false,
});
Expand All @@ -38,56 +44,141 @@ export const YarnTests: AcceptanceTests = {
t.same(
depGraph.pkgs.map((p) => p.id).sort(),
[
// TODO: add packages
'ansi-regex@2.1.1',
'ansi-regex@3.0.0',
'ansi-styles@3.2.1',
'balanced-match@1.0.0',
'bluebird@3.7.2',
'brace-expansion@1.1.11',
'camelcase@4.1.0',
'chalk@2.4.2',
'cliui@4.1.0',
'code-point-at@1.1.0',
'color-convert@1.9.3',
'color-name@1.1.3',
'concat-map@0.0.1',
'cross-spawn@5.1.0',
'decamelize@1.2.0',
'escape-string-regexp@1.0.5',
'execa@0.7.0',
'find-up@2.1.0',
'fs.realpath@1.0.0',
'get-caller-file@1.0.3',
'get-stream@3.0.0',
'glob@7.1.6',
'has-flag@3.0.0',
'inflight@1.0.6',
'inherits@2.0.4',
'invert-kv@1.0.0',
'is-fullwidth-code-point@1.0.0',
'is-fullwidth-code-point@2.0.0',
'is-stream@1.1.0',
'isexe@2.0.0',
'lcid@1.0.0',
'locate-path@2.0.0',
'lodash@4.17.15',
'lru-cache@4.1.5',
'mem@1.1.0',
'mimic-fn@1.2.0',
'minimatch@3.0.4',
'node-fetch@2.6.0',
'npm-run-path@2.0.2',
'number-is-nan@1.0.1',
'once@1.4.0',
'os-locale@2.1.0',
'p-finally@1.0.0',
'p-limit@1.3.0',
'p-locate@2.0.0',
'p-try@1.0.0',
'package.json@',
'path-exists@3.0.0',
'path-is-absolute@1.0.1',
'path-key@2.0.1',
'pseudomap@1.0.2',
'require-directory@2.1.1',
'require-main-filename@1.0.1',
'set-blocking@2.0.0',
'shebang-command@1.2.0',
'shebang-regex@1.0.0',
'signal-exit@3.0.3',
'snyk@1.320.0',
'split@1.0.1',
'string-width@1.0.2',
'string-width@2.1.1',
'strip-ansi@3.0.1',
'strip-ansi@4.0.0',
'strip-eof@1.0.0',
'supports-color@5.5.0',
'throat@4.1.0',
'through@2.3.8',
'which-module@2.0.0',
'which@1.3.1',
'wrap-ansi@2.1.0',
'wrappy@1.0.2',
'wsrun@3.6.6',
'y18n@3.2.1',
'yallist@2.1.2',
'yargs-parser@8.1.0',
'yargs@10.1.2',
].sort(),
'depGraph looks fine',
);
},

'`test yarn-workspace --yarn-workspaces --dev` sends pkg info': (
params,
utils,
) => async (t) => {
'test --yarn-workspaces --detection-depth=5': (params, utils) => async (
t,
) => {
utils.chdirWorkspaces();
await params.cli.test('yarn-workspace', {
yanrWorkspaces: true,
dev: true,
const result = await params.cli.test('yarn-workspaces', {
yarnWorkspaces: true,
detectionDepth: 5,
});
const req = params.server.popRequest();
t.match(req.url, '/test-dep-graph', 'posts to correct url');
t.match(req.body.targetFile, undefined, 'target is undefined');
const depGraph = req.body.depGraph;
t.same(
depGraph.pkgs.map((p) => p.id).sort(),
[
// TODO: add packages
].sort(),
'depGraph looks fine',
const loadPlugin = sinon.spy(params.plugins, 'loadPlugin');
// the parser is used directly
t.ok(loadPlugin.withArgs('yarn').notCalled, 'skips load plugin');
t.match(result.getDisplayResults(), 'Package manager: yarn\n');
t.match(
result.getDisplayResults(),
'Project name: package.json',
'yarn project in output',
);
},
'`test` on a yarn workspace does work and displays appropriate text': (
params,
utils,
) => async (t) => {
utils.chdirWorkspaces('yarn-workspace');
await params.cli.test();
const req = params.server.popRequest();
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(),
'Project name: tomatoes',
'yarn project in output',
);
t.match(req.url, '/test-dep-graph', 'posts to correct url');
t.match(req.body.targetFile, undefined, 'target is undefined');
const depGraph = req.body.depGraph;
t.same(
depGraph.pkgs.map((p) => p.id).sort(),
[
// TODO: add packages
].sort(),
'depGraph looks fine',
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.',
);

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.ok(req.body.policy, 'body contains policy');
t.equal(
req.body.policyLocations,
['yarn-workspaces', 'yarn-workspaces/apples'],
'policy locations',
);

t.equal(
req.body.depGraph.pkgManager.name,
'yarn',
'depGraph has package manager',
);
});
},
},
};
20 changes: 20 additions & 0 deletions test/acceptance/workspaces/yarn-workspace-out-of-sync/package.json
@@ -0,0 +1,20 @@
{
"private": true,
"workspaces": [
"packages/*"
],
"resolutions": {
"node-fetch": "^2.3.0"
},
"engines": {
"node": "^8.11.1 || ^10.11.0",
"yarn": "1.10.1"
},
"devDependencies": {
"wsrun": "^3.6.2"
},
"dependencies": {
"node-fetch": "^2.3.0",
"snyk": "1.320.0"
}
}
@@ -0,0 +1,13 @@
{
"name": "apples",
"version": "1.0.0",
"license": "UNLICENSED",
"main": "./src/index.js",
"scripts": {
"precommit": "lint-staged"
},
"dependencies": {
"debug": "^2.7.2",
"object-assign": "4.1.1"
}
}
@@ -0,0 +1,10 @@
{
"name": "tomatoes",
"version": "1.0.0",
"license": "UNLICENSED",
"main": "./src/index.js",
"dependencies": {
"object-assign": "4.1.1",
"node-fetch": "2.2.0"
}
}

0 comments on commit d942b3c

Please sign in to comment.