Skip to content

Commit 57a9ab9

Browse files
committed
WIP: support dry run, support ignore json keys and paths in response comparisons.
1 parent 8460747 commit 57a9ab9

File tree

8 files changed

+116
-54
lines changed

8 files changed

+116
-54
lines changed

deno.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@deno/black-box-testing",
3+
"description": "Black box testing app",
4+
"type": "module",
5+
"scripts": {
6+
"dev": "deno run --allow-env --allow-read --allow-write --allow-net scenario-black-box.ts -w black-box-tests/ltd -c recipe.json > output.json",
7+
"dev-dry": "deno run --allow-env --allow-read --allow-write --allow-net scenario-black-box.ts -w black-box-tests/ltd -c recipe.json -e dry > output.json",
8+
"debug": "deno run --allow-env --allow-read --allow-write --allow-net --inspect-brk scenario-black-box.ts -w black-box-tests/ltd -c recipe.json > output.json",
9+
"spotify": "deno run --allow-env --allow-read --allow-write --allow-net --inspect-brk -c ./test-data/spotify.json > output.json"
10+
}
11+
}

scenario-black-box.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ const scenarioJson = scenarioAlgorithms.createScenarios(input)
3939

4040
if (program.opts().execution && program.opts().execution.toLowerCase().includes('dry')) {
4141
console.log(JSON.stringify(scenarioJson, null, 2))
42-
4342
}
4443
else {
45-
sync.executeBlackBox(scenarioJson, 0)
44+
sync.executeBlackBox(scenarioJson.flatMap(a => a), 0)
4645
.then(data => {
4746
console.log(JSON.stringify(data, null, 2))
4847
})

src/compareJson.js

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ function isIdenticalArrays(a, b) {
8989
return toCompatible()
9090
}
9191

92-
function isCompatibleObjects(expected, actual, compareValues = true, compareExact = false) {
92+
function isCompatibleObjects(expected, actual, config) {
9393
if (Array.isArray(expected)) {
94-
return isCompatibleArrays(expected, actual, compareValues, compareExact)
94+
return isCompatibleArrays(expected, actual, config)
9595
}
9696

97-
if (compareExact) {
97+
if (config.compareExact) {
9898
const compatibilityMessage = isIdenticalArrays(Object.keys(expected), Object.keys(actual))
9999
if (!compatibilityMessage.isEqual) {
100100
return compatibilityMessage
@@ -103,13 +103,17 @@ function isCompatibleObjects(expected, actual, compareValues = true, compareExac
103103

104104
for (const key of Object.keys(expected)) {
105105

106+
if (config.ignoreJsonKeys.includes(key)) {
107+
continue
108+
}
109+
106110
if (expected[key] instanceof Object) {
107111

108112
if (!(actual[key] instanceof Object)) {
109113
return toNotCompatible(expected, actual, '!(' + actual[key] + ') instanceof Object')
110114
}
111115

112-
let compatibleObjects = isCompatibleObjects(expected[key], actual[key], compareValues, compareExact)
116+
let compatibleObjects = isCompatibleObjects(expected[key], actual[key], config)
113117
if (!compatibleObjects.isEqual) {
114118
return compatibleObjects
115119
}
@@ -120,7 +124,7 @@ function isCompatibleObjects(expected, actual, compareValues = true, compareExac
120124
return toNotCompatible(expected, actual, '!Array.isArray(' + actual[key] + ')')
121125
}
122126

123-
let compatibleArrays = isCompatibleArrays(expected[key], actual[key], compareValues, compareExact)
127+
let compatibleArrays = isCompatibleArrays(expected[key], actual[key], config)
124128
if (!compatibleArrays.isEqual) {
125129
return compatibleArrays
126130
}
@@ -131,15 +135,15 @@ function isCompatibleObjects(expected, actual, compareValues = true, compareExac
131135
return toNotCompatible(expected, actual, '!' + actual + '.hasOwnProperty(' + key + ')')
132136
}
133137

134-
if (compareValues && !isValueEqual(expected[key], actual[key], compareExact)) {
138+
if (config.compareValues && !isValueEqual(expected[key], actual[key], config.compareExact)) {
135139
return toNotCompatible(expected, actual, '!isValueEqual(' + expected[key] + ', ' + actual[key] + ')')
136140
}
137141
}
138142
}
139143
return toCompatible()
140144
}
141145

142-
function isCompatibleArrays(expected, actual, compareValues = true, compareExact = false) {
146+
function isCompatibleArrays(expected, actual, config) {
143147
if (!Array.isArray(actual)) {
144148
return toNotCompatible(expected, actual, 'expected array was object')
145149
}
@@ -161,13 +165,13 @@ function isCompatibleArrays(expected, actual, compareValues = true, compareExact
161165
const actualValue = actualToCompare[j]
162166

163167
if (Array.isArray(expectedValue)) {
164-
let compatibilityMessage = isCompatibleArrays(expectedValue, actualValue, compareValues, compareExact)
168+
let compatibilityMessage = isCompatibleArrays(expectedValue, actualValue, config)
165169
if (!compatibilityMessage.isEqual) {
166170
return compatibilityMessage
167171
}
168172
}
169173
else {
170-
let compatibilityMessage = isCompatibleObjects(expectedValue, actualValue, compareValues, compareExact)
174+
let compatibilityMessage = isCompatibleObjects(expectedValue, actualValue, config)
171175
if (compatibilityMessage.isEqual) {
172176
expectedFound.push(expectedValue)
173177
actualToCompare[j] = undefined
@@ -177,7 +181,7 @@ function isCompatibleArrays(expected, actual, compareValues = true, compareExact
177181
}
178182
}
179183

180-
if (compareExact) {
184+
if (config.compareExact) {
181185
const actualNotFound = actualToCompare.filter(a => a !== undefined)
182186
if (actualNotFound.length > 0) {
183187
return toNotCompatible(expected, actual, 'Json structures not exact equals', {
@@ -188,7 +192,7 @@ function isCompatibleArrays(expected, actual, compareValues = true, compareExact
188192
}
189193
}
190194

191-
if (compareValues) {
195+
if (config.compareValues) {
192196
return expectedFound.length === expected.length
193197
? toCompatible()
194198
: toNotCompatible(expected, actual, 'Json structures not compatible', {
@@ -210,28 +214,10 @@ function isCompatibleArrays(expected, actual, compareValues = true, compareExact
210214
}
211215
}
212216

213-
function isJsonStructureCompatible(expected, actual) {
214-
return Array.isArray(expected)
215-
? isCompatibleArrays(expected, actual, false, false)
216-
: isCompatibleObjects(expected, actual, false, false)
217-
}
218-
219-
function isJsonCompatible(expected, actual) {
220-
return Array.isArray(expected)
221-
? isCompatibleArrays(expected, actual, true, false)
222-
: isCompatibleObjects(expected, actual, true, false)
223-
}
224-
225-
function isJsonStructureExact(expected, actual) {
217+
export function compareJson(expected, actual, config) {
226218
return Array.isArray(expected)
227-
? isCompatibleArrays(expected, actual, false, true)
228-
: isCompatibleObjects(expected, actual, false, true)
229-
}
230-
231-
function isJsonIdentical(expected, actual) {
232-
return Array.isArray(expected)
233-
? isCompatibleArrays(expected, actual, true, true)
234-
: isCompatibleObjects(expected, actual, true, true)
219+
? isCompatibleArrays(expected, actual, config)
220+
: isCompatibleObjects(expected, actual, config)
235221
}
236222

237223
export const COMPARISON = {
@@ -241,20 +227,30 @@ export const COMPARISON = {
241227
EXACT: 'exact'
242228
}
243229

244-
export function compareJson(expected, actual, comparison) {
230+
export function toConfig(comparison, ignoreJsonKeys, ignoreJsonPaths, allValuePaths) {
245231
switch (comparison.toLowerCase()) {
246232
case COMPARISON.COMPATIBLE_STRUCTURE:
247-
return isJsonStructureCompatible(expected, actual)
233+
return toConfigDto(false, false, ignoreJsonKeys, ignoreJsonPaths, allValuePaths)
248234
case COMPARISON.COMPATIBLE:
249-
return isJsonCompatible(expected, actual)
235+
return toConfigDto(true, false, ignoreJsonKeys, ignoreJsonPaths, allValuePaths)
250236
case COMPARISON.EXACT_STRUCTURE:
251-
return isJsonStructureExact(expected, actual)
237+
return toConfigDto(false, true, ignoreJsonKeys, ignoreJsonPaths, allValuePaths)
252238
case COMPARISON.EXACT:
253-
return isJsonIdentical(expected, actual)
239+
return toConfigDto(true, true, ignoreJsonKeys, ignoreJsonPaths, allValuePaths)
254240
default:
255241
throw {
256242
error: 'Comparison unsupported: ' + comparison.toLowerCase(),
257243
comparisons: COMPARISON
258244
}
259245
}
260246
}
247+
248+
function toConfigDto(compareValues, compareExact, ignoreJsonKeys, ignoreJsonPaths, allValuePaths) {
249+
return {
250+
compareValues,
251+
compareExact,
252+
ignoreJsonKeys,
253+
ignoreJsonPaths,
254+
allValuePaths
255+
}
256+
}

src/execute-black-box.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {compareJson, COMPARISON} from './compareJson.js'
1+
import {compareJson, COMPARISON, toConfig} from './compareJson.js'
2+
import {getAllValuePaths} from './utils.js'
23

34
const SUCCESS = 'SUCCESS'
45
const FAILURE = 'FAILURE'
@@ -181,7 +182,17 @@ function executeInteraction(interactionWithConfig) {
181182
return toStatus(config, 'Server with no body in response. Expects a body.', actualJson, response, interaction)
182183
}
183184
else {
184-
let results = compareJson(interaction.response.body, actualJson, interaction.response?.comparison || COMPARISON.COMPATIBLE)
185+
let results = compareJson(
186+
interaction.response.body,
187+
actualJson,
188+
toConfig(
189+
interaction.response?.comparison || COMPARISON.COMPATIBLE,
190+
interaction.response?.ignoreJsonKeys || [],
191+
interaction.response?.ignoreJsonPaths || [],
192+
interaction.response?.ignoreJsonPaths ? getAllValuePaths(interaction.response.body) : []
193+
)
194+
)
195+
185196
if (!results.isEqual) {
186197
return toStatus(config, 'Expected response not the same as actual response', actualJson, response, interaction, results)
187198
}

src/scenario-algorithm.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,6 @@ function toInteractionsWithConfig(input, scenarios) {
431431
.flatMap(a => a)
432432
})
433433
.flatMap(a => a)
434-
.flatMap(a => a)
435434
}
436435

437436
export function createScenarios(input) {

src/type-generators.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function toRandomDayInMonth(month, min, max) {
8282
return toRandomInteger(1, 12)
8383
}
8484

85-
function toRandomDate(min, max) {
85+
function toRandomDateTimeIsoString(min, max) {
8686
if (max === 'now') {
8787
max = new Date(Date.now())
8888
.toISOString()
@@ -91,15 +91,14 @@ function toRandomDate(min, max) {
9191

9292
const year = toRandomYear(min, max)
9393
const month = toRandomMonthInYear(year, min, max)
94-
let day = toRandomDayInMonth(month, min, max)
94+
const day = toRandomDayInMonth(month, min, max)
9595

9696
return new Date(year, month - 1, day)
9797
.toISOString()
98-
.slice(0, 10)
9998
}
10099

101100
function toRandomFloat(min, max, decimals) {
102-
let num = Math.random() * (max - min) + min
101+
const num = Math.random() * (max - min) + min
103102
return roundToDecimals(num, decimals || 2)
104103
}
105104

@@ -135,7 +134,21 @@ function toRandomFloats(min, max, numberOf, decimals) {
135134
function toRandomDates(min, max, numberOf) {
136135
return new Array(numberOf)
137136
.fill(0)
138-
.map(() => toRandomDate(min, max))
137+
.map(() => {
138+
const dateTimeAsString = toRandomDateTimeIsoString(min, max)
139+
return dateTimeAsString
140+
.slice(0, 10)
141+
})
142+
}
143+
144+
function toRandomDateTimes(min, max, numberOf) {
145+
return new Array(numberOf)
146+
.fill(0)
147+
.map(() => {
148+
const dateTimeAsString = toRandomDateTimeIsoString(min, max)
149+
return dateTimeAsString
150+
.slice(0, dateTimeAsString.length - 1)
151+
})
139152
}
140153

141154
function toUuids(numberOf) {
@@ -161,6 +174,8 @@ export function toRandomFromType(type, min, max, numberOf, decimals) {
161174
return toRandomFloats(min, max, numberOf, decimals)
162175
case 'date':
163176
return toRandomDates(min, max, numberOf)
177+
case 'dateTime':
178+
return toRandomDateTimes(min, max, numberOf)
164179
case 'uuid':
165180
return toUuids(numberOf)
166181
}

src/utils.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,44 @@ function toPath(name) {
123123
return workingDirectory + '/' + name
124124
}
125125

126+
function getValuePaths(currPath, item, valuePaths = []) {
127+
if (!Array.isArray(item) && typeof item !== 'object')
128+
valuePaths.push(currPath)
129+
130+
if (Array.isArray(item)) {
131+
item.forEach((el, idx) => getValuePaths(`${currPath}.${idx}`, el, valuePaths))
132+
}
133+
else if (item && typeof item === 'object') {
134+
Object.entries(item)
135+
.forEach(([key, value]) => {
136+
getValuePaths(`${currPath}.${key}`, value, valuePaths)
137+
})
138+
}
139+
return valuePaths
140+
}
141+
142+
export function getAllValuePaths(data) {
143+
return Object.entries(data)
144+
.map(([key, value]) => {
145+
return [...new Set([key, ...getValuePaths(key, value)])]
146+
})
147+
.flatMap(a => a)
148+
}
149+
150+
export async function loadJsonFile(fileName) {
151+
return await import (
152+
fileName,
153+
{
154+
assert: {type: 'json'}
155+
}
156+
)
157+
.then(a => a.default)
158+
.catch(e => {
159+
console.error(e)
160+
return {}
161+
})
162+
}
163+
126164
export default {
127165
setWorkingDirectory: dir => workingDirectory = dir || '.',
128166

0 commit comments

Comments
 (0)