@usebruno/js 0.31.0 → 0.33.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebruno/js",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -12,10 +12,11 @@
12
12
  },
13
13
  "scripts": {
14
14
  "test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js",
15
- "sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js"
15
+ "sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js",
16
+ "prepack": "npm run test"
16
17
  },
17
18
  "dependencies": {
18
- "@usebruno/common": "0.6.0",
19
+ "@usebruno/common": "0.7.0",
19
20
  "@usebruno/crypto-js": "^3.1.9",
20
21
  "@usebruno/query": "0.1.0",
21
22
  "ajv": "^8.12.0",
package/src/bru.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { cloneDeep } = require('lodash');
2
2
  const { uuid } = require('./utils');
3
3
  const { interpolate: _interpolate } = require('@usebruno/common');
4
+ const { sendRequest } = require('@usebruno/requests').scripting;
4
5
 
5
6
  const variableNameRegex = /^[\w-.]*$/;
6
7
 
@@ -19,6 +20,7 @@ class Bru {
19
20
  this.historyLogger = historyLogger;
20
21
  this.setVisualizations = setVisualizations;
21
22
  this.collectionName = collectionName;
23
+ this.sendRequest = sendRequest;
22
24
  this.runner = {
23
25
  skipRequest: () => {
24
26
  this.skipRequest = true;
@@ -12,7 +12,10 @@ const { get } = require('lodash');
12
12
  const Bru = require('../bru');
13
13
  const BrunoRequest = require('../bruno-request');
14
14
  const BrunoResponse = require('../bruno-response');
15
+ const Test = require('../test');
16
+ const TestResults = require('../test-results');
15
17
  const { cleanJson } = require('../utils');
18
+ const { createBruTestResultMethods } = require('../utils/results');
16
19
 
17
20
  // Inbuilt Library Support
18
21
  const ajv = require('ajv');
@@ -64,6 +67,7 @@ class ScriptRuntime {
64
67
  const folderVariables = request?.folderVariables || {};
65
68
  const requestVariables = request?.requestVariables || {};
66
69
  const iterationDetails = request?.runnerIterationDetails || {};
70
+ const assertionResults = request?.assertionResults || [];
67
71
  // TODO please clean this up
68
72
  const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName);
69
73
  const req = new BrunoRequest(request);
@@ -86,9 +90,16 @@ class ScriptRuntime {
86
90
  }
87
91
  }
88
92
 
93
+ // extend bru with result getter methods
94
+ const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
95
+
89
96
  const context = {
90
97
  bru,
91
- req
98
+ req,
99
+ test,
100
+ expect: chai.expect,
101
+ assert: chai.assert,
102
+ __brunoTestResults: __brunoTestResults
92
103
  };
93
104
 
94
105
  if (onConsoleLog && typeof onConsoleLog === 'function') {
@@ -123,6 +134,7 @@ class ScriptRuntime {
123
134
  runtimeVariables: cleanJson(runtimeVariables),
124
135
  visualizations,
125
136
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
137
+ results: cleanJson(__brunoTestResults.getResults()),
126
138
  nextRequestName: bru.nextRequest,
127
139
  skipRequest: bru.skipRequest,
128
140
  stopExecution: bru.stopExecution
@@ -178,6 +190,7 @@ class ScriptRuntime {
178
190
  runtimeVariables: cleanJson(runtimeVariables),
179
191
  visualizations,
180
192
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
193
+ results: cleanJson(__brunoTestResults.getResults()),
181
194
  nextRequestName: bru.nextRequest,
182
195
  skipRequest: bru.skipRequest,
183
196
  stopExecution: bru.stopExecution
@@ -209,6 +222,7 @@ class ScriptRuntime {
209
222
  const folderVariables = request?.folderVariables || {};
210
223
  const requestVariables = request?.requestVariables || {};
211
224
  const iterationDetails = request?.runnerIterationDetails || {};
225
+ const assertionResults = request?.assertionResults || [];
212
226
  const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName);
213
227
  const req = new BrunoRequest(request);
214
228
  const res = new BrunoResponse(response);
@@ -231,10 +245,17 @@ class ScriptRuntime {
231
245
  }
232
246
  }
233
247
 
248
+ // extend bru with result getter methods
249
+ const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
250
+
234
251
  const context = {
235
252
  bru,
236
253
  req,
237
- res
254
+ res,
255
+ test,
256
+ expect: chai.expect,
257
+ assert: chai.assert,
258
+ __brunoTestResults: __brunoTestResults
238
259
  };
239
260
 
240
261
  if (onConsoleLog && typeof onConsoleLog === 'function') {
@@ -269,6 +290,7 @@ class ScriptRuntime {
269
290
  runtimeVariables: cleanJson(runtimeVariables),
270
291
  visualizations,
271
292
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
293
+ results: cleanJson(__brunoTestResults.getResults()),
272
294
  nextRequestName: bru.nextRequest,
273
295
  skipRequest: bru.skipRequest,
274
296
  stopExecution: bru.stopExecution
@@ -324,6 +346,7 @@ class ScriptRuntime {
324
346
  runtimeVariables: cleanJson(runtimeVariables),
325
347
  visualizations,
326
348
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
349
+ results: cleanJson(__brunoTestResults.getResults()),
327
350
  nextRequestName: bru.nextRequest,
328
351
  skipRequest: bru.skipRequest,
329
352
  stopExecution: bru.stopExecution
@@ -15,7 +15,8 @@ const BrunoRequest = require('../bruno-request');
15
15
  const BrunoResponse = require('../bruno-response');
16
16
  const Test = require('../test');
17
17
  const TestResults = require('../test-results');
18
- const { cleanJson, appendAwaitToTestFunc } = require('../utils');
18
+ const { cleanJson } = require('../utils');
19
+ const { createBruTestResultMethods } = require('../utils/results');
19
20
 
20
21
  // Inbuilt Library Support
21
22
  const ajv = require('ajv');
@@ -35,25 +36,6 @@ const cheerio = require('cheerio');
35
36
  const tv4 = require('tv4');
36
37
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
37
38
 
38
- const getResultsSummary = (results) => {
39
- const summary = {
40
- total: results.length,
41
- passed: 0,
42
- failed: 0,
43
- skipped: 0,
44
- };
45
-
46
- results.forEach((r) => {
47
- const passed = r.status === "pass";
48
- if (passed) summary.passed += 1;
49
- else if (r.status === "fail") summary.failed += 1;
50
- else summary.skipped += 1;
51
- });
52
-
53
- return summary;
54
- }
55
-
56
-
57
39
  class TestRuntime {
58
40
  constructor(props) {
59
41
  this.runtime = props?.runtime || 'vm2';
@@ -103,9 +85,8 @@ class TestRuntime {
103
85
  }
104
86
  }
105
87
 
106
- const __brunoTestResults = new TestResults();
107
-
108
- const test = Test(__brunoTestResults, chai);
88
+ // extend bru with result getter methods
89
+ const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
109
90
 
110
91
  if (!testsFile || !testsFile.length) {
111
92
  return {
@@ -118,36 +99,6 @@ class TestRuntime {
118
99
  };
119
100
  }
120
101
 
121
- bru.getTestResults = async () => {
122
- let results = await __brunoTestResults.getResults();
123
- const summary = getResultsSummary(results);
124
- return {
125
- summary,
126
- results: results?.map?.(r => ({
127
- status: r?.status,
128
- description: r?.description,
129
- expected: r?.expected,
130
- actual: r?.actual,
131
- error: r?.error
132
- }))
133
- };
134
- }
135
- bru.getAssertionResults = async () => {
136
- let results = assertionResults;
137
- const summary = getResultsSummary(results);
138
- return {
139
- summary,
140
- results: results?.map?.(r => ({
141
- status: r?.status,
142
- lhsExpr: r?.lhsExpr,
143
- rhsExpr: r?.rhsExpr,
144
- operator: r?.operator,
145
- rhsOperand: r?.rhsOperand,
146
- error: r?.error
147
- }))
148
- };
149
- }
150
-
151
102
  const context = {
152
103
  test,
153
104
  bru,
@@ -177,53 +128,60 @@ class TestRuntime {
177
128
  context.bru.runRequest = runRequestByItemPathname;
178
129
  }
179
130
 
180
- if (this.runtime === 'quickjs') {
181
- await executeQuickJsVmAsync({
182
- script: testsFile,
183
- context: context
184
- });
185
- } else {
186
- // default runtime is vm2
187
- const vm = new NodeVM({
188
- sandbox: context,
189
- require: {
190
- context: 'sandbox',
191
- external: true,
192
- root: [collectionPath, ...additionalContextRootsAbsolute],
193
- mock: {
194
- // node libs
195
- path,
196
- stream,
197
- util,
198
- url,
199
- http,
200
- https,
201
- punycode,
202
- zlib,
203
- // 3rd party libs
204
- ajv,
205
- 'ajv-formats': addFormats,
206
- btoa,
207
- atob,
208
- lodash,
209
- moment,
210
- uuid,
211
- nanoid,
212
- axios,
213
- chai,
214
- 'node-fetch': fetch,
215
- 'crypto-js': CryptoJS,
216
- 'xml2js': xml2js,
217
- cheerio,
218
- tv4,
219
- ...whitelistedModules,
220
- fs: allowScriptFilesystemAccess ? fs : undefined,
221
- 'node-vault': NodeVault
131
+ let scriptError = null;
132
+
133
+ try {
134
+ if (this.runtime === 'quickjs') {
135
+ await executeQuickJsVmAsync({
136
+ script: testsFile,
137
+ context: context
138
+ });
139
+ } else {
140
+ // default runtime is vm2
141
+ const vm = new NodeVM({
142
+ sandbox: context,
143
+ require: {
144
+ context: 'sandbox',
145
+ external: true,
146
+ root: [collectionPath, ...additionalContextRootsAbsolute],
147
+ mock: {
148
+ // node libs
149
+ path,
150
+ stream,
151
+ util,
152
+ url,
153
+ http,
154
+ https,
155
+ punycode,
156
+ zlib,
157
+ // 3rd party libs
158
+ ajv,
159
+ 'ajv-formats': addFormats,
160
+ btoa,
161
+ atob,
162
+ lodash,
163
+ moment,
164
+ uuid,
165
+ nanoid,
166
+ axios,
167
+ chai,
168
+ 'node-fetch': fetch,
169
+ 'crypto-js': CryptoJS,
170
+ 'xml2js': xml2js,
171
+ cheerio,
172
+ tv4,
173
+ ...whitelistedModules,
174
+ fs: allowScriptFilesystemAccess ? fs : undefined,
175
+ 'node-vault': NodeVault
176
+ }
222
177
  }
223
- }
224
- });
225
- const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
226
- await asyncVM();
178
+ });
179
+ const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
180
+ await asyncVM();
181
+ }
182
+ } catch (error) {
183
+ scriptError = error;
184
+ console.error('Test script execution error:', error);
227
185
  }
228
186
 
229
187
  if (historyLogger) {
@@ -235,7 +193,7 @@ class TestRuntime {
235
193
  });
236
194
  }
237
195
 
238
- return {
196
+ const result = {
239
197
  request,
240
198
  envVariables: cleanJson(envVariables),
241
199
  runtimeVariables: cleanJson(runtimeVariables),
@@ -243,6 +201,13 @@ class TestRuntime {
243
201
  results: cleanJson(__brunoTestResults.getResults()),
244
202
  nextRequestName: bru.nextRequest
245
203
  };
204
+
205
+ if (scriptError) {
206
+ scriptError.partialResults = result;
207
+ throw scriptError;
208
+ }
209
+
210
+ return result;
246
211
  }
247
212
  }
248
213
 
@@ -142,10 +142,10 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
142
142
 
143
143
  const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext;
144
144
 
145
+ consoleFn && addConsoleShimToContext(vm, consoleFn);
145
146
  bru && addBruShimToContext(vm, bru);
146
147
  req && addBrunoRequestShimToContext(vm, req);
147
148
  res && addBrunoResponseShimToContext(vm, res);
148
- consoleFn && addConsoleShimToContext(vm, consoleFn);
149
149
  addLocalModuleLoaderShimToContext(vm, collectionPath);
150
150
  addPathShimToContext(vm);
151
151
 
@@ -1,4 +1,4 @@
1
- const { cleanJson } = require('../../../utils');
1
+ const { cleanJson, cleanCircularJson } = require('../../../utils');
2
2
  const { marshallToVm } = require('../utils');
3
3
 
4
4
  const addBruShimToContext = (vm, bru) => {
@@ -252,8 +252,7 @@ const addBruShimToContext = (vm, bru) => {
252
252
  bru
253
253
  .runRequest(vm.dump(args))
254
254
  .then((response) => {
255
- const { status, headers, data, dataBuffer, size, statusText } = response || {};
256
- promise.resolve(marshallToVm(cleanJson({ status, statusText, headers, data, dataBuffer, size }), vm));
255
+ promise.resolve(marshallToVm(cleanCircularJson(response), vm));
257
256
  })
258
257
  .catch((err) => {
259
258
  promise.resolve(
@@ -270,6 +269,26 @@ const addBruShimToContext = (vm, bru) => {
270
269
  });
271
270
  runRequestHandle.consume((handle) => vm.setProp(bruObject, 'runRequest', handle));
272
271
 
272
+ let sendRequestHandle = vm.newFunction('_sendRequest', (args) => {
273
+ const promise = vm.newPromise();
274
+ bru
275
+ .sendRequest(vm.dump(args))
276
+ .then((response) => {
277
+ promise.resolve(marshallToVm(cleanCircularJson(response), vm));
278
+ })
279
+ .catch((err) => {
280
+ promise.reject(
281
+ marshallToVm(
282
+ cleanJson(err),
283
+ vm
284
+ )
285
+ );
286
+ });
287
+ promise.settled.then(vm.runtime.executePendingJobs);
288
+ return promise.handle;
289
+ });
290
+ sendRequestHandle.consume((handle) => vm.setProp(bruObject, '_sendRequest', handle));
291
+
273
292
  const sleep = vm.newFunction('sleep', (timer) => {
274
293
  const t = vm.getString(timer);
275
294
  const promise = vm.newPromise();
@@ -285,6 +304,29 @@ const addBruShimToContext = (vm, bru) => {
285
304
  vm.setProp(bruObject, 'runner', bruRunnerObject);
286
305
  vm.setProp(vm.global, 'bru', bruObject);
287
306
  bruObject.dispose();
307
+
308
+ vm.evalCode(`
309
+ globalThis.bru.sendRequest = async (requestConfig, callback) => {
310
+ if (!callback) return await globalThis.bru._sendRequest(requestConfig);
311
+ try {
312
+ const response = await globalThis.bru._sendRequest(requestConfig);
313
+ try {
314
+ await callback(null, response);
315
+ }
316
+ catch(error) {
317
+ return Promise.reject(error);
318
+ }
319
+ }
320
+ catch(error) {
321
+ try {
322
+ await callback(JSON.parse(JSON.stringify(error)), null);
323
+ }
324
+ catch(err) {
325
+ return Promise.reject(err);
326
+ }
327
+ }
328
+ }
329
+ `);
288
330
  };
289
331
 
290
332
  module.exports = addBruShimToContext;
@@ -0,0 +1,80 @@
1
+ const TestResults = require('../test-results');
2
+ const Test = require('../test');
3
+
4
+ // Calculate summary statistics for test results
5
+ const getResultsSummary = (results) => {
6
+ const summary = {
7
+ total: results.length,
8
+ passed: 0,
9
+ failed: 0,
10
+ skipped: 0,
11
+ };
12
+
13
+ results.forEach((r) => {
14
+ const passed = r.status === 'pass';
15
+ if (passed) summary.passed += 1;
16
+ else if (r.status === 'fail') summary.failed += 1;
17
+ else summary.skipped += 1;
18
+ });
19
+
20
+ return summary;
21
+ };
22
+
23
+ const createBruTestResultMethods = (bru, assertionResults, chai) => {
24
+ const __brunoTestResults = new TestResults();
25
+ const test = Test(__brunoTestResults, chai);
26
+ setupBruTestMethods(bru, __brunoTestResults, assertionResults);
27
+
28
+ return { __brunoTestResults, test };
29
+ };
30
+
31
+ const setupBruTestMethods = (bru, __brunoTestResults, assertionResults) => {
32
+ const getTestResults = async () => {
33
+ let results = await __brunoTestResults.getResults();
34
+ const summary = getResultsSummary(results);
35
+ return {
36
+ summary,
37
+ results: results.map(r => ({
38
+ status: r.status,
39
+ description: r.description,
40
+ expected: r.expected,
41
+ actual: r.actual,
42
+ error: r.error
43
+ }))
44
+ };
45
+ };
46
+
47
+ const getAssertionResults = async () => {
48
+ let results = assertionResults;
49
+ const summary = getResultsSummary(results);
50
+ return {
51
+ summary,
52
+ results: results.map(r => ({
53
+ status: r.status,
54
+ lhsExpr: r.lhsExpr,
55
+ rhsExpr: r.rhsExpr,
56
+ operator: r.operator,
57
+ rhsOperand: r.rhsOperand,
58
+ error: r.error
59
+ }))
60
+ };
61
+ };
62
+
63
+ // Set methods on bru object if provided
64
+ if (bru) {
65
+ bru.getTestResults = getTestResults;
66
+ bru.getAssertionResults = getAssertionResults;
67
+ }
68
+
69
+ // Also return the methods for direct use
70
+ return {
71
+ getTestResults,
72
+ getAssertionResults
73
+ };
74
+ };
75
+
76
+ module.exports = {
77
+ getResultsSummary,
78
+ createBruTestResultMethods,
79
+ setupBruTestMethods
80
+ };
package/src/utils.js CHANGED
@@ -158,6 +158,32 @@ const appendAwaitToTestFunc = (str) => {
158
158
  return str.replace(/(?<!\.\s*)(?<!await\s)(test\()/g, 'await $1');
159
159
  };
160
160
 
161
+ const cleanCircularJson = (data) => {
162
+ try {
163
+ // Handle circular references by keeping track of seen objects
164
+ const seen = new WeakSet();
165
+
166
+ const replacer = (key, value) => {
167
+ // Skip non-objects and null
168
+ if (typeof value !== 'object' || value === null) {
169
+ return value;
170
+ }
171
+
172
+ // Detect circular reference
173
+ if (seen.has(value)) {
174
+ return '[Circular Reference]';
175
+ }
176
+
177
+ seen.add(value);
178
+ return value;
179
+ };
180
+
181
+ return JSON.parse(JSON.stringify(data, replacer));
182
+ } catch (e) {
183
+ return data;
184
+ }
185
+ };
186
+
161
187
  module.exports = {
162
188
  evaluateJsExpression,
163
189
  evaluateJsTemplateLiteral,
@@ -165,5 +191,6 @@ module.exports = {
165
191
  internalExpressionCache,
166
192
  cleanJson,
167
193
  uuid,
168
- appendAwaitToTestFunc
194
+ appendAwaitToTestFunc,
195
+ cleanCircularJson
169
196
  };