Merge pull request #1043 from nelup20/bugfix/521-error_accessing_response_property_with_context_as_key

fix(#521): Allow "context" as the name of a key/var in a JS expression
This commit is contained in:
lohit 2024-12-15 20:01:33 +05:30 committed by GitHub
commit 380047e025
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 11 deletions

View File

@ -18,8 +18,8 @@ const JS_KEYWORDS = `
* ```js * ```js
* res.data.pets.map(pet => pet.name.toUpperCase()) * res.data.pets.map(pet => pet.name.toUpperCase())
* *
* function(context) { * function(__bruno__functionInnerContext) {
* const { res, pet } = context; * const { res, pet } = __bruno__functionInnerContext;
* return res.data.pets.map(pet => pet.name.toUpperCase()) * return res.data.pets.map(pet => pet.name.toUpperCase())
* } * }
* ``` * ```
@ -45,9 +45,11 @@ const compileJsExpression = (expr) => {
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}`; // param name that is unlikely to show up as a var in an expression
const param = `__bruno__functionInnerContext`;
const body = `let { ${code.vars} } = ${param}; ${code.globals}; return ${expr}`;
return new Function('context', body); return new Function(param, body);
}; };
const internalExpressionCache = new Map(); const internalExpressionCache = new Map();

View File

@ -5,7 +5,9 @@ describe('utils', () => {
describe('expression evaluation', () => { describe('expression evaluation', () => {
const context = { const context = {
res: { res: {
data: { pets: ['bruno', 'max'] } data: { pets: ['bruno', 'max'] },
context: 'testContext',
__bruno__functionInnerContext: 0
} }
}; };
@ -45,32 +47,32 @@ describe('utils', () => {
it('should identify top level variables', () => { it('should identify top level variables', () => {
const expr = 'res.data.pets[0].toUpperCase()'; const expr = 'res.data.pets[0].toUpperCase()';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should not duplicate variables', () => { it('should not duplicate variables', () => {
const expr = 'res.data.pets[0] + res.data.pets[1]'; const expr = 'res.data.pets[0] + res.data.pets[1]';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should exclude js keywords like true false from vars', () => { it('should exclude js keywords like true false from vars', () => {
const expr = 'res.data.pets.length > 0 ? true : false'; const expr = 'res.data.pets.length > 0 ? true : false';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should exclude numbers from vars', () => { it('should exclude numbers from vars', () => {
const expr = 'res.data.pets.length + 10'; const expr = 'res.data.pets.length + 10';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should pick variables from complex expressions', () => { it('should pick variables from complex expressions', () => {
const expr = 'res.data.pets.map(pet => pet.length)'; const expr = 'res.data.pets.map(pet => pet.length)';
const result = evaluateJsExpression(expr, context); const result = evaluateJsExpression(expr, context);
expect(result).toEqual([5, 3]); expect(result).toEqual([5, 3]);
expect(cache.get(expr).toString()).toContain('let { res, pet } = context;'); expect(cache.get(expr).toString()).toContain('let { res, pet } = __bruno__functionInnerContext;');
}); });
it('should be ok picking extra vars from strings', () => { it('should be ok picking extra vars from strings', () => {
@ -78,7 +80,7 @@ describe('utils', () => {
const result = evaluateJsExpression(expr, context); const result = evaluateJsExpression(expr, context);
expect(result).toBe('hello bruno'); expect(result).toBe('hello bruno');
// extra var hello is harmless // extra var hello is harmless
expect(cache.get(expr).toString()).toContain('let { hello, res } = context;'); expect(cache.get(expr).toString()).toContain('let { hello, res } = __bruno__functionInnerContext;');
}); });
it('should evaluate expressions referencing globals', () => { it('should evaluate expressions referencing globals', () => {
@ -112,6 +114,20 @@ describe('utils', () => {
expect(result).toBe(startTime); expect(result).toBe(startTime);
}); });
it('should allow "context" as a var name', () => {
const expr = 'res["context"].toUpperCase()';
evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res, context } = __bruno__functionInnerContext;');
});
it('should throw an error when we use "__bruno__functionInnerContext" as a var name', () => {
const expr = 'res["__bruno__functionInnerContext"].toUpperCase()';
expect(() => evaluateJsExpression(expr, context)).toThrow(SyntaxError);
expect(() => evaluateJsExpression(expr, context)).toThrow(
"Identifier '__bruno__functionInnerContext' has already been declared"
);
});
}); });
describe('response parser', () => { describe('response parser', () => {