Repository: lirantal/lockfile-lint
Pull request #20 (2019-10-13 13:24:23)

feat: provide the actual bad string in ValidateScheme
<!--- Provide a general summary of your changes in the Title above -->

## Description

In ValidateScheme providing the actual scheme string against which we validate.

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
<!--- Unit tests are expected for all changes. -->
Added/Updated unit tests.

## Screenshots (if appropriate):

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] I have updated the documentation (if required).
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] I added a picture of a cute animal cause it's fun
![funnycat](https://user-images.githubusercontent.com/15200937/66767623-77143f00-eece-11e9-8aa7-b0230766813a.jpg)



codecov-io: # [Codecov](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=h1) Report
> Merging [#20](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=desc) into [master](https://codecov.io/gh/lirantal/lockfile-lint/commit/4d07dde25b70ba119f20c72a6308564e75e343e5?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/lirantal/lockfile-lint/pull/20/graphs/tree.svg?width=650&token=P36VJa7ksa&height=150&src=pr)](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master     #20   +/-   ##
======================================
  Coverage    96.1%   96.1%           
======================================
  Files           9       9           
  Lines         154     154           
  Branches       21      21           
======================================
  Hits          148     148           
  Misses          6       6
```


| [Impacted Files](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...lockfile-lint-api/src/validators/ValidateScheme.js](https://codecov.io/gh/lirantal/lockfile-lint/pull/20/diff?src=pr&el=tree#diff-cGFja2FnZXMvbG9ja2ZpbGUtbGludC1hcGkvc3JjL3ZhbGlkYXRvcnMvVmFsaWRhdGVTY2hlbWUuanM=) | `94.73% <ø> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=footer). Last update [4d07dde...9140b45](https://codecov.io/gh/lirantal/lockfile-lint/pull/20?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/packages/lockfile-lint-api/CHANGELOG.md b/packages/lockfile-lint-api/CHANGELOG.md
index 2f04d90..fd5e0b1 100644
--- a/packages/lockfile-lint-api/CHANGELOG.md
+++ b/packages/lockfile-lint-api/CHANGELOG.md
@@ -3,6 +3,28 @@
 All notable changes to this project will be documented in this file.
 See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
 
+# [2.2.0](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint-api@2.1.4...lockfile-lint-api@2.2.0) (2019-10-24)
+
+
+### Features
+
+* **errors:** user friendly error messages in lockfile parsing ([#22](https://github.com/lirantal/lockfile-lint/issues/22)) ([17654d0](https://github.com/lirantal/lockfile-lint/commit/17654d0))
+
+
+
+
+
+## [2.1.4](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint-api@2.1.3...lockfile-lint-api@2.1.4) (2019-10-15)
+
+
+### Bug Fixes
+
+* **readme:** fixing typo in README.md example ([#19](https://github.com/lirantal/lockfile-lint/issues/19)) ([7307a74](https://github.com/lirantal/lockfile-lint/commit/7307a74))
+
+
+
+
+
 ## [2.1.3](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint-api@2.1.2...lockfile-lint-api@2.1.3) (2019-10-13)
 
 
diff --git a/packages/lockfile-lint-api/README.md b/packages/lockfile-lint-api/README.md
index 8eb90de..d6ea800 100644
--- a/packages/lockfile-lint-api/README.md
+++ b/packages/lockfile-lint-api/README.md
@@ -113,7 +113,7 @@ const {ValidateHost, ParseLockFile} = require('lockfile-lint-api')
 // path to the lockfile
 const yarnLockFilePath = '/path/to/my/yarn.lock'
 const options = {
-  lockFilePath: yarnLockFilePath
+  lockfilePath: yarnLockFilePath
 }
 
 // instantiate a new parser with options object
diff --git a/packages/lockfile-lint-api/package.json b/packages/lockfile-lint-api/package.json
index b37a188..470296d 100644
--- a/packages/lockfile-lint-api/package.json
+++ b/packages/lockfile-lint-api/package.json
@@ -1,6 +1,6 @@
 {
   "name": "lockfile-lint-api",
-  "version": "2.1.3",
+  "version": "2.2.0",
   "description": "Lint an npm or yarn lockfile to analyze and detect issues",
   "main": "index.js",
   "engines": {
diff --git a/packages/lockfile-lint-api/src/ParseLockfile.js b/packages/lockfile-lint-api/src/ParseLockfile.js
index 26784be..d0bbb8f 100644
--- a/packages/lockfile-lint-api/src/ParseLockfile.js
+++ b/packages/lockfile-lint-api/src/ParseLockfile.js
@@ -4,6 +4,15 @@
 const fs = require('fs')
 const path = require('path')
 const yarnLockfileParser = require('@yarnpkg/lockfile')
+const {ParsingError, ERROR_MESSAGES} = require('./common/ParsingError')
+const {
+  NO_OPTIONS,
+  NO_PARSER_FOR_PATH,
+  NO_PARSER_FOR_TYPE,
+  READ_FAILED,
+  PARSE_NPMLOCKFILE_FAILED,
+  PARSE_YARNLOCKFILE_FAILED
+} = ERROR_MESSAGES
 
 class ParseLockfile {
   /**
@@ -14,7 +23,7 @@ class ParseLockfile {
    */
   constructor (options) {
     if (!options || typeof options !== 'object') {
-      throw new Error('expecting options object')
+      throw new ParsingError(NO_OPTIONS)
     }
 
     this.options = {}
@@ -22,6 +31,14 @@ class ParseLockfile {
     this.options.lockfileType = options.lockfileType
   }
 
+  /**
+   * Checks if lockfile type option was provided
+   * @return boolean
+   */
+  isLockfileTypeGiven () {
+    return typeof this.options.lockfileType === 'string' && this.options.lockfileType
+  }
+
   /**
    * Synchronously parses a lockfile
    * @return {object} parsed file
@@ -29,11 +46,20 @@ class ParseLockfile {
   parseSync () {
     const lockfileParser = this.resolvePkgMgrForLockfile()
     if (!lockfileParser) {
-      throw new Error('unable to find relevant lockfile parser')
+      if (this.isLockfileTypeGiven()) {
+        throw new ParsingError(NO_PARSER_FOR_TYPE, this.options.lockfileType)
+      }
+      throw new ParsingError(NO_PARSER_FOR_PATH, this.options.lockfilePath)
+    }
+
+    let file
+    try {
+      // eslint-disable-next-line security/detect-non-literal-fs-filename
+      file = fs.readFileSync(this.options.lockfilePath, 'utf8')
+    } catch (error) {
+      throw new ParsingError(READ_FAILED, this.options.lockfilePath, error)
     }
 
-    // eslint-disable-next-line security/detect-non-literal-fs-filename
-    const file = fs.readFileSync(this.options.lockfilePath, 'utf8')
     return lockfileParser.call(this, file)
   }
 
@@ -46,7 +72,7 @@ class ParseLockfile {
     }
 
     let resolver
-    if (typeof this.options.lockfileType === 'string' && this.options.lockfileType) {
+    if (this.isLockfileTypeGiven()) {
       resolver = lockfileResolversByPackageManager[this.options.lockfileType]
     }
 
@@ -70,17 +96,27 @@ class ParseLockfile {
   }
 
   parseYarnLockfile (lockfileBuffer) {
-    return yarnLockfileParser.parse(lockfileBuffer)
+    let parsedFile
+    try {
+      parsedFile = yarnLockfileParser.parse(lockfileBuffer)
+    } catch (error) {
+      throw new ParsingError(PARSE_YARNLOCKFILE_FAILED, this.options.lockfilePath, error)
+    }
+    return parsedFile
   }
 
   parseNpmLockfile (lockfileBuffer) {
-    const packageJsonParsed = JSON.parse(lockfileBuffer)
-
-    // transform original format of npm's package-json
-    // to match yarns so we have a unified format to validate
-    // against
-    const npmDepsTree = packageJsonParsed.dependencies
-    const flattenedDepTree = this._flattenNpmDepsTree(npmDepsTree)
+    let flattenedDepTree
+    try {
+      const packageJsonParsed = JSON.parse(lockfileBuffer)
+
+      // transform original format of npm's package-json to match yarns
+      // so we have a unified format to validate against
+      const npmDepsTree = packageJsonParsed.dependencies
+      flattenedDepTree = this._flattenNpmDepsTree(npmDepsTree)
+    } catch (error) {
+      throw new ParsingError(PARSE_NPMLOCKFILE_FAILED, this.options.lockfilePath, error)
+    }
 
     return {
       type: 'success',
diff --git a/packages/lockfile-lint-api/src/common/PackageError.js b/packages/lockfile-lint-api/src/common/PackageError.js
index 04fefa1..c94fef8 100644
--- a/packages/lockfile-lint-api/src/common/PackageError.js
+++ b/packages/lockfile-lint-api/src/common/PackageError.js
@@ -4,7 +4,7 @@ module.exports = class PackageError extends Error {
   /**
    * constructor
    * @param {string} packageName - the name of the package where the error occured
-   * @param {string} error - the original error object
+   * @param {Error} error - the original error object
    */
   constructor (packageName = '', error = {}) {
     super()
diff --git a/packages/lockfile-lint-api/src/common/ParsingError.js b/packages/lockfile-lint-api/src/common/ParsingError.js
new file mode 100644
index 0000000..e8dcc36
--- /dev/null
+++ b/packages/lockfile-lint-api/src/common/ParsingError.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const {LOCKFILE_TYPES} = require('./constants')
+
+const ERROR_MESSAGES = {
+  NO_OPTIONS: () => 'Did not receive options for lockfile path or type',
+  NO_PARSER_FOR_TYPE: type =>
+    `Unable to find relevant lockfile parser for type "${type}", the currently available options are ${LOCKFILE_TYPES}.`,
+  NO_PARSER_FOR_PATH: path =>
+    `Unable to find relevant lockfile parser for "${path}", consider passing the --type option.`,
+  READ_FAILED: path => `Unable to read lockfile "${path}"`,
+  PARSE_NPMLOCKFILE_FAILED: path => `Unable to parse npm lockfile "${path}"`,
+  PARSE_YARNLOCKFILE_FAILED: path => `Unable to parse yarn lockfile "${path}"`
+}
+
+class ParsingError extends Error {
+  /**
+   * constructor
+   * @param {string} errorKey - the key corresponding to the error message
+   * @param {string} relatedValue - the value related to the error, to be used in the error message
+   * @param {Error} error - the original error (if one exists)
+   */
+  constructor (errorFn, relatedValue = '', error = {}) {
+    super()
+    this.name = 'ParsingError'
+    this.message = typeof errorFn === 'function' ? errorFn(relatedValue) : 'INVALID ERROR KEY'
+    this.stack = error.stack || new Error().stack
+  }
+}
+
+module.exports = {
+  ParsingError,
+  ERROR_MESSAGES
+}
diff --git a/packages/lockfile-lint-api/src/common/constants.js b/packages/lockfile-lint-api/src/common/constants.js
new file mode 100644
index 0000000..e85c509
--- /dev/null
+++ b/packages/lockfile-lint-api/src/common/constants.js
@@ -0,0 +1,10 @@
+'use strict'
+
+module.exports = {
+  LOCKFILE_TYPES: ['npm', 'yarn'], // currently supported types
+  REGISTRY: {
+    npm: 'registry.npmjs.org',
+    yarn: 'registry.yarnpkg.com',
+    verdaccio: 'registry.verdaccio.org'
+  }
+}
diff --git a/packages/lockfile-lint-api/src/validators/ValidateHost.js b/packages/lockfile-lint-api/src/validators/ValidateHost.js
index ae80d55..3499efc 100644
--- a/packages/lockfile-lint-api/src/validators/ValidateHost.js
+++ b/packages/lockfile-lint-api/src/validators/ValidateHost.js
@@ -2,12 +2,7 @@
 
 const {URL} = require('url')
 const PackageError = require('../common/PackageError')
-
-const REGISTRY = {
-  npm: 'registry.npmjs.org',
-  yarn: 'registry.yarnpkg.com',
-  verdaccio: 'registry.verdaccio.org'
-}
+const {REGISTRY} = require('../common/constants')
 
 module.exports = class ValidateHost {
   constructor ({packages} = {}) {
@@ -44,7 +39,9 @@ module.exports = class ValidateHost {
       if (allowedHosts.indexOf(packageResolvedURL.host) === -1) {
         // throw new Error(`detected invalid origin for package: ${packageName}`)
         validationResult.errors.push({
-          message: `detected invalid host for package: ${packageName}`,
+          message: `detected invalid host(s) for package: ${packageName}\n    expected: ${allowedHosts}\n    actual: ${
+            packageResolvedURL.host
+          }\n`,
           package: packageName
         })
       }
diff --git a/packages/lockfile-lint-api/src/validators/ValidateHttps.js b/packages/lockfile-lint-api/src/validators/ValidateHttps.js
index 16fd5f1..dc64ffc 100644
--- a/packages/lockfile-lint-api/src/validators/ValidateHttps.js
+++ b/packages/lockfile-lint-api/src/validators/ValidateHttps.js
@@ -30,7 +30,9 @@ module.exports = class ValidateHttps {
 
       if (packageResolvedURL.protocol !== HTTPS_PROTOCOL) {
         validationResult.errors.push({
-          message: `detected non-https protocol used for package: ${packageName}`,
+          message: `detected invalid protocol for package: ${packageName}\n    expected: ${HTTPS_PROTOCOL}\n    actual: ${
+            packageResolvedURL.protocol
+          }\n`,
           package: packageName
         })
       }
diff --git a/packages/lockfile-lint-api/src/validators/ValidateScheme.js b/packages/lockfile-lint-api/src/validators/ValidateScheme.js
index ab0d4dc..9cab6e1 100644
--- a/packages/lockfile-lint-api/src/validators/ValidateScheme.js
+++ b/packages/lockfile-lint-api/src/validators/ValidateScheme.js
@@ -29,11 +29,12 @@ module.exports = class ValidateProtocol {
       } catch (error) {
         throw new PackageError(packageName, error)
       }
-
       if (schemes.indexOf(packageResolvedURL.protocol) === -1) {
         // throw new Error(`detected invalid origin for package: ${packageName}`)
         validationResult.errors.push({
-          message: `detected invalid scheme for package: ${packageName}`,
+          message: `detected invalid scheme(s) for package: ${packageName}\n    expected: ${schemes}\n    actual: ${
+            packageResolvedURL.protocol
+          }\n`,
           package: packageName
         })
       }
diff --git a/packages/lockfile-lint/CHANGELOG.md b/packages/lockfile-lint/CHANGELOG.md
index 8ef1fb7..7aa3277 100644
--- a/packages/lockfile-lint/CHANGELOG.md
+++ b/packages/lockfile-lint/CHANGELOG.md
@@ -3,6 +3,22 @@
 All notable changes to this project will be documented in this file.
 See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
 
+## [2.1.6](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint@2.1.5...lockfile-lint@2.1.6) (2019-10-24)
+
+**Note:** Version bump only for package lockfile-lint
+
+
+
+
+
+## [2.1.5](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint@2.1.4...lockfile-lint@2.1.5) (2019-10-15)
+
+**Note:** Version bump only for package lockfile-lint
+
+
+
+
+
 ## [2.1.4](https://github.com/lirantal/lockfile-lint/compare/lockfile-lint@2.1.3...lockfile-lint@2.1.4) (2019-10-13)
 
 
diff --git a/packages/lockfile-lint/bin/lockfile-lint.js b/packages/lockfile-lint/bin/lockfile-lint.js
index d1caaa6..dc76377 100755
--- a/packages/lockfile-lint/bin/lockfile-lint.js
+++ b/packages/lockfile-lint/bin/lockfile-lint.js
@@ -34,7 +34,7 @@ try {
   console.error('ABORTING lockfile lint process due to error exceptions', '\n')
   console.error(error.message, '\n')
   console.error(error.stack, '\n')
-  console.error('error: command failed with exit code 1')
+  console.error('error: command failed with exit code 1', '\n')
   process.exit(1)
 }
 
@@ -45,6 +45,6 @@ debug(`total validator failures: ${validatorFailures}`)
 debug(`total validator successes: ${validatorSuccesses}`)
 
 if (validatorFailures !== 0) {
-  console.error('error: command failed with exit code 1')
+  console.error('error: command failed with exit code 1', '\n')
   process.exit(1)
 }
diff --git a/packages/lockfile-lint/package.json b/packages/lockfile-lint/package.json
index dde82e0..6f252d0 100644
--- a/packages/lockfile-lint/package.json
+++ b/packages/lockfile-lint/package.json
@@ -1,6 +1,6 @@
 {
   "name": "lockfile-lint",
-  "version": "2.1.4",
+  "version": "2.1.6",
   "description": "A CLI to lint a lockfile for security policies",
   "bin": {
     "lockfile-lint": "./bin/lockfile-lint.js"
@@ -53,7 +53,7 @@
   },
   "dependencies": {
     "debug": "^4.1.0",
-    "lockfile-lint-api": "^2.1.3",
+    "lockfile-lint-api": "^2.2.0",
     "yargs": "^13.2.4"
   },
   "devDependencies": {


Test patch:
diff --git a/packages/lockfile-lint-api/__tests__/__fixtures__/bad-package-lock.json b/packages/lockfile-lint-api/__tests__/__fixtures__/bad-package-lock.json
new file mode 100644
index 0000000..dc0c882
--- /dev/null
+++ b/packages/lockfile-lint-api/__tests__/__fixtures__/bad-package-lock.json
@@ -0,0 +1,3 @@
+{
+    "cat-generator": meow
+}
\ No newline at end of file
diff --git a/packages/lockfile-lint-api/__tests__/__fixtures__/bad-yarn.lock b/packages/lockfile-lint-api/__tests__/__fixtures__/bad-yarn.lock
new file mode 100644
index 0000000..dc0c882
--- /dev/null
+++ b/packages/lockfile-lint-api/__tests__/__fixtures__/bad-yarn.lock
@@ -0,0 +1,3 @@
+{
+    "cat-generator": meow
+}
\ No newline at end of file
diff --git a/packages/lockfile-lint-api/__tests__/parseLockFile.test.js b/packages/lockfile-lint-api/__tests__/parseLockFile.test.js
deleted file mode 100644
index 6bc6f6a..0000000
--- a/packages/lockfile-lint-api/__tests__/parseLockFile.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* eslint-disable no-new */
-'use strict'
-
-const ParseLockfile = require('../src/ParseLockfile')
-
-describe('ParseLockfile', () => {
-  it('instantiating a parser with invalid object throws an error', () => {
-    expect(() => {
-      new ParseLockfile()
-    }).toThrowError('expecting options object')
-  })
-
-  it('instantiating a parser with string type throws an error', () => {
-    expect(() => {
-      new ParseLockfile('/path/to/file')
-    }).toThrowError('expecting options object')
-  })
-})
diff --git a/packages/lockfile-lint-api/__tests__/parseLockfile.test.js b/packages/lockfile-lint-api/__tests__/parseLockfile.test.js
index 6bc6f6a..c235a21 100644
--- a/packages/lockfile-lint-api/__tests__/parseLockfile.test.js
+++ b/packages/lockfile-lint-api/__tests__/parseLockfile.test.js
@@ -4,15 +4,46 @@
 const ParseLockfile = require('../src/ParseLockfile')
 
 describe('ParseLockfile', () => {
+  it('instantiating a parser correctly works', () => {
+    expect(() => {
+      new ParseLockfile({lockfilePath: '/path/to/file', lockfileType: 'npm'})
+    }).not.toThrowError()
+  })
+
   it('instantiating a parser with invalid object throws an error', () => {
     expect(() => {
       new ParseLockfile()
-    }).toThrowError('expecting options object')
+    }).toThrowError('Did not receive options for lockfile path or type')
   })
 
   it('instantiating a parser with string type throws an error', () => {
     expect(() => {
       new ParseLockfile('/path/to/file')
-    }).toThrowError('expecting options object')
+    }).toThrowError('Did not receive options for lockfile path or type')
+  })
+
+  it('using a parser with an invalid option type throws an error', () => {
+    const parser = new ParseLockfile({lockfilePath: '/path/to/file', lockfileType: 'meow'})
+    expect(() => {
+      parser.parseSync()
+    }).toThrowError(
+      'Unable to find relevant lockfile parser for type "meow", the currently available options are npm,yarn.'
+    )
+  })
+
+  it('using a parser with a path option for a lockfile of unknown type throws an error', () => {
+    const parser = new ParseLockfile({lockfilePath: '/path/to/file'})
+    expect(() => {
+      parser.parseSync()
+    }).toThrowError(
+      'Unable to find relevant lockfile parser for "/path/to/file", consider passing the --type option.'
+    )
+  })
+
+  it('using a parser with a path option for a lockfile that does not exist throws an error', () => {
+    const parser = new ParseLockfile({lockfilePath: '/path/to/file', lockfileType: 'yarn'})
+    expect(() => {
+      parser.parseSync()
+    }).toThrowError('Unable to read lockfile "/path/to/file"')
   })
 })
diff --git a/packages/lockfile-lint-api/__tests__/parseNpmLockfile.test.js b/packages/lockfile-lint-api/__tests__/parseNpmLockfile.test.js
index b9e9e17..d9b1fa6 100644
--- a/packages/lockfile-lint-api/__tests__/parseNpmLockfile.test.js
+++ b/packages/lockfile-lint-api/__tests__/parseNpmLockfile.test.js
@@ -42,4 +42,16 @@ describe('ParseLockfile Npm', () => {
       })
     )
   })
+
+  it('parsing an npm lockfile with invalid content throws an error', () => {
+    const mockNpmLockfilePath = path.join(__dirname, './__fixtures__/bad-package-lock.json')
+    const options = {
+      lockfilePath: mockNpmLockfilePath,
+      lockfileType: 'npm'
+    }
+    const parser = new ParseLockfile(options)
+    expect(() => parser.parseSync()).toThrowError(
+      `Unable to parse npm lockfile "${mockNpmLockfilePath}"`
+    )
+  })
 })
diff --git a/packages/lockfile-lint-api/__tests__/parseYarnLockfile.test.js b/packages/lockfile-lint-api/__tests__/parseYarnLockfile.test.js
index 645dab6..db253c0 100644
--- a/packages/lockfile-lint-api/__tests__/parseYarnLockfile.test.js
+++ b/packages/lockfile-lint-api/__tests__/parseYarnLockfile.test.js
@@ -23,4 +23,16 @@ describe('ParseLockfile Yarn', () => {
       })
     )
   })
+
+  it('parsing a yarn lockfile with invalid content throws an error', () => {
+    const mockYarnLockfilePath = path.join(__dirname, './__fixtures__/bad-yarn.lock')
+    const options = {
+      lockfilePath: mockYarnLockfilePath,
+      lockfileType: 'yarn'
+    }
+    const parser = new ParseLockfile(options)
+    expect(() => parser.parseSync()).toThrowError(
+      `Unable to parse yarn lockfile "${mockYarnLockfilePath}"`
+    )
+  })
 })
diff --git a/packages/lockfile-lint-api/__tests__/validators.host.test.js b/packages/lockfile-lint-api/__tests__/validators.host.test.js
index dbfc45c..eda88d7 100644
--- a/packages/lockfile-lint-api/__tests__/validators.host.test.js
+++ b/packages/lockfile-lint-api/__tests__/validators.host.test.js
@@ -36,7 +36,7 @@ describe('Validator: Host', () => {
       type: 'error',
       errors: [
         {
-          message: 'detected invalid host for package: @babel/code-frame',
+          message: `detected invalid host(s) for package: @babel/code-frame\n    expected: registry.npmjs.org\n    actual: registry.yarnpkg.com\n`,
           package: '@babel/code-frame'
         }
       ]
@@ -137,7 +137,7 @@ describe('Validator: Host', () => {
       type: 'error',
       errors: [
         {
-          message: 'detected invalid host for package: @babel/code-frame',
+          message: `detected invalid host(s) for package: @babel/code-frame\n    expected: registry.npmjs.org,github.com\n    actual: registry.yarnpkg.com\n`,
           package: '@babel/code-frame'
         }
       ]
diff --git a/packages/lockfile-lint-api/__tests__/validators.https.test.js b/packages/lockfile-lint-api/__tests__/validators.https.test.js
index 2f4dab6..c8ebbc1 100644
--- a/packages/lockfile-lint-api/__tests__/validators.https.test.js
+++ b/packages/lockfile-lint-api/__tests__/validators.https.test.js
@@ -33,7 +33,7 @@ describe('Validator: HTTPS', () => {
       type: 'error',
       errors: [
         {
-          message: `detected non-https protocol used for package: ${failedPackage}`,
+          message: `detected invalid protocol for package: ${failedPackage}\n    expected: https:\n    actual: http:\n`,
           package: failedPackage
         }
       ]
diff --git a/packages/lockfile-lint-api/__tests__/validators.scheme.test.js b/packages/lockfile-lint-api/__tests__/validators.scheme.test.js
index 14e6dd2..2684fdd 100644
--- a/packages/lockfile-lint-api/__tests__/validators.scheme.test.js
+++ b/packages/lockfile-lint-api/__tests__/validators.scheme.test.js
@@ -30,11 +30,11 @@ describe('Validator: Protocol', () => {
       type: 'error',
       errors: [
         {
-          message: `detected invalid scheme for package: meow`,
+          message: `detected invalid scheme(s) for package: meow\n    expected: ${allowedProtocols}\n    actual: http:\n`,
           package: 'meow'
         },
         {
-          message: `detected invalid scheme for package: @babel/generator`,
+          message: `detected invalid scheme(s) for package: @babel/generator\n    expected: ${allowedProtocols}\n    actual: git+ssh:\n`,
           package: '@babel/generator'
         }
       ]
diff --git a/packages/lockfile-lint/__tests__/cli.test.js b/packages/lockfile-lint/__tests__/cli.test.js
index d0cf4f9..f9198dd 100644
--- a/packages/lockfile-lint/__tests__/cli.test.js
+++ b/packages/lockfile-lint/__tests__/cli.test.js
@@ -50,10 +50,10 @@ describe('CLI tests', () => {
     })
 
     process.stdout.on('close', exit => {
-      expect(output.indexOf('detected non-https protocol used for package: debug@^4.1.1')).not.toBe(
+      expect(output.indexOf('detected invalid protocol for package: debug@^4.1.1\n    expected: https:\n    actual: http:\n')).not.toBe(
         -1
       )
-      expect(output.indexOf('detected non-https protocol used for package: ms@^2.1.1')).not.toBe(-1)
+      expect(output.indexOf('detected invalid protocol for package: ms@^2.1.1\n    expected: https:\n    actual: http:\n')).not.toBe(-1)
       expect(output.indexOf('error: command failed with exit code 1')).not.toBe(-1)
       done()
     })