UNPKG

pactum

Version:

REST API Testing Tool for all levels in a Test Pyramid

181 lines (170 loc) 6.19 kB
const config = require('../config'); const handler = require('../exports/handler'); const helper = require('../helpers/helper'); const type_check = require('component-type'); class LikeJson { constructor(opts = {}) { this.target = opts.target || 'Json'; } compare(actual, expected, actualPath = '$', expectedPath = '$') { const valueRes = this.valueCompare(actual, expected, actualPath, expectedPath); if (valueRes !== null) { return valueRes; } if (Array.isArray(expected)) { const arrRes = this.arrayCompare(actual, expected, actualPath, expectedPath); if (arrRes) { return arrRes; } } else { const objRes = this.objectCompare(actual, expected, actualPath, expectedPath); if (objRes) { return objRes; } } return ''; } expressionCompare(actual, expected, actualPath, expectedPath) { if (typeof expected === 'string') { const value = config.strategy.assert.expression.includes; if (helper.matchesStrategy(expected, config.strategy.assert.expression)) { const expression = expected.replace(value, 'actual'); const res = eval(expression); if (res !== true) { return `${this.target} doesn't fulfil expression '${expression.replace('actual', expectedPath).trim()}'. Actual value found: ${actual}`; } return true; } } } valueAssertionCompare(actual, expected, actualPath, expectedPath) { const assert = config.strategy.assert; if (typeof expected === 'string' && helper.matchesStrategy(expected, assert.handler)) { const [handlerName, ..._args] = helper.sliceStrategy(expected, assert.handler).split(':'); try { const handlerFun = handler.getAssertHandler(handlerName); const args = _args.length > 0 ? _args[0].split(',') : _args; const res = handlerFun({ data: actual, args }); if (res !== true) { return `${this.target} doesn't fulfil assertion '${expected}' at '${expectedPath}'`; } return true; } catch (_) { return; } } } valueCompare(actual, expected, actualPath, expectedPath) { if (actual === expected) { return ''; } if (expected instanceof RegExp) { if (expected.test(actual)) { return ''; } return `${this.target} doesn't match with '${expected}' at '${expectedPath}' but found '${actual}'`; } const exprRes = this.expressionCompare(actual, expected, actualPath, expectedPath); if (exprRes) { return exprRes === true ? '' : exprRes; } const valueAssertRes = this.valueAssertionCompare(actual, expected, actualPath, expectedPath); if (valueAssertRes) { return valueAssertRes === true ? '' : valueAssertRes; } if (type_check(expected) !== type_check(actual)) { return `${this.target} doesn't have type '${type_check(expected)}' at '${expectedPath}' but found '${type_check(actual)}'`; } if (typeof expected !== 'object' && typeof actual !== 'object') { return `${this.target} doesn't have value '${expected}' at '${expectedPath}' but found '${actual}'`; } if (expected === null || actual === null) { return `${this.target} doesn't have value '${expected}' at '${expectedPath}' but found '${actual}'`; } if (Array.isArray(expected) && !Array.isArray(actual)) { return `${this.target} doesn't have type 'array' at '${expectedPath}' but found 'object'`; } if (!Array.isArray(expected) && Array.isArray(actual)) { return `${this.target} doesn't have type 'object' at '${expectedPath}' but found 'array'`; } return null; } arrayCompare(actual, expected, actualPath, expectedPath) { if (expected.length > actual.length) { return `${this.target} doesn't have 'array' with length '${expected.length}' at '${expectedPath}' but found 'array' with length '${actual.length}'`; } const seen = new Set(); for (let i = 0; i < expected.length; i++) { let found = false; const eItem = expected[i]; let aItem = actual[i]; const newExpectedPath = expectedPath + `[${i}]`; let actualPathResp = ''; if (seen.has(i)) { actualPathResp = `${this.target} doesn't have expected value at '${newExpectedPath}'`; } else { actualPathResp = this.compare(aItem, eItem, newExpectedPath, newExpectedPath); if (actualPathResp === '') { seen.add(i); continue; } } for (let j = i + 1; j < actual.length; j++) { if (!seen.has(j)) { aItem = actual[j]; const newActualPath = actualPath + `[${j}]`; const resp = this.compare(aItem, eItem, newActualPath, newExpectedPath); if (resp === '') { seen.add(j); found = true; break; } } } if (found) { continue; } for (let j = 0; j < i; j++) { if (!seen.has(j)) { aItem = actual[j]; const newActualPath = actualPath + `[${j}]`; const resp = this.compare(aItem, eItem, newActualPath, newExpectedPath); if (resp === '') { seen.add(j); found = true; break; } } } if (!found) { return actualPathResp; } } } objectCompare(actual, expected, actualPath, expectedPath) { for (const prop in expected) { if (!Object.prototype.hasOwnProperty.call(expected, prop)) { continue; } if (!Object.prototype.hasOwnProperty.call(actual, prop)) { return `${this.target} doesn't have property '${prop}' at '${expectedPath}'`; } const newPath = expectedPath + '.' + prop; const resp = this.compare(actual[prop], expected[prop], newPath, newPath); if (resp) { return resp; } } } } function validate(actual, expected, opts) { let actual_path = '$'; let expected_path = '$'; if (opts && opts.root_path) { expected_path = opts.root_path; } return new LikeJson(opts).compare(actual, expected, actual_path, expected_path); } module.exports = { validate };