Skip to content

Commit

Permalink
Support three types of json comparisons: structure, compatible and exact
Browse files Browse the repository at this point in the history
  • Loading branch information
knuthelv committed Dec 23, 2022
1 parent 67f259a commit 44c6322
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 60 deletions.
144 changes: 105 additions & 39 deletions src/compareJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function toCompatible() {
}
}

function isValueEqual(expected, actual) {
function isValueEqual(expected, actual, compareExact = false) {
if (!expected && !actual) {
return true
}
Expand All @@ -32,46 +32,73 @@ function isValueEqual(expected, actual) {
return false
}

if(expected === ANY) {
return true
}
if (!compareExact) {

if(expected === ANY_INTEGER) {
const number = Number.parseInt(actual)
return Number.isInteger(number)
}
if (expected === ANY) {
return true
}

if(expected === ANY_FLOAT) {
const number = Number.parseFloat(actual)
return number % 1 !== 0
}
if (expected === ANY_INTEGER) {
const number = Number.parseInt(actual)
return Number.isInteger(number)
}

if(expected === ANY_STRING) {
return typeof actual === 'string'
if (expected === ANY_FLOAT) {
const number = Number.parseFloat(actual)
return number % 1 !== 0
}

if (expected === ANY_STRING) {
return typeof actual === 'string'
}
}

if (Number.isFinite(expected) && !Number.isFinite(actual)) {
if (Number.isInteger(actual)) {
return Number.parseInt(actual) === expected
} else {
}
else {
return Number.parseFloat(actual) === expected
}
}

if (!Number.isFinite(expected) && Number.isFinite(actual)) {
if (Number.isInteger(actual)) {
return Number.parseInt(expected) === actual
} else {
}
else {
return Number.parseFloat(expected) === actual
}
}

return expected === actual
}

function isCompatibleObjects(expected, actual, compareValues = true) {
function isIdenticalArrays(a, b) {
for (const aKey of a) {
if (!b.find(bKey => bKey === aKey)) {
return toNotCompatible(a, b, ' Not exact equal keys in json object', {keyNotExpected: aKey})
}
}

for (const bKey of b) {
if (!a.find(aKey => aKey === bKey)) {
return toNotCompatible(a, b, ' Not exact equal keys in json object', {keyNotExpected: bKey})
}
}
return toCompatible()
}

function isCompatibleObjects(expected, actual, compareValues = true, compareExact = false) {
if (Array.isArray(expected)) {
return isCompatibleArrays(expected, actual, compareValues)
return isCompatibleArrays(expected, actual, compareValues, compareExact)
}

if (compareExact) {
const compatibilityMessage = isIdenticalArrays(Object.keys(expected), Object.keys(actual))
if (!compatibilityMessage.isEqual) {
return compatibilityMessage
}
}

for (const key of Object.keys(expected)) {
Expand All @@ -82,43 +109,41 @@ function isCompatibleObjects(expected, actual, compareValues = true) {
return toNotCompatible(expected, actual, '!(' + actual[key] + ') instanceof Object')
}

let compatibleObjects = isCompatibleObjects(expected[key], actual[key], compareValues)
let compatibleObjects = isCompatibleObjects(expected[key], actual[key], compareValues, compareExact)
if (!compatibleObjects.isEqual) {
return compatibleObjects
}
} else if (Array.isArray(expected[key])) {
}
else if (Array.isArray(expected[key])) {

if (!Array.isArray(actual[key])) {
return toNotCompatible(expected, actual, '!Array.isArray(' + actual[key] + ')')
}

let compatibleArrays = isCompatibleArrays(expected[key], actual[key], compareValues)
let compatibleArrays = isCompatibleArrays(expected[key], actual[key], compareValues, compareExact)
if (!compatibleArrays.isEqual) {
return compatibleArrays
}
} else {
}
else {

if (!actual.hasOwnProperty(key)) {
return toNotCompatible(expected, actual, '!' + actual + '.hasOwnProperty(' + key + ')')
}

if (compareValues && !isValueEqual(expected[key], actual[key])) {
if (compareValues && !isValueEqual(expected[key], actual[key], compareExact)) {
return toNotCompatible(expected, actual, '!isValueEqual(' + expected[key] + ', ' + actual[key] + ')')
}
}
}
return toCompatible()
}

function isCompatibleArrays(expected, actual, compareValues = true) {
function isCompatibleArrays(expected, actual, compareValues = true, compareExact = false) {
if (!Array.isArray(actual)) {
return toNotCompatible(expected, actual, 'expected array was object')
}

if (expected.length > actual.length) {
return toNotCompatible(expected, actual, 'Expected that expected.length > actual.length but was ' + expected.length + '<= ' + actual.length)
}

const expectedFound = []
const expectedNotFound = [...expected]
const actualToCompare = [...actual]
Expand All @@ -136,12 +161,13 @@ function isCompatibleArrays(expected, actual, compareValues = true) {
const actualValue = actualToCompare[j]

if (Array.isArray(expectedValue)) {
let compatibilityMessage = isCompatibleArrays(expectedValue, actualValue, compareValues)
let compatibilityMessage = isCompatibleArrays(expectedValue, actualValue, compareValues, compareExact)
if (!compatibilityMessage.isEqual) {
return compatibilityMessage
}
} else {
let compatibilityMessage = isCompatibleObjects(expectedValue, actualValue, compareValues)
}
else {
let compatibilityMessage = isCompatibleObjects(expectedValue, actualValue, compareValues, compareExact)
if (compatibilityMessage.isEqual) {
expectedFound.push(expectedValue)
actualToCompare[j] = undefined
Expand All @@ -151,15 +177,27 @@ function isCompatibleArrays(expected, actual, compareValues = true) {
}
}

if (compareExact) {
const actualNotFound = actualToCompare.filter(a => a !== undefined)
if (actualNotFound.length > 0) {
return toNotCompatible(expected, actual, 'Json structures not exact equals', {
expectedFound: expectedFound.filter(a => a !== undefined),
expectedNotFound: expectedNotFound.filter(a => a !== undefined),
actualNotFound: actualNotFound
})
}
}

if (compareValues) {
return expectedFound.length === expected.length
? toCompatible()
: toNotCompatible(expected, actual, 'Json structure not compatible', {
: toNotCompatible(expected, actual, 'Json structures not compatible', {
expectedFound: expectedFound.filter(a => a !== undefined),
expectedNotFound: expectedNotFound.filter(a => a !== undefined),
actualNotFound: actualToCompare.filter(a => a !== undefined)
})
} else {
}
else {
let foundExpected = expectedFound.length >= expected.length
if (!foundExpected) {
return toNotCompatible(expected, actual, 'Did not find the expected in actual', {
Expand All @@ -173,14 +211,42 @@ function isCompatibleArrays(expected, actual, compareValues = true) {
}


export function isJsonCompatible(expected, actual) {
function isJsonStructureCompatible(expected, actual) {
return Array.isArray(expected)
? isCompatibleArrays(expected, actual, true)
: isCompatibleObjects(expected, actual, true)
? isCompatibleArrays(expected, actual, false, false)
: isCompatibleObjects(expected, actual, false, false)
}

export function isJsonStructureCompatible(expected, actual) {
function isJsonCompatible(expected, actual) {
return Array.isArray(expected)
? isCompatibleArrays(expected, actual, false)
: isCompatibleObjects(expected, actual, false)
? isCompatibleArrays(expected, actual, true, false)
: isCompatibleObjects(expected, actual, true, false)
}

function isJsonIdentical(expected, actual) {
return Array.isArray(expected)
? isCompatibleArrays(expected, actual, true, true)
: isCompatibleObjects(expected, actual, true, true)
}

export const COMPARISON = {
EXACT: 'exact',
COMPATIBLE: 'compatible',
STRUCTURE: 'structure'
}

export function compareJson(expected, actual, comparison) {
switch (comparison.toLowerCase()) {
case COMPARISON.STRUCTURE:
return isJsonStructureCompatible(expected, actual)
case COMPARISON.COMPATIBLE:
return isJsonCompatible(expected, actual)
case COMPARISON.EXACT:
return isJsonIdentical(expected, actual)
default:
throw {
error: 'Comparison unsupported: ' + comparison.toLowerCase(),
comparisons: COMPARISON
}
}
}
26 changes: 10 additions & 16 deletions src/execute-black-box.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as compare from './compareJson.js'
import {COMPARISON} from './compareJson.js'

const SUCCESS = 'SUCCESS'
const FAILURE = 'FAILURE'
Expand Down Expand Up @@ -102,7 +103,8 @@ function toRequest(request) {

if (Array.isArray(item)) {
item.forEach((el, idx) => getValuePaths(`${currPath}.${idx}`, el, valuePaths))
} else if (typeof item == "object") {
}
else if (typeof item == 'object') {
Object.entries(item)
.forEach(([key, value]) => {
getValuePaths(`${currPath}.${key}`, value, valuePaths)
Expand Down Expand Up @@ -178,20 +180,11 @@ function executeInteraction(interactionWithConfig) {
if (interaction?.response?.body) {
if (!actualJson) {
return toStatus(config, 'Server with no body in response. Expects a body.', actualJson, response, interaction)
} else {

{
let results = compare.isJsonStructureCompatible(interaction.response.body, actualJson)
if (!results.isEqual) {
return toStatus(config, 'Expected response incompatible with actual response', actualJson, response, interaction, results)
}
}

{
let results = compare.isJsonCompatible(interaction.response.body, actualJson)
if (!results.isEqual) {
return toStatus(config, 'Expected response not the same as actual response', actualJson, response, interaction, results)
}
}
else {
let results = compare.compareJson(interaction.response.body, actualJson, interaction.response?.comparison || COMPARISON.COMPATIBLE)
if (!results.isEqual) {
return toStatus(config, 'Expected response not the same as actual response', actualJson, response, interaction, results)
}
}
}
Expand Down Expand Up @@ -247,7 +240,8 @@ export function executeBlackBox(interactions, index) {
.catch(e => {
return {...data, ...e}
})
} else {
}
else {
return data
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/scenario-algorithm.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ function toInteraction(input) {
.forEach(key => {
if (input.generateAlways) {
interaction.request[key] = replaceAlways(JSON.stringify(input.requestTemplate[key]), input.replace, input.generateAlways)
} else {
}
else {
interaction.request[key] = replaceData(JSON.stringify(input.requestTemplate[key]), input.replace)
}
})
Expand All @@ -101,7 +102,8 @@ function toInteraction(input) {
.forEach(key => {
if (input.generateAlways) {
interaction.response[key] = replaceAlways(JSON.stringify(input.responseTemplate[key]), input.replace, input.generateAlways)
} else {
}
else {
interaction.response[key] = replaceData(JSON.stringify(input.responseTemplate[key]), input.replace)
}
})
Expand Down Expand Up @@ -311,7 +313,7 @@ function toScenario(input, globalReplace, replaceRule = {}, scenarioExecutionNum

if (interactionTemplate === undefined || interactionTemplate.request === undefined) {
console.log(interaction)
throw new Error("Cannot find interaction template")
throw new Error('Cannot find interaction template')
}

return toInteractions(
Expand Down Expand Up @@ -419,4 +421,4 @@ export function createScenarios(input) {
)

return toInteractionsWithConfig(input, scenarioJson)
}
}
2 changes: 1 addition & 1 deletion templates/create-another-resource-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
"statusCode": "200"
}
}
}
}

0 comments on commit 44c6322

Please sign in to comment.