From fa1498e2a85699cd761eb227058363be8040a63e Mon Sep 17 00:00:00 2001 From: Chirag Chandrashekhar Date: Fri, 24 Oct 2025 16:23:23 +0530 Subject: [PATCH] Bugfix/incorrect space encode (#5870) * Fix the space encoding issue * fix: incorrect space encoding Fixed an issue in Code Generation for requests. The original fix was raised in [PR](https://github.com/usebruno/bruno/pull/4478). The current PR fixes some merge conflicts and resolves some unimported dependencies error. * test: add URL encoding tests for code generation feature Add Playwright tests to verify proper URL encoding behavior in Bruno's code generation dialog for both encoded and unencoded query parameters. * moved the test script inside request * updated the snippet generation code to reuse code and reduce redundancy * removed redundant code and reverted autoformat * reverted some auto formatted changes * reverting format during commit hook * chore: reset formatting * chore: reformat --------- Co-authored-by: Vipin Sundar <86339268+vipin-sundar@users.noreply.github.com> Co-authored-by: Chirag Chandrashekhar Co-authored-by: Sid chore: reformat --- package-lock.json | 1 + .../GenerateCodeItem/CodeView/index.js | 26 ++--- .../bruno-app/src/utils/codegenerator/har.js | 9 +- tests/request/encoding/curl-encoding.spec.ts | 100 ++++++++++++++++++ 4 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 tests/request/encoding/curl-encoding.spec.ts diff --git a/package-lock.json b/package-lock.json index 8fcda3224..83491b236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31986,6 +31986,7 @@ "cheerio": "^1.0.0", "crypto-js": "^4.2.0", "json-query": "^2.2.2", + "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "moment": "^2.29.4", "nanoid": "3.3.8", diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js index c3bfbcafb..a33d41749 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js @@ -10,7 +10,6 @@ import { findCollectionByItemUid, getGlobalEnvironmentVariables } from 'utils/co import { cloneDeep } from 'lodash'; import { useMemo } from 'react'; import { generateSnippet } from '../utils/snippet-generator'; - const CodeView = ({ language, item }) => { const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); @@ -32,23 +31,14 @@ const CodeView = ({ language, item }) => { return c; }, [collectionOriginal, globalEnvironments, activeGlobalEnvironmentUid]); - const snippet = useMemo(() => { - let snippet = ''; - try { - const request = cloneDeep(item.request); - if (request.url) { - request.url = decodeURIComponent(request.url); - } - return new HTTPSnippet(buildHarRequest({ request: request, headers, type: item.type })).convert( - target, - client - ); - } catch (e) { - console.error(e); - return 'Error generating code snippet'; - } - }, [language, item, collection, generateCodePrefs.shouldInterpolate]); - + const snippet = useMemo(() => { + return generateSnippet({ + language, + item, + collection, + shouldInterpolate: generateCodePrefs.shouldInterpolate + }); + }, [language, item, collection, generateCodePrefs.shouldInterpolate]); return ( diff --git a/packages/bruno-app/src/utils/codegenerator/har.js b/packages/bruno-app/src/utils/codegenerator/har.js index 836631056..1411ffe03 100644 --- a/packages/bruno-app/src/utils/codegenerator/har.js +++ b/packages/bruno-app/src/utils/codegenerator/har.js @@ -131,9 +131,16 @@ const createPostData = (body) => { }; export const buildHarRequest = ({ request, headers }) => { + // NOTE: + // This is just a safety check. + // The interpolateUrlPathParams method validates the url, but it does not throw + if (!URL.canParse(request.url)) { + throw new Error('invalid request url'); + } + return { method: request.method, - url: encodeURI(request.url), + url: request.url, httpVersion: 'HTTP/1.1', cookies: [], headers: createHeaders(request, headers), diff --git a/tests/request/encoding/curl-encoding.spec.ts b/tests/request/encoding/curl-encoding.spec.ts new file mode 100644 index 000000000..28d908a8e --- /dev/null +++ b/tests/request/encoding/curl-encoding.spec.ts @@ -0,0 +1,100 @@ +import { test, expect } from '../../../playwright'; +import { closeAllCollections } from '../../utils/page'; + +test.describe('Code Generation URL Encoding', () => { + test.afterEach(async ({ pageWithUserData: page }) => { + try { + const modalCloseButton = page.locator('[data-test-id="modal-close-button"]'); + if (await modalCloseButton.isVisible()) { + await modalCloseButton.click(); + await modalCloseButton.waitFor({ state: 'hidden' }); + } + } catch (e) {} + + await closeAllCollections(page); + }); + + test('Should generate code with proper URL encoding for unencoded input', async ({ + pageWithUserData: page, + createTmpDir + }) => { + await page.locator('.dropdown-icon').click(); + await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click(); + await page.getByLabel('Name').fill('unencoded-test-collection'); + await page.getByLabel('Location').fill(await createTmpDir('unencoded-test-collection')); + await page.getByRole('button', { name: 'Create', exact: true }).click(); + + await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'unencoded-test-collection' })).toBeVisible(); + await page.locator('#sidebar-collection-name').filter({ hasText: 'unencoded-test-collection' }).click(); + await page.getByLabel('Safe Mode').check(); + await page.getByRole('button', { name: 'Save' }).click(); + + await page.locator('#create-new-tab').getByRole('img').click(); + await page.getByPlaceholder('Request Name').fill('unencoded-request'); + await page.locator('#new-request-url .CodeMirror').click(); + await page.locator('textarea').fill('http://base.source?name=John Doe'); + await page.getByRole('button', { name: 'Create' }).click(); + + await expect(page.locator('.collection-item-name').filter({ hasText: 'unencoded-request' })).toBeVisible(); + + await page.locator('.collection-item-name').filter({ hasText: 'unencoded-request' }).click(); + + await page.locator('#send-request .infotip').first().click(); + + await expect(page.getByRole('dialog')).toBeVisible(); + await expect(page.getByRole('dialog').locator('.bruno-modal-header-title')).toContainText('Generate Code'); + + const codeEditor = page.locator('.editor-content .CodeMirror').first(); + await expect(codeEditor).toBeVisible(); + + const generatedCode = await codeEditor.textContent(); + + expect(generatedCode).toContain('http://base.source/?name=John%20Doe'); + + await page.locator('[data-test-id="modal-close-button"]').click(); + + await page.locator('[data-test-id="modal-close-button"]').waitFor({ state: 'hidden' }); + }); + + test('Should generate code with proper URL encoding for encoded input', async ({ + pageWithUserData: page, + createTmpDir + }) => { + await page.locator('.dropdown-icon').click(); + await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click(); + await page.getByLabel('Name').fill('encoded-test-collection'); + await page.getByLabel('Location').fill(await createTmpDir('encoded-test-collection')); + await page.getByRole('button', { name: 'Create', exact: true }).click(); + + await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'encoded-test-collection' })).toBeVisible(); + await page.locator('#sidebar-collection-name').filter({ hasText: 'encoded-test-collection' }).click(); + await page.getByLabel('Safe Mode').check(); + await page.getByRole('button', { name: 'Save' }).click(); + + await page.locator('#create-new-tab').getByRole('img').click(); + await page.getByPlaceholder('Request Name').fill('encoded-request'); + await page.locator('#new-request-url .CodeMirror').click(); + await page.locator('textarea').fill('http://base.source?name=John%20Doe'); + await page.getByRole('button', { name: 'Create' }).click(); + + await expect(page.locator('.collection-item-name').filter({ hasText: 'encoded-request' })).toBeVisible(); + + await page.locator('.collection-item-name').filter({ hasText: 'encoded-request' }).click(); + + await page.locator('#send-request .infotip').first().click(); + + await expect(page.getByRole('dialog')).toBeVisible(); + await expect(page.getByRole('dialog').locator('.bruno-modal-header-title')).toContainText('Generate Code'); + + const codeEditor = page.locator('.editor-content .CodeMirror').first(); + await expect(codeEditor).toBeVisible(); + + const generatedCode = await codeEditor.textContent(); + + expect(generatedCode).toContain('http://base.source/?name=John%20Doe'); + + await page.locator('[data-test-id="modal-close-button"]').click(); + + await page.locator('[data-test-id="modal-close-button"]').waitFor({ state: 'hidden' }); + }); +});