mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-01 00:24:08 +00:00
* internal emit chain for clearance * fix: crash ui * fix(ErrorBoundary): ensure cache clearing is awaited before force quitting * test(e2e): environment persistence across collections * test: migration test * Update environment.spec.ts * feat: add missing status assertions and .not negation support (#7660) * feat: add new status assertions and negated variants for response checks - Introduced new status assertions: `pm.response.to.be.info`, `pm.response.to.be.accepted`, `pm.response.to.be.badRequest`, `pm.response.to.be.unauthorized`, `pm.response.to.be.forbidden`, `pm.response.to.be.notFound`, `pm.response.to.be.rateLimited`, and `pm.response.to.be.withoutBody`. - Implemented negated variants for existing assertions, allowing checks like `pm.response.to.not.be.ok` and `pm.response.to.not.be.success`. - Enhanced test coverage to validate the translation of these new assertions and their negated forms in the Postman to Bruno conversion process. * refactor: update response translation for body assertions - Changed the translation logic for `pm.response.to.be.withoutBody` to `pm.response.to.be.withBody`, reflecting the actual body content checks. - Updated corresponding test cases to validate the new assertions and ensure correct translation behavior for body presence checks. - Enhanced negated variants for body assertions to align with the updated logic. * feat: add header transformation methods for Postman to Bruno conversion - Introduced new transformations for `pm.request.headers.prepend`, `pm.request.headers.insert`, and `pm.request.headers.insertAfter` to map to `req.headerList.add`, enhancing header management during the conversion process. - Updated the transformation logic to ensure only the first argument is retained for these methods, aligning with the intended behavior of the header list operations. * refactor: update header transformation logic for Postman to Bruno conversion - Simplified the transformation for `pm.response.to.have.header` and `pm.response.to.not.have.header` to use `res.getHeader` instead of `res.getHeaders`, improving clarity and consistency in the assertions. - Adjusted related test cases to validate the new transformation logic, ensuring accurate translation of header checks in the conversion process. * refactor: update header transformation logic for Postman to Bruno conversion - Enhanced the transformation for `pm.response.to.have.header` and `pm.response.to.not.have.header` to utilize `res.getHeaders()` with lowercased header names, improving consistency and accuracy in header assertions. - Updated related test cases to reflect the new transformation logic, ensuring correct translation of header checks in the conversion process. * feat: add data-driven status assertions for response checks - Introduced a new utility to generate data-driven status assertion entries for `pm.response.to.be.*` checks, including positive and negated variants. - Integrated the new status assertions into the Postman to Bruno translation logic, enhancing the capability to handle various response status checks. - Updated tests to validate the translation of new assertions, ensuring accurate conversion of status checks in the response handling process. * feat: enhance response assertion translations for negated variants - Updated the transformation logic for `pm.response.to.have.*` assertions to include negated variants, allowing for patterns like `pm.response.to.have.not.status`, `pm.response.to.have.not.header`, and `pm.response.to.have.not.body`. - Adjusted related test cases to validate the new translations, ensuring accurate conversion of negated assertions in the response handling process. * refactor: convert status assertion utility to ES module syntax - Changed the export of `buildStatusAssertionEntries` to ES module syntax for better compatibility with modern JavaScript practices. - Updated the import statement in the Postman to Bruno translator to reflect the new export format, ensuring seamless integration of the status assertion utility. * feat: update response body assertion translations to use undefined checks - Modified the transformation logic for `pm.response.to.be.withBody`, `pm.response.to.not.be.withBody`, and `pm.response.to.be.not.withBody` to use an undefined check instead of truthiness, allowing for accurate handling of falsy body values. - Updated related test cases to reflect these changes, ensuring correct translation of body presence assertions in the response handling process. * feat: support newer Postman export format with collection envelope (#8038) - Updated the Postman collection importer to handle collections wrapped in a { collection: { ... } } format. - Enhanced the parsing logic to extract collection info correctly from both legacy and newer formats. - Added a new test case for importing a Postman v2.1 collection with the wrapped format to ensure compatibility. * fix: reduce padding for dark mode app errors --------- Co-authored-by: sanish chirayath <sanish@usebruno.com>
221 lines
10 KiB
TypeScript
221 lines
10 KiB
TypeScript
import path from 'path';
|
|
import fs from 'fs';
|
|
import { test, expect, closeElectronApp } from '../../../playwright';
|
|
import {
|
|
createCollection,
|
|
createEnvironment,
|
|
openCollection,
|
|
selectEnvironment,
|
|
waitForReadyPage
|
|
} from '../../utils/page';
|
|
|
|
const readSnapshot = (userDataPath: string) => {
|
|
const snapshotPath = path.join(userDataPath, 'ui-state-snapshot.json');
|
|
if (!fs.existsSync(snapshotPath)) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'));
|
|
};
|
|
|
|
const legacyPromptVariablesInitUserDataPath = path.join(
|
|
__dirname,
|
|
'init-user-data'
|
|
);
|
|
|
|
const migrationCollectionPath = path.join(
|
|
__dirname,
|
|
'fixtures/collection'
|
|
);
|
|
|
|
test.describe('Snapshot: Collection Environment Persistence', () => {
|
|
test('migrates legacy snapshot format and preserves selected collection environment', async ({ launchElectronApp, createTmpDir }) => {
|
|
const userDataPath = await createTmpDir('snap-legacy-env-migration');
|
|
|
|
const app = await launchElectronApp({
|
|
initUserDataPath: legacyPromptVariablesInitUserDataPath,
|
|
userDataPath
|
|
});
|
|
const page = await waitForReadyPage(app);
|
|
|
|
await test.step('Verify legacy selected environment is hydrated in UI', async () => {
|
|
await openCollection(page, 'migration-collection');
|
|
await expect(page.locator('.current-environment')).toContainText('local');
|
|
});
|
|
|
|
await test.step('Close app and verify snapshot migrated to new shape', async () => {
|
|
await page.waitForTimeout(2000);
|
|
await closeElectronApp(app);
|
|
|
|
const snapshot = readSnapshot(userDataPath);
|
|
expect(snapshot).not.toBeNull();
|
|
expect(snapshot).toHaveProperty('version');
|
|
expect(snapshot).toHaveProperty('activeWorkspacePath');
|
|
expect(snapshot).toHaveProperty('extras');
|
|
expect(snapshot).toHaveProperty('workspaces');
|
|
expect(snapshot).toHaveProperty('collections');
|
|
expect(Array.isArray(snapshot?.workspaces)).toBe(true);
|
|
expect(Array.isArray(snapshot?.collections)).toBe(true);
|
|
|
|
const migratedCollectionEntry = snapshot?.collections?.find(
|
|
(collection: any) => collection?.pathname === migrationCollectionPath
|
|
);
|
|
expect(migratedCollectionEntry).toBeTruthy();
|
|
console.log(JSON.stringify(migratedCollectionEntry));
|
|
|
|
expect(migratedCollectionEntry?.selectedEnvironment).toBe('local');
|
|
});
|
|
});
|
|
|
|
test('keeps selected environments for non-active collections across snapshot saves', async ({ launchElectronApp, createTmpDir }) => {
|
|
const userDataPath = await createTmpDir('snap-env-persistence');
|
|
const firstCollectionPath = await createTmpDir('snap-col-a');
|
|
const secondCollectionPath = await createTmpDir('snap-col-b');
|
|
const firstCollectionRoot = path.join(firstCollectionPath, 'Collection A');
|
|
const secondCollectionRoot = path.join(secondCollectionPath, 'Collection B');
|
|
|
|
const app = await launchElectronApp({ userDataPath });
|
|
const page = await waitForReadyPage(app);
|
|
|
|
await test.step('Create two collections with distinct selected environments', async () => {
|
|
await createCollection(page, 'Collection A', firstCollectionPath);
|
|
await openCollection(page, 'Collection A');
|
|
await createEnvironment(page, 'local-a', 'collection');
|
|
await selectEnvironment(page, 'local-a', 'collection');
|
|
|
|
await createCollection(page, 'Collection B', secondCollectionPath);
|
|
await openCollection(page, 'Collection B');
|
|
await createEnvironment(page, 'local-b', 'collection');
|
|
await selectEnvironment(page, 'local-b', 'collection');
|
|
});
|
|
|
|
await test.step('Switch back to first collection and verify environment did not drift', async () => {
|
|
await openCollection(page, 'Collection A');
|
|
await expect(page.locator('.current-environment')).toContainText('local-a');
|
|
await openCollection(page, 'Collection B');
|
|
await expect(page.locator('.current-environment')).toContainText('local-b');
|
|
});
|
|
|
|
await test.step('Close app and assert snapshot stores both environments', async () => {
|
|
await page.waitForTimeout(2000);
|
|
await closeElectronApp(app);
|
|
|
|
const snapshot = readSnapshot(userDataPath);
|
|
expect(snapshot).not.toBeNull();
|
|
|
|
const collections = Array.isArray(snapshot?.collections) ? snapshot.collections : [];
|
|
const firstEntry = collections.find((collection: any) => collection?.pathname === firstCollectionRoot);
|
|
const secondEntry = collections.find((collection: any) => collection?.pathname === secondCollectionRoot);
|
|
|
|
expect(firstEntry?.selectedEnvironment).toBe('local-a');
|
|
expect(secondEntry?.selectedEnvironment).toBe('local-b');
|
|
expect(firstEntry?.environmentPath).toContain(path.join('environments', 'local-a'));
|
|
expect(secondEntry?.environmentPath).toContain(path.join('environments', 'local-b'));
|
|
});
|
|
|
|
await test.step('Restart app and verify both selections are still restored', async () => {
|
|
const app2 = await launchElectronApp({ userDataPath });
|
|
const page2 = await waitForReadyPage(app2);
|
|
|
|
await openCollection(page2, 'Collection A');
|
|
await expect(page2.locator('.current-environment')).toContainText('local-a');
|
|
|
|
await openCollection(page2, 'Collection B');
|
|
await expect(page2.locator('.current-environment')).toContainText('local-b');
|
|
|
|
await closeElectronApp(app2);
|
|
});
|
|
});
|
|
|
|
test('keeps selected environments for three collections across delayed switches and snapshot updates', async ({ launchElectronApp, createTmpDir }) => {
|
|
const userDataPath = await createTmpDir('snap-env-persistence-three');
|
|
const firstCollectionPath = await createTmpDir('snap-col-a-three');
|
|
const secondCollectionPath = await createTmpDir('snap-col-b-three');
|
|
const thirdCollectionPath = await createTmpDir('snap-col-c-three');
|
|
const firstCollectionRoot = path.join(firstCollectionPath, 'Collection A');
|
|
const secondCollectionRoot = path.join(secondCollectionPath, 'Collection B');
|
|
const thirdCollectionRoot = path.join(thirdCollectionPath, 'Collection C');
|
|
|
|
const app = await launchElectronApp({ userDataPath });
|
|
const page = await waitForReadyPage(app);
|
|
|
|
await test.step('Create three collections with distinct selected environments', async () => {
|
|
await createCollection(page, 'Collection A', firstCollectionPath);
|
|
await openCollection(page, 'Collection A');
|
|
await createEnvironment(page, 'local-a', 'collection');
|
|
await selectEnvironment(page, 'local-a', 'collection');
|
|
|
|
await createCollection(page, 'Collection B', secondCollectionPath);
|
|
await openCollection(page, 'Collection B');
|
|
await createEnvironment(page, 'local-b', 'collection');
|
|
await selectEnvironment(page, 'local-b', 'collection');
|
|
|
|
await createCollection(page, 'Collection C', thirdCollectionPath);
|
|
await openCollection(page, 'Collection C');
|
|
await createEnvironment(page, 'local-c', 'collection');
|
|
await selectEnvironment(page, 'local-c', 'collection');
|
|
});
|
|
|
|
await test.step('Switch to each collection with delays and verify selected environment stays correct', async () => {
|
|
await openCollection(page, 'Collection A');
|
|
await expect(page.locator('.current-environment')).toContainText('local-a');
|
|
|
|
await openCollection(page, 'Collection B');
|
|
await expect(page.locator('.current-environment')).toContainText('local-b');
|
|
|
|
await openCollection(page, 'Collection C');
|
|
await expect(page.locator('.current-environment')).toContainText('local-c');
|
|
});
|
|
|
|
await test.step('Close app and assert snapshot stores all three environments', async () => {
|
|
await closeElectronApp(app);
|
|
|
|
const snapshot = readSnapshot(userDataPath);
|
|
expect(snapshot).not.toBeNull();
|
|
|
|
const collections = Array.isArray(snapshot?.collections) ? snapshot.collections : [];
|
|
const firstEntry = collections.find((collection: any) => collection?.pathname === firstCollectionRoot);
|
|
const secondEntry = collections.find((collection: any) => collection?.pathname === secondCollectionRoot);
|
|
const thirdEntry = collections.find((collection: any) => collection?.pathname === thirdCollectionRoot);
|
|
|
|
expect(firstEntry?.selectedEnvironment).toBe('local-a');
|
|
expect(secondEntry?.selectedEnvironment).toBe('local-b');
|
|
expect(thirdEntry?.selectedEnvironment).toBe('local-c');
|
|
expect(firstEntry?.environmentPath).toContain(path.join('environments', 'local-a'));
|
|
expect(secondEntry?.environmentPath).toContain(path.join('environments', 'local-b'));
|
|
expect(thirdEntry?.environmentPath).toContain(path.join('environments', 'local-c'));
|
|
});
|
|
|
|
await test.step('Restart app, switch through collections with delays, and verify all selections are restored', async () => {
|
|
const app2 = await launchElectronApp({ userDataPath });
|
|
const page2 = await waitForReadyPage(app2);
|
|
|
|
await openCollection(page2, 'Collection A');
|
|
await expect(page2.locator('.current-environment')).toContainText('local-a');
|
|
await page2.waitForTimeout(2000);
|
|
|
|
await openCollection(page2, 'Collection B');
|
|
await expect(page2.locator('.current-environment')).toContainText('local-b');
|
|
await page2.waitForTimeout(2000);
|
|
|
|
await openCollection(page2, 'Collection C');
|
|
await expect(page2.locator('.current-environment')).toContainText('local-c');
|
|
await page2.waitForTimeout(2000);
|
|
|
|
await closeElectronApp(app2);
|
|
|
|
const updatedSnapshot = readSnapshot(userDataPath);
|
|
expect(updatedSnapshot).not.toBeNull();
|
|
|
|
const updatedCollections = Array.isArray(updatedSnapshot?.collections) ? updatedSnapshot.collections : [];
|
|
const firstUpdatedEntry = updatedCollections.find((collection: any) => collection?.pathname === firstCollectionRoot);
|
|
const secondUpdatedEntry = updatedCollections.find((collection: any) => collection?.pathname === secondCollectionRoot);
|
|
const thirdUpdatedEntry = updatedCollections.find((collection: any) => collection?.pathname === thirdCollectionRoot);
|
|
|
|
expect(firstUpdatedEntry?.selectedEnvironment).toBe('local-a');
|
|
expect(secondUpdatedEntry?.selectedEnvironment).toBe('local-b');
|
|
expect(thirdUpdatedEntry?.selectedEnvironment).toBe('local-c');
|
|
});
|
|
});
|
|
});
|