UNPKG

@ln-maf/validations

Version:

This module is created to allow other projects to easily validate JSON utilizing a set of steps.

715 lines (657 loc) 24.1 kB
const { Before } = require('@cucumber/cucumber') const { Given, When, Then } = require('@cucumber/cucumber') const fs = require('fs') const chai = require('chai') const assert = chai.assert let world = null const { fillTemplate } = require('@ln-maf/core') const { MAFSave, tryAttach, performJSONObjectTransform, applyJSONToString, readFile, writeFile, writeFileBuffer, readFileBuffer, getFilePath, MAFWhen } = require('@ln-maf/core') Before((scenario) => { world = scenario }) const toISO = d => { const val = (Number(d).valueOf()) if (isNaN(val)) { return d } const date = new Date(val).toISOString() return date } function setToString(location, value, scenario) { MAFSave.call(scenario, location, applyJSONToString(value, scenario)) } MAFWhen('run templateString', function (docString) { return fillTemplate(docString, this.results) }) MAFWhen('convert csv {jsonObject} to json', async function (obj) { const content = performJSONObjectTransform.call(this, obj) const Papa = require('papaparse') let res = await Papa.parse(content, { header: true }) const keyLength = Object.keys(res.data[0]).length res = res.data.filter(i => Object.keys(i).length === keyLength) return res }) // Stub function to test applying parameters to ensure that command line args can be included. When('parameters are:', function (docString) { this.parameters = JSON.parse(docString) }) MAFWhen('apply parameters', function () { Object.assign(this.results, this.parameters) tryAttach.call(this, this.parameters) }) When('set {string} to {jsonObject}', function (itemName, jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) MAFSave.call(this, itemName, obj) }) When('set {string} to:', function (location, value) { setToString(location, value, this) }) When('set {string} to', function (location, value) { setToString(location, value, this) }) Then('{jsonObject} {validationsEquivalence} {jsonObject}', function (obj1, equiv, obj2) { obj1 = Number(performJSONObjectTransform.call(this, obj1)) obj2 = Number(performJSONObjectTransform.call(this, obj2)) let result switch (equiv) { case '=': case '==': case '===': result = obj1 === obj2 break case '!=': result = obj1 !== obj2 break case '>': result = obj1 > obj2 break case '>=': result = obj1 >= obj2 break case '<': result = obj1 < obj2 break case '<=': result = obj1 <= obj2 break default: throw new Error('Invalid equivalence operator: ' + equiv) } if (!result) { throw new Error(JSON.stringify(obj1) + ' was not ' + equiv + ' to ' + JSON.stringify(obj2)) } }) Then('{jsonObject} is {timeQualifier} now', function (jsonObject, isBefore) { const obj = performJSONObjectTransform.call(this, jsonObject) const functionName = isBefore === 'before' ? 'isBefore' : 'isAfter' let dateIn = obj dateIn = toISO(dateIn) const validator = require('validator') assert(validator[functionName](dateIn, new Date().toISOString()), `${dateIn} was not ${isBefore} now`) }) Then('{jsonObject} is {timeQualifier} {jsonObject}', function (string, isBefore, date) { let obj = performJSONObjectTransform.call(this, string) let obj2 = performJSONObjectTransform.call(this, date) const functionName = isBefore === 'before' ? 'isBefore' : 'isAfter' obj = toISO(obj) obj2 = toISO(obj2) const validator = require('validator') assert(validator[functionName](obj, obj2), `${obj} was not ${isBefore} ${obj2}`) }) Then('{jsonObject} is not null', function (jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) assert.exists(obj) }) Then('{jsonObject} is null', function (jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) assert.notExists(obj) }) MAFWhen('run json path {string} on {jsonObject}', function (jPath, jsonObject) { fillTemplate(jPath, this.results) const jp = require('jsonpath') const obj = performJSONObjectTransform.call(this, jsonObject) return jp.query(obj, jPath) }) const setNamespace = (namespace, scenario) => { if (!scenario.results) { scenario.results = {} } if (!scenario.results.namespace) { scenario.results.namespace = {} } scenario.results.namespace = JSON.parse(namespace) } Given('xPath namespace is {string}', function (namespace) { setNamespace(namespace, this) }) Given('xPath namespace is', function (namespace) { setNamespace(namespace, this) }) Given('add xPath namespace {string} = {string}', function (namespace, url) { if (!this.results) { this.results = {} } if (!this.results.namespace) { this.results.namespace = {} } this.results.namespace[namespace] = url }) MAFWhen('generate rsa key', function () { const crypto = require('crypto') const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }) return privateKey }) MAFWhen('run xPath {string} on item {string}', function (xPath, element) { if (!this.results.namespace) { this.results.namespace = {} } const xpath = require('xpath') const Dom = require('@xmldom/xmldom').DOMParser const doc = new Dom().parseFromString(this.results[element]) const sel = xpath.useNamespaces(this.results.namespace) return sel(xPath, doc).map(i => i.toString()).join('\n') }) const doSetsMatch = (set1, set2, scenario) => { const isSetsEqual = (a, b) => a.size === b.size && Array.from(a).every(value => b.has(value)) const queryResult = new Set(set1.map(json => JSON.stringify(json, null, 2))) const expected = new Set(set2.map(json => JSON.stringify(json, null, 2))) const difference = { queryResult: [...queryResult].filter(x => !expected.has(x)).map(e => JSON.parse(e)), expected: [...expected].filter(x => !queryResult.has(x)).map(e => JSON.parse(e)) } const res = isSetsEqual(expected, queryResult) const diffedJSON = JSON.stringify(difference, null, 2) tryAttach.call(scenario, diffedJSON) assert(res, `The difference is: ${diffedJSON}`) } const setMatch = function (set1, set2) { set1 = this.results[set1] set2 = this.results[set2] doSetsMatch(set1, set2, this) } const setFileMatch = function (set, file) { file = readFile(file, this) doSetsMatch(applyJSONToString(this.results[set], this), applyJSONToString(file, this), this) } /** * Removes keys from a json object * @param {string} jsonKey the key/s to remove from the JSON object * @param {JSON} object A JSON object * @returns true if the key was successfully removed */ function jsonDeleteKey(jsonKey, object) { const original = JSON.parse(JSON.stringify(object)) const keys = jsonKey.split('.') let currentItem = object for (let i = 0; i < keys.length - 1; i++) { if (!currentItem[keys[i]]) { currentItem[keys[i]] = {} } currentItem = currentItem[keys[i]] } delete currentItem[keys[keys.length - 1]] delete object[jsonKey] assert.notDeepEqual(object, original) return true } /** * Pulls the JSON keys from a JSON object into a new, smaller JSON object * @param {JSON} sourceJSON The source JSON object to preform whitelist on * @param {string[]} whitelist A list of keys to keep from sourceJSON * @param {string} separator A one character string to signal a split * @returns A new filtered JSON object */ function whitelistJson(sourceJSON, whitelist, separator) { const object = {} for (let i = 0, length = whitelist.length; i < length; ++i) { let k = 0 const names = whitelist[i].split(separator || '.') let value = sourceJSON let name const count = names.length let ref = object while (k < count - 1) { name = names[k++] value = value[name] ref[name] = {} ref = ref[name] } ref[names[count - 1]] = value[names[count - 1]] } return object } /** * Removes the JSON key/value from the JSON Object provided */ MAFWhen('JSON key {string} is removed from {jsonObject}', function (jsonpath, jsonObject) { jsonpath = fillTemplate(jsonpath, this.results) let obj = performJSONObjectTransform.call(this, jsonObject) if (typeof obj === 'string') { obj = this.results[obj] } assert(jsonDeleteKey.call(this, jsonpath, obj, 'Could not delete key')) return obj }) /** * Returns the JSON key from a variable to lastRun */ MAFWhen('JSON key {string} is extracted from {jsonObject}', function (jsonPath, jsonObject) { jsonPath = fillTemplate(jsonPath, this.results) jsonObject = performJSONObjectTransform.call(this, jsonObject) let value = jsonObject for (const key of jsonPath.split('.')) { if (key.includes('[') && key.includes(']')) { const index = key.match(/\[(.*?)\]/)[1] value = value[key.split('[')[0]][index] } else { value = value[key] } } return value }) /** * Returns the JSON key from a variable {jsonObject} to lastRun */ MAFWhen('JSON keys {string} are extracted from {jsonObject}', function (array, variable) { const obj = performJSONObjectTransform.call(this, variable) array = fillTemplate(array, this.results) try { array = JSON.parse(array) } catch (e) { array = array.replace('[', '') array = array.replace(']', '') array = array.split(',') array = array.map(i => i.trim()) } return whitelistJson.call(this, obj, array) }) /** * Replaces the value of all found JSON keys in a file, using the JSON path to identify the keys */ When('{string} is written to file {string} on JSON path {string}', function (value, fileName, jsonPath) { value = fillTemplate(value, this.results) fileName = fillTemplate(fileName, this.results) jsonPath = fillTemplate(jsonPath, this.results) const jp = require('jsonpath') const fileContents = JSON.parse(readFile(fileName, this)) jp.apply(fileContents, jsonPath, function () { return value }) writeFile(fileName, JSON.stringify(fileContents), this) tryAttach.call(this, fileContents) }) /** * Replaces the value of all found JSON keys in an item, using the JSON path to identify the keys */ When('{string} is applied to item {string} on JSON path {string}', function (value, item, jsonPath) { value = fillTemplate(value, this.results) item = fillTemplate(item, this.results) jsonPath = fillTemplate(jsonPath, this.results) const jp = require('jsonpath') const fileContents = this.results[item] value = fillTemplate(value, this.results) if (value.trim() !== '') { try { const tmp = JSON.parse(value) if (typeof tmp === 'object') { value = tmp } } catch (e) { } } jp.apply(fileContents, jsonPath, function () { return value }) this.results[item] = fileContents tryAttach.call(this, this.results[item]) }) When('{jsonObject} is written in json line delimited format to file {string}', function (item, file) { let obj = performJSONObjectTransform.call(this, item) file = fillTemplate(file, this.results) try { obj = JSON.parse(obj) } catch (e) { } writeFile(file, obj.map(i => JSON.stringify(i)).join('\n'), this) }) When('{jsonObject} is written to file {string}', function (jsonObject, file) { let obj = performJSONObjectTransform.call(this, jsonObject) file = fillTemplate(file, this.results) if (typeof (obj) === 'object') { obj = JSON.stringify(obj) } writeFile(file, obj, this) }) Then('it matches the set from file {string}', function (set1) { return setFileMatch.call(this, 'lastRun', set1) }) Then('the set {string} matches the set from file {string}', function (f, s) { const res = setFileMatch.call(this, f, s) return res }) Then('the set {string} matches the set {string}', setMatch) Then('it matches the set {string}', function (set) { return setMatch.call(this, 'lastRun', set) }) MAFWhen('the file {string} is gzipped', function (filename) { filename = fillTemplate(filename, this.results) try { fs.deleteFileSync(getFilePath(filename, this)) } catch (e) { } const zlib = require('zlib') const bf = readFileBuffer(filename, this) const buffer = zlib.gzipSync(bf) writeFileBuffer(filename + '.gz', buffer, this) return '' }) MAFWhen('file {string} is gzip unzipped to file {string}', function (file, fileOut) { file = fillTemplate(file, this.results) fileOut = fillTemplate(fileOut, this.results) const zlib = require('zlib') const bf = readFileBuffer(file, this) const buffer = zlib.unzipSync(bf) writeFileBuffer(fileOut, buffer, this) return '' }) When('set config from json {jsonObject}', function (jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) for (const i in obj) { setToString(i, obj[i], this) } }) When('set:', function (dataTable) { dataTable = dataTable.rawTable const indices = dataTable[0] let item = [] indices.forEach((i, index) => { item[index] = [] }) dataTable = dataTable.slice(1) dataTable.forEach((i) => { i.forEach((j, index) => { item[index].push(j) }) }) item = item.map(i => { if (i.length === 1) { return i[0] } return i }) indices.forEach((i, index) => { setToString(i, item[index], this) }) }) MAFWhen('set result to {jsonObject}', function (item) { return performJSONObjectTransform.call(this, item) }) MAFWhen('{jsonObject} is base64 encoded', function (item) { item = performJSONObjectTransform.call(this, item) if (typeof item !== 'string') { item = JSON.stringify(item) } const encode = (Buffer.from(item, 'ascii').toString('base64')) return encode }) MAFWhen('{jsonObject} is base64 decoded', function (item) { item = performJSONObjectTransform.call(this, item) if (typeof item !== 'string') { throw new Error('Item type needs to be a string for base64 decoding, but it was a ' + typeof item) } return (Buffer.from(item, 'base64').toString('ascii')) }) MAFWhen('the value {string} is base64 decoded and resaved', function (item) { item = fillTemplate(item, this.results) const unencrypted = (Buffer.from(this.results[item], 'base64').toString('ascii')) this.results[item] = unencrypted tryAttach.call(this, 'Decoded value: ' + unencrypted) }) function lowerCaseItemKeys(item) { Object.keys(item).forEach(i => { if (i.toLowerCase() !== i) { item[i.toLowerCase()] = item[i] delete item[i] } if (typeof item[i.toLowerCase()] === 'object') { lowerCaseItemKeys(item[i.toLowerCase()]) } }) } When('make json keys for item {string} lower case', function (item) { item = fillTemplate(item, this.results) lowerCaseItemKeys(this.results[item]) tryAttach.call(this, this.results[item]) }) function flatten(item, res) { Object.keys(item).forEach(i => { if (typeof item[i] === 'object') { flatten(item[i], res) } else { res[i] = item[i] } }) } When('json item {string} is flattened', function (item) { const res = {} flatten(this.results[item], res) this.results[item] = res tryAttach.call(this, this.results[item]) }) function numberify(item) { Object.keys(item).forEach(i => { if (typeof item[i] === 'object') { numberify(item[i]) } else if (typeof item[i] === 'string') { const intVal = Number(item[i]) if (!Number.isNaN(intVal)) { item[i] = intVal } } }) } When('json item {string} is numberifyed', function (item) { numberify(this.results[item]) tryAttach.call(this, this.results[item]) }) const trimIt = function (item) { Object.keys(item).forEach(i => { if (typeof item[i] === 'object') { trimIt(item[i]) } else if (typeof item[i] === 'string') { item[i] = item[i].trim() } }) } When('json item {string} is trimmed', function (item) { trimIt(this.results[item]) tryAttach.call(this, this.results[item]) }) Then('{jsonObject} is not equal to {jsonObject}', function (item1, item2) { item1 = performJSONObjectTransform.call(this, item1) item2 = performJSONObjectTransform.call(this, item2) if (typeof item1 === 'object' && typeof item2 === 'object') { assert.notDeepEqual(item1, item2) } else { assert.notEqual(item1, item2) } }) Then('{jsonObject} is equal to {jsonObject}', function (item1, item2) { item1 = performJSONObjectTransform.call(this, item1) item2 = performJSONObjectTransform.call(this, item2) if (typeof item1 === 'object' && typeof item2 === 'object') { assert.deepEqual(item1, item2) } else { assert.equal(item1, item2) } }) Then('{jsonObject} is not equal to:', function (item1, item2) { item1 = performJSONObjectTransform.call(this, item1) let expected = fillTemplate(item2, this.results) try { expected = JSON.parse(expected) } catch (e) { } if (typeof item1 === 'object' && typeof expected === 'object') { assert.notDeepEqual(item1, expected) } else { assert.notEqual(item1, expected) } }) Then('{jsonObject} is equal to:', function (item1, item2) { item1 = performJSONObjectTransform.call(this, item1) let expected = fillTemplate(item2, this.results) try { expected = JSON.parse(expected) } catch (e) { } if (typeof item1 === 'object' && typeof expected === 'object') { assert.deepEqual(item1, expected) } else { assert.equal(item1, expected) } }) Then('element {string} does not exist in {jsonObject}', function (element, jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) element = fillTemplate(element, this.results) assert.doesNotHaveAnyKeys(obj, [element]) }) Then('element {string} exists in {jsonObject}', function (element, jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) element = fillTemplate(element, this.results) assert.containsAllKeys(obj, [element]) }) Then('elements {string} do not exist in {jsonObject}', function (element, jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) try { element = JSON.parse(element) } catch (e) { element = element.replace('[', '') element = element.replace(']', '') element = element.split(',') element = element.map(i => i.trim()) } assert.doesNotHaveAnyKeys(obj, element) }) Then('elements {string} exist in {jsonObject}', function (element, jsonObject) { const obj = performJSONObjectTransform.call(this, jsonObject) element = fillTemplate(element, this.results) try { element = JSON.parse(element) } catch (e) { element = element.replace('[', '') element = element.replace(']', '') element = element.split(',') element = element.map(i => i.trim()) } assert.containsAllKeys(obj, element) }) const performEncrypt = function () { const options = {} options.header = this.results.header const jwt = require('jsonwebtoken') setToString('lastRun', jwt.sign(this.results.jwtPayload, this.results.privateKey, options), this) } When('sign item {string} using jwt', function (item) { item = fillTemplate(item, this.results) item = fillTemplate(this.results[item], this.results) setToString('jwtPayload', item, this, false) performEncrypt.call(this) }) When('sign using jwt:', function (docString) { setToString('jwtPayload', docString, this, false) performEncrypt.call(this) }) const sleep = { msleep: function (n) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n) } } When('wait {int} milliseconds', function (milliseconds) { sleep.msleep(milliseconds) }) Given('set examples', async function () { // Write code here that turns the phrase above into concrete actions const a = world const flatten = (acc, cumulation) => { if (typeof cumulation === 'undefined') { return acc } if (Array.isArray(cumulation)) { return [...acc, ...cumulation] } else { acc.push(cumulation) return acc } } let extras = a.pickle.steps.map(i => i.astNodeIds) extras = extras.reduce(flatten) let examples = a.gherkinDocument.feature.children.map(i => { if (!i.scenario) return [] if (!i.scenario.examples) return [] return i.scenario.examples.map( i => i.tableBody.filter( i => extras.includes(i.id))) }) .reduce(flatten, []) let res = a.gherkinDocument.feature.children.map(i => { if (!i.scenario) return [] return i.scenario.examples.filter(i => { if (!i.tableBody) return [] return i.tableBody.map(i => extras.includes(i.id)).includes(true) }) }).reduce(flatten, []) let headers = res.map(i => i.tableHeader.cells).reduce(flatten, []) headers = headers.map(i => i.value) examples = examples.reduce(flatten, []).map(i => i.cells).reduce(flatten, []).map(i => i.value) res = headers.reduce((prev, curr, i) => { prev[curr] = examples[i] return prev } , {}) if (!this.results) { this.results = {} } const keys = Object.keys(res) for (let key in keys) { key = keys[key] res[key] = fillTemplate(res[key], this.results) this.results[key] = res[key] } tryAttach.call(this, res) }) Then('{jsonObject} contains {string}', function (jsonObject, checkString) { let obj = performJSONObjectTransform.call(this, jsonObject) checkString = fillTemplate(checkString, this.results) obj = JSON.stringify(obj) assert.isTrue(obj.includes(checkString), `String '${checkString}' is not in ${obj}`) }) Then('{jsonObject} does not contain {string}', function (jsonObject, checkString) { let obj = performJSONObjectTransform.call(this, jsonObject) checkString = fillTemplate(checkString, this.results) obj = JSON.stringify(obj) assert.isFalse(obj.includes(checkString), `String '${checkString}' is in ${obj}`) }) function toArrayBuffer(buf) { const ab = new ArrayBuffer(buf.length) const view = new Uint8Array(ab) for (let i = 0; i < buf.length; ++i) { view[i] = buf[i] } return ab } MAFWhen('blob is read from file {string}', async function (fileName) { let res = readFileBuffer(fileName, this) const arrayBuff = toArrayBuffer(res) const f = () => arrayBuff f.bind(this) res = { arrayBuffer: f } return res }) When('blob item {string} is written to file {string}', async function (blob, fileName) { blob = fillTemplate(blob, this.results) blob = this.results[blob] const b = Buffer.from(await blob.arrayBuffer()) writeFile(`${fileName}`, b, this) }) When('blob item {string} is attached', async function (blob) { blob = fillTemplate(blob, this.results) blob = this.results[blob] const b = Buffer.from(await blob.arrayBuffer()) return this.attach(b, 'image/png') }) Then('blob item {string} is equal to file {string}', async function (blob, fileName) { blob = fillTemplate(blob, this.results) blob = this.results[blob] const b = await blob.arrayBuffer() const actualImage = readFileBuffer(`${fileName}`, this) assert.isTrue(Buffer.compare(actualImage, Buffer.from(b)) === 0) })