mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix: restore saved credentials when switching back to original auth mode (#7911)
This commit is contained in:
@@ -1683,8 +1683,14 @@ export const collectionsSlice = createSlice({
|
||||
if (!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
item.draft.request.auth = {};
|
||||
item.draft.request.auth.mode = action.payload.mode;
|
||||
const newMode = action.payload.mode;
|
||||
const savedAuth = get(item, 'request.auth');
|
||||
const savedMode = get(savedAuth, 'mode');
|
||||
if (newMode === savedMode) {
|
||||
item.draft.request.auth = cloneDeep(savedAuth);
|
||||
} else {
|
||||
item.draft.request.auth = { mode: newMode };
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2113,8 +2119,14 @@ export const collectionsSlice = createSlice({
|
||||
root: cloneDeep(collection.root)
|
||||
};
|
||||
}
|
||||
set(collection, 'draft.root.request.auth', {});
|
||||
set(collection, 'draft.root.request.auth.mode', action.payload.mode);
|
||||
const newMode = action.payload.mode;
|
||||
const savedAuth = get(collection, 'root.request.auth');
|
||||
const savedMode = get(savedAuth, 'mode');
|
||||
if (newMode === savedMode) {
|
||||
set(collection, 'draft.root.request.auth', cloneDeep(savedAuth));
|
||||
} else {
|
||||
set(collection, 'draft.root.request.auth', { mode: newMode });
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCollectionAuth: (state, action) => {
|
||||
@@ -3322,8 +3334,14 @@ export const collectionsSlice = createSlice({
|
||||
if (!folder.draft) {
|
||||
folder.draft = cloneDeep(folder.root);
|
||||
}
|
||||
set(folder, 'draft.request.auth', {});
|
||||
set(folder, 'draft.request.auth.mode', action.payload.mode);
|
||||
const newMode = action.payload.mode;
|
||||
const savedAuth = get(folder, 'root.request.auth');
|
||||
const savedMode = get(savedAuth, 'mode');
|
||||
if (newMode === savedMode) {
|
||||
set(folder, 'draft.request.auth', cloneDeep(savedAuth));
|
||||
} else {
|
||||
set(folder, 'draft.request.auth', { mode: newMode });
|
||||
}
|
||||
}
|
||||
},
|
||||
streamDataReceived: (state, action) => {
|
||||
|
||||
66
tests/auth/auth-mode-switch-draft-indicator.spec.ts
Normal file
66
tests/auth/auth-mode-switch-draft-indicator.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
import {
|
||||
closeAllCollections,
|
||||
createCollection,
|
||||
createRequest,
|
||||
openRequest,
|
||||
readField,
|
||||
saveRequest,
|
||||
selectAuthMode,
|
||||
selectRequestPaneTab,
|
||||
typeIntoField
|
||||
} from '../utils/page';
|
||||
|
||||
type CollectionFormat = 'bru' | 'yml';
|
||||
|
||||
const runDraftIndicatorScenario = (format: CollectionFormat) => {
|
||||
test(`(${format}) switching back to the saved auth mode hides the draft indicator`, async ({ page, createTmpDir }) => {
|
||||
const collectionName = `auth-draft-indicator-${format}`;
|
||||
const requestName = `request-${format}`;
|
||||
|
||||
await createCollection(page, collectionName, await createTmpDir(), format);
|
||||
await createRequest(page, requestName, collectionName, { url: 'https://example.com/api' });
|
||||
await openRequest(page, collectionName, requestName);
|
||||
await selectRequestPaneTab(page, 'Auth');
|
||||
|
||||
const requestTab = page
|
||||
.locator('.request-tab')
|
||||
.filter({ has: page.locator('.tab-label', { hasText: requestName }) });
|
||||
|
||||
await test.step('Save Bearer with a token — draft indicator clears', async () => {
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
await typeIntoField(page, 'Token', 'saved-bearer-token');
|
||||
await saveRequest(page);
|
||||
|
||||
await expect(requestTab.locator('.close-icon')).toBeVisible();
|
||||
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Switching to Basic Auth without saving shows the draft indicator', async () => {
|
||||
await selectAuthMode(page, 'Basic Auth');
|
||||
|
||||
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
|
||||
await expect(requestTab.locator('.close-icon')).not.toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Switching back to the saved Bearer mode without saving hides the draft indicator', async () => {
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
|
||||
// Saved token is restored
|
||||
await expect.poll(() => readField(page, 'Token')).toBe('saved-bearer-token');
|
||||
|
||||
// Draft now deep-equals the saved state — indicator must be gone
|
||||
await expect(requestTab.locator('.close-icon')).toBeVisible();
|
||||
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
test.describe('Auth mode switch — draft indicator clears on return to saved mode', () => {
|
||||
test.afterEach(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
runDraftIndicatorScenario('bru');
|
||||
runDraftIndicatorScenario('yml');
|
||||
});
|
||||
101
tests/auth/auth-mode-switch.spec.ts
Normal file
101
tests/auth/auth-mode-switch.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
import {
|
||||
closeAllCollections,
|
||||
createCollection,
|
||||
createRequest,
|
||||
createFolder,
|
||||
openRequest,
|
||||
readField,
|
||||
saveRequest,
|
||||
selectAuthMode,
|
||||
selectRequestPaneTab,
|
||||
typeIntoField
|
||||
} from '../utils/page';
|
||||
|
||||
test.describe('Auth mode switch preserves saved data', () => {
|
||||
test.afterEach(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('Request: switching back to the saved mode restores its credentials', async ({ page, createTmpDir }) => {
|
||||
await createCollection(page, 'auth-mode-switch-req', await createTmpDir());
|
||||
await createRequest(page, 'request-1', 'auth-mode-switch-req', { url: 'https://example.com/api' });
|
||||
await openRequest(page, 'auth-mode-switch-req', 'request-1');
|
||||
await selectRequestPaneTab(page, 'Auth');
|
||||
|
||||
await test.step('Save Bearer with a token', async () => {
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
await typeIntoField(page, 'Token', 'saved-bearer-token');
|
||||
await saveRequest(page);
|
||||
});
|
||||
|
||||
await test.step('Bearer → Basic → Bearer restores the saved token (the bug fix)', async () => {
|
||||
await selectAuthMode(page, 'Basic Auth');
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
|
||||
await expect.poll(() => readField(page, 'Token')).toBe('saved-bearer-token');
|
||||
});
|
||||
|
||||
await test.step('Switching to a non-saved mode shows empty fields (no regression)', async () => {
|
||||
await selectAuthMode(page, 'Basic Auth');
|
||||
|
||||
await expect.poll(() => readField(page, 'Username')).toBe('');
|
||||
await expect.poll(() => readField(page, 'Password')).toBe('');
|
||||
});
|
||||
|
||||
await test.step('Switching to a third unrelated mode also leaves fields empty', async () => {
|
||||
// Bearer is the saved mode; Digest has never been touched.
|
||||
await selectAuthMode(page, 'Digest Auth');
|
||||
|
||||
await expect.poll(() => readField(page, 'Username')).toBe('');
|
||||
await expect.poll(() => readField(page, 'Password')).toBe('');
|
||||
});
|
||||
|
||||
await test.step('Returning once more to Bearer still restores the saved token', async () => {
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
await expect.poll(() => readField(page, 'Token')).toBe('saved-bearer-token');
|
||||
});
|
||||
});
|
||||
|
||||
test('Collection: switching back to the saved mode restores its credentials', async ({ page, createTmpDir }) => {
|
||||
await createCollection(page, 'auth-mode-switch-col', await createTmpDir());
|
||||
|
||||
// The collection settings tab opens automatically on creation.
|
||||
await page.locator('.tab.auth').click();
|
||||
|
||||
await test.step('Save Bearer at the collection level', async () => {
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
await typeIntoField(page, 'Token', 'collection-bearer-token');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
});
|
||||
|
||||
await test.step('Bearer → Basic → Bearer restores the saved collection token', async () => {
|
||||
await selectAuthMode(page, 'Basic Auth');
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
|
||||
await expect.poll(() => readField(page, 'Token')).toBe('collection-bearer-token');
|
||||
});
|
||||
});
|
||||
|
||||
test('Folder: switching back to the saved mode restores its credentials', async ({ page, createTmpDir }) => {
|
||||
await createCollection(page, 'auth-mode-switch-folder', await createTmpDir());
|
||||
await createFolder(page, 'folder-1', 'auth-mode-switch-folder', true);
|
||||
|
||||
// Open the folder settings tab.
|
||||
await page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).dblclick();
|
||||
await page.locator('.tab.auth').click();
|
||||
|
||||
await test.step('Save Bearer at the folder level', async () => {
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
await typeIntoField(page, 'Token', 'folder-bearer-token');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
});
|
||||
|
||||
await test.step('Bearer → Basic → Bearer restores the saved folder token', async () => {
|
||||
await selectAuthMode(page, 'Basic Auth');
|
||||
await selectAuthMode(page, 'Bearer Token');
|
||||
|
||||
await expect.poll(() => readField(page, 'Token')).toBe('folder-bearer-token');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -80,7 +80,12 @@ const openCollection = async (page, collectionName: string) => {
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const createCollection = async (page, collectionName: string, collectionLocation: string) => {
|
||||
const createCollection = async (
|
||||
page,
|
||||
collectionName: string,
|
||||
collectionLocation: string,
|
||||
format?: 'bru' | 'yml'
|
||||
) => {
|
||||
await test.step(`Create collection "${collectionName}"`, async () => {
|
||||
await page.getByTestId('collections-header-add-menu').click();
|
||||
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
|
||||
@@ -123,6 +128,15 @@ const createCollection = async (page, collectionName: string, collectionLocation
|
||||
await nameInput.fill(collectionName);
|
||||
// Verify the name is correct before creating
|
||||
await expect(nameInput).toHaveValue(collectionName, { timeout: 2000 });
|
||||
|
||||
if (format) {
|
||||
await createCollectionModal.locator('.advanced-options .btn-advanced').click();
|
||||
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Show File Format' }).click();
|
||||
const formatSelect = createCollectionModal.locator('#format');
|
||||
await formatSelect.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await formatSelect.selectOption(format);
|
||||
}
|
||||
|
||||
await createCollectionModal.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
|
||||
// The modal closes via `onClose()` in the form's `onSubmit` success path,
|
||||
@@ -1340,6 +1354,45 @@ const sendAndWaitForResponse = async (page: Page) => {
|
||||
});
|
||||
};
|
||||
|
||||
const fieldEditor = (page: Page, labelText: string) =>
|
||||
page
|
||||
.locator('label')
|
||||
.filter({ hasText: new RegExp(`^${escapeRegExp(labelText)}$`) })
|
||||
.locator('..')
|
||||
.locator('.single-line-editor-wrapper .CodeMirror');
|
||||
|
||||
/**
|
||||
* Open the auth mode dropdown and pick a mode by its visible label.
|
||||
* @param page - The page object
|
||||
* @param modeLabel - Dropdown item text (e.g. 'Bearer Token', 'Basic Auth')
|
||||
*/
|
||||
const selectAuthMode = async (page: Page, modeLabel: string) => {
|
||||
await page.locator('.auth-mode-label').click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: modeLabel }).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Type into a single-line CodeMirror editor identified by its sibling label.
|
||||
* @param page - The page object
|
||||
* @param labelText - Exact label text next to the editor
|
||||
* @param value - The text to type
|
||||
*/
|
||||
const typeIntoField = async (page: Page, labelText: string, value: string) => {
|
||||
await fieldEditor(page, labelText).click();
|
||||
await page.keyboard.type(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the current value of a single-line CodeMirror editor identified by its sibling label.
|
||||
* @param page - The page object
|
||||
* @param labelText - Exact label text next to the editor
|
||||
*/
|
||||
const readField = async (page: Page, labelText: string): Promise<string> => {
|
||||
const editor = fieldEditor(page, labelText).first();
|
||||
await editor.waitFor({ state: 'visible' });
|
||||
return editor.evaluate((el: any) => (el as any).CodeMirror?.getValue() ?? '');
|
||||
};
|
||||
|
||||
const createExampleFromSidebar = async (page: Page, requestName: string, exampleName: string, description: string = '') => {
|
||||
const requestRow = page.locator('.collection-item-name').filter({ hasText: requestName }).first();
|
||||
|
||||
@@ -1422,6 +1475,9 @@ export {
|
||||
addTestScript,
|
||||
sendAndWaitForErrorCard,
|
||||
sendAndWaitForResponse,
|
||||
selectAuthMode,
|
||||
typeIntoField,
|
||||
readField,
|
||||
createExampleFromSidebar,
|
||||
openExampleFromSidebar
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user