@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.
- package/package.json +4 -3
- package/src/bruno-request.js +13 -7
- package/src/bruno-response.js +4 -0
- package/src/header-list.js +497 -0
- package/src/runtime/assert-runtime.js +156 -0
- package/src/sandbox/bundle-browser-rollup.js +72 -66
- package/src/sandbox/bundle-libraries.js +9 -1
- package/src/sandbox/quickjs/index.js +6 -7
- package/src/sandbox/quickjs/shims/bruno-request.js +24 -3
- package/src/sandbox/quickjs/shims/bruno-response.js +28 -3
- package/src/sandbox/quickjs/shims/test.js +173 -0
- package/src/sandbox/quickjs/utils/property-list-bridge.js +30 -5
|
@@ -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
|
*
|