From 1cad9fe0f2b6fe61790bf3a489c56a706c7d00a2 Mon Sep 17 00:00:00 2001 From: Maarten Ackermans Date: Tue, 30 Jul 2019 18:09:29 +0200 Subject: [PATCH 1/3] fix(stdin): throw errors on invalid input replicates behavior prior to addition of stdin feature --- src/command.js | 29 ++++ src/utils.js | 18 ++- test/jq.test.js | 49 +++++- test/options.test.js | 356 +++++++++++++++++++++++++++---------------- 4 files changed, 311 insertions(+), 141 deletions(-) diff --git a/src/command.js b/src/command.js index 905821e..3e7d628 100644 --- a/src/command.js +++ b/src/command.js @@ -4,6 +4,13 @@ import { validateJSONPath } from './utils' const JQ_PATH = process.env.JQ_PATH || path.join(__dirname, '..', 'bin', 'jq') +export const FILTER_UNDEFINED_ERROR = + 'node-jq: invalid filter argument supplied: "undefined"' +export const INPUT_JSON_UNDEFINED_ERROR = + 'node-jq: invalid json object argument supplied: "undefined"' +export const INPUT_STRING_ERROR = + 'node-jq: invalid json string argument supplied' + const getFileArray = path => { if (Array.isArray(path)) { return path.reduce((array, file) => { @@ -15,11 +22,33 @@ const getFileArray = path => { return [path] } +const validateArguments = (filter, json, options) => { + if (typeof filter === 'undefined') { + throw new Error(FILTER_UNDEFINED_ERROR) + } + + switch (options.input) { + case 'json': + if (typeof json === 'undefined') { + throw new Error(INPUT_JSON_UNDEFINED_ERROR) + } + break + case 'string': + if (!json) { + throw new Error(`${INPUT_STRING_ERROR}: "${json === '' ? '' : json}"`) + } + break + } +} + export const commandFactory = (filter, json, options = {}) => { const mergedOptions = { ...optionDefaults, ...options } + + validateArguments(filter, json, mergedOptions) + let args = [filter, ...parseOptions(mergedOptions)] let stdin = '' diff --git a/src/utils.js b/src/utils.js index 86ff21c..71a69e2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,18 +1,20 @@ import isPathValid from 'is-valid-path' -const INVALID_PATH_ERROR = 'Invalid path' -const INVALID_JSON_PATH_ERROR = 'Not a json file' +export const INVALID_PATH_ERROR = + 'node-jq: invalid path argument supplied (not a valid path)' +export const INVALID_JSON_PATH_ERROR = + 'node-jq: invalid path argument supplied (not a .json file)' -export const isJSONPath = (path) => { +export const isJSONPath = path => { return /\.json$/.test(path) } -export const validateJSONPath = (JSONFile) => { - if (!isPathValid(JSONFile)) { - throw (Error(INVALID_PATH_ERROR)) +export const validateJSONPath = path => { + if (!isPathValid(path)) { + throw new Error(`${INVALID_PATH_ERROR}: "${path}"`) } - if (!isJSONPath(JSONFile)) { - throw (Error(INVALID_JSON_PATH_ERROR)) + if (!isJSONPath(path)) { + throw new Error(`${INVALID_JSON_PATH_ERROR}: "${path === '' ? '' : path}"`) } } diff --git a/test/jq.test.js b/test/jq.test.js index 428cad5..1f99744 100644 --- a/test/jq.test.js +++ b/test/jq.test.js @@ -2,6 +2,8 @@ import { expect } from 'chai' import path from 'path' import { run } from '../src/jq' +import { FILTER_UNDEFINED_ERROR } from '../src/command' +import { INVALID_PATH_ERROR, INVALID_JSON_PATH_ERROR } from '../src/utils' const PATH_ROOT = path.join(__dirname, '..') const PATH_ASTERISK_FIXTURE = path.join(PATH_ROOT, 'src', '*.js') @@ -12,14 +14,15 @@ const PATH_JS_FIXTURE = path.join(PATH_FIXTURES, '1.js') const PATH_LARGE_JSON_FIXTURE = path.join(PATH_FIXTURES, 'large.json') const PATH_VARIABLE_JSON_FIXTURE = path.join(PATH_FIXTURES, 'var.json') +const FIXTURE_JSON = require('./fixtures/1.json') +const FIXTURE_JSON_STRING = JSON.stringify(FIXTURE_JSON, null, 2) + const FILTER_VALID = '.repository.type' const FILTER_INVALID = 'invalid' const FILTER_WITH_VARIABLE = '[ . as $x | .user[] | {"user": ., "site": $x.site} ]' const ERROR_INVALID_FILTER = /invalid/ -const ERROR_INVALID_PATH = 'Invalid path' -const ERROR_INVALID_JSON_PATH = 'Not a json file' describe('jq core', () => { it('should fulfill its promise', done => { @@ -33,6 +36,28 @@ describe('jq core', () => { }) }) + it('should pass on an empty filter', done => { + run('', PATH_JSON_FIXTURE) + .then(output => { + expect(output).to.equal(FIXTURE_JSON_STRING) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should pass on a null filter', done => { + run(null, PATH_JSON_FIXTURE) + .then(output => { + expect(output).to.equal('null') + done() + }) + .catch(error => { + done(error) + }) + }) + it('should fail on an invalid filter', done => { run(FILTER_INVALID, PATH_JSON_FIXTURE) .catch(error => { @@ -45,11 +70,25 @@ describe('jq core', () => { }) }) + it('should fail on an undefined filter', done => { + run(undefined, PATH_JSON_FIXTURE) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(FILTER_UNDEFINED_ERROR) + done() + }) + .catch(error => { + done(error) + }) + }) + it('should fail on an invalid path', done => { run(FILTER_VALID, PATH_ASTERISK_FIXTURE) .catch(error => { expect(error).to.be.an.instanceof(Error) - expect(error.message).to.equal(ERROR_INVALID_PATH) + expect(error.message).to.equal( + `${INVALID_PATH_ERROR}: "${PATH_ASTERISK_FIXTURE}"` + ) done() }) .catch(error => { @@ -61,7 +100,9 @@ describe('jq core', () => { run(FILTER_VALID, PATH_JS_FIXTURE) .catch(error => { expect(error).to.be.an.instanceof(Error) - expect(error.message).to.equal(ERROR_INVALID_JSON_PATH) + expect(error.message).to.equal( + `${INVALID_JSON_PATH_ERROR}: "${PATH_JS_FIXTURE}"` + ) done() }) .catch(error => { diff --git a/test/options.test.js b/test/options.test.js index 8cb0373..c66b8dd 100644 --- a/test/options.test.js +++ b/test/options.test.js @@ -3,6 +3,8 @@ import path from 'path' import { run } from '../src/jq' import { optionDefaults } from '../src/options' +import { INPUT_JSON_UNDEFINED_ERROR, INPUT_STRING_ERROR } from '../src/command' +import { INVALID_PATH_ERROR, INVALID_JSON_PATH_ERROR } from '../src/utils' const PATH_FIXTURES = path.join('test', 'fixtures') const PATH_JSON_FIXTURE = path.join(PATH_FIXTURES, '1.json') @@ -22,7 +24,7 @@ const OPTION_DEFAULTS = { raw: false } -const multiEOL = (text) => { +const multiEOL = text => { return [text, text.replace(/\n/g, '\r\n')] } @@ -31,192 +33,288 @@ describe('options', () => { expect(optionDefaults).to.deep.equal(OPTION_DEFAULTS) }) - describe('input: file', () => { - it('should accept a file path as input', (done) => { - run('.', PATH_JSON_FIXTURE, { input: 'file' }) - .then((output) => { - expect(output).to.not.equal(null) - done() - }) - .catch((error) => { - done(error) - }) - }) - }) + describe('input', () => { + describe('file', () => { + it('should accept a file path as input', done => { + run('.', PATH_JSON_FIXTURE, { input: 'file' }) + .then(output => { + expect(output).to.not.equal(null) + done() + }) + .catch(error => { + done(error) + }) + }) - describe('input: file', () => { - it('should accept an array with multiple file paths as input', (done) => { - run('.', [ - PATH_JSON_FIXTURE, - PATH_JSON_FIXTURE - ], { input: 'file' }) - .then((output) => { - expect(output).to.not.equal(null) - done() - }) - .catch((error) => { - done(error) - }) + it('should accept an array with multiple file paths as input', done => { + run('.', [PATH_JSON_FIXTURE, PATH_JSON_FIXTURE], { input: 'file' }) + .then(output => { + expect(output).to.not.equal(null) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on an empty path', done => { + run('.', '', { input: 'file' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(`${INVALID_JSON_PATH_ERROR}: ""`) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on a null path', done => { + run('.', null, { input: 'file' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(`${INVALID_PATH_ERROR}: "null"`) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on an undefined path', done => { + run('.', undefined, { input: 'file' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(`${INVALID_PATH_ERROR}: "undefined"`) + done() + }) + .catch(error => { + done(error) + }) + }) }) - }) - describe('input: json', () => { - it('should accept a json object as input', (done) => { - run('.', FIXTURE_JSON, { input: 'json' }) - .then((output) => { - expect(output).to.not.equal(null) - done() - }) - .catch((error) => { - done(error) - }) + describe('json', () => { + it('should accept a json object as input', done => { + run('.', FIXTURE_JSON, { input: 'json' }) + .then(output => { + expect(output).to.not.equal(null) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should accept an empty string as json object', done => { + run('.', '', { input: 'json' }) + .then(output => { + expect(output).to.equal('""') + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should accept a null json object', done => { + run('.', null, { input: 'json' }) + .then(output => { + expect(output).to.equal('null') + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on an undefined json object', done => { + run('.', undefined, { input: 'json' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(INPUT_JSON_UNDEFINED_ERROR) + done() + }) + .catch(error => { + done(error) + }) + }) }) - }) - describe('input: string', () => { - it('should accept a json string as input', (done) => { - run('.', FIXTURE_JSON_STRING, { input: 'string' }) - .then((output) => { - expect(output).to.not.equal(null) - done() - }) - .catch((error) => { - done(error) - }) + describe('string', () => { + it('should accept a json string as input', done => { + run('.', FIXTURE_JSON_STRING, { input: 'string' }) + .then(output => { + expect(output).to.not.equal(null) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on an empty string', done => { + run('.', '', { input: 'string' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(`${INPUT_STRING_ERROR}: ""`) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on a null string', done => { + run('.', null, { input: 'string' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(`${INPUT_STRING_ERROR}: "null"`) + done() + }) + .catch(error => { + done(error) + }) + }) + + it('should fail on an undefined string', done => { + run('.', undefined, { input: 'string' }) + .catch(error => { + expect(error).to.be.an.instanceof(Error) + expect(error.message).to.equal(`${INPUT_STRING_ERROR}: "undefined"`) + done() + }) + .catch(error => { + done(error) + }) + }) }) }) - describe('output: json', () => { - it('should return a stringified json', (done) => { - run('.', PATH_JSON_FIXTURE, { output: 'json' }) - .then((obj) => { - expect(obj).to.deep.equal(FIXTURE_JSON) - done() - }) - .catch((error) => { - done(error) - }) - }) + describe('output', () => { + describe('json', () => { + it('should return a stringified json', done => { + run('.', PATH_JSON_FIXTURE, { output: 'json' }) + .then(obj => { + expect(obj).to.deep.equal(FIXTURE_JSON) + done() + }) + .catch(error => { + done(error) + }) + }) - it('it should return a string if the filter calls an array', (done) => { - run('.contributors[]', PATH_JSON_FIXTURE, { output: 'json' }) - .then((obj) => { - expect(obj).to.be.oneOf( - multiEOL( - JSON.stringify(FIXTURE_JSON.contributors[0], null, 2) + - '\n' + - JSON.stringify(FIXTURE_JSON.contributors[1], null, 2) + it('it should return a string if the filter calls an array', done => { + run('.contributors[]', PATH_JSON_FIXTURE, { output: 'json' }) + .then(obj => { + expect(obj).to.be.oneOf( + multiEOL( + JSON.stringify(FIXTURE_JSON.contributors[0], null, 2) + + '\n' + + JSON.stringify(FIXTURE_JSON.contributors[1], null, 2) + ) ) - ) - done() - }) - .catch((error) => { - done(error) - }) + done() + }) + .catch(error => { + done(error) + }) + }) }) - }) - describe('output: string', () => { - it('should return a minified json string', (done) => { - run('.', PATH_JSON_FIXTURE, { output: 'string' }) - .then((output) => { - expect(output).to.equal(FIXTURE_JSON_STRING) - done() - }) - .catch((error) => { - done(error) - }) + describe('string', () => { + it('should return a minified json string', done => { + run('.', PATH_JSON_FIXTURE, { output: 'string' }) + .then(output => { + expect(output).to.equal(FIXTURE_JSON_STRING) + done() + }) + .catch(error => { + done(error) + }) + }) }) - }) - describe('output: compact', () => { - it('should return a minified json string', (done) => { - run('.', PATH_JSON_FIXTURE, { output: 'compact' }) - .then((output) => { - expect(output).to.equal(FIXTURE_JSON_STRING) - done() - }) - .catch((error) => { - done(error) - }) + describe('compact', () => { + it('should return a minified json string', done => { + run('.', PATH_JSON_FIXTURE, { output: 'compact' }) + .then(output => { + expect(output).to.equal(FIXTURE_JSON_STRING) + done() + }) + .catch(error => { + done(error) + }) + }) }) - }) - describe('output: pretty', () => { - it('should return a prettified json string', (done) => { - run('.', PATH_JSON_FIXTURE, { output: 'pretty' }) - .then((output) => { - expect(output).to.be.oneOf( - multiEOL(FIXTURE_JSON_PRETTY) - ) - done() - }) - .catch((error) => { - done(error) - }) + describe('pretty', () => { + it('should return a prettified json string', done => { + run('.', PATH_JSON_FIXTURE, { output: 'pretty' }) + .then(output => { + expect(output).to.be.oneOf(multiEOL(FIXTURE_JSON_PRETTY)) + done() + }) + .catch(error => { + done(error) + }) + }) }) }) describe('slurp: true', () => { - it('should run the filter once on multiple objects as input', (done) => { - run('.', [ - PATH_SLURP_FIXTURE_1, - PATH_SLURP_FIXTURE_2 - ], { + it('should run the filter once on multiple objects as input', done => { + run('.', [PATH_SLURP_FIXTURE_1, PATH_SLURP_FIXTURE_2], { output: 'json', slurp: true }) - .then((output) => { + .then(output => { expect(output).to.be.an('array') expect(output).to.have.lengthOf(2) done() }) - .catch((error) => { + .catch(error => { done(error) }) }) }) describe('sort: false', () => { - it('keys should be ordered the same as in input file', (done) => { - run('.', [ - PATH_SORT_FIXTURE - ], { + it('keys should be ordered the same as in input file', done => { + run('.', [PATH_SORT_FIXTURE], { output: 'string', sort: false }) - .then((output) => { + .then(output => { expect(output).to.be.a('string') expect(output).to.eql('{"z":1,"a":2}') done() }) - .catch((error) => { + .catch(error => { done(error) }) }) }) describe('sort: true', () => { - it('keys should be ordered alphabetically', (done) => { - run('.', [ - PATH_SORT_FIXTURE - ], { + it('keys should be ordered alphabetically', done => { + run('.', [PATH_SORT_FIXTURE], { output: 'string', sort: true }) - .then((output) => { + .then(output => { expect(output).to.be.a('string') expect(output).to.eql('{"a":2,"z":1}') done() }) - .catch((error) => { + .catch(error => { done(error) }) }) }) describe('raw: true', () => { - it('output should be able to output raw strings instead of JSON texts', (done) => { + it('output should be able to output raw strings instead of JSON texts', done => { run('.name', PATH_JSON_FIXTURE, { input: 'file', raw: true @@ -228,7 +326,7 @@ describe('options', () => { .catch(e => done(e)) }) - it('output should be able to output JSON texts instead of raw strings', (done) => { + it('output should be able to output JSON texts instead of raw strings', done => { run('.name', PATH_JSON_FIXTURE, { input: 'file', raw: false From 8d45a45e8ce301a0b745d6b9863a380600342aaa Mon Sep 17 00:00:00 2001 From: Maarten Ackermans Date: Tue, 30 Jul 2019 18:20:16 +0200 Subject: [PATCH 2/3] test(windows): remove carriage returns from expected output --- test/jq.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/jq.test.js b/test/jq.test.js index 1f99744..7b39fff 100644 --- a/test/jq.test.js +++ b/test/jq.test.js @@ -39,7 +39,8 @@ describe('jq core', () => { it('should pass on an empty filter', done => { run('', PATH_JSON_FIXTURE) .then(output => { - expect(output).to.equal(FIXTURE_JSON_STRING) + const normalizedOutput = output.replace(/\r\n/g, '\n') + expect(normalizedOutput).to.equal(FIXTURE_JSON_STRING) done() }) .catch(error => { From f1abdfd38fb3d290c99efa91a6a678c2d903d302 Mon Sep 17 00:00:00 2001 From: Maarten Ackermans Date: Tue, 30 Jul 2019 18:24:03 +0200 Subject: [PATCH 3/3] fix(jq-core): check test output value --- test/jq.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jq.test.js b/test/jq.test.js index 7b39fff..ee6d627 100644 --- a/test/jq.test.js +++ b/test/jq.test.js @@ -28,7 +28,7 @@ describe('jq core', () => { it('should fulfill its promise', done => { run(FILTER_VALID, PATH_JSON_FIXTURE) .then(output => { - expect(output).to.be.a('string') + expect(output).to.equal('"git"') done() }) .catch(error => {