From 7de5bbbdf615716b6e007db9d0694279f68c6d68 Mon Sep 17 00:00:00 2001 From: Nelu Platonov Date: Thu, 25 Jan 2024 15:06:32 +0100 Subject: [PATCH] fix(#1143): Fix PR #971 - Use literal segments only for env/collection variables + Add to CLI (#1154) * fix(#1143): Fix PR #971 - Add literal-segment notation in string only to variables that are not process env vars * fix(#1143): Fix PR #971 - Add to CLI as well * fix(#1143): Fix PR #971 - Use improved Regex after CR + add test case for escaped vars --- .../bruno-cli/src/runner/interpolate-vars.js | 6 + .../src/ipc/network/interpolate-vars.js | 10 +- .../tests/network/interpolate-vars.spec.js | 130 ++++++++++++++++++ 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 packages/bruno-electron/tests/network/interpolate-vars.spec.js diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index b92616478..a764e791d 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -28,6 +28,8 @@ const interpolateEnvVars = (str, processEnvVars) => { }); }; +const varsRegex = /(? { // we clone envVars because we don't want to modify the original object envVars = cloneDeep(envVars); @@ -43,6 +45,10 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces return str; } + if (varsRegex.test(str)) { + // Handlebars doesn't allow dots as identifiers, so we need to use literal segments + str = str.replaceAll(varsRegex, '{{[$1]}}'); + } const template = Handlebars.compile(str, { noEscape: true }); // collectionVariables take precedence over envVars diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 4a709f5ae..8c165e1b0 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -28,6 +28,8 @@ const interpolateEnvVars = (str, processEnvVars) => { }); }; +const varsRegex = /(? { // we clone envVars because we don't want to modify the original object envVars = cloneDeep(envVars); @@ -43,9 +45,11 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces return str; } - // Handlebars doesn't allow dots as identifiers, so we need to use literal segments - const strLiteralSegment = str.replace('{{', '{{[').replace('}}', ']}}'); - const template = Handlebars.compile(strLiteralSegment, { noEscape: true }); + if (varsRegex.test(str)) { + // Handlebars doesn't allow dots as identifiers, so we need to use literal segments + str = str.replaceAll(varsRegex, '{{[$1]}}'); + } + const template = Handlebars.compile(str, { noEscape: true }); // collectionVariables take precedence over envVars const combinedVars = { diff --git a/packages/bruno-electron/tests/network/interpolate-vars.spec.js b/packages/bruno-electron/tests/network/interpolate-vars.spec.js new file mode 100644 index 000000000..d33a49bf4 --- /dev/null +++ b/packages/bruno-electron/tests/network/interpolate-vars.spec.js @@ -0,0 +1,130 @@ +const interpolateVars = require('../../src/ipc/network/interpolate-vars'); + +describe('interpolate-vars: interpolateVars', () => { + describe('Interpolates string', () => { + describe('With environment variables', () => { + it("If there's a var with only alphanumeric characters in its name", async () => { + const request = { method: 'GET', url: '{{testUrl1}}' }; + + const result = interpolateVars(request, { testUrl1: 'test.com' }, null, null); + expect(result.url).toEqual('test.com'); + }); + + it("If there's a var with a '.' in its name", async () => { + const request = { method: 'GET', url: '{{test.url}}' }; + + const result = interpolateVars(request, { 'test.url': 'test.com' }, null, null); + expect(result.url).toEqual('test.com'); + }); + + it("If there's a var with a '-' in its name", async () => { + const request = { method: 'GET', url: '{{test-url}}' }; + + const result = interpolateVars(request, { 'test-url': 'test.com' }, null, null); + expect(result.url).toEqual('test.com'); + }); + + it("If there's a var with a '_' in its name", async () => { + const request = { method: 'GET', url: '{{test_url}}' }; + + const result = interpolateVars(request, { test_url: 'test.com' }, null, null); + expect(result.url).toEqual('test.com'); + }); + + it('If there are multiple variables', async () => { + const body = + '{\n "firstElem": {{body-var-1}},\n "secondElem": [{{body.var.2}}],\n "thirdElem": {\n "fourthElem": {{body_var_3}},\n "{{varAsKey}}": {{valueForKey}} }}'; + const expectedBody = + '{\n "firstElem": Test1,\n "secondElem": [Test2],\n "thirdElem": {\n "fourthElem": Test3,\n "TestKey": TestValueForKey }}'; + + const request = { method: 'POST', url: 'test', data: body, headers: { 'content-type': 'json' } }; + const result = interpolateVars( + request, + { + 'body-var-1': 'Test1', + 'body.var.2': 'Test2', + body_var_3: 'Test3', + varAsKey: 'TestKey', + valueForKey: 'TestValueForKey' + }, + null, + null + ); + expect(result.data).toEqual(expectedBody); + }); + }); + + describe('With process environment variables', () => { + /* + * It should NOT turn process env vars into literal segments. + * Otherwise, Handlebars will try to access the var literally + */ + it("If there's a var that starts with 'process.env.'", async () => { + const request = { method: 'GET', url: '{{process.env.TEST_VAR}}' }; + + const result = interpolateVars(request, null, null, { TEST_VAR: 'test.com' }); + expect(result.url).toEqual('test.com'); + }); + }); + }); + + describe('Does NOT interpolate string', () => { + describe('With environment variables', () => { + it('If the var is escaped', async () => { + const request = { method: 'GET', url: `\\{{test.url}}` }; + + const result = interpolateVars(request, { 'test.url': 'test.com' }, null, null); + expect(result.url).toEqual('{{test.url}}'); + }); + + it("If it's not a var (no braces)", async () => { + const request = { method: 'GET', url: 'test' }; + + const result = interpolateVars(request, { 'test.url': 'test.com' }, null, null); + expect(result.url).toEqual('test'); + }); + + it("If it's not a var (only 1 set of braces)", async () => { + const request = { method: 'GET', url: '{test.url}' }; + + const result = interpolateVars(request, { 'test.url': 'test.com' }, null, null); + expect(result.url).toEqual('{test.url}'); + }); + + it("If it's not a var (1 opening & 2 closing braces)", async () => { + const request = { method: 'GET', url: '{test.url}}' }; + + const result = interpolateVars(request, { 'test.url': 'test.com' }, null, null); + expect(result.url).toEqual('{test.url}}'); + }); + + it('If there are no variables (multiple)', async () => { + let gqlBody = `{"query":"mutation {\\n test(input: { native: { firstElem: \\"{should-not-get-interpolated}\\", secondElem: \\"{should-not-get-interpolated}}"}}) {\\n __typename\\n ... on TestType {\\n id\\n identifier\\n }\\n }\\n}","variables":"{}"}`; + + const request = { method: 'POST', url: 'test', data: gqlBody }; + const result = interpolateVars(request, { 'should-not-get-interpolated': 'ERROR' }, null, null); + expect(result.data).toEqual(gqlBody); + }); + }); + + describe('With process environment variables', () => { + it("If there's a var that doesn't start with 'process.env.'", async () => { + const request = { method: 'GET', url: '{{process-env-TEST_VAR}}' }; + + const result = interpolateVars(request, null, null, { TEST_VAR: 'test.com' }); + expect(result.url).toEqual(''); + }); + }); + }); + + describe('Throws an error', () => { + it("If there's a var with an invalid character in its name", async () => { + '!@#%^&*()[{]}=+,<>;\\|'.split('').forEach((character) => { + const request = { method: 'GET', url: `{{test${character}Url}}` }; + expect(() => interpolateVars(request, { [`test${character}Url`]: 'test.com' }, null, null)).toThrow( + /Parse error.*/ + ); + }); + }); + }); +});