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 <chiragchan@Chirags-MacBook-Air.local>
Co-authored-by: Sid <siddharth@usebruno.com>

chore: reformat
This commit is contained in:
Chirag Chandrashekhar
2025-10-24 16:23:23 +05:30
committed by Sid
parent 045141efaf
commit fa1498e2a8
4 changed files with 117 additions and 19 deletions

1
package-lock.json generated
View File

@@ -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",

View File

@@ -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 (
<StyledWrapper>

View File

@@ -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),

View File

@@ -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' });
});
});