mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 22:18:33 +00:00
feat: add JSON Schema validation support with custom chai assertion (#7301)
* feat: add JSON Schema validation support with custom chai assertion - Introduced a new custom assertion for JSON Schema validation in chai, allowing users to validate response bodies against defined schemas. - Updated the postman translation logic to translate `pm.response.to.have.jsonSchema` to the new assertion format. - Enhanced tests to cover various scenarios for JSON Schema validation, ensuring accurate translations and functionality. - Updated package dependencies to include the latest versions of relevant libraries. * refactor: enhance JSON Schema validation assertion and add comprehensive test cases * chore: add @rollup/plugin-json dependency and enhance JSON Schema validation tests - Added @rollup/plugin-json as a development dependency in package.json and package-lock.json. - Introduced new test cases for JSON Schema validation, covering various scenarios including valid schema matching, type mismatches, and required field checks. - Updated existing assertions to utilize the new validation capabilities. * refactor: streamline JSON Schema validation with default Ajv instance - Updated the custom chai assertion for JSON Schema validation to utilize a default Ajv instance, improving consistency and reducing redundancy in the code. - Enhanced the error messages in the assertion to include the actual data being validated, providing clearer feedback during validation failures. * refactor: improve error messaging in JSON Schema validation assertion - Enhanced the custom chai assertion for JSON Schema validation to provide clearer error messages by including a stringified version of the data being validated, improving feedback during validation failures. * refactor: simplify Ajv instance creation in JSON Schema validation - Removed the default Ajv instance and streamlined the creation of Ajv instances in the custom chai assertion for JSON Schema validation, ensuring consistent handling of ajvOptions across the codebase. * feat: add support for negated JSON Schema assertions in Postman translations - Introduced translations for `pm.response.to.not.have.jsonSchema`, `pm.response.not.to.have.jsonSchema`, and `pm.response.to.have.not.jsonSchema` to the new assertion format using `expect`. - Enhanced the translation logic to handle these new patterns and added corresponding test cases to ensure accurate functionality. - Updated existing tests to cover various scenarios for negated assertions, improving overall test coverage for JSON Schema validation. * fix: improve error handling in JSON Schema validation assertions - Added error handling for JSON schema compilation in the custom chai assertion, ensuring that any compilation errors are caught and reported with a clear message. - Updated tests to verify that malformed schemas correctly trigger assertion errors, enhancing the robustness of JSON Schema validation.
This commit is contained in:
349
packages/bruno-tests/collection/scripting/api/res/jsonSchema.bru
Normal file
349
packages/bruno-tests/collection/scripting/api/res/jsonSchema.bru
Normal file
@@ -0,0 +1,349 @@
|
||||
meta {
|
||||
name: jsonSchema
|
||||
type: http
|
||||
seq: 9
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo/json
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"name": "John",
|
||||
"age": 30,
|
||||
"email": "john@example.com",
|
||||
"status": "active",
|
||||
"score": 95.5,
|
||||
"isVerified": true,
|
||||
"tags": ["developer", "tester"],
|
||||
"address": {
|
||||
"street": "123 Main St",
|
||||
"city": "Springfield",
|
||||
"zip": "62701"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tests {
|
||||
// --- Passing validations ---
|
||||
|
||||
test("Basic object with properties and required", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
email: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
score: { type: 'number' },
|
||||
isVerified: { type: 'boolean' },
|
||||
tags: { type: 'array' },
|
||||
address: { type: 'object' }
|
||||
},
|
||||
required: ['name', 'age', 'email']
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("Nested object validation", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
address: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
street: { type: 'string' },
|
||||
city: { type: 'string' },
|
||||
zip: { type: 'string' }
|
||||
},
|
||||
required: ['street', 'city', 'zip']
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("Array items validation", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' }
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("String pattern (regex)", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("Enum validation", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'pending']
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("Number range (minimum/maximum)", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
age: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 150
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("String length constraints (minLength/maxLength)", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("allOf composition", function() {
|
||||
const schema = {
|
||||
allOf: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
required: ['name']
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: { age: { type: 'number' } },
|
||||
required: ['age']
|
||||
}
|
||||
]
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("anyOf composition", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
score: {
|
||||
anyOf: [
|
||||
{ type: 'number' },
|
||||
{ type: 'string' }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("jsonSchema with ajvOptions", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number' }
|
||||
},
|
||||
required: ['name', 'age']
|
||||
};
|
||||
expect(res.getBody()).to.have.jsonSchema(schema, { allErrors: true });
|
||||
});
|
||||
|
||||
// --- Failure validations ---
|
||||
|
||||
test("Type mismatch - schema expects array, response is object", function() {
|
||||
const schema = { type: 'array' };
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
test("Missing required field", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
required: ['nonExistentField']
|
||||
};
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
test("additionalProperties false rejects extra fields", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' }
|
||||
},
|
||||
additionalProperties: false
|
||||
};
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
test("Enum mismatch", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['deleted', 'archived']
|
||||
}
|
||||
},
|
||||
required: ['status']
|
||||
};
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
test("Pattern mismatch - name does not match digits-only", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
pattern: '^[0-9]+$'
|
||||
}
|
||||
},
|
||||
required: ['name']
|
||||
};
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
// --- Malformed schema (ajv.compile error) ---
|
||||
|
||||
test("Malformed schema - invalid type throws assertion error", function() {
|
||||
const schema = { type: 'invalidType' };
|
||||
expect(() => expect(res.getBody()).to.have.jsonSchema(schema)).to.throw('JSON schema compile error');
|
||||
});
|
||||
|
||||
// --- .not (negation) validations ---
|
||||
|
||||
test(".not with mismatched type - body is object, not array", function() {
|
||||
const schema = { type: 'array' };
|
||||
expect(res.getBody()).to.not.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test(".not with missing required field", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
required: ['nonExistent']
|
||||
};
|
||||
expect(res.getBody()).to.not.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test(".not fails when schema actually matches", function() {
|
||||
const schema = { type: 'object' };
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.not.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
// --- not.to.have (negation) validations ---
|
||||
|
||||
test("not.to.have with mismatched type - body is object, not array", function() {
|
||||
const schema = { type: 'array' };
|
||||
expect(res.getBody()).not.to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("not.to.have with missing required field", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
required: ['nonExistent']
|
||||
};
|
||||
expect(res.getBody()).not.to.have.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("not.to.have fails when schema actually matches", function() {
|
||||
const schema = { type: 'object' };
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).not.to.have.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
|
||||
// --- to.have.not (negation) validations ---
|
||||
|
||||
test("to.have.not with mismatched type - body is object, not array", function() {
|
||||
const schema = { type: 'array' };
|
||||
expect(res.getBody()).to.have.not.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("to.have.not with missing required field", function() {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
required: ['nonExistent']
|
||||
};
|
||||
expect(res.getBody()).to.have.not.jsonSchema(schema);
|
||||
});
|
||||
|
||||
test("to.have.not fails when schema actually matches", function() {
|
||||
const schema = { type: 'object' };
|
||||
let failed = false;
|
||||
try {
|
||||
expect(res.getBody()).to.have.not.jsonSchema(schema);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).to.be.true;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user