@usebruno/js 0.23.0 → 0.25.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.23.0",
3
+ "version": "0.25.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -16,25 +16,27 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@usebruno/common": "0.3.0",
19
+ "@usebruno/crypto-js": "^3.1.9",
19
20
  "@usebruno/query": "0.1.0",
20
21
  "ajv": "^8.12.0",
21
22
  "ajv-formats": "^2.1.1",
22
23
  "atob": "^2.1.2",
23
- "axios": "1.7.5",
24
+ "axios": "1.7.7",
24
25
  "btoa": "^1.2.1",
25
26
  "chai": "^4.3.7",
26
27
  "chai-string": "^1.5.0",
28
+ "cheerio": "^1.0.0",
27
29
  "crypto-js": "^4.1.1",
28
- "crypto-js-3.1.9-1": "npm:crypto-js@^3.1.9-1",
29
30
  "json-query": "^2.2.2",
30
31
  "lodash": "^4.17.21",
31
32
  "moment": "^2.29.4",
32
- "nanoid": "3.3.4",
33
+ "nanoid": "3.3.8",
33
34
  "node-fetch": "2.7.0",
34
35
  "node-vault": "^0.10.2",
35
36
  "path": "^0.12.7",
36
37
  "quickjs-emscripten": "^0.29.2",
37
- "uuid": "^9.0.0"
38
+ "uuid": "^9.0.0",
39
+ "xml2js": "^0.6.2"
38
40
  },
39
41
  "devDependencies": {
40
42
  "@rollup/plugin-commonjs": "^23.0.2",
package/src/bru.js CHANGED
@@ -17,6 +17,17 @@ class Bru {
17
17
  this.collectionPath = collectionPath;
18
18
  this.historyLogger = historyLogger;
19
19
  this.setVisualizations = setVisualizations;
20
+ this.runner = {
21
+ skipRequest: () => {
22
+ this.skipRequest = true;
23
+ },
24
+ stopExecution: () => {
25
+ this.stopExecution = true;
26
+ },
27
+ setNextRequest: (nextRequest) => {
28
+ this.nextRequest = nextRequest;
29
+ }
30
+ };
20
31
  }
21
32
 
22
33
  _interpolate = (str) => {
@@ -1,3 +1,5 @@
1
+ const { get } = require('@usebruno/query');
2
+
1
3
  class BrunoResponse {
2
4
  constructor(res) {
3
5
  this.res = res;
@@ -6,6 +8,13 @@ class BrunoResponse {
6
8
  this.headers = res ? res.headers : null;
7
9
  this.body = res ? res.data : null;
8
10
  this.responseTime = res ? res.responseTime : null;
11
+
12
+ // Make the instance callable
13
+ const callable = (...args) => get(this.body, ...args);
14
+ Object.setPrototypeOf(callable, this.constructor.prototype);
15
+ Object.assign(callable, this);
16
+
17
+ return callable;
9
18
  }
10
19
 
11
20
  getStatus() {
@@ -58,6 +58,7 @@ chai.use(function (chai, utils) {
58
58
  * endsWith : ends with
59
59
  * between : between
60
60
  * isEmpty : is empty
61
+ * isNotEmpty : is not empty
61
62
  * isNull : is null
62
63
  * isUndefined : is undefined
63
64
  * isDefined : is defined
@@ -95,6 +96,7 @@ const parseAssertionOperator = (str = '') => {
95
96
  'endsWith',
96
97
  'between',
97
98
  'isEmpty',
99
+ 'isNotEmpty',
98
100
  'isNull',
99
101
  'isUndefined',
100
102
  'isDefined',
@@ -109,6 +111,7 @@ const parseAssertionOperator = (str = '') => {
109
111
 
110
112
  const unaryOperators = [
111
113
  'isEmpty',
114
+ 'isNotEmpty',
112
115
  'isNull',
113
116
  'isUndefined',
114
117
  'isDefined',
@@ -147,6 +150,7 @@ const parseAssertionOperator = (str = '') => {
147
150
  const isUnaryOperator = (operator) => {
148
151
  const unaryOperators = [
149
152
  'isEmpty',
153
+ 'isNotEmpty',
150
154
  'isNull',
151
155
  'isUndefined',
152
156
  'isDefined',
@@ -348,6 +352,9 @@ class AssertRuntime {
348
352
  case 'isEmpty':
349
353
  expect(lhs).to.be.empty;
350
354
  break;
355
+ case 'isNotEmpty':
356
+ expect(lhs).to.not.be.empty;
357
+ break;
351
358
  case 'isNull':
352
359
  expect(lhs).to.be.null;
353
360
  break;
@@ -412,6 +419,7 @@ class AssertRuntime {
412
419
  createdAt: new Date().toISOString()
413
420
  });
414
421
  }
422
+ request.assertionResults = assertionResults;
415
423
 
416
424
  return assertionResults;
417
425
  }
@@ -28,6 +28,8 @@ const fetch = require('node-fetch');
28
28
  const chai = require('chai');
29
29
  const CryptoJS = require('crypto-js');
30
30
  const NodeVault = require('node-vault');
31
+ const xml2js = require('xml2js');
32
+ const cheerio = require('cheerio');
31
33
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
32
34
 
33
35
  class ScriptRuntime {
@@ -47,7 +49,8 @@ class ScriptRuntime {
47
49
  processEnvVars,
48
50
  scriptingConfig,
49
51
  historyLogger,
50
- secretVariables
52
+ secretVariables,
53
+ runRequestByItemPathname
51
54
  ) {
52
55
  let visualizations = [];
53
56
  let setVisualizations = (data) => {
@@ -98,6 +101,10 @@ class ScriptRuntime {
98
101
  };
99
102
  }
100
103
 
104
+ if(runRequestByItemPathname) {
105
+ context.bru.runRequest = runRequestByItemPathname;
106
+ }
107
+
101
108
  if (this.runtime === 'quickjs') {
102
109
  await executeQuickJsVmAsync({
103
110
  script: script,
@@ -111,7 +118,9 @@ class ScriptRuntime {
111
118
  runtimeVariables: cleanJson(runtimeVariables),
112
119
  visualizations,
113
120
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
114
- nextRequestName: bru.nextRequest
121
+ nextRequestName: bru.nextRequest,
122
+ skipRequest: bru.skipRequest,
123
+ stopExecution: bru.stopExecution
115
124
  };
116
125
  }
117
126
 
@@ -145,6 +154,8 @@ class ScriptRuntime {
145
154
  chai,
146
155
  'node-fetch': fetch,
147
156
  'crypto-js': CryptoJS,
157
+ 'xml2js': xml2js,
158
+ cheerio,
148
159
  ...whitelistedModules,
149
160
  fs: allowScriptFilesystemAccess ? fs : undefined,
150
161
  'node-vault': NodeVault
@@ -160,7 +171,9 @@ class ScriptRuntime {
160
171
  runtimeVariables: cleanJson(runtimeVariables),
161
172
  visualizations,
162
173
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
163
- nextRequestName: bru.nextRequest
174
+ nextRequestName: bru.nextRequest,
175
+ skipRequest: bru.skipRequest,
176
+ stopExecution: bru.stopExecution
164
177
  };
165
178
  }
166
179
 
@@ -175,7 +188,8 @@ class ScriptRuntime {
175
188
  processEnvVars,
176
189
  scriptingConfig,
177
190
  historyLogger,
178
- secretVariables
191
+ secretVariables,
192
+ runRequestByItemPathname
179
193
  ) {
180
194
  let visualizations = [];
181
195
  let setVisualizations = (data) => {
@@ -223,6 +237,10 @@ class ScriptRuntime {
223
237
  };
224
238
  }
225
239
 
240
+ if(runRequestByItemPathname) {
241
+ context.bru.runRequest = runRequestByItemPathname;
242
+ }
243
+
226
244
  if (this.runtime === 'quickjs') {
227
245
  await executeQuickJsVmAsync({
228
246
  script: script,
@@ -236,7 +254,9 @@ class ScriptRuntime {
236
254
  runtimeVariables: cleanJson(runtimeVariables),
237
255
  visualizations,
238
256
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
239
- nextRequestName: bru.nextRequest
257
+ nextRequestName: bru.nextRequest,
258
+ skipRequest: bru.skipRequest,
259
+ stopExecution: bru.stopExecution
240
260
  };
241
261
  }
242
262
 
@@ -285,7 +305,9 @@ class ScriptRuntime {
285
305
  runtimeVariables: cleanJson(runtimeVariables),
286
306
  visualizations,
287
307
  globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
288
- nextRequestName: bru.nextRequest
308
+ nextRequestName: bru.nextRequest,
309
+ skipRequest: bru.skipRequest,
310
+ stopExecution: bru.stopExecution
289
311
  };
290
312
  }
291
313
  }
@@ -30,8 +30,29 @@ const axios = require('axios');
30
30
  const fetch = require('node-fetch');
31
31
  const CryptoJS = require('crypto-js');
32
32
  const NodeVault = require('node-vault');
33
+ const xml2js = require('xml2js');
34
+ const cheerio = require('cheerio');
33
35
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
34
36
 
37
+ const getResultsSummary = (results) => {
38
+ const summary = {
39
+ total: results.length,
40
+ passed: 0,
41
+ failed: 0,
42
+ skipped: 0,
43
+ };
44
+
45
+ results.forEach((r) => {
46
+ const passed = r.status === "pass";
47
+ if (passed) summary.passed += 1;
48
+ else if (r.status === "fail") summary.failed += 1;
49
+ else summary.skipped += 1;
50
+ });
51
+
52
+ return summary;
53
+ }
54
+
55
+
35
56
  class TestRuntime {
36
57
  constructor(props) {
37
58
  this.runtime = props?.runtime || 'vm2';
@@ -48,12 +69,14 @@ class TestRuntime {
48
69
  processEnvVars,
49
70
  scriptingConfig,
50
71
  historyLogger,
51
- secretVariables
72
+ secretVariables,
73
+ runRequestByItemPathname
52
74
  ) {
53
75
  const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
54
76
  const collectionVariables = request?.collectionVariables || {};
55
77
  const folderVariables = request?.folderVariables || {};
56
78
  const requestVariables = request?.requestVariables || {};
79
+ const assertionResults = request?.assertionResults || [];
57
80
  const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
58
81
  const req = new BrunoRequest(request, historyLogger);
59
82
  const res = new BrunoResponse(response);
@@ -77,6 +100,7 @@ class TestRuntime {
77
100
  }
78
101
 
79
102
  const __brunoTestResults = new TestResults();
103
+
80
104
  const test = Test(__brunoTestResults, chai);
81
105
 
82
106
  if (!testsFile || !testsFile.length) {
@@ -90,6 +114,36 @@ class TestRuntime {
90
114
  };
91
115
  }
92
116
 
117
+ bru.getTestResults = async () => {
118
+ let results = await __brunoTestResults.getResults();
119
+ const summary = getResultsSummary(results);
120
+ return {
121
+ summary,
122
+ results: results?.map?.(r => ({
123
+ status: r?.status,
124
+ description: r?.description,
125
+ expected: r?.expected,
126
+ actual: r?.actual,
127
+ error: r?.error
128
+ }))
129
+ };
130
+ }
131
+ bru.getAssertionResults = async () => {
132
+ let results = assertionResults;
133
+ const summary = getResultsSummary(results);
134
+ return {
135
+ summary,
136
+ results: results?.map?.(r => ({
137
+ status: r?.status,
138
+ lhsExpr: r?.lhsExpr,
139
+ rhsExpr: r?.rhsExpr,
140
+ operator: r?.operator,
141
+ rhsOperand: r?.rhsOperand,
142
+ error: r?.error
143
+ }))
144
+ };
145
+ }
146
+
93
147
  const context = {
94
148
  test,
95
149
  bru,
@@ -115,6 +169,10 @@ class TestRuntime {
115
169
  };
116
170
  }
117
171
 
172
+ if(runRequestByItemPathname) {
173
+ context.bru.runRequest = runRequestByItemPathname;
174
+ }
175
+
118
176
  if (this.runtime === 'quickjs') {
119
177
  await executeQuickJsVmAsync({
120
178
  script: testsFile,
@@ -151,6 +209,8 @@ class TestRuntime {
151
209
  chai,
152
210
  'node-fetch': fetch,
153
211
  'crypto-js': CryptoJS,
212
+ 'xml2js': xml2js,
213
+ cheerio,
154
214
  ...whitelistedModules,
155
215
  fs: allowScriptFilesystemAccess ? fs : undefined,
156
216
  'node-vault': NodeVault
@@ -11,7 +11,7 @@ const bundleLibraries = async () => {
11
11
  import moment from "moment";
12
12
  import btoa from "btoa";
13
13
  import atob from "atob";
14
- import * as CryptoJS from "crypto-js-3.1.9-1";
14
+ import * as CryptoJS from "@usebruno/crypto-js";
15
15
  globalThis.expect = expect;
16
16
  globalThis.assert = assert;
17
17
  globalThis.moment = moment;
@@ -22,12 +22,6 @@ const toNumber = (value) => {
22
22
  return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value);
23
23
  };
24
24
 
25
- const removeQuotes = (str) => {
26
- if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
27
- return str.slice(1, -1);
28
- }
29
- return str;
30
- };
31
25
 
32
26
  const executeQuickJsVm = ({ script: externalScript, context: externalContext, scriptType = 'template-literal' }) => {
33
27
  if (!externalScript?.length || typeof externalScript !== 'string') {
@@ -44,7 +38,6 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
44
38
  if (externalScript === 'null') return null;
45
39
  if (externalScript === 'undefined') return undefined;
46
40
 
47
- externalScript = removeQuotes(externalScript);
48
41
 
49
42
  const vm = QuickJSSyncContext;
50
43
 
@@ -94,7 +87,6 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
94
87
  if (externalScript === 'null') return null;
95
88
  if (externalScript === 'undefined') return undefined;
96
89
 
97
- externalScript = removeQuotes(externalScript);
98
90
 
99
91
  try {
100
92
  const module = await newQuickJSWASMModule();
@@ -1,7 +1,9 @@
1
+ const { cleanJson } = require('../../../utils');
1
2
  const { marshallToVm } = require('../utils');
2
3
 
3
4
  const addBruShimToContext = (vm, bru) => {
4
5
  const bruObject = vm.newObject();
6
+ const bruRunnerObject = vm.newObject();
5
7
 
6
8
  let cwd = vm.newFunction('cwd', function () {
7
9
  return marshallToVm(bru.cwd(), vm);
@@ -93,6 +95,24 @@ const addBruShimToContext = (vm, bru) => {
93
95
  vm.setProp(bruObject, 'setNextRequest', setNextRequest);
94
96
  setNextRequest.dispose();
95
97
 
98
+ let runnerSkipRequest = vm.newFunction('skipRequest', function () {
99
+ bru?.runner?.skipRequest();
100
+ });
101
+ vm.setProp(bruRunnerObject, 'skipRequest', runnerSkipRequest);
102
+ runnerSkipRequest.dispose();
103
+
104
+ let runnerStopExecution = vm.newFunction('stopExecution', function () {
105
+ bru?.runner?.stopExecution();
106
+ });
107
+ vm.setProp(bruRunnerObject, 'stopExecution', runnerStopExecution);
108
+ runnerStopExecution.dispose();
109
+
110
+ let runnerSetNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
111
+ bru?.runner?.setNextRequest(vm.dump(nextRequest));
112
+ });
113
+ vm.setProp(bruRunnerObject, 'setNextRequest', runnerSetNextRequest);
114
+ runnerSetNextRequest.dispose();
115
+
96
116
  let visualize = vm.newFunction('visualize', function (type, data) {
97
117
  bru.visualize(vm.dump(type), vm.dump(data));
98
118
  });
@@ -123,6 +143,70 @@ const addBruShimToContext = (vm, bru) => {
123
143
  vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
124
144
  getCollectionVar.dispose();
125
145
 
146
+ let getTestResults = vm.newFunction('getTestResults', () => {
147
+ const promise = vm.newPromise();
148
+ bru.getTestResults()
149
+ .then((results) => {
150
+ promise.resolve(marshallToVm(cleanJson(results), vm));
151
+ })
152
+ .catch((err) => {
153
+ promise.resolve(
154
+ marshallToVm(
155
+ cleanJson({
156
+ message: err.message
157
+ }),
158
+ vm
159
+ )
160
+ );
161
+ });
162
+ promise.settled.then(vm.runtime.executePendingJobs);
163
+ return promise.handle;
164
+ });
165
+ getTestResults.consume((handle) => vm.setProp(bruObject, 'getTestResults', handle));
166
+
167
+ let getAssertionResults = vm.newFunction('getAssertionResults', () => {
168
+ const promise = vm.newPromise();
169
+ bru.getAssertionResults()
170
+ .then((results) => {
171
+ promise.resolve(marshallToVm(cleanJson(results), vm));
172
+ })
173
+ .catch((err) => {
174
+ promise.resolve(
175
+ marshallToVm(
176
+ cleanJson({
177
+ message: err.message
178
+ }),
179
+ vm
180
+ )
181
+ );
182
+ });
183
+ promise.settled.then(vm.runtime.executePendingJobs);
184
+ return promise.handle;
185
+ });
186
+ getAssertionResults.consume((handle) => vm.setProp(bruObject, 'getAssertionResults', handle));
187
+
188
+ let runRequestHandle = vm.newFunction('runRequest', (args) => {
189
+ const promise = vm.newPromise();
190
+ bru.runRequest(vm.dump(args))
191
+ .then((response) => {
192
+ const { status, headers, data, dataBuffer, size } = response || {};
193
+ promise.resolve(marshallToVm(cleanJson({ status, headers, data, dataBuffer, size }), vm));
194
+ })
195
+ .catch((err) => {
196
+ promise.resolve(
197
+ marshallToVm(
198
+ cleanJson({
199
+ message: err.message
200
+ }),
201
+ vm
202
+ )
203
+ );
204
+ });
205
+ promise.settled.then(vm.runtime.executePendingJobs);
206
+ return promise.handle;
207
+ });
208
+ runRequestHandle.consume((handle) => vm.setProp(bruObject, 'runRequest', handle));
209
+
126
210
  const sleep = vm.newFunction('sleep', (timer) => {
127
211
  const t = vm.getString(timer);
128
212
  const promise = vm.newPromise();
@@ -134,6 +218,7 @@ const addBruShimToContext = (vm, bru) => {
134
218
  });
135
219
  sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
136
220
 
221
+ vm.setProp(bruObject, 'runner', bruRunnerObject);
137
222
  vm.setProp(vm.global, 'bru', bruObject);
138
223
  bruObject.dispose();
139
224
  };
package/src/utils.js CHANGED
@@ -19,8 +19,8 @@ const JS_KEYWORDS = `
19
19
  * ```js
20
20
  * res.data.pets.map(pet => pet.name.toUpperCase())
21
21
  *
22
- * function(context) {
23
- * const { res, pet } = context;
22
+ * function(__bruno__functionInnerContext) {
23
+ * const { res, pet } = __bruno__functionInnerContext;
24
24
  * return res.data.pets.map(pet => pet.name.toUpperCase())
25
25
  * }
26
26
  * ```
@@ -46,9 +46,11 @@ const compileJsExpression = (expr) => {
46
46
  globals: globals.map((name) => ` ${name} = ${name} ?? globalThis.${name};`).join('')
47
47
  };
48
48
 
49
- const body = `let { ${code.vars} } = context; ${code.globals}; return ${expr}`;
49
+ // param name that is unlikely to show up as a var in an expression
50
+ const param = `__bruno__functionInnerContext`;
51
+ const body = `let { ${code.vars} } = ${param}; ${code.globals}; return ${expr}`;
50
52
 
51
- return new Function('context', body);
53
+ return new Function(param, body);
52
54
  };
53
55
 
54
56
  const internalExpressionCache = new Map();
@@ -84,14 +86,6 @@ const evaluateJsTemplateLiteral = (templateLiteral, context) => {
84
86
  return undefined;
85
87
  }
86
88
 
87
- if (templateLiteral.startsWith('"') && templateLiteral.endsWith('"')) {
88
- return templateLiteral.slice(1, -1);
89
- }
90
-
91
- if (templateLiteral.startsWith("'") && templateLiteral.endsWith("'")) {
92
- return templateLiteral.slice(1, -1);
93
- }
94
-
95
89
  if (!isNaN(templateLiteral)) {
96
90
  const number = Number(templateLiteral);
97
91
  // Check if the number is too high. Too high number might get altered, see #1000