- {item.status !== 'error' && item.testStatus === 'pass' && item.status !== 'skipped' ? (
+ {item.testStatus === 'pass' && item.assertionStatus === 'pass' ?
- ) : (
+ : null}
+ {item.status === 'skipped' ?
+
+ :null}
+ {item.status === 'error' || item.testStatus === 'fail' || item.assertionStatus === 'fail' ?
- )}
+ :null}
{item.displayName}
@@ -263,11 +271,15 @@ export default function RunnerResults({ collection }) {
{selectedItem.displayName}
- {selectedItem.testStatus === 'pass' ? (
+ {selectedItem.testStatus === 'pass' && selectedItem.assertionStatus === 'pass' ?
- ) : (
-
- )}
+ : null}
+ {selectedItem.status === 'error' || selectedItem.testStatus === 'fail' || selectedItem.assertionStatus === 'fail' ?
+
+ : null}
+ {selectedItem.status === 'skipped' ?
+
+ : null}
diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
index c5cf174ea..cef99a22d 100644
--- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
+++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
@@ -6,10 +6,7 @@
* LICENSE file at https://github.com/graphql/codemirror-graphql/tree/v0.8.3
*/
-// Todo: Fix this
-// import { interpolate } from '@usebruno/common';
-import brunoCommon from '@usebruno/common';
-const { interpolate } = brunoCommon;
+import { interpolate } from '@usebruno/common';
let CodeMirror;
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
diff --git a/packages/bruno-app/src/utils/codemirror/javascript-lint.js b/packages/bruno-app/src/utils/codemirror/javascript-lint.js
index 371fd2a30..88829322e 100644
--- a/packages/bruno-app/src/utils/codemirror/javascript-lint.js
+++ b/packages/bruno-app/src/utils/codemirror/javascript-lint.js
@@ -76,6 +76,18 @@ if (!SERVER_RENDERED) {
return true;
}
+ /*
+ * Filter out errors due to atob/btoa redefinition
+ *
+ * - W079: Redefinition of '{a}'
+ * This JSHint warning triggers when a variable name conflicts with a built-in global.
+ * We filter this for atob/btoa to allow explicit requires in Node.js environments
+ * where these browser functions might not be available.
+ */
+ if (error.code === 'W079' && (error.a === 'atob' || error.a === 'btoa')) {
+ return false;
+ }
+
return true;
});
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index 73049b918..e258c80ba 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -1,8 +1,6 @@
import {cloneDeep, isEqual, sortBy, filter, map, isString, findIndex, find, each, get } from 'lodash';
import { uuid } from 'utils/common';
import path from 'utils/common/path';
-import brunoCommon from '@usebruno/common';
-const { interpolate } = brunoCommon;
const replaceTabsWithSpaces = (str, numSpaces = 2) => {
if (!str || !str.length || !isString(str)) {
diff --git a/packages/bruno-app/src/utils/exporters/postman-collection.js b/packages/bruno-app/src/utils/exporters/postman-collection.js
index f13a0abd8..65fc7e1ca 100644
--- a/packages/bruno-app/src/utils/exporters/postman-collection.js
+++ b/packages/bruno-app/src/utils/exporters/postman-collection.js
@@ -1,6 +1,5 @@
import * as FileSaver from 'file-saver';
-import brunoConverters from '@usebruno/converters';
-const { brunoToPostman } = brunoConverters;
+import { brunoToPostman } from '@usebruno/converters';
export const exportCollection = (collection) => {
diff --git a/packages/bruno-app/src/utils/importers/insomnia-collection.js b/packages/bruno-app/src/utils/importers/insomnia-collection.js
index 7c8ea7863..c81efaee7 100644
--- a/packages/bruno-app/src/utils/importers/insomnia-collection.js
+++ b/packages/bruno-app/src/utils/importers/insomnia-collection.js
@@ -1,8 +1,7 @@
import jsyaml from 'js-yaml';
import fileDialog from 'file-dialog';
import { BrunoError } from 'utils/common/error';
-import brunoConverters from '@usebruno/converters';
-const { insomniaToBruno } = brunoConverters;
+import { insomniaToBruno } from '@usebruno/converters';
const readFile = (files) => {
return new Promise((resolve, reject) => {
diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js
index 705a8e1ec..70cbe918c 100644
--- a/packages/bruno-app/src/utils/importers/openapi-collection.js
+++ b/packages/bruno-app/src/utils/importers/openapi-collection.js
@@ -1,8 +1,7 @@
import jsyaml from 'js-yaml';
import fileDialog from 'file-dialog';
import { BrunoError } from 'utils/common/error';
-import brunoConverters from '@usebruno/converters';
-const { openApiToBruno } = brunoConverters;
+import { openApiToBruno } from '@usebruno/converters';
const readFile = (files) => {
return new Promise((resolve, reject) => {
diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js
index c768e7389..75db9aaee 100644
--- a/packages/bruno-app/src/utils/importers/postman-collection.js
+++ b/packages/bruno-app/src/utils/importers/postman-collection.js
@@ -1,8 +1,7 @@
import fileDialog from 'file-dialog';
import { BrunoError } from 'utils/common/error';
-import brunoConverters from '@usebruno/converters';
+import { postmanToBruno } from '@usebruno/converters';
import { safeParseJSON } from 'utils/common/index';
-const { postmanToBruno } = brunoConverters;
const readFile = (files) => {
return new Promise((resolve, reject) => {
diff --git a/packages/bruno-app/src/utils/importers/postman-environment.js b/packages/bruno-app/src/utils/importers/postman-environment.js
index a9cc22cbd..e4185deb5 100644
--- a/packages/bruno-app/src/utils/importers/postman-environment.js
+++ b/packages/bruno-app/src/utils/importers/postman-environment.js
@@ -1,12 +1,19 @@
import fileDialog from 'file-dialog';
import { BrunoError } from 'utils/common/error';
-import brunoConverters from '@usebruno/converters';
-const { postmanToBrunoEnvironment } = brunoConverters;
+import { postmanToBrunoEnvironment } from '@usebruno/converters';
const readFile = (files) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
- fileReader.onload = (e) => resolve(e.target.result);
+ fileReader.onload = (e) => {
+ try {
+ let parsedPostmanEnvironment = JSON.parse(e.target.result);
+ resolve(parsedPostmanEnvironment);
+ } catch (err) {
+ console.error(err);
+ reject(new BrunoError('Unable to parse the postman environment json file'));
+ }
+ }
fileReader.onerror = (err) => reject(err);
fileReader.readAsText(files[0]);
});
diff --git a/packages/bruno-app/src/utils/url/index.js b/packages/bruno-app/src/utils/url/index.js
index 852b5fab3..3a82398a1 100644
--- a/packages/bruno-app/src/utils/url/index.js
+++ b/packages/bruno-app/src/utils/url/index.js
@@ -1,11 +1,9 @@
import isEmpty from 'lodash/isEmpty';
import trim from 'lodash/trim';
import each from 'lodash/each';
-import filter from 'lodash/filter';
import find from 'lodash/find';
-import brunoCommon from '@usebruno/common';
-const { interpolate } = brunoCommon;
+import { interpolate } from '@usebruno/common';
const hasLength = (str) => {
if (!str || !str.length) {
diff --git a/packages/bruno-cli/package.json b/packages/bruno-cli/package.json
index 2de5c7142..7347f78fb 100644
--- a/packages/bruno-cli/package.json
+++ b/packages/bruno-cli/package.json
@@ -51,6 +51,7 @@
"@usebruno/js": "0.12.0",
"@usebruno/lang": "0.12.0",
"@usebruno/vm2": "^3.9.13",
+ "@usebruno/requests": "^0.1.0",
"aws4-axios": "^3.3.0",
"axios": "^1.8.3",
"axios-ntlm": "^1.4.2",
diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js
index 83911f876..ba1e671dc 100644
--- a/packages/bruno-cli/src/commands/run.js
+++ b/packages/bruno-cli/src/commands/run.js
@@ -746,7 +746,7 @@ const handler = async function (argv) {
// bail if option is set and there is a failure
if (bail) {
- const requestFailure = result?.error;
+ const requestFailure = result?.error && !result?.skipped;
const testFailure = result?.testResults?.find((iter) => iter.status === 'fail');
const assertionFailure = result?.assertionResults?.find((iter) => iter.status === 'fail');
if (requestFailure || testFailure || assertionFailure) {
diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js
index 514ed850e..2d11350eb 100644
--- a/packages/bruno-cli/src/runner/interpolate-vars.js
+++ b/packages/bruno-cli/src/runner/interpolate-vars.js
@@ -32,7 +32,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
});
});
- const _interpolate = (str) => {
+ const _interpolate = (str, { escapeJSONStrings } = {}) => {
if (!str || !str.length || typeof str !== 'string') {
return str;
}
@@ -51,7 +51,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
}
};
- return interpolate(str, combinedVars);
+ return interpolate(str, combinedVars, { escapeJSONStrings });
};
request.url = _interpolate(request.url);
@@ -67,14 +67,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
if (typeof request.data === 'object') {
try {
let parsed = JSON.stringify(request.data);
- parsed = _interpolate(parsed);
+ parsed = _interpolate(parsed, { escapeJSONStrings: true });
request.data = JSON.parse(parsed);
} catch (err) {}
}
if (typeof request.data === 'string') {
if (request?.data?.length) {
- request.data = _interpolate(request.data);
+ request.data = _interpolate(request.data, { escapeJSONStrings: true });
}
}
} else if (contentType === 'application/x-www-form-urlencoded') {
diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js
index b9efac616..7e7f5d3ac 100644
--- a/packages/bruno-cli/src/runner/prepare-request.js
+++ b/packages/bruno-cli/src/runner/prepare-request.js
@@ -65,6 +65,13 @@ const prepareRequest = (item = {}, collection = {}) => {
}
}
}
+
+ if (collectionAuth.mode === 'digest') {
+ axiosRequest.digestConfig = {
+ username: get(collectionAuth, 'digest.username'),
+ password: get(collectionAuth, 'digest.password')
+ };
+ }
}
if (request.auth && request.auth.mode !== 'inherit') {
@@ -115,6 +122,13 @@ const prepareRequest = (item = {}, collection = {}) => {
'X-WSSE'
] = `UsernameToken Username="${username}", PasswordDigest="${digest}", Nonce="${nonce}", Created="${ts}"`;
}
+
+ if (request.auth.mode === 'digest') {
+ axiosRequest.digestConfig = {
+ username: get(request, 'auth.digest.username'),
+ password: get(request, 'auth.digest.password')
+ };
+ }
}
request.body = request.body || {};
diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js
index c8e137b7b..774587614 100644
--- a/packages/bruno-cli/src/runner/run-single-request.js
+++ b/packages/bruno-cli/src/runner/run-single-request.js
@@ -24,7 +24,7 @@ const { getCookieStringForUrl, saveCookies, shouldUseCookies } = require('../uti
const { createFormData } = require('../utils/form-data');
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
const { NtlmClient } = require('axios-ntlm');
-
+const { addDigestInterceptor } = require('@usebruno/requests');
const onConsoleLog = (type, args) => {
console[type](...args);
@@ -333,6 +333,11 @@ const runSingleRequest = async function (
delete request.awsv4config;
}
+ if (request.digestConfig) {
+ addDigestInterceptor(axiosInstance, request);
+ delete request.digestConfig;
+ }
+
/** @type {import('axios').AxiosResponse} */
response = await axiosInstance(request);
diff --git a/packages/bruno-common/babel.config.js b/packages/bruno-common/babel.config.js
new file mode 100644
index 000000000..2f87e5c7c
--- /dev/null
+++ b/packages/bruno-common/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ presets: [
+ ['@babel/preset-env', { modules: 'auto' }],
+ '@babel/preset-typescript',
+ ],
+};
diff --git a/packages/bruno-common/jest.config.js b/packages/bruno-common/jest.config.js
index a58c252f8..cd4a5f5ae 100644
--- a/packages/bruno-common/jest.config.js
+++ b/packages/bruno-common/jest.config.js
@@ -1,5 +1,9 @@
-/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
- preset: 'ts-jest',
+ transform: {
+ '^.+\\.(ts|js)$': 'babel-jest',
+ },
+ transformIgnorePatterns: [
+ '/node_modules/(?!(lodash-es)/)',
+ ],
testEnvironment: 'node'
};
diff --git a/packages/bruno-common/package.json b/packages/bruno-common/package.json
index df2d6f969..2664d1a84 100644
--- a/packages/bruno-common/package.json
+++ b/packages/bruno-common/package.json
@@ -5,6 +5,12 @@
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/esm/index.js",
+ "require": "./dist/cjs/index.js"
+ }
+ },
"files": [
"dist",
"src",
@@ -18,17 +24,25 @@
"build": "rollup -c",
"prepack": "npm run test && npm run build"
},
+ "dependencies": {
+ "@faker-js/faker": "^9.7.0"
+ },
"devDependencies": {
+ "@babel/preset-env": "^7.26.9",
+ "@babel/preset-typescript": "^7.27.0",
+ "@jest/globals": "^29.7.0",
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
- "@rollup/plugin-typescript": "^9.0.2",
- "rollup":"3.29.5",
+ "@rollup/plugin-typescript": "^12.1.2",
+ "babel-jest": "^29.7.0",
+ "moment": "^2.29.4",
+ "rollup": "3.29.5",
"rollup-plugin-dts": "^5.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-terser": "^7.0.2",
- "typescript": "^4.8.4"
+ "typescript": "^5.8.3"
},
"overrides": {
- "rollup":"3.29.5"
+ "rollup": "3.29.5"
}
}
diff --git a/packages/bruno-common/rollup.config.js b/packages/bruno-common/rollup.config.js
index 51aedecb6..51fc15a4e 100644
--- a/packages/bruno-common/rollup.config.js
+++ b/packages/bruno-common/rollup.config.js
@@ -31,10 +31,5 @@ module.exports = [
typescript({ tsconfig: './tsconfig.json' }),
terser()
]
- },
- {
- input: 'dist/esm/index.d.ts',
- output: [{ file: 'dist/index.d.ts', format: 'esm' }],
- plugins: [dts.default()]
}
];
diff --git a/packages/bruno-common/src/index.ts b/packages/bruno-common/src/index.ts
index 04a709c57..7d3b6e72d 100644
--- a/packages/bruno-common/src/index.ts
+++ b/packages/bruno-common/src/index.ts
@@ -1,5 +1 @@
-import interpolate from './interpolate';
-
-export default {
- interpolate
-};
+export { default as interpolate } from './interpolate';
diff --git a/packages/bruno-common/src/interpolate/index.spec.ts b/packages/bruno-common/src/interpolate/index.spec.ts
index 9dc76b7f1..925886dcd 100644
--- a/packages/bruno-common/src/interpolate/index.spec.ts
+++ b/packages/bruno-common/src/interpolate/index.spec.ts
@@ -1,5 +1,5 @@
import interpolate from './index';
-
+import moment from 'moment';
describe('interpolate', () => {
it('should replace placeholders with values from the object', () => {
const inputString = 'Hello, my name is {{user.name}} and I am {{user.age}} years old';
@@ -41,7 +41,7 @@ describe('interpolate', () => {
Hi, I am {{user.full_name}},
I am {{user.age}} years old.
My favorite food is {{user.fav-food[0]}} and {{user.fav-food[1]}}.
- I like attention: {{user.want.attention}}
+ I like attention: {{user['want.attention']}}
`;
const expectedStr = `
Hi, I am Bruno,
@@ -67,19 +67,21 @@ describe('interpolate', () => {
expect(result).toBe('Hello, my name is {{ user.name }} and I am 4 years old');
});
- it('should give precedence to the last key in case of duplicates', () => {
- const inputString = 'Hello, my name is {{user.name}} and I am {{user.age}} years old';
+ test('should give precedence to the last key in case of duplicates (not at the top level)', () => {
+ const inputString = `Hello, my name is {{data['user.name']}} and {{data.user.name}} I am {{data.user.age}} years old`;
const inputObject = {
- 'user.name': 'Bruno',
- user: {
- name: 'Not Bruno',
- age: 4
+ data: {
+ 'user.name': 'Bruno',
+ user: {
+ name: 'Not _Bruno_',
+ age: 4
+ }
}
};
const result = interpolate(inputString, inputObject);
- expect(result).toBe('Hello, my name is Not Bruno and I am 4 years old');
+ expect(result).toBe('Hello, my name is Bruno and Not _Bruno_ I am 4 years old');
});
});
@@ -238,7 +240,7 @@ describe('interpolate - recursive', () => {
Hi, I am {{user.full_name}},
I am {{user.age}} years old.
My favorite food is {{user.fav-food[0]}} and {{user.fav-food[1]}}.
- I like attention: {{user.want.attention}}
+ I like attention: {{user['want.attention']}}
`;
const inputObject = {
user: {
@@ -354,3 +356,218 @@ describe('interpolate - recursive', () => {
}`);
});
});
+
+describe('interpolate - object handling', () => {
+ it('should stringify simple objects', () => {
+ const inputString = 'User: {{user}}';
+ const inputObject = {
+ 'user': { name: 'Bruno', age: 4 }
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('User: {"name":"Bruno","age":4}');
+ });
+
+ it('should stringify simple objects (dot notation)', () => {
+ const inputString = 'User: {{user.data}}';
+ const inputObject = {
+ 'user.data': { name: 'Bruno', age: 4 }
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('User: {"name":"Bruno","age":4}');
+ });
+
+ it('should stringify nested objects', () => {
+ const inputString = 'User: {{user}}';
+ const inputObject = {
+ 'user': {
+ name: 'Bruno',
+ age: 4,
+ preferences: {
+ food: ['egg', 'meat'],
+ toys: { favorite: 'ball' }
+ }
+ }
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('User: {"name":"Bruno","age":4,"preferences":{"food":["egg","meat"],"toys":{"favorite":"ball"}}}');
+ });
+
+ it('should stringify arrays', () => {
+ const inputString = 'User favorites: {{favorites}}';
+ const inputObject = {
+ favorites: ['egg', 'meat', 'treats']
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('User favorites: ["egg","meat","treats"]');
+ });
+
+ it('should handle null values correctly', () => {
+ const inputString = 'User: {{user}}';
+ const inputObject = {
+ 'user': null
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('User: null');
+ });
+
+ it('should handle objects with nested interpolation', () => {
+ const inputString = 'User: {{user}}';
+ const inputObject = {
+ 'user': {
+ name: 'Bruno',
+ message: '{{user.greeting}}'
+ },
+ 'user.greeting': 'Hello there!'
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('User: {"name":"Bruno","message":"Hello there!"}');
+ });
+
+ it('should handle objects within arrays', () => {
+ const inputString = 'Items: {{items}}';
+ const inputObject = {
+ 'items': [
+ { id: 1, name: 'Toy' },
+ { id: 2, name: 'Bone' },
+ { id: 3, name: 'Ball', colors: ['red', 'blue'] }
+ ]
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('Items: [{"id":1,"name":"Toy"},{"id":2,"name":"Bone"},{"id":3,"name":"Ball","colors":["red","blue"]}]');
+ });
+});
+
+describe('interpolate - mock variable interpolation', () => {
+ it('should replace mock variables with generated values', () => {
+ const inputString = '{{$randomInt}}, {{$randomIP}}, {{$randomIPV4}}, {{$randomIPV6}}, {{$randomBoolean}}';
+
+ const result = interpolate(inputString, {});
+
+ // Validate the result using regex patterns
+ const randomIntPattern = /^\d+$/;
+ const randomIPPattern = /^([\da-f]{1,4}:){7}[\da-f]{1,4}$|^(\d{1,3}\.){3}\d{1,3}$/;
+ const randomIPV4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
+ const randomIPV6Pattern = /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/;
+ const randomBooleanPattern = /^(true|false)$/;
+
+ const [randomInt, randomIP, randomIPV4, randomIPV6, randomBoolean] = result.split(', ');
+
+ expect(randomIntPattern.test(randomInt)).toBe(true);
+ expect(randomIPPattern.test(randomIP)).toBe(true);
+ expect(randomIPV4Pattern.test(randomIPV4)).toBe(true);
+ expect(randomIPV6Pattern.test(randomIPV6)).toBe(true);
+ expect(randomBooleanPattern.test(randomBoolean)).toBe(true);
+ });
+
+ it('should leave mock variables unchanged if no corresponding function exists', () => {
+ const inputString = 'Random number: {{$nonExistentMock}}';
+
+ const result = interpolate(inputString, {});
+
+ expect(result).toBe('Random number: {{$nonExistentMock}}');
+ });
+
+ it('should escape special characters in mock variable values and produce valid JSON when escapeJSONStrings is true', () => {
+ const inputString = '{"escapedValue": "{{$randomLoremParagraphs}}"}';
+
+ expect(() => {
+ const result = interpolate(inputString, {}, { escapeJSONStrings: true });
+ JSON.parse(result); // This should not throw an error
+ }).not.toThrow();
+ });
+
+ it('should not produce valid JSON when escapeJSONStrings is false', () => {
+ const inputString = '{"escapedValue": "{{$randomLoremParagraphs}}"}';
+
+ expect(() => {
+ const result = interpolate(inputString, {}, { escapeJSONStrings: false });
+ JSON.parse(result); // This should throw an error
+ }).toThrow();
+ });
+
+ it('should throw an error when producing invalid JSON regardless of escapeJSONStrings option', () => {
+ const inputString = '{"escapedValue": "{{$randomLoremParagraphs}}"}';
+
+ // Test without providing the options argument
+ expect(() => {
+ const result = interpolate(inputString, {});
+ JSON.parse(result); // This should throw an error
+ }).toThrow();
+
+ // Test with escapeJSONStrings explicitly set to false
+ expect(() => {
+ const result = interpolate(inputString, {}, { escapeJSONStrings: false });
+ JSON.parse(result); // This should throw an error
+ }).toThrow();
+ });
+});
+
+describe('interpolate - Date() handling', () => {
+ it('should interpolate Date() using JSON.stringify', () => {
+ const inputString = 'Date is {{date}}';
+ const inputObject = {
+ date: new Date("2025-04-17T15:33:41.117Z")
+ };
+
+ const jsonStringifiedDate = JSON.stringify(inputObject.date);
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('Date is "2025-04-17T15:33:41.117Z"');
+ expect(result).toBe(`Date is ${jsonStringifiedDate}`);
+ })
+
+ it('should interpolate Date() when its nested in an object', () => {
+ const inputString = 'Date is {{date}}';
+ const inputObject = {
+ date: {
+ now: new Date("2025-04-17T15:33:41.117Z")
+ }
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('Date is {"now":"2025-04-17T15:33:41.117Z"}');
+ })
+});
+
+describe('interpolate - moment() handling', () => {
+ it('should interpolate moment() using JSON.stringify', () => {
+ const inputString = 'Date is {{date}}';
+ const inputObject = {
+ date: moment("2025-04-17T15:33:41.117Z")
+ };
+
+ const jsonStringifiedDate = JSON.stringify(inputObject.date);
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('Date is "2025-04-17T15:33:41.117Z"');
+ expect(result).toBe(`Date is ${jsonStringifiedDate}`);
+ })
+
+ it('should interpolate moment() when its nested in an object', () => {
+ const inputString = 'Date is {{date}}';
+ const inputObject = {
+ date: {
+ now: moment("2025-04-17T15:33:41.117Z")
+ }
+ };
+
+ const result = interpolate(inputString, inputObject);
+
+ expect(result).toBe('Date is {"now":"2025-04-17T15:33:41.117Z"}');
+ })
+})
\ No newline at end of file
diff --git a/packages/bruno-common/src/interpolate/index.ts b/packages/bruno-common/src/interpolate/index.ts
index 4a4092d88..83d803480 100644
--- a/packages/bruno-common/src/interpolate/index.ts
+++ b/packages/bruno-common/src/interpolate/index.ts
@@ -11,22 +11,51 @@
* Output: Hello, my name is Bruno and I am 4 years old
*/
-import { Set } from 'typescript';
-import { flattenObject } from '../utils';
+import { mockDataFunctions } from '../utils/faker-functions';
+import { get } from "lodash-es";
-const interpolate = (str: string, obj: Record
): string => {
- if (!str || typeof str !== 'string' || !obj || typeof obj !== 'object') {
+const interpolate = (
+ str: string,
+ obj: Record,
+ options: { escapeJSONStrings?: boolean } = { escapeJSONStrings: false }
+): string => {
+ if (!str || typeof str !== 'string') {
return str;
}
- const flattenedObj = flattenObject(obj);
+ const { escapeJSONStrings } = options;
- return replace(str, flattenedObj);
+ const patternRegex = /\{\{\$(\w+)\}\}/g;
+ str = str.replace(patternRegex, (match, keyword) => {
+ let replacement = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.();
+
+ if (replacement === undefined) return match;
+ replacement = String(replacement);
+
+ if (!escapeJSONStrings) return replacement;
+
+ // All the below chars inside of a JSON String field
+ // will make it invalid JSON. So we will have to escape them with `\`.
+ // This is not exhaustive but selective to what faker-js can output.
+ if (!/[\\\n\r\t\"]/.test(replacement)) return replacement;
+ return replacement
+ .replace(/\\/g, '\\\\')
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/\t/g, '\\t')
+ .replace(/\"/g, '\\"');
+ });
+
+ if (!obj || typeof obj !== 'object') {
+ return str;
+ }
+
+ return replace(str, obj);
};
const replace = (
str: string,
- flattenedObj: Record,
+ obj: Record,
visited = new Set(),
results = new Map()
): string => {
@@ -37,7 +66,10 @@ const replace = (
const patternRegex = /\{\{([^}]+)\}\}/g;
matchFound = false;
resultStr = resultStr.replace(patternRegex, (match, placeholder) => {
- const replacement = flattenedObj[placeholder];
+ let replacement = get(obj, placeholder);
+ if (typeof replacement === 'object' && replacement !== null) {
+ replacement = JSON.stringify(replacement);
+ }
if (results.has(match)) {
return results.get(match);
@@ -45,7 +77,7 @@ const replace = (
if (patternRegex.test(replacement) && !visited.has(match)) {
visited.add(match);
- const result = replace(replacement, flattenedObj, visited, results);
+ const result = replace(replacement, obj, visited, results);
results.set(match, result);
matchFound = true;
@@ -64,4 +96,4 @@ const replace = (
return resultStr;
};
-export default interpolate;
+export default interpolate;
\ No newline at end of file
diff --git a/packages/bruno-common/src/utils/faker-functions.spec.ts b/packages/bruno-common/src/utils/faker-functions.spec.ts
new file mode 100644
index 000000000..8d1b482da
--- /dev/null
+++ b/packages/bruno-common/src/utils/faker-functions.spec.ts
@@ -0,0 +1,141 @@
+import { mockDataFunctions } from "./faker-functions";
+
+describe("mockDataFunctions Regex Validation", () => {
+ test("all values should match their expected patterns", () => {
+ const patterns: Record = {
+ guid: /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/,
+ timestamp: /^\d{13,}$/,
+ isoTimestamp: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
+ randomUUID: /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/,
+ randomAlphaNumeric: /^[\w]$/,
+ randomBoolean: /^(true|false)$/,
+ randomInt: /^\d+$/,
+ randomColor: /^[\w\s]+$/,
+ randomHexColor: /^#[\da-f]{6}$/,
+ randomAbbreviation: /^\w{2,6}$/,
+ randomIP: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$|^(\d{1,3}\.){3}\d{1,3}$/,
+ randomIPV4: /^(\d{1,3}\.){3}\d{1,3}$/,
+ randomIPV6: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/,
+ randomMACAddress: /^([\da-f]{2}:){5}[\da-f]{2}$/,
+ randomPassword: /^[\w\d]{8,}$/,
+ randomLocale: /^[A-Z]{2}$/,
+ randomUserAgent: /^[\w\/\.\s\(\)\+\-;:_,]+$/,
+ randomProtocol: /^(http|https|ftp)s?$/,
+ randomSemver: /^\d+\.\d+\.\d+$/,
+ randomFirstName: /^[\s\S]+$/,
+ randomLastName: /^[\s\S]+$/,
+ randomFullName: /^[\s\S]+$/,
+ randomNamePrefix: /^[\s\S]+$/,
+ randomNameSuffix: /^[\s\S]+$/,
+ randomJobArea: /^[\s\S]+$/,
+ randomJobDescriptor: /^[\s\S]+$/,
+ randomJobTitle: /^[\s\S]+$/,
+ randomJobType: /^[\s\S]+$/,
+ randomPhoneNumber: /^[\s\S]+$/,
+ randomPhoneNumberExt: /^[\s\S]+$/,
+ randomCity: /^[\s\S]+$/,
+ randomStreetName: /^[\s\S]+$/,
+ randomStreetAddress: /^[\s\S]+$/,
+ randomCountry: /^[\s\S]+$/,
+ randomCountryCode: /^[\s\S]+$/,
+ randomLatitude: /^[\s\S]+$/,
+ randomLongitude: /^[\s\S]+$/,
+ randomAvatarImage: /^[\s\S]+$/,
+ randomImageUrl: /^[\s\S]+$/,
+ randomAbstractImage: /^[\s\S]+$/,
+ randomAnimalsImage: /^[\s\S]+$/,
+ randomBusinessImage: /^[\s\S]+$/,
+ randomCatsImage: /^[\s\S]+$/,
+ randomCityImage: /^[\s\S]+$/,
+ randomFoodImage: /^[\s\S]+$/,
+ randomNightlifeImage: /^[\s\S]+$/,
+ randomFashionImage: /^[\s\S]+$/,
+ randomPeopleImage: /^[\s\S]+$/,
+ randomNatureImage: /^[\s\S]+$/,
+ randomSportsImage: /^[\s\S]+$/,
+ randomTransportImage: /^[\s\S]+$/,
+ randomImageDataUri: /^[\s\S]+$/,
+ randomBankAccount: /^[\s\S]+$/,
+ randomBankAccountName: /^[\s\S]+$/,
+ randomCreditCardMask: /^[\s\S]+$/,
+ randomBankAccountBic: /^[\s\S]+$/,
+ randomBankAccountIban: /^[\s\S]+$/,
+ randomTransactionType: /^[\s\S]+$/,
+ randomCurrencyCode: /^[\s\S]+$/,
+ randomCurrencyName: /^[\s\S]+$/,
+ randomCurrencySymbol: /^[\s\S]+$/,
+ randomBitcoin: /^[\s\S]+$/,
+ randomCompanyName: /^[\s\S]+$/,
+ randomCompanySuffix: /^[\s\S]+$/,
+ randomBs: /^[\s\S]+$/,
+ randomBsAdjective: /^[\s\S]+$/,
+ randomBsBuzz: /^[\s\S]+$/,
+ randomBsNoun: /^[\s\S]+$/,
+ randomCatchPhrase: /^[\s\S]+$/,
+ randomCatchPhraseAdjective: /^[\s\S]+$/,
+ randomCatchPhraseDescriptor: /^[\s\S]+$/,
+ randomCatchPhraseNoun: /^[\s\S]+$/,
+ randomDatabaseColumn: /^[\s\S]+$/,
+ randomDatabaseType: /^[\s\S]+$/,
+ randomDatabaseCollation: /^[\s\S]+$/,
+ randomDatabaseEngine: /^[\s\S]+$/,
+ randomDateFuture: /^[\s\S]+$/,
+ randomDatePast: /^[\s\S]+$/,
+ randomDateRecent: /^[\s\S]+$/,
+ randomWeekday: /^[\s\S]+$/,
+ randomMonth: /^[\s\S]+$/,
+ randomDomainName: /^[\s\S]+$/,
+ randomDomainSuffix: /^[\s\S]+$/,
+ randomDomainWord: /^[\s\S]+$/,
+ randomEmail: /^[\w_.\-]+@[\w]+\.[a-z]+$/,
+ randomExampleEmail: /^[\w\.-]+@example\.[a-z]+$/,
+ randomUserName: /^[\w.\-]+$/,
+ randomUrl: /^https:\/\/[\w\-]+\.[a-z]+\/?$/,
+ randomFileName: /^[\w\_]+\.[\w\d]+$/,
+ randomFileType: /^[\w]+$/,
+ randomFileExt: /^[\w\d]+$/,
+ randomCommonFileName: /^[\w\_]+\.[\w\d]+$/,
+ randomCommonFileType: /^[\w]+$/,
+ randomCommonFileExt: /^[\w\d]+$/,
+ randomFilePath: /^[\s\S]+$/,
+ randomDirectoryPath: /^\/[-\w\+\/]+$/,
+ randomMimeType: /^[\w]+\/[\w\d\-\+\.]+$/,
+ randomPrice: /^\d+\.\d{2}$/,
+ randomProduct: /^[\s\S]+$/,
+ randomProductAdjective: /^[\s\S]+$/,
+ randomProductMaterial: /^[\s\S]+$/,
+ randomProductName: /^[\s\S]+$/,
+ randomDepartment: /^[\s\S]+$/,
+ randomNoun: /^[\s\S]+$/,
+ randomVerb: /^[\s\S]+$/,
+ randomIngverb: /^[\s\S]+$/,
+ randomAdjective: /^[\s\S]+$/,
+ randomWord: /^[\s\S]+$/,
+ randomWords: /^[\s\S]+$/,
+ randomPhrase: /^[\s\S]+$/,
+ randomLoremWord: /^[\s\S]+$/,
+ randomLoremWords: /^[\s\S]+$/,
+ randomLoremSentence: /^[\s\S]+$/,
+ randomLoremSentences: /^[\s\S]+$/,
+ randomLoremParagraph: /^[\s\S]+$/,
+ randomLoremParagraphs: /^[\s\S]+$/,
+ randomLoremText: /^[\s\S]+$/,
+ randomLoremSlug: /^[\s\S]+$/,
+ randomLoremLines: /^[\s\S]+$/,
+ };
+
+ const errors: string[] = [];
+
+ Object.entries(mockDataFunctions).forEach(([key, func]) => {
+ const pattern = patterns[key];
+ const value = String(func());
+ if (!value.match(pattern)) {
+ errors.push(`Pattern mismatch for ${key}: expected ${pattern}, received ${value}`);
+ }
+ });
+
+ if (errors.length > 0) {
+ throw new Error(errors.join("\n"));
+ }
+ });
+});
diff --git a/packages/bruno-electron/src/ipc/network/faker-functions.js b/packages/bruno-common/src/utils/faker-functions.ts
similarity index 97%
rename from packages/bruno-electron/src/ipc/network/faker-functions.js
rename to packages/bruno-common/src/utils/faker-functions.ts
index c97d262a2..64d1ed87b 100644
--- a/packages/bruno-electron/src/ipc/network/faker-functions.js
+++ b/packages/bruno-common/src/utils/faker-functions.ts
@@ -1,6 +1,6 @@
-const { faker } = require('@faker-js/faker');
+import { faker } from '@faker-js/faker';
-const mockDataFunctions = {
+export const mockDataFunctions = {
guid: () => faker.string.uuid(),
timestamp: () => faker.date.anytime().getTime().toString(),
isoTimestamp: () => faker.date.anytime().toISOString(),
@@ -9,7 +9,7 @@ const mockDataFunctions = {
randomBoolean: () => faker.datatype.boolean(),
randomInt: () => faker.number.int(),
randomColor: () => faker.color.human(),
- randomHexColor: () => faker.internet.color(),
+ randomHexColor: () => faker.color.rgb(),
randomAbbreviation: () => faker.hacker.abbreviation(),
randomIP: () => faker.internet.ip(),
randomIPV4: () => faker.internet.ipv4(),
@@ -121,7 +121,3 @@ const mockDataFunctions = {
randomLoremSlug: () => faker.lorem.slug(),
randomLoremLines: () => faker.lorem.lines()
};
-
-module.exports = {
- mockDataFunctions
-};
diff --git a/packages/bruno-common/src/utils/index.spec.ts b/packages/bruno-common/src/utils/index.spec.ts
deleted file mode 100644
index 09689ac65..000000000
--- a/packages/bruno-common/src/utils/index.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { flattenObject } from './index';
-
-describe('flattenObject', () => {
- it('should flatten a simple object', () => {
- const input = { a: 1, b: { c: 2, d: { e: 3 } } };
- const output = flattenObject(input);
- expect(output).toEqual({ a: 1, 'b.c': 2, 'b.d.e': 3 });
- });
-
- it('should flatten an object with arrays', () => {
- const input = { a: 1, b: { c: [2, 3, 4], d: { e: 5 } } };
- const output = flattenObject(input);
- expect(output).toEqual({ a: 1, 'b.c[0]': 2, 'b.c[1]': 3, 'b.c[2]': 4, 'b.d.e': 5 });
- });
-
- it('should flatten an object with arrays having objects', () => {
- const input = { a: 1, b: { c: [{ d: 2 }, { e: 3 }], f: { g: 4 } } };
- const output = flattenObject(input);
- expect(output).toEqual({ a: 1, 'b.c[0].d': 2, 'b.c[1].e': 3, 'b.f.g': 4 });
- });
-
- it('should handle null values', () => {
- const input = { a: 1, b: { c: null, d: { e: 3 } } };
- const output = flattenObject(input);
- expect(output).toEqual({ a: 1, 'b.c': null, 'b.d.e': 3 });
- });
-
- it('should handle an empty object', () => {
- const input = {};
- const output = flattenObject(input);
- expect(output).toEqual({});
- });
-
- it('should handle an object with nested empty objects', () => {
- const input = { a: { b: {}, c: { d: {} } } };
- const output = flattenObject(input);
- expect(output).toEqual({});
- });
-
- it('should handle an object with duplicate keys - dot notation used to define the last duplicate key', () => {
- const input = { a: { b: 2 }, 'a.b': 1 };
- const output = flattenObject(input);
- expect(output).toEqual({ 'a.b': 1 });
- });
-
- it('should handle an object with duplicate keys - inner object used to define the last duplicate key', () => {
- const input = { 'a.b': 1, a: { b: 2 } };
- const output = flattenObject(input);
- expect(output).toEqual({ 'a.b': 2 });
- });
-});
diff --git a/packages/bruno-common/src/utils/index.ts b/packages/bruno-common/src/utils/index.ts
deleted file mode 100644
index bba8f1310..000000000
--- a/packages/bruno-common/src/utils/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export const flattenObject = (obj: Record, parentKey: string = ''): Record => {
- return Object.entries(obj).reduce((acc: Record, [key, value]: [string, any]) => {
- const newKey = parentKey ? (Array.isArray(obj) ? `${parentKey}[${key}]` : `${parentKey}.${key}`) : key;
- if (typeof value === 'object' && value !== null) {
- Object.assign(acc, flattenObject(value, newKey));
- } else {
- acc[newKey] = value;
- }
- return acc;
- }, {});
-};
diff --git a/packages/bruno-common/tsconfig.json b/packages/bruno-common/tsconfig.json
index 57a8bcc74..9978d57dc 100644
--- a/packages/bruno-common/tsconfig.json
+++ b/packages/bruno-common/tsconfig.json
@@ -6,14 +6,14 @@
"skipLibCheck": true,
"jsx": "react",
"module": "ESNext",
- "declaration": true,
- "declarationDir": "types",
"sourceMap": true,
"outDir": "dist",
"moduleResolution": "node",
- "emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true
+ "forceConsistentCasingInFileNames": true,
+ "allowJs": true,
+ "checkJs": false
},
+ "include": ["src/**/*.ts", "src/**/*.js"],
"exclude": ["dist", "node_modules", "tests"]
}
diff --git a/packages/bruno-converters/src/index.js b/packages/bruno-converters/src/index.js
index a256c0b31..fa89457ed 100644
--- a/packages/bruno-converters/src/index.js
+++ b/packages/bruno-converters/src/index.js
@@ -1,16 +1,5 @@
-import postmanToBruno from './postman/postman-to-bruno.js';
-import postmanToBrunoEnvironment from './postman/postman-env-to-bruno-env.js';
-
-import brunoToPostman from './postman/bruno-to-postman.js';
-
-import openApiToBruno from './openapi/openapi-to-bruno.js';
-
-import insomniaToBruno from './insomnia/insomnia-to-bruno.js';
-
-export default {
- postmanToBruno,
- postmanToBrunoEnvironment,
- brunoToPostman,
- openApiToBruno,
- insomniaToBruno
-};
+export { default as postmanToBruno } from './postman/postman-to-bruno.js';
+export { default as postmanToBrunoEnvironment } from './postman/postman-env-to-bruno-env.js';
+export { default as brunoToPostman } from './postman/bruno-to-postman.js';
+export { default as openApiToBruno } from './openapi/openapi-to-bruno.js';
+export { default as insomniaToBruno } from './insomnia/insomnia-to-bruno.js';
\ No newline at end of file
diff --git a/packages/bruno-converters/src/insomnia/insomnia-to-bruno.js b/packages/bruno-converters/src/insomnia/insomnia-to-bruno.js
index 976550965..63af45d77 100644
--- a/packages/bruno-converters/src/insomnia/insomnia-to-bruno.js
+++ b/packages/bruno-converters/src/insomnia/insomnia-to-bruno.js
@@ -159,7 +159,79 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
return brunoRequestItem;
};
-const parseInsomniaCollection = (_insomniaCollection) => {
+const isInsomniaV5Export = (data) => {
+ // V5 format has a type property at the root level
+ if (data.type && data.type.startsWith('collection.insomnia.rest/5')) {
+ return true;
+ }
+ return false;
+};
+
+const parseInsomniaV5Collection = (data) => {
+ const brunoCollection = {
+ name: data.name || 'Untitled Collection',
+ uid: uuid(),
+ version: '1',
+ items: [],
+ environments: []
+ };
+
+ try {
+ // Parse the collection items
+ const parseCollectionItems = (items, allItems = []) => {
+ if (!Array.isArray(items)) {
+ throw new Error('Invalid items format: expected array');
+ }
+
+ return items.map((item, index) => {
+ if (!item) {
+ return null;
+ }
+
+ // In v5, requests might be defined with method property or meta.type
+ if (item.method && item.url) {
+ const request = {
+ _id: item.meta?.id || uuid(),
+ name: item.name || 'Untitled Request',
+ url: item.url || '',
+ method: item.method || '',
+ headers: item.headers || [],
+ parameters: item.parameters || [],
+ pathParameters: item.pathParameters || [],
+ authentication: item.authentication || {},
+ body: item.body || {}
+ };
+ return transformInsomniaRequestItem(request, index, allItems);
+ } else if (item.children && Array.isArray(item.children)) {
+ // Process folder
+ return {
+ uid: uuid(),
+ name: item.name || 'Untitled Folder',
+ type: 'folder',
+ items: parseCollectionItems(item.children, item.children)
+ };
+ }
+ return null;
+ }).filter(Boolean);
+ };
+
+ if (data.collection && Array.isArray(data.collection)) {
+ brunoCollection.items = parseCollectionItems(data.collection, data.collection);
+ }
+
+ // Parse environments if available
+ if (data.environments) {
+ // Handle environments implementation if needed
+ }
+
+ return brunoCollection;
+ } catch (err) {
+ console.error('Error parsing collection:', err);
+ throw new Error('An error occurred while parsing the Insomnia v5 collection: ' + err.message);
+ }
+};
+
+const parseInsomniaCollection = (data) => {
const brunoCollection = {
name: '',
uid: uuid(),
@@ -169,8 +241,7 @@ const parseInsomniaCollection = (_insomniaCollection) => {
};
try {
- const insomniaExport = _insomniaCollection;
- const insomniaResources = get(insomniaExport, 'resources', []);
+ const insomniaResources = get(data, 'resources', []);
const insomniaCollection = insomniaResources.find((resource) => resource._type === 'workspace');
if (!insomniaCollection) {
@@ -180,8 +251,8 @@ const parseInsomniaCollection = (_insomniaCollection) => {
brunoCollection.name = insomniaCollection.name;
const requestsAndFolders =
- insomniaResources.filter((resource) => resource._type === 'request' || resource._type === 'request_group') ||
- [];
+ insomniaResources.filter((resource) => resource._type === 'request' || resource._type === 'request_group') ||
+ [];
function createFolderStructure(resources, parentId = null) {
const requestGroups =
@@ -194,11 +265,13 @@ const parseInsomniaCollection = (_insomniaCollection) => {
(resource) => resource._type === 'request' && resource.parentId === folder._id
);
- return {
+ return {
uid: uuid(),
name,
type: 'folder',
- items: createFolderStructure(resources, folder._id).concat(requests.map(transformInsomniaRequestItem))
+ items: createFolderStructure(resources, folder._id).concat(
+ requests.filter(r => r.parentId === folder._id).map(transformInsomniaRequestItem)
+ )
};
});
@@ -208,20 +281,27 @@ const parseInsomniaCollection = (_insomniaCollection) => {
brunoCollection.items = createFolderStructure(requestsAndFolders, insomniaCollection._id);
return brunoCollection;
} catch (err) {
- throw new Error('An error occurred while parsing the Insomnia collection');
+ console.error('Error parsing collection:', err);
+ throw new Error('An error occurred while parsing the Insomnia collection: ' + err.message);
}
};
export const insomniaToBruno = (insomniaCollection) => {
try {
- const collection = parseInsomniaCollection(insomniaCollection);
+ let collection;
+ if (isInsomniaV5Export(insomniaCollection)) {
+ collection = parseInsomniaV5Collection(insomniaCollection);
+ } else {
+ collection = parseInsomniaCollection(insomniaCollection);
+ }
+
const transformedCollection = transformItemsInCollection(collection);
const hydratedCollection = hydrateSeqInCollection(transformedCollection);
const validatedCollection = validateSchema(hydratedCollection);
return validatedCollection;
} catch (err) {
console.error(err);
- throw new Error('Import collection failed');
+ throw new Error('Import collection failed: ' + err.message);
}
};
diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js
index bc6b0ffb1..b741cd3b2 100644
--- a/packages/bruno-converters/src/postman/postman-translations.js
+++ b/packages/bruno-converters/src/postman/postman-translations.js
@@ -15,11 +15,17 @@ const replacements = {
'pm\\.expect\\(': 'expect(',
'pm\\.environment\\.has\\(([^)]+)\\)': 'bru.getEnvVar($1) !== undefined && bru.getEnvVar($1) !== null',
'pm\\.response\\.code': 'res.getStatus()',
- 'pm\\.response\\.text\\(': 'res.getBody()?.toString(',
+ 'pm\\.response\\.text\\(\\)': 'JSON.stringify(res.getBody())',
'pm\\.expect\\.fail\\(': 'expect.fail(',
'pm\\.response\\.responseTime': 'res.getResponseTime()',
'pm\\.environment\\.name': 'bru.getEnvName()',
+ 'pm\\.response\\.status': 'res.statusText',
+ 'pm\\.response\\.headers': 'req.getHeaders()',
"tests\\['([^']+)'\\]\\s*=\\s*([^;]+);": 'test("$1", function() { expect(Boolean($2)).to.be.true; });',
+ 'pm\\.request\\.url': 'req.getUrl()',
+ 'pm\\.request\\.method': 'req.getMethod()',
+ 'pm\\.request\\.headers': 'req.getHeaders()',
+ 'pm\\.request\\.body': 'req.getBody()',
// deprecated translations
'postman\\.setEnvironmentVariable\\(': 'bru.setEnvVar(',
'postman\\.getEnvironmentVariable\\(': 'bru.getEnvVar(',
diff --git a/packages/bruno-converters/tests/insomnia/insomnia-collection-v5.spec.js b/packages/bruno-converters/tests/insomnia/insomnia-collection-v5.spec.js
new file mode 100644
index 000000000..dfd93044a
--- /dev/null
+++ b/packages/bruno-converters/tests/insomnia/insomnia-collection-v5.spec.js
@@ -0,0 +1,160 @@
+import { describe, it, expect } from '@jest/globals';
+import insomniaToBruno from '../../src/insomnia/insomnia-to-bruno';
+import jsyaml from 'js-yaml';
+
+describe('insomnia-collection', () => {
+ it('should correctly import a valid Insomnia v5 collection file', async () => {
+ const brunoCollection = insomniaToBruno(jsyaml.load(insomniaCollection));
+
+ expect(brunoCollection).toMatchObject(expectedOutput)
+ });
+});
+
+const insomniaCollection = `
+type: collection.insomnia.rest/5.0
+name: Hello World Workspace Insomnia
+meta:
+ id: wrk_9381cf78cb0a4eaaab1d571f29f928dc
+ created: 1744194421962
+ modified: 1744194421962
+collection:
+ - name: Folder1
+ meta:
+ id: fld_6beacec0bd2f4370be98169217e82a2c
+ created: 1744194421968
+ modified: 1744194421968
+ sortKey: -1744194421968
+ children:
+ - url: https://httpbin.org/get
+ name: Request1
+ meta:
+ id: req_e9fbdc9c88984068a04f442e052d4ff1
+ created: 1744194421965
+ modified: 1744194421965
+ isPrivate: false
+ sortKey: -1744194421965
+ method: GET
+ settings:
+ renderRequestBody: true
+ encodeUrl: true
+ followRedirects: global
+ cookies:
+ send: true
+ store: true
+ rebuildPath: true
+ - name: Folder2
+ meta:
+ id: fld_96508d79bf06420a853b07482ab280d7
+ created: 1744194421969
+ modified: 1744194421969
+ sortKey: -1744194421969
+ children:
+ - url: https://httpbin.org/get
+ name: Request2
+ meta:
+ id: req_3c572aa26a964f1f800bfa5c53cacb75
+ created: 1744194421967
+ modified: 1744194421967
+ isPrivate: false
+ sortKey: -1744194421968
+ method: GET
+ settings:
+ renderRequestBody: true
+ encodeUrl: true
+ followRedirects: global
+ cookies:
+ send: true
+ store: true
+ rebuildPath: true
+cookieJar:
+ name: Default Jar
+ meta:
+ id: jar_9ecb97079037c7d5bb888f0bfdec9b0e1275c6d1
+ created: 1744194421971
+ modified: 1744194421971
+environments:
+ name: Imported Environment
+ meta:
+ id: env_a8a9a8ff952d4d079edf53f8ee22a423
+ created: 1744194421970
+ modified: 1744194421970
+ isPrivate: false
+ data:
+ var1: value1
+ var2: value2
+`
+
+const expectedOutput = {
+ "environments": [],
+ "items": [
+ {
+ "items": [
+ {
+ "name": "Request1",
+ "request": {
+ "auth": {
+ "basic": null,
+ "bearer": null,
+ "digest": null,
+ "mode": "none",
+ },
+ "body": {
+ "formUrlEncoded": [],
+ "json": null,
+ "mode": "none",
+ "multipartForm": [],
+ "text": null,
+ "xml": null,
+ },
+ "headers": [],
+ "method": "GET",
+ "params": [],
+ "url": "https://httpbin.org/get",
+ },
+ "seq": 1,
+ "type": "http-request",
+ "uid": "mockeduuidvalue123456",
+ },
+ ],
+ "name": "Folder1",
+ "type": "folder",
+ "uid": "mockeduuidvalue123456",
+ },
+ {
+ "items": [
+ {
+ "name": "Request2",
+ "request": {
+ "auth": {
+ "basic": null,
+ "bearer": null,
+ "digest": null,
+ "mode": "none",
+ },
+ "body": {
+ "formUrlEncoded": [],
+ "json": null,
+ "mode": "none",
+ "multipartForm": [],
+ "text": null,
+ "xml": null,
+ },
+ "headers": [],
+ "method": "GET",
+ "params": [],
+ "url": "https://httpbin.org/get",
+ },
+ "seq": 1,
+ "type": "http-request",
+ "uid": "mockeduuidvalue123456",
+ },
+ ],
+ "name": "Folder2",
+ "type": "folder",
+ "uid": "mockeduuidvalue123456",
+ },
+ ],
+ "name": "Hello World Workspace Insomnia",
+ "uid": "mockeduuidvalue123456",
+ "version": "1",
+};
\ No newline at end of file
diff --git a/packages/bruno-converters/tests/postman/postman-translations.spec.js b/packages/bruno-converters/tests/postman/postman-translations.spec.js
index 95a2c96f9..a2fcf1560 100644
--- a/packages/bruno-converters/tests/postman/postman-translations.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-translations.spec.js
@@ -137,17 +137,3 @@ describe('postmanTranslation function', () => {
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
});
-
-test('should handle response commands', () => {
- const inputScript = `
- const responseTime = pm.response.responseTime;
- const responseCode = pm.response.code;
- const responseText = pm.response.text();
- `;
- const expectedOutput = `
- const responseTime = res.getResponseTime();
- const responseCode = res.getStatus();
- const responseText = res.getBody()?.toString();
- `;
- expect(postmanTranslation(inputScript)).toBe(expectedOutput);
-});
diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-request.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-request.spec.js
new file mode 100644
index 000000000..5b1305f73
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/postman-request.spec.js
@@ -0,0 +1,27 @@
+const { default: postmanTranslation } = require("../../../src/postman/postman-translations");
+
+describe('postmanTranslations - request commands', () => {
+ test('should handle request commands', () => {
+ const inputScript = `
+ const requestUrl = pm.request.url;
+ const requestMethod = pm.request.method;
+ const requestHeaders = pm.request.headers;
+ const requestBody = pm.request.body;
+
+ pm.test('Request method is POST', function() {
+ pm.expect(pm.request.method).to.equal('POST');
+ });
+ `;
+ const expectedOutput = `
+ const requestUrl = req.getUrl();
+ const requestMethod = req.getMethod();
+ const requestHeaders = req.getHeaders();
+ const requestBody = req.getBody();
+
+ test('Request method is POST', function() {
+ expect(req.getMethod()).to.equal('POST');
+ });
+ `;
+ expect(postmanTranslation(inputScript)).toBe(expectedOutput);
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-response.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-response.spec.js
new file mode 100644
index 000000000..6e54af13b
--- /dev/null
+++ b/packages/bruno-converters/tests/postman/postman-translations/postman-response.spec.js
@@ -0,0 +1,34 @@
+const { default: postmanTranslation } = require("../../../src/postman/postman-translations");
+
+describe('postmanTranslations - response commands', () => {
+ test('should handle response commands', () => {
+ const inputScript = `
+ const responseTime = pm.response.responseTime;
+ const responseCode = pm.response.code;
+ const responseText = pm.response.text();
+ const responseJson = pm.response.json();
+ const responseStatus = pm.response.status;
+ const responseHeaders = pm.response.headers;
+
+ pm.test('Status code is 200', function() {
+ pm.response.to.have.status(200);
+ });
+ `;
+ const expectedOutput = `
+ const responseTime = res.getResponseTime();
+ const responseCode = res.getStatus();
+ const responseText = JSON.stringify(res.getBody());
+ const responseJson = res.getBody();
+ const responseStatus = res.statusText;
+ const responseHeaders = req.getHeaders();
+
+ test('Status code is 200', function() {
+ expect(res.getStatus()).to.equal(200);
+ });
+ `;
+ expect(postmanTranslation(inputScript)).toBe(expectedOutput);
+ });
+});
+
+
+
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json
index 11820c568..dbc921211 100644
--- a/packages/bruno-electron/package.json
+++ b/packages/bruno-electron/package.json
@@ -33,6 +33,7 @@
"@usebruno/node-machine-id": "^2.0.0",
"@usebruno/schema": "0.7.0",
"@usebruno/vm2": "^3.9.13",
+ "@usebruno/requests": "^0.1.0",
"about-window": "^1.15.2",
"aws4-axios": "^3.3.0",
"axios": "^1.8.3",
diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js
index 39c22bb1a..3ee646e81 100644
--- a/packages/bruno-electron/src/app/watcher.js
+++ b/packages/bruno-electron/src/app/watcher.js
@@ -531,7 +531,8 @@ class Watcher {
stabilityThreshold: 80,
pollInterval: 10
},
- depth: 20
+ depth: 20,
+ disableGlobbing: true
});
let startedNewWatcher = false;
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 325ff0391..b59285f23 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -14,7 +14,7 @@ const { NtlmClient } = require('axios-ntlm');
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
const { interpolateString } = require('./interpolate-string');
const { resolveAwsV4Credentials, addAwsV4Interceptor } = require('./awsv4auth-helper');
-const { addDigestInterceptor } = require('./digestauth-helper');
+const { addDigestInterceptor } = require('@usebruno/requests');
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
const { prepareRequest } = require('./prepare-request');
const interpolateVars = require('./interpolate-vars');
@@ -433,6 +433,8 @@ const registerNetworkIpc = (mainWindow) => {
mainWindow.webContents.send('main:global-environment-variables-update', {
globalEnvironmentVariables: result.globalEnvironmentVariables
});
+
+ collection.globalEnvironmentVariables = result.globalEnvironmentVariables;
}
if (result?.error) {
@@ -737,6 +739,8 @@ const registerNetworkIpc = (mainWindow) => {
mainWindow.webContents.send('main:global-environment-variables-update', {
globalEnvironmentVariables: testResults.globalEnvironmentVariables
});
+
+ collection.globalEnvironmentVariables = testResults.globalEnvironmentVariables;
}
return {
@@ -1199,6 +1203,8 @@ const registerNetworkIpc = (mainWindow) => {
mainWindow.webContents.send('main:global-environment-variables-update', {
globalEnvironmentVariables: testResults.globalEnvironmentVariables
});
+
+ collection.globalEnvironmentVariables = testResults.globalEnvironmentVariables;
}
} catch (error) {
mainWindow.webContents.send('main:run-folder-event', {
diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
index 932753fed..78c5454ed 100644
--- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js
+++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
@@ -1,7 +1,6 @@
const { interpolate } = require('@usebruno/common');
const { each, forOwn, cloneDeep, find } = require('lodash');
const FormData = require('form-data');
-const { mockDataFunctions } = require('./faker-functions');
const getContentType = (headers = {}) => {
let contentType = '';
@@ -14,14 +13,6 @@ const getContentType = (headers = {}) => {
return contentType;
};
-const interpolateMockVars = (str) => {
- const patternRegex = /\{\{\$(\w+)\}\}/g;
- return str.replace(patternRegex, (match, keyword) => {
- const replacement = mockDataFunctions[keyword]?.();
- return replacement || match;
- });
-};
-
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const oauth2CredentialVariables = request?.oauth2CredentialVariables || {};
@@ -43,7 +34,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
});
});
- const _interpolate = (str) => {
+ const _interpolate = (str, { escapeJSONStrings } = {}) => {
if (!str || !str.length || typeof str !== 'string') {
return str;
}
@@ -64,7 +55,9 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
}
};
- return interpolateMockVars(interpolate(str, combinedVars));
+ return interpolate(str, combinedVars, {
+ escapeJSONStrings
+ });
};
request.url = _interpolate(request.url);
@@ -83,12 +76,16 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
if (contentType.includes('json') && !Buffer.isBuffer(request.data)) {
if (typeof request.data === 'string') {
if (request.data.length) {
- request.data = _interpolate(request.data);
+ request.data = _interpolate(request.data, {
+ escapeJSONStrings: true
+ });
}
} else if (typeof request.data === 'object') {
try {
- let parsed = JSON.stringify(request.data);
- parsed = _interpolate(parsed);
+ const jsonDoc = JSON.stringify(request.data);
+ const parsed = _interpolate(jsonDoc, {
+ escapeJSONStrings: true
+ });
request.data = JSON.parse(parsed);
} catch (err) {}
}
diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js
index 82b37f43d..d6fed9da6 100644
--- a/packages/bruno-electron/src/utils/collection.js
+++ b/packages/bruno-electron/src/utils/collection.js
@@ -10,9 +10,10 @@ const mergeHeaders = (collection, request, requestTreePath) => {
let collectionHeaders = get(collection, 'root.request.headers', []);
collectionHeaders.forEach((header) => {
if (header.enabled) {
- headers.set(header.name?.toLowerCase?.(), header.value);
- if (header?.name?.toLowerCase() === 'content-type') {
- contentTypeDefined = true;
+ if (header?.name?.toLowerCase?.() === 'content-type') {
+ headers.set('content-type', header.value);
+ } else {
+ headers.set(header.name, header.value);
}
}
});
@@ -22,14 +23,22 @@ const mergeHeaders = (collection, request, requestTreePath) => {
let _headers = get(i, 'root.request.headers', []);
_headers.forEach((header) => {
if (header.enabled) {
- headers.set(header.name?.toLowerCase?.(), header.value);
+ if (header.name.toLowerCase() === 'content-type') {
+ headers.set('content-type', header.value);
+ } else {
+ headers.set(header.name, header.value);
+ }
}
});
} else {
const _headers = i?.draft ? get(i, 'draft.request.headers', []) : get(i, 'request.headers', []);
_headers.forEach((header) => {
if (header.enabled) {
- headers.set(header.name?.toLowerCase?.(), header.value);
+ if (header.name.toLowerCase() === 'content-type') {
+ headers.set('content-type', header.value);
+ } else {
+ headers.set(header.name, header.value);
+ }
}
});
}
diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js
index ee78ea980..2a8d02a87 100644
--- a/packages/bruno-js/src/runtime/script-runtime.js
+++ b/packages/bruno-js/src/runtime/script-runtime.js
@@ -283,6 +283,8 @@ class ScriptRuntime {
axios,
'node-fetch': fetch,
'crypto-js': CryptoJS,
+ 'xml2js': xml2js,
+ cheerio,
...whitelistedModules,
fs: allowScriptFilesystemAccess ? fs : undefined,
'node-vault': NodeVault
diff --git a/packages/bruno-requests/.gitignore b/packages/bruno-requests/.gitignore
new file mode 100644
index 000000000..f6eabff32
--- /dev/null
+++ b/packages/bruno-requests/.gitignore
@@ -0,0 +1,22 @@
+# dependencies
+node_modules
+yarn.lock
+pnpm-lock.yaml
+package-lock.json
+.pnp
+.pnp.js
+
+# testing
+coverage
+
+# production
+dist
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/packages/bruno-requests/package.json b/packages/bruno-requests/package.json
new file mode 100644
index 000000000..f43820549
--- /dev/null
+++ b/packages/bruno-requests/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@usebruno/requests",
+ "version": "0.1.0",
+ "license": "MIT",
+ "main": "dist/cjs/index.js",
+ "module": "dist/esm/index.js",
+ "types": "dist/index.d.js",
+ "files": [
+ "dist",
+ "src",
+ "package.json"
+ ],
+ "scripts": {
+ "clean": "rimraf dist",
+ "prebuild": "npm run clean",
+ "build": "rollup -c",
+ "prepack": "npm run test && npm run build"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^23.0.2",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^9.0.2",
+ "rollup": "3.29.5",
+ "rollup-plugin-dts": "^5.0.0",
+ "rollup-plugin-peer-deps-external": "^2.2.4",
+ "rollup-plugin-terser": "^7.0.2",
+ "typescript": "^4.8.4"
+ },
+ "overrides": {
+ "rollup": "3.29.5"
+ }
+}
diff --git a/packages/bruno-requests/rollup.config.js b/packages/bruno-requests/rollup.config.js
new file mode 100644
index 000000000..fa04da640
--- /dev/null
+++ b/packages/bruno-requests/rollup.config.js
@@ -0,0 +1,37 @@
+const { nodeResolve } = require('@rollup/plugin-node-resolve');
+const commonjs = require('@rollup/plugin-commonjs');
+const typescript = require('@rollup/plugin-typescript');
+const dts = require('rollup-plugin-dts');
+const { terser } = require('rollup-plugin-terser');
+const peerDepsExternal = require('rollup-plugin-peer-deps-external');
+
+const packageJson = require('./package.json');
+
+module.exports = [
+ {
+ input: 'src/index.ts',
+ output: [
+ {
+ file: packageJson.main,
+ format: 'cjs',
+ sourcemap: true,
+ exports: 'named'
+ },
+ {
+ file: packageJson.module,
+ format: 'esm',
+ sourcemap: true,
+ exports: 'named'
+ }
+ ],
+ plugins: [
+ peerDepsExternal(),
+ nodeResolve({
+ extensions: ['.js', '.ts', '.tsx', '.json', '.css']
+ }),
+ commonjs(),
+ typescript({ tsconfig: './tsconfig.json' }),
+ terser()
+ ]
+ }
+];
diff --git a/packages/bruno-electron/src/ipc/network/digestauth-helper.js b/packages/bruno-requests/src/auth/digestauth-helper.js
similarity index 97%
rename from packages/bruno-electron/src/ipc/network/digestauth-helper.js
rename to packages/bruno-requests/src/auth/digestauth-helper.js
index f01ba86df..25911a6b3 100644
--- a/packages/bruno-electron/src/ipc/network/digestauth-helper.js
+++ b/packages/bruno-requests/src/auth/digestauth-helper.js
@@ -25,7 +25,7 @@ function md5(input) {
return crypto.createHash('md5').update(input).digest('hex');
}
-function addDigestInterceptor(axiosInstance, request) {
+export function addDigestInterceptor(axiosInstance, request) {
const { username, password } = request.digestConfig;
console.debug('Digest Auth Interceptor Initialized');
@@ -122,5 +122,3 @@ function addDigestInterceptor(axiosInstance, request) {
}
);
}
-
-module.exports = { addDigestInterceptor };
diff --git a/packages/bruno-requests/src/auth/index.ts b/packages/bruno-requests/src/auth/index.ts
new file mode 100644
index 000000000..cd302427c
--- /dev/null
+++ b/packages/bruno-requests/src/auth/index.ts
@@ -0,0 +1 @@
+export { addDigestInterceptor } from './digestauth-helper';
\ No newline at end of file
diff --git a/packages/bruno-requests/src/index.ts b/packages/bruno-requests/src/index.ts
new file mode 100644
index 000000000..19b02f764
--- /dev/null
+++ b/packages/bruno-requests/src/index.ts
@@ -0,0 +1 @@
+export { addDigestInterceptor } from './auth';
\ No newline at end of file
diff --git a/packages/bruno-requests/tsconfig.json b/packages/bruno-requests/tsconfig.json
new file mode 100644
index 000000000..6a74f54cf
--- /dev/null
+++ b/packages/bruno-requests/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "resolveJsonModule": true,
+ "allowSyntheticDefaultImports": true,
+ "moduleResolution": "node",
+ "declaration": true,
+ "declarationDir": "./dist/types",
+ "allowJs": true,
+ "checkJs": false
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js"],
+ "exclude": ["node_modules", "dist"]
+}
\ No newline at end of file
diff --git a/packages/bruno-tests/collection/auth/digest/Digest Auth 200.bru b/packages/bruno-tests/collection/auth/digest/Digest Auth 200.bru
new file mode 100644
index 000000000..7efd6bee0
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/digest/Digest Auth 200.bru
@@ -0,0 +1,21 @@
+meta {
+ name: Digest Auth 200
+ type: http
+ seq: 1
+}
+
+get {
+ url: https://httpbin.org/digest-auth/auth/foo/passwd
+ body: none
+ auth: digest
+}
+
+auth:digest {
+ username: foo
+ password: passwd
+}
+
+assert {
+ res.status: eq 200
+ res.body.authenticated: isTruthy
+}
diff --git a/packages/bruno-tests/collection/auth/digest/Digest Auth 401.bru b/packages/bruno-tests/collection/auth/digest/Digest Auth 401.bru
new file mode 100644
index 000000000..52f3698ae
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/digest/Digest Auth 401.bru
@@ -0,0 +1,20 @@
+meta {
+ name: Digest Auth 401
+ type: http
+ seq: 2
+}
+
+get {
+ url: https://httpbin.org/digest-auth/auth/foo/passw
+ body: none
+ auth: digest
+}
+
+auth:digest {
+ username: foo
+ password: passwd
+}
+
+assert {
+ res.status: eq 401
+}
diff --git a/packages/bruno-tests/collection/auth/digest/folder.bru b/packages/bruno-tests/collection/auth/digest/folder.bru
new file mode 100644
index 000000000..6b16b9610
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/digest/folder.bru
@@ -0,0 +1,3 @@
+meta {
+ name: digest
+}
diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru
index 07aad76b2..ce7a6346c 100644
--- a/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru
+++ b/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru
@@ -19,18 +19,35 @@ script:pre-request {
const $ = cheerio.load('Hello world
');
- $('h2.title').text('Hello there!');
+ $('h2.title').text('Hello pre-request!');
$('h2').addClass('welcome');
- bru.setVar("cheerio-test-html", $.html());
+ bru.setVar("cheerio-test-pre-request", $.html());
+}
+
+script:post-response {
+ const cheerio = require('cheerio');
+
+ const $ = cheerio.load('Hello world
');
+
+ $('h2.title').text('Hello post-response!');
+ $('h2').addClass('welcome');
+
+ bru.setVar("cheerio-test-post-response", $.html());
}
tests {
const cheerio = require('cheerio');
- test("cheerio html - from scripts", function() {
- const expected = 'Hello there!
';
- const html = bru.getVar('cheerio-test-html');
+ test("cheerio html - from pre request script", function() {
+ const expected = 'Hello pre-request!
';
+ const html = bru.getVar('cheerio-test-pre-request');
+ expect(html).to.eql(expected);
+ });
+
+ test("cheerio html - from post response script", function() {
+ const expected = 'Hello post-response!
';
+ const html = bru.getVar('cheerio-test-post-response');
expect(html).to.eql(expected);
});
diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru
index db8748ec3..935263117 100644
--- a/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru
+++ b/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru
@@ -12,20 +12,36 @@ get {
script:pre-request {
var parseString = require('xml2js').parseString;
- var xml = "Hello xml2js!"
+ var xml = "Hello xml2js - pre request!"
parseString(xml, function (err, result) {
- bru.setVar("xml2js-test-result", result);
+ bru.setVar("xml2js-test-result-pre-request", result);
+ });
+}
+
+script:post-response {
+ var parseString = require('xml2js').parseString;
+ var xml = "Hello xml2js - post response!"
+ parseString(xml, function (err, result) {
+ bru.setVar("xml2js-test-result-post-response", result);
});
}
tests {
var parseString = require('xml2js').parseString;
- test("xml2js parseString in scripts", function() {
+ test("xml2js parseString in scripts - pre request", function() {
const expected = {
- root: 'Hello xml2js!'
+ root: 'Hello xml2js - pre request!'
};
- const result = bru.getVar('xml2js-test-result');
+ const result = bru.getVar('xml2js-test-result-pre-request');
+ expect(result).to.eql(expected);
+ });
+
+ test("xml2js parseString in scripts - post response", function() {
+ const expected = {
+ root: 'Hello xml2js - post response!'
+ };
+ const result = bru.getVar('xml2js-test-result-post-response');
expect(result).to.eql(expected);
});
diff --git a/packages/bruno-tests/collection/string interpolation/objects-arrays interpolation.bru b/packages/bruno-tests/collection/string interpolation/objects-arrays interpolation.bru
new file mode 100644
index 000000000..3c47c9d32
--- /dev/null
+++ b/packages/bruno-tests/collection/string interpolation/objects-arrays interpolation.bru
@@ -0,0 +1,81 @@
+meta {
+ name: objects/arrays interpolation
+ type: http
+ seq: 5
+}
+
+post {
+ url: https://echo.usebruno.com
+ body: json
+ auth: none
+}
+
+body:json {
+ {
+ "undefined": "{{obj.undefined}}",
+ "null": {{obj.null}},
+ "number": {{obj.number}},
+ "boolean": {{obj.boolean}},
+ "array": {{arr}},
+ "array[0]": {{arr[0]}},
+ "object": {{obj}},
+ "object.foo": {{obj.foo}},
+ "object.foo.bar": {{obj.foo.bar}},
+ "object.foo.bar.baz": {{obj.foo.bar.baz}}
+ }
+}
+
+script:pre-request {
+ bru.setVar("arr", [1,2,3,4,5]);
+
+ bru.setVar("obj", {
+ "null": null,
+ "number": 1,
+ "boolean": true,
+ "foo": {
+ "bar": {
+ "baz": 1
+ }
+ }
+ });
+}
+
+tests {
+ test("should interpolate arrays and objects in request payload body", () => {
+ const resBody = res.getBody();
+ const expectedOutput = {
+ "undefined": "{{obj.undefined}}",
+ "null": null,
+ "number": 1,
+ "boolean": true,
+ "array": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ],
+ "array[0]": 1,
+ "object": {
+ "null": null,
+ "number": 1,
+ "boolean": true,
+ "foo": {
+ "bar": {
+ "baz": 1
+ }
+ }
+ },
+ "object.foo": {
+ "bar": {
+ "baz": 1
+ }
+ },
+ "object.foo.bar": {
+ "baz": 1
+ },
+ "object.foo.bar.baz": 1
+ };
+ expect(resBody).to.be.eql(expectedOutput);
+ })
+}
diff --git a/packages/bruno-tests/collection/string interpolation/runtime vars.bru b/packages/bruno-tests/collection/string interpolation/runtime vars.bru
index 6e70647e8..3bcdef9e9 100644
--- a/packages/bruno-tests/collection/string interpolation/runtime vars.bru
+++ b/packages/bruno-tests/collection/string interpolation/runtime vars.bru
@@ -30,7 +30,7 @@ body:text {
Hi, I am {{rUser.full_name}},
I am {{rUser.age}} years old.
My favorite food is {{rUser.fav-food[0]}} and {{rUser.fav-food[1]}}.
- I like attention: {{rUser.want.attention}}
+ I like attention: {{rUser['want.attention']}}
}
assert {
diff --git a/scripts/setup.js b/scripts/setup.js
index b2c497292..fc8b67a6b 100644
--- a/scripts/setup.js
+++ b/scripts/setup.js
@@ -75,6 +75,7 @@ async function setup() {
execCommand('npm run build:bruno-query', 'Building bruno-query');
execCommand('npm run build:bruno-common', 'Building bruno-common');
execCommand('npm run build:bruno-converters', 'Building bruno-converters');
+ execCommand('npm run build:bruno-requests', 'Building bruno-requests');
// Bundle JS sandbox libraries
execCommand(