@usebruno/js 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,8 @@ const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser, u
7
7
  const { interpolateString } = require('../interpolate-string');
8
8
  const { executeQuickJsVm } = require('../sandbox/quickjs');
9
9
 
10
+ const Ajv = require('ajv');
11
+ const addFormats = require('ajv-formats');
10
12
  const { expect } = chai;
11
13
  chai.use(require('chai-string'));
12
14
  chai.use(function (chai, utils) {
@@ -24,6 +26,48 @@ chai.use(function (chai, utils) {
24
26
  });
25
27
  });
26
28
 
29
+ // Custom assertion for JSON Schema validation
30
+ const defaultAjv = new Ajv({ allErrors: true });
31
+ addFormats(defaultAjv);
32
+
33
+ const SUPPORTED_SCHEMA_VERSIONS = [
34
+ 'http://json-schema.org/draft-07/schema#',
35
+ 'http://json-schema.org/draft-07/schema'
36
+ ];
37
+
38
+ chai.use(function (chai) {
39
+ chai.Assertion.addMethod('jsonSchema', function (schema, ajvOptions) {
40
+ if (schema && schema.$schema && !SUPPORTED_SCHEMA_VERSIONS.includes(schema.$schema)) {
41
+ this.assert(
42
+ false,
43
+ `Unsupported JSON Schema version: "${schema.$schema}". Bruno currently only supports Draft-07 (http://json-schema.org/draft-07/schema#). Please update your schema to be Draft-07 compatible and remove the $schema property.`,
44
+ `Unsupported JSON Schema version: "${schema.$schema}".`
45
+ );
46
+ }
47
+ let ajv;
48
+ if (ajvOptions) {
49
+ ajv = new Ajv({ allErrors: true, ...ajvOptions });
50
+ addFormats(ajv);
51
+ } else {
52
+ ajv = defaultAjv;
53
+ }
54
+ let validate;
55
+ try {
56
+ validate = ajv.compile(schema);
57
+ } catch (e) {
58
+ this.assert(false, 'JSON schema compile error: ' + e.message, 'JSON schema compile error: ' + e.message);
59
+ }
60
+ const data = this._obj;
61
+ const isValid = validate(data);
62
+
63
+ this.assert(
64
+ isValid,
65
+ 'expected #{this} to match JSON schema, validation errors: ' + (validate.errors ? JSON.stringify(validate.errors) : 'none'),
66
+ 'expected #{this} to not match JSON schema'
67
+ );
68
+ });
69
+ });
70
+
27
71
  // Custom assertion for matching regex
28
72
  chai.use(function (chai, utils) {
29
73
  chai.Assertion.addMethod('match', function (regex) {
@@ -43,6 +87,118 @@ chai.use(function (chai, utils) {
43
87
  });
44
88
  });
45
89
 
90
+ // Custom assertion for jsonBody (Postman parity)
91
+ chai.use(function (chai, utils) {
92
+ // Parse a property path into an array of keys.
93
+ // Handles: dot notation (a.b), numeric brackets (a[0]), quoted brackets (a["b.c"], a['key']),
94
+ // and combinations like data[0]["a.b"].name
95
+ //
96
+ // Examples:
97
+ // "a.b.c" -> ["a", "b", "c"]
98
+ // "items[0].name" -> ["items", "0", "name"]
99
+ // 'data["a.b"]' -> ["data", "a.b"]
100
+ // "matrix[0][1]" -> ["matrix", "0", "1"]
101
+ // 'nested["x.y"].z' -> ["nested", "x.y", "z"]
102
+ // '["say \\"hi\\""]' -> ["say \"hi\""]
103
+ function parsePath(path) {
104
+ const keys = [];
105
+ let i = 0;
106
+ while (i < path.length) {
107
+ if (path[i] === '.') {
108
+ // Skip dot separator
109
+ i++;
110
+ } else if (path[i] === '[') {
111
+ i++; // skip '['
112
+ if (i < path.length && (path[i] === '\'' || path[i] === '"')) {
113
+ // Quoted key — collect until matching unescaped quote + ']'
114
+ const quote = path[i];
115
+ i++; // skip opening quote
116
+ let key = '';
117
+ while (i < path.length && path[i] !== quote) {
118
+ if (path[i] === '\\' && i + 1 < path.length && path[i + 1] === quote) {
119
+ key += quote;
120
+ i += 2; // skip backslash + escaped quote
121
+ } else {
122
+ key += path[i];
123
+ i++;
124
+ }
125
+ }
126
+ i++; // skip closing quote
127
+ i++; // skip ']'
128
+ keys.push(key);
129
+ } else {
130
+ // Unquoted (numeric) key — collect until ']'
131
+ let key = '';
132
+ while (i < path.length && path[i] !== ']') {
133
+ key += path[i];
134
+ i++;
135
+ }
136
+ i++; // skip ']'
137
+ keys.push(key);
138
+ }
139
+ } else {
140
+ // Bare key — collect until '.', '[', or end
141
+ let key = '';
142
+ while (i < path.length && path[i] !== '.' && path[i] !== '[') {
143
+ key += path[i];
144
+ i++;
145
+ }
146
+ keys.push(key);
147
+ }
148
+ }
149
+ return keys;
150
+ }
151
+
152
+ function getNestedValue(obj, path) {
153
+ const keys = parsePath(path);
154
+ let current = obj;
155
+ for (const key of keys) {
156
+ if (current === null || current === undefined || !Object.prototype.hasOwnProperty.call(Object(current), key)) {
157
+ return { found: false };
158
+ }
159
+ current = current[key];
160
+ }
161
+ return { found: true, value: current };
162
+ }
163
+
164
+ chai.Assertion.addMethod('jsonBody', function () {
165
+ const obj = this._obj;
166
+ const args = Array.prototype.slice.call(arguments);
167
+
168
+ if (args.length === 0) {
169
+ // No args: check body is valid JSON (object or array)
170
+ this.assert(
171
+ typeof obj === 'object' && obj !== null,
172
+ `expected ${utils.inspect(obj)} to be a JSON body (object or array)`,
173
+ `expected ${utils.inspect(obj)} not to be a JSON body`
174
+ );
175
+ } else if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) {
176
+ // Object arg: deep equality
177
+ this.assert(
178
+ utils.eql(obj, args[0]),
179
+ `expected body to deeply equal ${utils.inspect(args[0])}`,
180
+ `expected body to not deeply equal ${utils.inspect(args[0])}`
181
+ );
182
+ } else if (args.length === 1) {
183
+ // String path: check nested property exists
184
+ const result = getNestedValue(obj, String(args[0]));
185
+ this.assert(
186
+ result.found,
187
+ `expected body to have nested property '${args[0]}'`,
188
+ `expected body to not have nested property '${args[0]}'`
189
+ );
190
+ } else {
191
+ // Path + value: check nested property equals value
192
+ const result = getNestedValue(obj, String(args[0]));
193
+ this.assert(
194
+ result.found && utils.eql(result.value, args[1]),
195
+ `expected body to have nested property '${args[0]}' equal to ${utils.inspect(args[1])}`,
196
+ `expected body to not have nested property '${args[0]}' equal to ${utils.inspect(args[1])}`
197
+ );
198
+ }
199
+ });
200
+ });
201
+
46
202
  /**
47
203
  * Assertion operators
48
204
  *