mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix: 3014 - Postman import ignores API Key "in" placement setting and defaults to header (#7968)
* fix: 3014 - Postman import ignores API Key "in" placement setting and defaults to header * addressed review comments * addressed review comment * addressed review comment - added a test id * addressed reveiew comment
This commit is contained in:
@@ -28,7 +28,8 @@ const ModalFooter = ({
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter,
|
||||
confirmButtonColor = 'primary'
|
||||
confirmButtonColor = 'primary',
|
||||
dataTestId = 'modal'
|
||||
}) => {
|
||||
confirmText = confirmText || 'Save';
|
||||
cancelText = cancelText || 'Cancel';
|
||||
@@ -51,6 +52,7 @@ const ModalFooter = ({
|
||||
disabled={confirmDisabled}
|
||||
onClick={handleSubmit}
|
||||
className="submit"
|
||||
data-testid={`${dataTestId}-submit-btn`}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
@@ -151,6 +153,7 @@ const Modal = ({
|
||||
hideCancel={hideCancel}
|
||||
hideFooter={hideFooter}
|
||||
confirmButtonColor={confirmButtonColor}
|
||||
dataTestId={dataTestId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const ApiKeyAuth = ({ item, collection, updateAuth, request, save }) => {
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
|
||||
<div ref={ref} data-testid="auth-placement-label" className="flex items-center justify-end auth-type-label select-none">
|
||||
{humanizeRequestAPIKeyPlacement(apikeyAuth?.placement)}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@ const ApiKeyAuth = ({ item, collection, updateAuth, request, save }) => {
|
||||
</div>
|
||||
|
||||
<label className="block mb-1">Add To</label>
|
||||
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
|
||||
<div data-testid="auth-placement-selector" className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
|
||||
@@ -70,6 +70,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
|
||||
{errorMessage && (
|
||||
<div
|
||||
data-testid="import-error-message"
|
||||
className="mb-4 p-2 border rounded-md"
|
||||
style={{
|
||||
backgroundColor: theme.status.danger.background,
|
||||
|
||||
@@ -261,7 +261,11 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, rawData, format, sour
|
||||
) : null}
|
||||
|
||||
<div className="mt-1">
|
||||
<span className="text-link cursor-pointer hover:underline" onClick={browse}>
|
||||
<span
|
||||
data-testid="import-collection-browse-link"
|
||||
className="text-link cursor-pointer hover:underline"
|
||||
onClick={browse}
|
||||
>
|
||||
Browse
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -267,7 +267,7 @@ export const processAuth = (auth, requestObject, isCollection = false) => {
|
||||
requestObject.auth.apikey = {
|
||||
key: ensureString(authValues.key),
|
||||
value: ensureString(authValues.value),
|
||||
placement: 'header' // By default we are placing the apikey values in headers!
|
||||
placement: authValues.in === 'query' ? 'queryparams' : 'header' // map Postman `in` to Bruno placement; defaults to header
|
||||
};
|
||||
break;
|
||||
case AUTH_TYPES.DIGEST:
|
||||
|
||||
@@ -580,4 +580,67 @@ describe('Request Authentication', () => {
|
||||
basic: null, bearer: null, awsv4: null, apikey: null, oauth2: null, digest: null, oauth1: null
|
||||
});
|
||||
});
|
||||
|
||||
describe('API Key Auth placement', () => {
|
||||
const buildApiKeyCollection = (apikey) => ({
|
||||
info: {
|
||||
name: 'API Key Collection',
|
||||
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
|
||||
},
|
||||
item: [
|
||||
{
|
||||
name: 'API Key Request',
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: 'https://api.example.com/test',
|
||||
auth: { type: 'apikey', apikey }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
it('should map Postman in=query to Bruno placement=queryparams', async () => {
|
||||
const postmanCollection = buildApiKeyCollection([
|
||||
{ key: 'key', value: 'X-API-Key' },
|
||||
{ key: 'value', value: 'secret-token' },
|
||||
{ key: 'in', value: 'query' }
|
||||
]);
|
||||
|
||||
const result = await postmanToBruno(postmanCollection);
|
||||
|
||||
expect(result.items[0].request.auth.mode).toBe('apikey');
|
||||
expect(result.items[0].request.auth.apikey).toEqual({
|
||||
key: 'X-API-Key',
|
||||
value: 'secret-token',
|
||||
placement: 'queryparams'
|
||||
});
|
||||
});
|
||||
|
||||
it('should map Postman in=header to Bruno placement=header', async () => {
|
||||
const postmanCollection = buildApiKeyCollection([
|
||||
{ key: 'key', value: 'X-API-Key' },
|
||||
{ key: 'value', value: 'secret-token' },
|
||||
{ key: 'in', value: 'header' }
|
||||
]);
|
||||
|
||||
const result = await postmanToBruno(postmanCollection);
|
||||
|
||||
expect(result.items[0].request.auth.apikey).toEqual({
|
||||
key: 'X-API-Key',
|
||||
value: 'secret-token',
|
||||
placement: 'header'
|
||||
});
|
||||
});
|
||||
|
||||
it('should default placement to header when Postman in is absent', async () => {
|
||||
const postmanCollection = buildApiKeyCollection([
|
||||
{ key: 'key', value: 'X-API-Key' },
|
||||
{ key: 'value', value: 'secret-token' }
|
||||
]);
|
||||
|
||||
const result = await postmanToBruno(postmanCollection);
|
||||
|
||||
expect(result.items[0].request.auth.apikey.placement).toBe('header');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "6df527f0-77aa-4b67-957f-8a3f0c96a027",
|
||||
"name": "My Collection",
|
||||
"description": "### Welcome to Postman! This is your first collection. \n\nCollections are your starting point for building and testing APIs. You can use this one to:\n\n• Group related requests\n• Test your API in real-world scenarios\n• Document and share your requests\n\nUpdate the name and overview whenever you’re ready to make it yours.\n\n[Learn more about Postman Collections.](https://learning.postman.com/docs/collections/collections-overview/)",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "54316404",
|
||||
"_collection_link": "https://go.postman.co/collection/54316404-6df527f0-77aa-4b67-957f-8a3f0c96a027?source=collection_link"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Headers with API Key",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "in",
|
||||
"value": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"value": "hello",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "hii",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://api.com/users?X-API-KEY=12345",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api",
|
||||
"com"
|
||||
],
|
||||
"path": [
|
||||
"users"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "X-API-KEY",
|
||||
"value": "12345"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "6df527f0-77aa-4b67-957f-8a3f0c96a027",
|
||||
"name": "My Collection",
|
||||
"description": "### Welcome to Postman! This is your first collection. \n\nCollections are your starting point for building and testing APIs. You can use this one to:\n\n• Group related requests\n• Test your API in real-world scenarios\n• Document and share your requests\n\nUpdate the name and overview whenever you’re ready to make it yours.\n\n[Learn more about Postman Collections.](https://learning.postman.com/docs/collections/collections-overview/)",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "54316404",
|
||||
"_collection_link": "https://go.postman.co/collection/54316404-6df527f0-77aa-4b67-957f-8a3f0c96a027?source=collection_link"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Query with API Key",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "in",
|
||||
"value": "query",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"value": "hello",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "hii",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "https://api.com/users?X-API-KEY=12345",
|
||||
"protocol": "https",
|
||||
"host": [
|
||||
"api",
|
||||
"com"
|
||||
],
|
||||
"path": [
|
||||
"users"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "X-API-KEY",
|
||||
"value": "12345"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
90
tests/import/postman/import-apikey-header-collection.spec.ts
Normal file
90
tests/import/postman/import-apikey-header-collection.spec.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import * as path from 'path';
|
||||
import { closeAllCollections, openCollection, selectRequestPaneTab } from '../../utils/page';
|
||||
import { buildCommonLocators } from '../../utils/page/locators';
|
||||
|
||||
test.describe('Import Postman Collection with API Key in Header', () => {
|
||||
let originalShowOpenDialog;
|
||||
|
||||
test.beforeAll(async ({ electronApp }) => {
|
||||
await electronApp.evaluate(({ dialog }) => {
|
||||
originalShowOpenDialog = dialog.showOpenDialog;
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ electronApp, page }) => {
|
||||
await closeAllCollections(page);
|
||||
await electronApp.evaluate(({ dialog }) => {
|
||||
dialog.showOpenDialog = originalShowOpenDialog;
|
||||
});
|
||||
});
|
||||
|
||||
test('should import Postman collection with API Key in Header successfully', async ({ page, electronApp, createTmpDir }) => {
|
||||
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-import-apikey-header-collection.json');
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
const importDir = await createTmpDir('imported-collection');
|
||||
|
||||
await electronApp.evaluate(({ dialog }, { importDir }) => {
|
||||
dialog.showOpenDialog = async () => ({
|
||||
canceled: false,
|
||||
filePaths: [importDir]
|
||||
});
|
||||
}, { importDir });
|
||||
|
||||
await test.step('Open import collection modal', async () => {
|
||||
await locators.plusMenu.button().click();
|
||||
await locators.plusMenu.importCollection().click();
|
||||
});
|
||||
|
||||
await test.step('Wait for import modal and verify title', async () => {
|
||||
const importModal = page.getByRole('dialog');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
await expect(locators.modal.title('Import Collection')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Upload Postman collection file using hidden file input', async () => {
|
||||
await locators.import.fileInput().setInputFiles(postmanFile);
|
||||
await locators.import.locationModal().waitFor({ state: 'visible', timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('Verify no parsing errors occurred', async () => {
|
||||
const hasError = await locators.import.parsingError().isVisible().catch(() => false);
|
||||
if (hasError) {
|
||||
throw new Error('Collection import failed with parsing error');
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('Verify location selection modal appears', async () => {
|
||||
await expect(locators.modal.title('Import Collection')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify collection name appears in location modal', async () => {
|
||||
await expect(locators.import.locationModal().getByText('My Collection')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Click Browse link to select collection folder', async () => {
|
||||
await locators.import.browseLink(locators.import.locationModal()).click();
|
||||
});
|
||||
|
||||
await test.step('Complete import by clicking import button', async () => {
|
||||
const locationModal = locators.import.locationModal();
|
||||
await locators.import.importButton(locationModal).click();
|
||||
await locationModal.waitFor({ state: 'hidden' });
|
||||
});
|
||||
|
||||
await test.step('Open collection and verify request is displayed', async () => {
|
||||
await openCollection(page, 'My Collection');
|
||||
await expect(locators.sidebar.collection('My Collection')).toBeVisible();
|
||||
await expect(locators.sidebar.request('Headers with API Key')).toBeVisible();
|
||||
await locators.sidebar.request('Headers with API Key').click();
|
||||
await expect(locators.request.pane()).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify API key is set to Header within Auth section', async () => {
|
||||
await selectRequestPaneTab(page, 'Auth');
|
||||
await expect(locators.auth.apiKey.placementSelector()).toBeVisible();
|
||||
await expect(locators.auth.apiKey.placementLabel()).toHaveText('Header');
|
||||
});
|
||||
});
|
||||
});
|
||||
90
tests/import/postman/import-apikey-query-collection.spec.ts
Normal file
90
tests/import/postman/import-apikey-query-collection.spec.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import * as path from 'path';
|
||||
import { closeAllCollections, openCollection, selectRequestPaneTab } from '../../utils/page';
|
||||
import { buildCommonLocators } from '../../utils/page/locators';
|
||||
|
||||
test.describe('Import Postman Collection with API Key in Query Params', () => {
|
||||
let originalShowOpenDialog;
|
||||
|
||||
test.beforeAll(async ({ electronApp }) => {
|
||||
await electronApp.evaluate(({ dialog }) => {
|
||||
originalShowOpenDialog = dialog.showOpenDialog;
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ electronApp, page }) => {
|
||||
await closeAllCollections(page);
|
||||
await electronApp.evaluate(({ dialog }) => {
|
||||
dialog.showOpenDialog = originalShowOpenDialog;
|
||||
});
|
||||
});
|
||||
|
||||
test('should import Postman collection with API Key in Query Params successfully', async ({ page, electronApp, createTmpDir }) => {
|
||||
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-import-apikey-query-collection.json');
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
const importDir = await createTmpDir('imported-collection');
|
||||
|
||||
await electronApp.evaluate(({ dialog }, { importDir }) => {
|
||||
dialog.showOpenDialog = async () => ({
|
||||
canceled: false,
|
||||
filePaths: [importDir]
|
||||
});
|
||||
}, { importDir });
|
||||
|
||||
await test.step('Open import collection modal', async () => {
|
||||
await locators.plusMenu.button().click();
|
||||
await locators.plusMenu.importCollection().click();
|
||||
});
|
||||
|
||||
await test.step('Wait for import modal and verify title', async () => {
|
||||
const importModal = page.getByRole('dialog');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
await expect(locators.modal.title('Import Collection')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Upload Postman collection file using hidden file input', async () => {
|
||||
await locators.import.fileInput().setInputFiles(postmanFile);
|
||||
await locators.import.locationModal().waitFor({ state: 'visible', timeout: 10000 });
|
||||
});
|
||||
|
||||
await test.step('Verify no parsing errors occurred', async () => {
|
||||
const hasError = await locators.import.parsingError().isVisible().catch(() => false);
|
||||
if (hasError) {
|
||||
throw new Error('Collection import failed with parsing error');
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('Verify location selection modal appears', async () => {
|
||||
await expect(locators.modal.title('Import Collection')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify collection name appears in location modal', async () => {
|
||||
await expect(locators.import.locationModal().getByText('My Collection')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Click Browse link to select collection folder', async () => {
|
||||
await locators.import.browseLink(locators.import.locationModal()).click();
|
||||
});
|
||||
|
||||
await test.step('Complete import by clicking import button', async () => {
|
||||
const locationModal = locators.import.locationModal();
|
||||
await locators.import.importButton(locationModal).click();
|
||||
await locationModal.waitFor({ state: 'hidden' });
|
||||
});
|
||||
|
||||
await test.step('Open collection and verify request is displayed', async () => {
|
||||
await openCollection(page, 'My Collection');
|
||||
await expect(locators.sidebar.collection('My Collection')).toBeVisible();
|
||||
await expect(locators.sidebar.request('Query with API Key')).toBeVisible();
|
||||
await locators.sidebar.request('Query with API Key').click();
|
||||
await expect(locators.request.pane()).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify API key is set to Query Params within Auth section', async () => {
|
||||
await selectRequestPaneTab(page, 'Auth');
|
||||
await expect(locators.auth.apiKey.placementSelector()).toBeVisible();
|
||||
await expect(locators.auth.apiKey.placementLabel()).toHaveText('Query Params');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,14 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
requestTestId: () => page.getByTestId('request-name'),
|
||||
generateCodeButton: () => page.locator('#request-actions .infotip').first(),
|
||||
bodyModeSelector: () => page.getByTestId('request-body-mode-selector'),
|
||||
bodyEditor: () => page.getByTestId('request-body-editor')
|
||||
bodyEditor: () => page.getByTestId('request-body-editor'),
|
||||
pane: () => page.getByTestId('request-pane')
|
||||
},
|
||||
auth: {
|
||||
apiKey: {
|
||||
placementSelector: () => page.getByTestId('auth-placement-selector'),
|
||||
placementLabel: () => page.getByTestId('auth-placement-label')
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
input: () => page.getByTestId('tag-input').getByRole('textbox'),
|
||||
@@ -118,7 +125,10 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
locationModal: () => page.locator('[data-testid="import-collection-location-modal"]'),
|
||||
locationInput: () => page.locator('#collection-location'),
|
||||
fileInput: () => page.locator('input[type="file"]'),
|
||||
envOption: (name: string) => page.locator('.dropdown-item').getByText(name, { exact: true })
|
||||
envOption: (name: string) => page.locator('.dropdown-item').getByText(name, { exact: true }),
|
||||
parsingError: () => page.getByTestId('import-error-message'),
|
||||
browseLink: (root?: Locator) => (root ?? page).getByTestId('import-collection-browse-link'),
|
||||
importButton: (root?: Locator) => (root ?? page).getByTestId('import-collection-location-modal-submit-btn')
|
||||
},
|
||||
/**
|
||||
* Build generic table locators for any table with a testId
|
||||
|
||||
Reference in New Issue
Block a user