@usebruno/js 0.34.0 → 0.36.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.34.0",
3
+ "version": "0.36.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -16,7 +16,7 @@
16
16
  "prepack": "npm run test"
17
17
  },
18
18
  "dependencies": {
19
- "@usebruno/common": "0.8.0",
19
+ "@usebruno/common": "0.10.0",
20
20
  "@usebruno/crypto-js": "^3.1.9",
21
21
  "@usebruno/query": "0.1.0",
22
22
  "ajv": "^8.12.0",
package/src/bru.js CHANGED
@@ -2,6 +2,7 @@ const { cloneDeep } = require('lodash');
2
2
  const { uuid } = require('./utils');
3
3
  const { interpolate: _interpolate } = require('@usebruno/common');
4
4
  const { sendRequest } = require('@usebruno/requests').scripting;
5
+ const { jar: createCookieJar } = require('@usebruno/common').cookies;
5
6
 
6
7
  const variableNameRegex = /^[\w-.]*$/;
7
8
 
@@ -21,6 +22,50 @@ class Bru {
21
22
  this.setVisualizations = setVisualizations;
22
23
  this.collectionName = collectionName;
23
24
  this.sendRequest = sendRequest;
25
+
26
+ this.cookies = {
27
+ jar: () => {
28
+ const cookieJar = createCookieJar();
29
+
30
+ return {
31
+ getCookie: (url, cookieName, callback) => {
32
+ const interpolatedUrl = this.interpolate(url);
33
+ return cookieJar.getCookie(interpolatedUrl, cookieName, callback);
34
+ },
35
+
36
+ getCookies: (url, callback) => {
37
+ const interpolatedUrl = this.interpolate(url);
38
+ return cookieJar.getCookies(interpolatedUrl, callback);
39
+ },
40
+
41
+ setCookie: (url, nameOrCookieObj, valueOrCallback, maybeCallback) => {
42
+ const interpolatedUrl = this.interpolate(url);
43
+ return cookieJar.setCookie(interpolatedUrl, nameOrCookieObj, valueOrCallback, maybeCallback);
44
+ },
45
+
46
+ setCookies: (url, cookiesArray, callback) => {
47
+ const interpolatedUrl = this.interpolate(url);
48
+ return cookieJar.setCookies(interpolatedUrl, cookiesArray, callback);
49
+ },
50
+
51
+ // Clear entire cookie jar
52
+ clear: (callback) => {
53
+ return cookieJar.clear(callback);
54
+ },
55
+
56
+ // Delete cookies for a specific URL/domain
57
+ deleteCookies: (url, callback) => {
58
+ const interpolatedUrl = this.interpolate(url);
59
+ return cookieJar.deleteCookies(interpolatedUrl, callback);
60
+ },
61
+
62
+ deleteCookie: (url, cookieName, callback) => {
63
+ const interpolatedUrl = this.interpolate(url);
64
+ return cookieJar.deleteCookie(interpolatedUrl, cookieName, callback);
65
+ }
66
+ };
67
+ }
68
+ };
24
69
  this.runner = {
25
70
  skipRequest: () => {
26
71
  this.skipRequest = true;
@@ -9,6 +9,7 @@ class BrunoResponse {
9
9
  this.headers = res ? res.headers : null;
10
10
  this.body = res ? res.data : null;
11
11
  this.responseTime = res ? res.responseTime : null;
12
+ this.url = res?.request ? res.request.protocol + '//' + res.request.host + res.request.path : null;
12
13
 
13
14
  // Make the instance callable
14
15
  const callable = (...args) => get(this.body, ...args);
@@ -42,6 +43,10 @@ class BrunoResponse {
42
43
  return this.res ? this.res.responseTime : null;
43
44
  }
44
45
 
46
+ getUrl() {
47
+ return this.res ? this.url : null;
48
+ }
49
+
45
50
  setBody(data) {
46
51
  if (!this.res) {
47
52
  return;
@@ -51,6 +56,50 @@ class BrunoResponse {
51
56
  this.res.data = clonedData;
52
57
  this.body = clonedData;
53
58
  }
59
+
60
+ // TODO: Refactor: dataBuffer size calculation should be handled in a shared utility so it can be passed and reused across the application
61
+ getSize() {
62
+ if (!this.res) {
63
+ return { header: 0, body: 0, total: 0 };
64
+ }
65
+
66
+ const { data, dataBuffer, headers } = this.res;
67
+ let bodySize = 0;
68
+
69
+ // Use raw received bytes
70
+ if (Buffer.isBuffer(dataBuffer)) {
71
+ bodySize = dataBuffer.length;
72
+ } else {
73
+ // Use server-reported Content-Length
74
+ const contentLength = headers && (headers['content-length'] || headers['Content-Length']);
75
+ if (contentLength && !isNaN(contentLength)) {
76
+ bodySize = parseInt(contentLength, 10);
77
+ } else if (data != null) {
78
+ // Manual calculation
79
+ const raw = typeof data === 'string' ? data : JSON.stringify(data);
80
+ bodySize = Buffer.byteLength(raw);
81
+ }
82
+ }
83
+
84
+ const headerLines = [
85
+ `HTTP/1.1 ${this.res.status} ${this.res.statusText}`,
86
+ ...Object.entries(this.res.headers || {}).flatMap(([key, value]) =>
87
+ Array.isArray(value)
88
+ ? value.map((v) => `${key}: ${v}`)
89
+ : [`${key}: ${value}`]
90
+ ),
91
+ '',
92
+ ''
93
+ ];
94
+ const headerSize = Buffer.byteLength(headerLines.join('\r\n'));
95
+
96
+ return { header: headerSize, body: bodySize, total: headerSize + bodySize };
97
+
98
+ }
99
+
100
+ getDataBuffer() {
101
+ return this.res ? this.res.dataBuffer : null;
102
+ }
54
103
  }
55
104
 
56
105
  module.exports = BrunoResponse;
@@ -12,8 +12,6 @@ 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');
17
15
  const { cleanJson } = require('../utils');
18
16
  const { createBruTestResultMethods } = require('../utils/results');
19
17
 
@@ -181,7 +181,6 @@ class TestRuntime {
181
181
  }
182
182
  } catch (error) {
183
183
  scriptError = error;
184
- console.error('Test script execution error:', error);
185
184
  }
186
185
 
187
186
  if (historyLogger) {
@@ -173,6 +173,9 @@ const addBruShimToContext = (vm, bru) => {
173
173
  vm.setProp(bruRunnerIterationDataObject, 'stringify', runnerStringifyIterationData);
174
174
  runnerStringifyIterationData.dispose();
175
175
 
176
+ vm.setProp(bruRunnerObject, 'iterationData', bruRunnerIterationDataObject);
177
+ bruRunnerIterationDataObject.dispose();
178
+
176
179
  let visualize = vm.newFunction('visualize', function (type, data) {
177
180
  bru.visualize(vm.dump(type), vm.dump(data));
178
181
  });
@@ -300,8 +303,139 @@ const addBruShimToContext = (vm, bru) => {
300
303
  });
301
304
  sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
302
305
 
303
- vm.setProp(bruRunnerObject, 'iterationData', bruRunnerIterationDataObject);
306
+ let bruCookiesObject = vm.newObject();
307
+
308
+ const _jarFn = vm.newFunction('_jar', () => {
309
+ const nativeJar = bru.cookies.jar();
310
+ const jarObj = vm.newObject();
311
+
312
+ const _getCookieFn = vm.newFunction('_getCookie', (url, cookieName) => {
313
+ const promise = vm.newPromise();
314
+ nativeJar.getCookie(vm.dump(url), vm.dump(cookieName), (err, cookie) => {
315
+ if (err) {
316
+ promise.reject(marshallToVm(cleanJson(err), vm));
317
+ } else {
318
+ promise.resolve(marshallToVm(cleanCircularJson(cookie), vm));
319
+ }
320
+ });
321
+ promise.settled.then(vm.runtime.executePendingJobs);
322
+ return promise.handle;
323
+ });
324
+ _getCookieFn.consume((handle) => vm.setProp(jarObj, '_getCookie', handle));
325
+
326
+ const _getCookiesFn = vm.newFunction('_getCookies', (url) => {
327
+ const promise = vm.newPromise();
328
+ nativeJar.getCookies(vm.dump(url), (err, cookies) => {
329
+ if (err) {
330
+ promise.reject(marshallToVm(cleanJson(err), vm));
331
+ } else {
332
+ promise.resolve(marshallToVm(cleanCircularJson(cookies), vm));
333
+ }
334
+ });
335
+ promise.settled.then(vm.runtime.executePendingJobs);
336
+ return promise.handle;
337
+ });
338
+ _getCookiesFn.consume((handle) => vm.setProp(jarObj, '_getCookies', handle));
339
+
340
+ const _setCookieFn = vm.newFunction('_setCookie', (url, nameOrCookieObj, value) => {
341
+ const promise = vm.newPromise();
342
+ const dumpedUrl = vm.dump(url);
343
+ const dumpedNameOrObj = vm.dump(nameOrCookieObj);
344
+
345
+ // Check if the second argument is an object (cookie object case)
346
+ if (typeof dumpedNameOrObj === 'object' && dumpedNameOrObj !== null) {
347
+ // Cookie object case: setCookie(url, cookieObject, callback)
348
+ nativeJar.setCookie(dumpedUrl, dumpedNameOrObj, (err) => {
349
+ if (err) {
350
+ promise.reject(marshallToVm(cleanJson(err), vm));
351
+ } else {
352
+ promise.resolve(vm.undefined);
353
+ }
354
+ });
355
+ } else {
356
+ // Name/value case: setCookie(url, name, value, callback)
357
+ const dumpedValue = value ? vm.dump(value) : '';
358
+ nativeJar.setCookie(dumpedUrl, dumpedNameOrObj, dumpedValue, (err) => {
359
+ if (err) {
360
+ promise.reject(marshallToVm(cleanJson(err), vm));
361
+ } else {
362
+ promise.resolve(vm.undefined);
363
+ }
364
+ });
365
+ }
366
+
367
+ promise.settled.then(vm.runtime.executePendingJobs);
368
+ return promise.handle;
369
+ });
370
+ _setCookieFn.consume((handle) => vm.setProp(jarObj, '_setCookie', handle));
371
+
372
+ const _setCookiesFn = vm.newFunction('_setCookies', (url, cookiesArray) => {
373
+ const promise = vm.newPromise();
374
+
375
+ nativeJar.setCookies(vm.dump(url), vm.dump(cookiesArray), (err) => {
376
+ if (err) {
377
+ promise.reject(marshallToVm(cleanJson(err), vm));
378
+ } else {
379
+ promise.resolve(vm.undefined);
380
+ }
381
+ });
382
+ promise.settled.then(vm.runtime.executePendingJobs);
383
+ return promise.handle;
384
+ });
385
+ _setCookiesFn.consume((handle) => vm.setProp(jarObj, '_setCookies', handle));
386
+
387
+ const _clearFn = vm.newFunction('_clear', () => {
388
+ const promise = vm.newPromise();
389
+ nativeJar.clear((err) => {
390
+ if (err) {
391
+ promise.reject(marshallToVm(cleanJson(err), vm));
392
+ } else {
393
+ promise.resolve(vm.undefined);
394
+ }
395
+ });
396
+ promise.settled.then(vm.runtime.executePendingJobs);
397
+ return promise.handle;
398
+ });
399
+ _clearFn.consume((handle) => vm.setProp(jarObj, '_clear', handle));
400
+
401
+ const _deleteCookiesFn = vm.newFunction('_deleteCookies', (url) => {
402
+ const promise = vm.newPromise();
403
+ nativeJar.deleteCookies(vm.dump(url), (err) => {
404
+ if (err) {
405
+ promise.reject(marshallToVm(cleanJson(err), vm));
406
+ } else {
407
+ promise.resolve(vm.undefined);
408
+ }
409
+ });
410
+ promise.settled.then(vm.runtime.executePendingJobs);
411
+ return promise.handle;
412
+ });
413
+ _deleteCookiesFn.consume((handle) => vm.setProp(jarObj, '_deleteCookies', handle));
414
+
415
+ const _deleteCookieFn = vm.newFunction('_deleteCookie', (url, cookieName) => {
416
+ const promise = vm.newPromise();
417
+ nativeJar.deleteCookie(vm.dump(url), vm.dump(cookieName), (err) => {
418
+ if (err) {
419
+ promise.reject(marshallToVm(cleanJson(err), vm));
420
+ } else {
421
+ promise.resolve(vm.undefined);
422
+ }
423
+ });
424
+ promise.settled.then(vm.runtime.executePendingJobs);
425
+ return promise.handle;
426
+ });
427
+ _deleteCookieFn.consume((handle) => vm.setProp(jarObj, '_deleteCookie', handle));
428
+
429
+ return jarObj;
430
+ });
431
+ _jarFn.consume((handle) => vm.setProp(bruCookiesObject, '_jar', handle));
432
+
433
+ vm.setProp(bruObject, 'cookies', bruCookiesObject);
434
+ bruCookiesObject.dispose();
435
+
304
436
  vm.setProp(bruObject, 'runner', bruRunnerObject);
437
+ bruRunnerObject.dispose();
438
+
305
439
  vm.setProp(vm.global, 'bru', bruObject);
306
440
  bruObject.dispose();
307
441
 
@@ -325,7 +459,41 @@ const addBruShimToContext = (vm, bru) => {
325
459
  return Promise.reject(err);
326
460
  }
327
461
  }
328
- }
462
+ };
463
+
464
+ globalThis.bru.cookies.jar = () => {
465
+ const _jar = globalThis.bru.cookies._jar();
466
+
467
+ const callWithCallback = async (promiseFn, callback) => {
468
+ if (!callback) return await promiseFn();
469
+ try {
470
+ const result = await promiseFn();
471
+ try { await callback(null, result); } catch(cbErr) { return Promise.reject(cbErr); }
472
+ } catch(err) {
473
+ try { await callback(err, null); } catch(cbErr) { return Promise.reject(cbErr); }
474
+ }
475
+ };
476
+
477
+ return {
478
+ getCookie: (url, name, cb) => callWithCallback(() => _jar._getCookie(url, name), cb),
479
+ getCookies: (url, cb) => callWithCallback(() => _jar._getCookies(url), cb),
480
+ setCookie: (url, nameOrCookieObj, valueOrCallback, maybeCallback) => {
481
+ if (typeof nameOrCookieObj === 'object' && nameOrCookieObj !== null) {
482
+ const callback = typeof valueOrCallback === 'function' ? valueOrCallback : undefined;
483
+ return callWithCallback(() => _jar._setCookie(url, nameOrCookieObj), callback);
484
+ } else {
485
+ const value = typeof valueOrCallback === 'string' ? valueOrCallback : '';
486
+ const callback = typeof maybeCallback === 'function' ? maybeCallback :
487
+ (typeof valueOrCallback === 'function' ? valueOrCallback : undefined);
488
+ return callWithCallback(() => _jar._setCookie(url, nameOrCookieObj, value), callback);
489
+ }
490
+ },
491
+ setCookies: (url, cookiesArray, cb) => callWithCallback(() => _jar._setCookies(url, cookiesArray), cb),
492
+ clear: (cb) => callWithCallback(() => _jar._clear(), cb),
493
+ deleteCookies: (url, cb) => callWithCallback(() => _jar._deleteCookies(url), cb),
494
+ deleteCookie: (url, name, cb) => callWithCallback(() => _jar._deleteCookie(url, name), cb)
495
+ };
496
+ };
329
497
  `);
330
498
  };
331
499
 
@@ -10,17 +10,20 @@ const addBrunoResponseShimToContext = (vm, res) => {
10
10
  const headers = marshallToVm(res?.headers, vm);
11
11
  const body = marshallToVm(res?.body, vm);
12
12
  const responseTime = marshallToVm(res?.responseTime, vm);
13
+ const url = marshallToVm(res?.url, vm);
13
14
 
14
15
  vm.setProp(resFn, 'status', status);
15
16
  vm.setProp(resFn, 'statusText', statusText);
16
17
  vm.setProp(resFn, 'headers', headers);
17
18
  vm.setProp(resFn, 'body', body);
18
19
  vm.setProp(resFn, 'responseTime', responseTime);
20
+ vm.setProp(resFn, 'url', url);
19
21
 
20
22
  status.dispose();
21
23
  headers.dispose();
22
24
  body.dispose();
23
25
  responseTime.dispose();
26
+ url.dispose();
24
27
  statusText.dispose();
25
28
 
26
29
  let getStatusText = vm.newFunction('getStatusText', function () {
@@ -59,12 +62,24 @@ const addBrunoResponseShimToContext = (vm, res) => {
59
62
  vm.setProp(resFn, 'getResponseTime', getResponseTime);
60
63
  getResponseTime.dispose();
61
64
 
65
+ let getUrl = vm.newFunction('getUrl', function () {
66
+ return marshallToVm(res.getUrl(), vm);
67
+ });
68
+ vm.setProp(resFn, 'getUrl', getUrl);
69
+ getUrl.dispose();
70
+
62
71
  let setBody = vm.newFunction('setBody', function (data) {
63
72
  res.setBody(vm.dump(data));
64
73
  });
65
74
  vm.setProp(resFn, 'setBody', setBody);
66
75
  setBody.dispose();
67
76
 
77
+ let getSize = vm.newFunction('getSize', function () {
78
+ return marshallToVm(res.getSize(), vm);
79
+ });
80
+ vm.setProp(resFn, 'getSize', getSize);
81
+ getSize.dispose();
82
+
68
83
  vm.setProp(vm.global, 'res', resFn);
69
84
  resFn.dispose();
70
85
  };
package/src/utils.js CHANGED
@@ -118,6 +118,7 @@ const createResponseParser = (response = {}) => {
118
118
  res.headers = response.headers;
119
119
  res.body = response.data;
120
120
  res.responseTime = response.responseTime;
121
+ res.url = response.request ? response.request.protocol + '//' + response.request.host + response.request.path : null;
121
122
 
122
123
  res.jq = (expr) => {
123
124
  const output = jsonQuery(expr, { data: response.data });