diff --git a/package-lock.json b/package-lock.json index f7a9884..afdda90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pactum", - "version": "3.0.17", + "version": "3.0.18", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 32a2920..1df976c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pactum", - "version": "3.0.17", + "version": "3.0.18", "description": "REST API Testing Tool for all levels in a Test Pyramid", "main": "./src/index.js", "types": "./src/index.d.ts", diff --git a/src/adapters/json.schema.js b/src/adapters/json.schema.js index b9091e9..602a36b 100644 --- a/src/adapters/json.schema.js +++ b/src/adapters/json.schema.js @@ -1,7 +1,8 @@ const { validator } = require('@exodus/schemasafe'); -function validate(schema, target) { - const validate = validator(schema, { includeErrors: true }); +function validate(schema, target, options = {}) { + options.includeErrors = true; + const validate = validator(schema, options); validate(target); if (validate.errors) { return JSON.stringify(validate.errors, null, 2); diff --git a/src/exports/expect.d.ts b/src/exports/expect.d.ts index 4008df3..83b22e6 100644 --- a/src/exports/expect.d.ts +++ b/src/exports/expect.d.ts @@ -11,7 +11,9 @@ export interface Have { jsonLike(value: any): void; jsonLike(path: string, value: any): void; jsonSchema(schema: object): void; + jsonSchema(schema: object, options: object): void; jsonSchema(path: string, schema: object): void; + jsonSchema(path: string, schema: object, options: object): void; jsonMatch(value: object): void; jsonMatch(path: string, value: object): void; jsonMatchStrict(value: object): void; diff --git a/src/exports/expect.js b/src/exports/expect.js index b3c9468..b7ad79a 100644 --- a/src/exports/expect.js +++ b/src/exports/expect.js @@ -54,8 +54,20 @@ class Have { this._validate(); } - jsonSchema(path, value) { - typeof value === 'undefined' ? this.expect.jsonSchema.push(path) : this.expect.jsonSchemaQuery.push({ path, value }); + jsonSchema(path, value, options) { + if (typeof options === 'object') { + this.expect.jsonSchemaQuery.push({ path, value, options }); + } else { + if (typeof value === 'undefined') { + this.expect.jsonSchema.push({ value: path }); + } else { + if (typeof path === 'object' && typeof value === 'object') { + this.expect.jsonSchema.push({ value: path, options: value }); + } else { + this.expect.jsonSchemaQuery.push({ path, value }); + } + } + } this._validate(); } diff --git a/src/exports/settings.d.ts b/src/exports/settings.d.ts index 0569651..719863f 100644 --- a/src/exports/settings.d.ts +++ b/src/exports/settings.d.ts @@ -16,6 +16,29 @@ export interface Logger { } export function setLogger(logger: Logger): void; +export type JsonLikeValidateFunction = (actual: any, expected: any, options?: any) => any; +export interface JsonLikeAdapter { + validate: JsonLikeValidateFunction; +} +export function setJsonLikeAdapter(adapter: JsonLikeAdapter): void; + +export type GetMatchingRulesFunction = (data: any, path: any, rules: any) => any; +export type GetRawValueFunction = (data: any) => any; +export type JsonMatchValidateFunction = (actual: any, expected: any, rules: any, path: any, strict: any) => any; +export interface JsonMatchAdapter { + getMatchingRules: GetMatchingRulesFunction; + getRawValue: GetRawValueFunction; + validate: JsonMatchValidateFunction; +} +export function setJsonMatchAdapter(adapter: JsonMatchAdapter): void; + +export type JsonSchemaValidateFunction = (schema: any, target: any, options?: any) => any; +export interface JsonSchemaAdapter { + validate: JsonSchemaValidateFunction; +} +export function setJsonSchemaAdapter(adapter: JsonSchemaAdapter): void; + + export interface Strategy { starts?: string; ends?: string; diff --git a/src/exports/settings.js b/src/exports/settings.js index afccc05..93a3e57 100644 --- a/src/exports/settings.js +++ b/src/exports/settings.js @@ -1,5 +1,8 @@ const config = require('../config'); const logger = require('../plugins/logger'); +const jsonLike = require('../plugins/json.like'); +const jsonMatch = require('../plugins/json.match'); +const jsonSchema = require('../plugins/json.schema'); const settings = { @@ -11,6 +14,18 @@ const settings = { logger.setAdapter(lgr); }, + setJsonLikeAdapter(adapter) { + jsonLike.setAdapter(adapter); + }, + + setJsonMatchAdapter(adapter) { + jsonMatch.setAdapter(adapter); + }, + + setJsonSchemaAdapter(adapter) { + jsonSchema.setAdapter(adapter); + }, + setAssertHandlerStrategy(strategy) { config.strategy.assert.handler = strategy; }, diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts index d9604a2..64aa5da 100644 --- a/src/models/Spec.d.ts +++ b/src/models/Spec.d.ts @@ -298,7 +298,9 @@ declare class Spec { * @see https://pactumjs.github.io/#/response-validation?id=expectjsonschema */ expectJsonSchema(schema: object): Spec; + expectJsonSchema(schema: object, options: object): Spec; expectJsonSchema(path: string, schema: object): Spec; + expectJsonSchema(path: string, schema: object, options: object): Spec; /** * expects the json to match with value diff --git a/src/models/Spec.js b/src/models/Spec.js index 8b60055..4082900 100644 --- a/src/models/Spec.js +++ b/src/models/Spec.js @@ -369,8 +369,20 @@ class Spec { } expectJsonLikeAt(...args) { return this.expectJsonLike(...args); } - expectJsonSchema(path, value) { - typeof value === 'undefined' ? this._expect.jsonSchema.push(path) : this._expect.jsonSchemaQuery.push({ path, value }); + expectJsonSchema(path, value, options) { + if (typeof options === 'object') { + this._expect.jsonSchemaQuery.push({ path, value, options }); + } else { + if (typeof value === 'undefined') { + this._expect.jsonSchema.push({ value: path }); + } else { + if (typeof path === 'object' && typeof value === 'object') { + this._expect.jsonSchema.push({ value: path, options: value }); + } else { + this._expect.jsonSchemaQuery.push({ path, value }); + } + } + } return this; } expectJsonSchemaAt(...args) { return this.expectJsonSchema(...args); } diff --git a/src/models/expect.js b/src/models/expect.js index f31dcd2..e5a64a1 100644 --- a/src/models/expect.js +++ b/src/models/expect.js @@ -246,7 +246,7 @@ class Expect { _validateJsonSchema(response) { this.jsonSchema = processor.processData(this.jsonSchema); for (let i = 0; i < this.jsonSchema.length; i++) { - const errors = jsv.validate(this.jsonSchema[i], response.json); + const errors = jsv.validate(this.jsonSchema[i].value, response.json, this.jsonSchema[i].options); if (errors) { this.fail(`Response doesn't match with JSON schema - ${errors}`); } @@ -258,7 +258,7 @@ class Expect { for (let i = 0; i < this.jsonSchemaQuery.length; i++) { const jQ = this.jsonSchemaQuery[i]; const value = jqy(jQ.path, { data: response.json }).value; - const errors = jsv.validate(jQ.value, value); + const errors = jsv.validate(jQ.value, value, jQ.options); if (errors) { this.fail(`Response doesn't match with JSON schema at ${jQ.path}: \n ${JSON.stringify(errors, null, 2)}`); } diff --git a/src/plugins/json.schema.js b/src/plugins/json.schema.js index 8a19a94..15d9e3a 100644 --- a/src/plugins/json.schema.js +++ b/src/plugins/json.schema.js @@ -1,8 +1,8 @@ const BasePlugin = require('./plugin.base'); class JsonSchemaValidator extends BasePlugin { - validate(schema, target) { - return this.adapter.validate(schema, target); + validate(schema, target, options) { + return this.adapter.validate(schema, target, options); } } diff --git a/test/component/bdd.spec.js b/test/component/bdd.spec.js index 68049cf..734c06c 100644 --- a/test/component/bdd.spec.js +++ b/test/component/bdd.spec.js @@ -76,10 +76,39 @@ describe('BDD', () => { expect(response).to.have.jsonSchema({ properties: { name: { type: 'string' } } }); }); + it('should return a valid schema with options', async () => { + expect(response).to.have.jsonSchema( + { + properties: { + name: { + type: 'string', + format: 'only-snow' + } + } + }, + { + formats: { + 'only-snow': /^snow$/ + } + } + ); + }); + it('should return a valid schema at', async () => { expect(response).to.have.jsonSchema('.', { properties: { name: { type: 'string' } } }); }); + it('should return a valid schema at with options', async () => { + expect(response).to.have.jsonSchema('.', + { properties: { name: { type: 'string', format: 'only-snow' } } }, + { + formats: { + 'only-snow': /^snow$/ + } + } + ); + }); + it('should return a match', async () => { expect(response).to.have.jsonMatch(like({ name: 'snow' })); }); diff --git a/test/component/expects.spec.js b/test/component/expects.spec.js index 8eb1ad0..80d185e 100644 --- a/test/component/expects.spec.js +++ b/test/component/expects.spec.js @@ -1028,4 +1028,37 @@ describe('Expects', () => { expect(err).not.undefined; }); + it('json schema with options', async () => { + await pactum.spec() + .useInteraction('default get') + .get('http://localhost:9393/default/get') + .expectJsonSchema({ + type: 'object', + properties: { + method: { + type: 'string', + format: 'only-get' + } + } + }, { + formats: { + 'only-get': /^GET$/ + } + }); + }); + + it('json schema at with options', async () => { + await pactum.spec() + .useInteraction('default get') + .get('http://localhost:9393/default/get') + .expectJsonSchema('method', { + type: 'string', + format: 'only-get' + }, { + formats: { + 'only-get': /^GET$/ + } + }); + }); + }); \ No newline at end of file diff --git a/test/unit/settings.spec.js b/test/unit/settings.spec.js index 778fb37..620628d 100644 --- a/test/unit/settings.spec.js +++ b/test/unit/settings.spec.js @@ -15,6 +15,18 @@ describe('Settings', () => { settings.setLogger(logger); }); + it('setJsonLikeAdapter', () => { + settings.setJsonLikeAdapter(require('../../src/adapters/json.like')); + }); + + it('setJsonMatchAdapter', () => { + settings.setJsonMatchAdapter(require('../../src/adapters/json.match')); + }); + + it('setJsonSchemaAdapter', () => { + settings.setJsonSchemaAdapter(require('../../src/adapters/json.schema')); + }); + after(() => { settings.setSnapshotDirectoryPath('.pactum/snapshots'); });