chore(#197): ran prettier on packages/bruno-js

This commit is contained in:
Anoop M D 2023-09-22 00:42:14 +05:30
parent a53dd76854
commit 5af2f68252
10 changed files with 169 additions and 140 deletions

View File

@ -1,4 +1,3 @@
class Bru {
constructor(envVariables, collectionVariables) {
this.envVariables = envVariables;
@ -18,20 +17,20 @@ class Bru {
}
setEnvVar(key, value) {
if(!key) {
if (!key) {
throw new Error('Key is required');
}
// gracefully ignore if key is not present in environment
if(!this.envVariables.hasOwnProperty(key)) {
return;
if (!this.envVariables.hasOwnProperty(key)) {
return;
}
this.envVariables[key] = value;
}
setVar(key, value) {
if(!key) {
if (!key) {
throw new Error('Key is required');
}

View File

@ -48,4 +48,4 @@ class BrunoRequest {
}
}
module.exports = BrunoRequest;
module.exports = BrunoRequest;

View File

@ -12,7 +12,7 @@ class BrunoResponse {
}
getHeader(name) {
return (this.res && this.res.headers) ? this.res.headers[name] : null;
return this.res && this.res.headers ? this.res.headers[name] : null;
}
getHeaders() {

View File

@ -12,11 +12,7 @@ chai.use(function (chai, utils) {
const obj = this._obj;
const isJson = typeof obj === 'object' && obj !== null && !Array.isArray(obj) && obj.constructor === Object;
this.assert(
isJson,
`expected ${utils.inspect(obj)} to be JSON`,
`expected ${utils.inspect(obj)} not to be JSON`
);
this.assert(isJson, `expected ${utils.inspect(obj)} to be JSON`, `expected ${utils.inspect(obj)} not to be JSON`);
});
});
@ -25,7 +21,7 @@ chai.use(function (chai, utils) {
chai.Assertion.addMethod('match', function (regex) {
const obj = this._obj;
let match = false;
if(obj === undefined) {
if (obj === undefined) {
match = false;
} else {
match = regex.test(obj);
@ -41,7 +37,7 @@ chai.use(function (chai, utils) {
/**
* Assertion operators
*
*
* eq : equal to
* neq : not equal to
* gt : greater than
@ -70,7 +66,7 @@ chai.use(function (chai, utils) {
* isBoolean : is boolean
*/
const parseAssertionOperator = (str = '') => {
if(!str || typeof str !== 'string' || !str.length) {
if (!str || typeof str !== 'string' || !str.length) {
return {
operator: 'eq',
value: str
@ -78,27 +74,58 @@ const parseAssertionOperator = (str = '') => {
}
const operators = [
'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn',
'contains', 'notContains', 'length', 'matches', 'notMatches',
'startsWith', 'endsWith', 'between', 'isEmpty', 'isNull', 'isUndefined',
'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
'eq',
'neq',
'gt',
'gte',
'lt',
'lte',
'in',
'notIn',
'contains',
'notContains',
'length',
'matches',
'notMatches',
'startsWith',
'endsWith',
'between',
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
const unaryOperators = [
'isEmpty', 'isNull', 'isUndefined', 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
const [operator, ...rest] = str.trim().split(' ');
const value = rest.join(' ');
if(unaryOperators.includes(operator)) {
if (unaryOperators.includes(operator)) {
return {
operator,
value: ''
};
}
if(operators.includes(operator)) {
if (operators.includes(operator)) {
return {
operator,
value
@ -113,34 +140,43 @@ const parseAssertionOperator = (str = '') => {
const isUnaryOperator = (operator) => {
const unaryOperators = [
'isEmpty', 'isNull', 'isUndefined', 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
return unaryOperators.includes(operator);
};
const evaluateRhsOperand = (rhsOperand, operator, context) => {
if(isUnaryOperator(operator)) {
if (isUnaryOperator(operator)) {
return;
}
// gracefully allow both a,b as well as [a, b]
if(operator === 'in' || operator === 'notIn') {
if(rhsOperand.startsWith('[') && rhsOperand.endsWith(']')) {
if (operator === 'in' || operator === 'notIn') {
if (rhsOperand.startsWith('[') && rhsOperand.endsWith(']')) {
rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1);
}
return rhsOperand.split(',').map((v) => evaluateJsTemplateLiteral(v.trim(), context));
}
if(operator === 'between') {
if (operator === 'between') {
const [lhs, rhs] = rhsOperand.split(',').map((v) => evaluateJsTemplateLiteral(v.trim(), context));
return [lhs, rhs];
}
// gracefully allow both ^[a-Z] as well as /^[a-Z]/
if(operator === 'matches' || operator === 'notMatches') {
if(rhsOperand.startsWith('/') && rhsOperand.endsWith('/')) {
if (operator === 'matches' || operator === 'notMatches') {
if (rhsOperand.startsWith('/') && rhsOperand.endsWith('/')) {
rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1);
}
@ -153,7 +189,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
class AssertRuntime {
runAssertions(assertions, request, response, envVariables, collectionVariables, collectionPath) {
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
if(!enabledAssertions.length) {
if (!enabledAssertions.length) {
return [];
}
@ -171,7 +207,7 @@ class AssertRuntime {
...envVariables,
...collectionVariables,
...bruContext
}
};
const assertionResults = [];
@ -179,16 +215,13 @@ class AssertRuntime {
for (const v of enabledAssertions) {
const lhsExpr = v.name;
const rhsExpr = v.value;
const {
operator,
value: rhsOperand
} = parseAssertionOperator(rhsExpr);
const { operator, value: rhsOperand } = parseAssertionOperator(rhsExpr);
try {
const lhs = evaluateJsExpression(lhsExpr, context);
const rhs = evaluateRhsOperand(rhsOperand, operator, context);
switch(operator) {
switch (operator) {
case 'eq':
expect(lhs).to.equal(rhs);
break;
@ -281,8 +314,7 @@ class AssertRuntime {
operator,
status: 'pass'
});
}
catch (err) {
} catch (err) {
assertionResults.push({
uid: nanoid(),
lhsExpr,

View File

@ -23,10 +23,9 @@ const fetch = require('node-fetch');
const CryptoJS = require('crypto-js');
class ScriptRuntime {
constructor() {
}
constructor() {}
async runRequestScript(script, request, envVariables, collectionVariables, collectionPath, onConsoleLog){
async runRequestScript(script, request, envVariables, collectionVariables, collectionPath, onConsoleLog) {
const bru = new Bru(envVariables, collectionVariables);
const req = new BrunoRequest(request);
@ -35,18 +34,18 @@ class ScriptRuntime {
req
};
if(onConsoleLog && typeof onConsoleLog === 'function') {
if (onConsoleLog && typeof onConsoleLog === 'function') {
const customLogger = (type) => {
return (...args) => {
onConsoleLog(type, args);
}
};
};
context.console = {
log: customLogger('log'),
info: customLogger('info'),
warn: customLogger('warn'),
error: customLogger('error')
}
};
}
const vm = new NodeVM({
@ -78,7 +77,7 @@ class ScriptRuntime {
}
}
});
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
await asyncVM();
return {
request,
@ -87,7 +86,7 @@ class ScriptRuntime {
};
}
async runResponseScript(script, request, response, envVariables, collectionVariables, collectionPath, onConsoleLog){
async runResponseScript(script, request, response, envVariables, collectionVariables, collectionPath, onConsoleLog) {
const bru = new Bru(envVariables, collectionVariables);
const req = new BrunoRequest(request);
const res = new BrunoResponse(response);
@ -98,18 +97,18 @@ class ScriptRuntime {
res
};
if(onConsoleLog && typeof onConsoleLog === 'function') {
if (onConsoleLog && typeof onConsoleLog === 'function') {
const customLogger = (type) => {
return (...args) => {
onConsoleLog(type, args);
}
};
};
context.console = {
log: customLogger('log'),
info: customLogger('info'),
warn: customLogger('warn'),
error: customLogger('error')
}
};
}
const vm = new NodeVM({
@ -132,7 +131,7 @@ class ScriptRuntime {
}
});
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
await asyncVM();
return {

View File

@ -1,5 +1,5 @@
const { NodeVM } = require('vm2');
const chai = require('chai');
const chai = require('chai');
const path = require('path');
const Bru = require('../bru');
const BrunoRequest = require('../bruno-request');
@ -17,10 +17,9 @@ const nanoid = require('nanoid');
const CryptoJS = require('crypto-js');
class TestRuntime {
constructor() {
}
constructor() {}
runTests(testsFile, request, response, envVariables, collectionVariables, collectionPath, onConsoleLog){
runTests(testsFile, request, response, envVariables, collectionVariables, collectionPath, onConsoleLog) {
const bru = new Bru(envVariables, collectionVariables);
const req = new BrunoRequest(request);
const res = new BrunoResponse(response);
@ -28,7 +27,7 @@ class TestRuntime {
const __brunoTestResults = new TestResults();
const test = Test(__brunoTestResults, chai);
if(!testsFile || !testsFile.length) {
if (!testsFile || !testsFile.length) {
return {
request,
envVariables,
@ -47,18 +46,18 @@ class TestRuntime {
__brunoTestResults: __brunoTestResults
};
if(onConsoleLog && typeof onConsoleLog === 'function') {
if (onConsoleLog && typeof onConsoleLog === 'function') {
const customLogger = (type) => {
return (...args) => {
onConsoleLog(type, args);
}
};
};
context.console = {
log: customLogger('log'),
info: customLogger('info'),
warn: customLogger('warn'),
error: customLogger('error')
}
};
}
const vm = new NodeVM({

View File

@ -6,7 +6,7 @@ const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser }
class VarsRuntime {
runPreRequestVars(vars, request, envVariables, collectionVariables, collectionPath) {
const enabledVars = _.filter(vars, (v) => v.enabled);
if(!enabledVars.length) {
if (!enabledVars.length) {
return;
}
@ -22,7 +22,7 @@ class VarsRuntime {
...envVariables,
...collectionVariables,
...bruContext
}
};
_.each(enabledVars, (v) => {
const value = evaluateJsTemplateLiteral(v.value, context);
@ -36,7 +36,7 @@ class VarsRuntime {
runPostResponseVars(vars, request, response, envVariables, collectionVariables, collectionPath) {
const enabledVars = _.filter(vars, (v) => v.enabled);
if(!enabledVars.length) {
if (!enabledVars.length) {
return;
}
@ -54,7 +54,7 @@ class VarsRuntime {
...envVariables,
...collectionVariables,
...bruContext
}
};
_.each(enabledVars, (v) => {
const value = evaluateJsExpression(v.value, context);

View File

@ -1,14 +1,14 @@
const Test = (__brunoTestResults, chai) => (description, callback) => {
try {
callback();
__brunoTestResults.addResult({ description, status: "pass" });
__brunoTestResults.addResult({ description, status: 'pass' });
} catch (error) {
console.log(chai.AssertionError);
if (error instanceof chai.AssertionError) {
const { message, actual, expected } = error;
__brunoTestResults.addResult({
description,
status: "fail",
status: 'fail',
error: message,
actual,
expected
@ -16,12 +16,12 @@ const Test = (__brunoTestResults, chai) => (description, callback) => {
} else {
__brunoTestResults.addResult({
description,
status: "fail",
status: 'fail',
error: error.message || 'An unexpected error occurred.'
});
}
console.log(error);
}
}
};
module.exports = Test;
module.exports = Test;

View File

@ -1,21 +1,23 @@
const jsonQuery = require('json-query');
const { get } = require("@usebruno/query");
const { get } = require('@usebruno/query');
const JS_KEYWORDS = `
break case catch class const continue debugger default delete do
else export extends false finally for function if import in instanceof
new null return super switch this throw true try typeof var void while with
undefined let static yield arguments of
`.split(/\s+/).filter(word => word.length > 0);
`
.split(/\s+/)
.filter((word) => word.length > 0);
/**
* Creates a function from a Javascript expression
*
*
* When the function is called, the variables used in this expression are picked up from the context
*
*
* ```js
* res.data.pets.map(pet => pet.name.toUpperCase())
*
*
* function(context) {
* const { res, pet } = context;
* return res.data.pets.map(pet => pet.name.toUpperCase())
@ -29,25 +31,23 @@ const compileJsExpression = (expr) => {
// get valid js identifiers (foo, bar)
const vars = new Set(
matches
.filter(match => /^[a-zA-Z$_]/.test(match)) // starts with valid js identifier (foo.bar)
.map(match => match.split('.')[0]) // top level identifier (foo)
.filter(name => !JS_KEYWORDS.includes(name)) // exclude js keywords
.filter((match) => /^[a-zA-Z$_]/.test(match)) // starts with valid js identifier (foo.bar)
.map((match) => match.split('.')[0]) // top level identifier (foo)
.filter((name) => !JS_KEYWORDS.includes(name)) // exclude js keywords
);
// globals such as Math
const globals = [...vars].filter(name => name in globalThis);
const globals = [...vars].filter((name) => name in globalThis);
const code = {
vars: [...vars].join(", "),
vars: [...vars].join(', '),
// pick global from context or globalThis
globals: globals
.map(name => ` ${name} = ${name} ?? globalThis.${name};`)
.join('')
globals: globals.map((name) => ` ${name} = ${name} ?? globalThis.${name};`).join('')
};
const body = `let { ${code.vars} } = context; ${code.globals}; return ${expr}`;
return new Function("context", body);
return new Function('context', body);
};
const internalExpressionCache = new Map();
@ -55,47 +55,47 @@ const internalExpressionCache = new Map();
const evaluateJsExpression = (expression, context) => {
let fn = internalExpressionCache.get(expression);
if (fn == null) {
internalExpressionCache.set(expression, fn = compileJsExpression(expression));
internalExpressionCache.set(expression, (fn = compileJsExpression(expression)));
}
return fn(context);
};
const evaluateJsTemplateLiteral = (templateLiteral, context) => {
if(!templateLiteral || !templateLiteral.length || typeof templateLiteral !== 'string') {
if (!templateLiteral || !templateLiteral.length || typeof templateLiteral !== 'string') {
return templateLiteral;
}
templateLiteral = templateLiteral.trim();
if(templateLiteral === 'true') {
if (templateLiteral === 'true') {
return true;
}
if(templateLiteral === 'false') {
if (templateLiteral === 'false') {
return false;
}
if(templateLiteral === 'null') {
if (templateLiteral === 'null') {
return null;
}
if(templateLiteral === 'undefined') {
if (templateLiteral === 'undefined') {
return undefined;
}
if(templateLiteral.startsWith('"') && templateLiteral.endsWith('"')) {
if (templateLiteral.startsWith('"') && templateLiteral.endsWith('"')) {
return templateLiteral.slice(1, -1);
}
if(templateLiteral.startsWith("'") && templateLiteral.endsWith("'")) {
if (templateLiteral.startsWith("'") && templateLiteral.endsWith("'")) {
return templateLiteral.slice(1, -1);
}
if(!isNaN(templateLiteral)) {
if (!isNaN(templateLiteral)) {
return Number(templateLiteral);
}
templateLiteral = "`" + templateLiteral + "`";
templateLiteral = '`' + templateLiteral + '`';
return evaluateJsExpression(templateLiteral, context);
};
@ -123,4 +123,4 @@ module.exports = {
evaluateJsTemplateLiteral,
createResponseParser,
internalExpressionCache
};
};

View File

@ -1,35 +1,35 @@
const { describe, it, expect } = require("@jest/globals");
const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser } = require("../src/utils");
const { describe, it, expect } = require('@jest/globals');
const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser } = require('../src/utils');
describe("utils", () => {
describe("expression evaluation", () => {
describe('utils', () => {
describe('expression evaluation', () => {
const context = {
res: {
data: { pets: ["bruno", "max"] }
data: { pets: ['bruno', 'max'] }
}
};
beforeEach(() => cache.clear());
afterEach(() => cache.clear());
it("should evaluate expression", () => {
it('should evaluate expression', () => {
let result;
result = evaluateJsExpression("res.data.pets", context);
expect(result).toEqual(["bruno", "max"]);
result = evaluateJsExpression('res.data.pets', context);
expect(result).toEqual(['bruno', 'max']);
result = evaluateJsExpression("res.data.pets[0].toUpperCase()", context);
expect(result).toEqual("BRUNO");
result = evaluateJsExpression('res.data.pets[0].toUpperCase()', context);
expect(result).toEqual('BRUNO');
});
it("should cache expression", () => {
it('should cache expression', () => {
expect(cache.size).toBe(0);
evaluateJsExpression("res.data.pets", context);
evaluateJsExpression('res.data.pets', context);
expect(cache.size).toBe(1);
});
it("should use cached expression", () => {
const expr = "res.data.pets";
it('should use cached expression', () => {
const expr = 'res.data.pets';
evaluateJsExpression(expr, context);
@ -42,79 +42,79 @@ describe("utils", () => {
expect(cache.get(expr)).toBe(fn);
});
it("should identify top level variables", () => {
const expr = "res.data.pets[0].toUpperCase()";
it('should identify top level variables', () => {
const expr = 'res.data.pets[0].toUpperCase()';
evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain("let { res } = context;");
expect(cache.get(expr).toString()).toContain('let { res } = context;');
});
it("should not duplicate variables", () => {
const expr = "res.data.pets[0] + res.data.pets[1]";
it('should not duplicate variables', () => {
const expr = 'res.data.pets[0] + res.data.pets[1]';
evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain("let { res } = context;");
expect(cache.get(expr).toString()).toContain('let { res } = context;');
});
it("should exclude js keywords like true false from vars", () => {
const expr = "res.data.pets.length > 0 ? true : false";
it('should exclude js keywords like true false from vars', () => {
const expr = 'res.data.pets.length > 0 ? true : false';
evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain("let { res } = context;");
expect(cache.get(expr).toString()).toContain('let { res } = context;');
});
it("should exclude numbers from vars", () => {
const expr = "res.data.pets.length + 10";
it('should exclude numbers from vars', () => {
const expr = 'res.data.pets.length + 10';
evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain("let { res } = context;");
expect(cache.get(expr).toString()).toContain('let { res } = context;');
});
it("should pick variables from complex expressions", () => {
const expr = "res.data.pets.map(pet => pet.length)";
it('should pick variables from complex expressions', () => {
const expr = 'res.data.pets.map(pet => pet.length)';
const result = evaluateJsExpression(expr, context);
expect(result).toEqual([5, 3]);
expect(cache.get(expr).toString()).toContain("let { res, pet } = context;");
expect(cache.get(expr).toString()).toContain('let { res, pet } = context;');
});
it("should be ok picking extra vars from strings", () => {
it('should be ok picking extra vars from strings', () => {
const expr = "'hello' + ' ' + res.data.pets[0]";
const result = evaluateJsExpression(expr, context);
expect(result).toBe("hello bruno");
expect(result).toBe('hello bruno');
// extra var hello is harmless
expect(cache.get(expr).toString()).toContain("let { hello, res } = context;");
expect(cache.get(expr).toString()).toContain('let { hello, res } = context;');
});
it("should evaluate expressions referencing globals", () => {
const startTime = new Date("2022-02-01").getTime();
const currentTime = new Date("2022-02-02").getTime();
it('should evaluate expressions referencing globals', () => {
const startTime = new Date('2022-02-01').getTime();
const currentTime = new Date('2022-02-02').getTime();
jest.useFakeTimers({ now: currentTime });
const expr = "Math.max(Date.now(), startTime)";
const expr = 'Math.max(Date.now(), startTime)';
const result = evaluateJsExpression(expr, { startTime });
expect(result).toBe(currentTime);
expect(cache.get(expr).toString()).toContain("Math = Math ?? globalThis.Math;");
expect(cache.get(expr).toString()).toContain("Date = Date ?? globalThis.Date;");
expect(cache.get(expr).toString()).toContain('Math = Math ?? globalThis.Math;');
expect(cache.get(expr).toString()).toContain('Date = Date ?? globalThis.Date;');
});
it("should use global overridden in context", () => {
const startTime = new Date("2022-02-01").getTime();
const currentTime = new Date("2022-02-02").getTime();
it('should use global overridden in context', () => {
const startTime = new Date('2022-02-01').getTime();
const currentTime = new Date('2022-02-02').getTime();
jest.useFakeTimers({ now: currentTime });
const context = {
Date: { now: () => new Date("2022-01-31").getTime() },
Date: { now: () => new Date('2022-01-31').getTime() },
startTime
};
const expr = "Math.max(Date.now(), startTime)";
const expr = 'Math.max(Date.now(), startTime)';
const result = evaluateJsExpression(expr, context);
expect(result).toBe(startTime);
});
});
describe("response parser", () => {
describe('response parser', () => {
const res = createResponseParser({
status: 200,
data: {
@ -127,13 +127,13 @@ describe("utils", () => {
}
});
it("should default to bruno query", () => {
const value = res("..items[?].amount[0]", i => i.amount > 10);
it('should default to bruno query', () => {
const value = res('..items[?].amount[0]', (i) => i.amount > 10);
expect(value).toBe(20);
});
it("should allow json-query", () => {
const value = res.jq("order.items[amount > 10].amount");
it('should allow json-query', () => {
const value = res.jq('order.items[amount > 10].amount');
expect(value).toBe(20);
});
});