diff --git a/src/exports/events.d.ts b/src/exports/events.d.ts index 9e5a41f..995fd7b 100644 --- a/src/exports/events.d.ts +++ b/src/exports/events.d.ts @@ -5,4 +5,9 @@ export const EVENT_TYPES: { AFTER_RESPONSE: 'AFTER_RESPONSE'; }; -export const events: EventEmitter; \ No newline at end of file +export const pactumEvents: EventEmitter; + +/** + * @deprecated + */ +export const events: EventEmitter; diff --git a/src/exports/events.js b/src/exports/events.js index fb837e1..bcfcb4c 100644 --- a/src/exports/events.js +++ b/src/exports/events.js @@ -1,7 +1,12 @@ const { EventEmitter } = require('events'); +/** + * @deprecated + */ const events = new EventEmitter(); +const pactumEvents = new EventEmitter(); + const EVENT_TYPES = { BEFORE_REQUEST: "BEFORE_REQUEST", AFTER_RESPONSE: "AFTER_RESPONSE", @@ -9,5 +14,6 @@ const EVENT_TYPES = { module.exports = { events, + pactumEvents, EVENT_TYPES } \ No newline at end of file diff --git a/src/exports/expect.js b/src/exports/expect.js index 165c7d9..891f52f 100644 --- a/src/exports/expect.js +++ b/src/exports/expect.js @@ -1,5 +1,6 @@ const ExpectModel = require('../models/expect'); const utils = require('../helpers/utils'); +const http = require('http'); class Have { @@ -143,7 +144,11 @@ class Expect { } function expect(response, spec) { - return new Expect(response, spec); + if (response instanceof http.IncomingMessage) { + return new Expect(response, spec); + } + const res = { json: response }; + return new Expect(res, spec); } module.exports = expect; \ No newline at end of file diff --git a/src/exports/utils.d.ts b/src/exports/utils.d.ts new file mode 100644 index 0000000..ea744fc --- /dev/null +++ b/src/exports/utils.d.ts @@ -0,0 +1,15 @@ +export function clone(input: T): T; + +/** + * sleeps for a certain amount of time + * @param {number} ms the amount of time to sleep in milliseconds + * @returns + */ +export function sleep(ms: number): Promise; + +/** + * find a file recursively in a directory + * @param name the name of the file + * @param dir the directory to search in. Defaults to config.data.dir + */ +export function findFile(name: string, dir?: string): string; \ No newline at end of file diff --git a/src/exports/utils.js b/src/exports/utils.js new file mode 100644 index 0000000..7d52e7f --- /dev/null +++ b/src/exports/utils.js @@ -0,0 +1,35 @@ +const { findFileRecursively } = require("../helpers/file.utils"); +const config = require("../config"); +const { klona } = require("klona"); + +const utils = { + + clone(value) { + return klona(value); + }, + + /** + * sleeps for a certain amount of time + * @param {number} ms the amount of time to sleep in milliseconds + * @returns + */ + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + }, + + /** + * finds a file recursively in a directory + * @param {string} name + * @param {string} dir + * @returns + */ + findFile(name, dir = config.data.dir) { + const result = findFileRecursively(name, dir); + if (result) { + return result; + } + throw new Error(`File Not Found - '${name}'`); + } +} + +module.exports = utils; \ No newline at end of file diff --git a/src/helpers/dataProcessor.js b/src/helpers/dataProcessor.js index 8f72128..588e90b 100644 --- a/src/helpers/dataProcessor.js +++ b/src/helpers/dataProcessor.js @@ -1,7 +1,7 @@ const override = require('deep-override'); const jq = require('json-query'); -const { klona } = require("klona"); +const { clone } = require('../exports/utils'); const stash = require('../exports/stash'); const handler = require('../exports/handler'); const log = require('../plugins/logger'); @@ -17,7 +17,7 @@ const dataProcessor = { processMaps() { if (!config.data.ref.map.processed) { const orgMap = stash.getDataMap(); - this.map = klona(orgMap); + this.map = clone(orgMap); this.map = this.processDataRefs(this.map); config.data.ref.map.processed = true; if (Object.keys(this.map).length > 0) { @@ -29,7 +29,7 @@ const dataProcessor = { processTemplates() { if (!config.data.template.processed) { const orgTemplate = stash.getDataTemplate(); - this.template = klona(orgTemplate); + this.template = clone(orgTemplate); this.template = this.processDataTemplates(this.template); config.data.template.processed = true; if (Object.keys(this.template).length > 0) { @@ -58,7 +58,7 @@ const dataProcessor = { if (templateName) { const templateValue = this.template[templateName]; if (templateValue) { - data = klona(templateValue); + data = clone(templateValue); data = this.processDataTemplates(data); if (overrides) { override(data, overrides); @@ -134,7 +134,7 @@ const dataProcessor = { getOverrides(data) { if (config.data.template.direct_override) { - const cloned_data = klona(data); + const cloned_data = clone(data); delete cloned_data['@DATA:TEMPLATE@']; delete cloned_data['@REMOVES@']; return cloned_data; diff --git a/src/helpers/file.utils.js b/src/helpers/file.utils.js index 2049198..6fad142 100644 --- a/src/helpers/file.utils.js +++ b/src/helpers/file.utils.js @@ -28,19 +28,8 @@ function saveSnapshot(name, data) { fs.writeFileSync(`${snapshotDir}/${snapshotFile}`, JSON.stringify(data, null, 2)); } -/** - * - * @param {string} name - */ -function findFile(name, dir = config.data.dir) { - const result = _findFile(name, dir); - if (result) { - return result; - } - throw new Error(`File Not Found - '${name}'`); -} -function _findFile(name, dir = config.data.dir) { +function findFileRecursively(name, dir = config.data.dir) { if (fs.existsSync(name)) { return fs.readFileSync(name); } @@ -55,7 +44,7 @@ function _findFile(name, dir = config.data.dir) { const dirPath = path.resolve(dir, file); const stats = fs.statSync(dirPath); if (stats.isDirectory()) { - const result = _findFile(name, dirPath); + const result = findFileRecursively(name, dirPath); if (result) { return result; } @@ -67,5 +56,5 @@ function _findFile(name, dir = config.data.dir) { module.exports = { getSnapshotFile, saveSnapshot, - findFile + findFileRecursively }; \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts index 0dabbf7..2d22b7a 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -14,6 +14,7 @@ export * as response from './exports/response'; export * as settings from './exports/settings'; export * as stash from './exports/stash'; export * as state from './exports/state'; +export * as utils from './exports/utils'; export interface SpecOptions { memo?: any diff --git a/src/index.js b/src/index.js index ae561be..60ccd19 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,6 @@ require('./plugins/json.match').setAdapter(require('./adapters/json.match')); require('./plugins/json.like').setAdapter(require('./adapters/json.like')); require('./plugins/form.data').setAdapter(require('./adapters/form.data')); -const { klona } = require('klona') - const Spec = require('./models/Spec'); const Fuzz = require('./models/Fuzz'); const E2E = require('./models/E2E'); @@ -22,6 +20,7 @@ const stash = require('./exports/stash'); const expect = require('./exports/expect'); const reporter = require('./exports/reporter'); const events = require('./exports/events'); +const utils = require('./exports/utils'); const processor = require('./helpers/dataProcessor'); @@ -43,6 +42,7 @@ const pactum = { expect, reporter, events, + utils, spec(name, data, opts) { return new Spec(name, data, opts); @@ -68,7 +68,7 @@ const pactum = { }, clone(value) { - return klona(value); + return utils.clone(value); }, parse, diff --git a/src/models/Interaction.model.js b/src/models/Interaction.model.js index b95cbf1..b12930d 100644 --- a/src/models/Interaction.model.js +++ b/src/models/Interaction.model.js @@ -3,7 +3,7 @@ const { setMatchingRules, getValue } = require('pactum-matchers').utils; const processor = require('../helpers/dataProcessor'); const helper = require('../helpers/helper'); const { PactumInteractionError } = require('../helpers/errors'); -const { klona: clone } = require('klona'); +const { clone } = require('../exports/utils'); const ALLOWED_REQUEST_METHODS = new Set([ 'GET', 'POST', @@ -122,7 +122,7 @@ class InteractionRequest { } if (request.cookies && typeof request.cookies === 'object') { const cookie = lc.serialize(request.cookies); - if(!this.headers) { + if (!this.headers) { this.headers = {}; } this.headers['cookie'] = cookie; @@ -169,9 +169,9 @@ class InteractionResponse { this.headers = getValue(response.headers); setMatchingRules(this.matchingRules, response.body, '$.body'); this.body = getValue(response.body); - if(response.cookies) { + if (response.cookies) { const cookie = lc.serialize(response.cookies); - if(!this.headers) { + if (!this.headers) { this.headers = {}; } this.headers['set-cookie'] = cookie; diff --git a/src/models/Spec.js b/src/models/Spec.js index 1a7da3b..a9f8cb4 100644 --- a/src/models/Spec.js +++ b/src/models/Spec.js @@ -14,7 +14,7 @@ const responseExpect = require('../exports/expect'); const hr = require('../helpers/handler.runner'); const rlc = require('../helpers/reporter.lifeCycle'); const config = require('../config'); -const { findFile } = require('../helpers/file.utils'); +const { findFile } = require('../exports/utils'); const stash = require('../exports/stash'); const { memorize_spec, is_spec_memoized } = require('../helpers/memo'); @@ -45,7 +45,7 @@ class Spec { this._opts.handler_name = name; this._expect.setDefaultResponseExpectations(); } - + sleep(ms) { this._sleep = ms; return this; @@ -209,7 +209,7 @@ class Spec { withJson(json) { if (typeof json === 'string') { - json = get_json_from_template_or_file(json); + json = getJsonFromTemplateOrFile(json); } else if (typeof json !== 'object') { throw new PactumRequestError(`Invalid json in request - ${json}`); } @@ -400,13 +400,13 @@ class Spec { } expectJson(path, value) { - typeof value === 'undefined' ? this._expect.json.push(get_json_from_template_or_file(path)) : this._expect.jsonQuery.push({ path, value }); + typeof value === 'undefined' ? this._expect.json.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonQuery.push({ path, value }); return this; } expectJsonAt(...args) { return this.expectJson(...args); } expectJsonLike(path, value) { - typeof value === 'undefined' ? this._expect.jsonLike.push(get_json_from_template_or_file(path)) : this._expect.jsonQueryLike.push({ path, value }); + typeof value === 'undefined' ? this._expect.jsonLike.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonQueryLike.push({ path, value }); return this; } expectJsonLikeAt(...args) { return this.expectJsonLike(...args); } @@ -416,7 +416,7 @@ class Spec { this._expect.jsonSchemaQuery.push({ path, value, options }); } else { if (typeof value === 'undefined') { - this._expect.jsonSchema.push({ value: get_json_from_template_or_file(path) }); + this._expect.jsonSchema.push({ value: getJsonFromTemplateOrFile(path) }); } else { if (typeof path === 'object' && typeof value === 'object') { this._expect.jsonSchema.push({ value: path, options: value }); @@ -430,13 +430,13 @@ class Spec { expectJsonSchemaAt(...args) { return this.expectJsonSchema(...args); } expectJsonMatch(path, value) { - typeof value === 'undefined' ? this._expect.jsonMatch.push(get_json_from_template_or_file(path)) : this._expect.jsonMatchQuery.push({ path, value }); + typeof value === 'undefined' ? this._expect.jsonMatch.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonMatchQuery.push({ path, value }); return this; } expectJsonMatchAt(...args) { return this.expectJsonMatch(...args); } expectJsonMatchStrict(path, value) { - typeof value === 'undefined' ? this._expect.jsonMatchStrict.push(get_json_from_template_or_file(path)) : this._expect.jsonMatchStrictQuery.push({ path, value }); + typeof value === 'undefined' ? this._expect.jsonMatchStrict.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonMatchStrictQuery.push({ path, value }); return this; } expectJsonMatchStrictAt(...args) { return this.expectJsonMatchStrict(...args); } @@ -572,7 +572,7 @@ function validateRequestUrl(request, url) { } } -function get_json_from_template_or_file(path) { +function getJsonFromTemplateOrFile(path) { if (typeof path === 'string') { if (stash.getDataTemplate()[path]) { return { '@DATA:TEMPLATE@': path }; diff --git a/src/models/Tosser.js b/src/models/Tosser.js index 5cca5ca..5882273 100644 --- a/src/models/Tosser.js +++ b/src/models/Tosser.js @@ -10,7 +10,7 @@ const mock = require('../exports/mock'); const request = require('../exports/request'); const config = require('../config'); const hr = require('../helpers/handler.runner'); -const { events, EVENT_TYPES } = require('../exports/events'); +const { events, pactumEvents, EVENT_TYPES } = require('../exports/events'); class Tosser { @@ -257,6 +257,7 @@ async function getResponse(tosser) { const requestStartTime = Date.now(); try { events.emit(EVENT_TYPES.BEFORE_REQUEST, request); + pactumEvents.emit(EVENT_TYPES.BEFORE_REQUEST, { request }); log.debug(`${request.method} ${request.url}`); res = await phin(request); res.buffer = res.body; @@ -274,6 +275,7 @@ async function getResponse(tosser) { } finally { res.responseTime = Date.now() - requestStartTime; events.emit(EVENT_TYPES.AFTER_RESPONSE, res); + pactumEvents.emit(EVENT_TYPES.AFTER_RESPONSE, { request, response: res }); } return res; } diff --git a/src/models/expect.js b/src/models/expect.js index 5916884..361afbe 100644 --- a/src/models/expect.js +++ b/src/models/expect.js @@ -1,7 +1,6 @@ const assert = require('assert'); const jqy = require('json-query'); const lc = require('lightcookie'); -const { klona: clone } = require('klona'); const config = require('../config'); const utils = require('../helpers/utils'); @@ -10,6 +9,7 @@ const file = require('../helpers/file.utils'); const log = require('../plugins/logger'); const processor = require('../helpers/dataProcessor'); const handler = require('../exports/handler'); +const { clone } = require('../exports/utils'); const jsv = require('../plugins/json.schema'); const jmv = require('../plugins/json.match'); const jlv = require('../plugins/json.like'); @@ -348,7 +348,7 @@ class Expect { } if (value) { const current_rules = jmv.getMatchingRules(value, '$.body'); - let errors = jmv.validate(actual, jmv.getRawValue(value), current_rules, '$.body'); + let errors = jmv.validate(actual, jmv.getRawValue(value), current_rules, '$.body'); if (errors) { this.fail(errors.replace('$.body', '$')); } diff --git a/test/component/events.spec.js b/test/component/events.spec.js index c9bfdf4..2efaa46 100644 --- a/test/component/events.spec.js +++ b/test/component/events.spec.js @@ -1,19 +1,19 @@ const { spec } = require('../../src'); -const { events, EVENT_TYPES } = require('../../src').events; +const { pactumEvents, EVENT_TYPES } = require('../../src').events; describe('events', () => { before(() => { - events.on(EVENT_TYPES.BEFORE_REQUEST, (r) => { + pactumEvents.on(EVENT_TYPES.BEFORE_REQUEST, (r) => { console.log(r); }); - events.on(EVENT_TYPES.AFTER_RESPONSE, (r) => { - console.log(r.body); + pactumEvents.on(EVENT_TYPES.AFTER_RESPONSE, (r) => { + console.log(r.response.body); }); }); after(() => { - events.removeAllListeners(); + pactumEvents.removeAllListeners(); }); it('get', async () => { diff --git a/test/unit/expect.spec.js b/test/unit/expect.spec.js index b57936a..0a408ab 100644 --- a/test/unit/expect.spec.js +++ b/test/unit/expect.spec.js @@ -1,4 +1,6 @@ +const { expect } = require('../../src'); const Expect = require('../../src/models/expect'); +const ce = require('chai').expect; describe('Expect', () => { @@ -13,4 +15,29 @@ describe('Expect', () => { ex._validateHeaders({ headers: { c: 'ss'}}); }); +}); + +describe('expect', () => { + + it('empty objects', () => { + expect({}).to.have.jsonLike({}); + }); + + it('objects with properties', () => { + expect({ name: 'mom' }).to.have.jsonLike({ name: 'mom' }); + }); + + it('objects with extra properties in actual json', () => { + expect({ name: 'mom', age: 50 }).to.have.jsonLike({ name: 'mom' }); + }); + + it('objects with different properties', () => { + let err; + try { + expect({ name: 'mom' }).to.have.jsonLike({ role: 'mom' }); + } catch (error) { + err = error; + } + ce(err.message).equals(`Json doesn't have property 'role' at '$'`); + }); }); \ No newline at end of file