mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-26 22:25:40 +00:00
Merge remote-tracking branch 'origin/main' into oauth2_additional_params
This commit is contained in:
33
package-lock.json
generated
33
package-lock.json
generated
@@ -13937,6 +13937,21 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
@@ -15411,13 +15426,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -16010,14 +16027,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/graphql-request/node_modules/form-data": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.2.tgz",
|
||||
"integrity": "sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz",
|
||||
"integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
|
||||
@@ -111,5 +111,10 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
},
|
||||
"overrides": {
|
||||
"httpsnippet": {
|
||||
"form-data": "4.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { IconLoader2, IconFile, IconAlertTriangle } from '@tabler/icons';
|
||||
import { loadRequest, loadRequestViaWorker } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { loadLargeRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestNotLoaded = ({ collection, item }) => {
|
||||
const dispatch = useDispatch();
|
||||
const handleLoadRequestViaWorker = () => {
|
||||
!item?.loading && dispatch(loadRequestViaWorker({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||
}
|
||||
|
||||
const handleLoadRequest = () => {
|
||||
!item?.loading && dispatch(loadRequest({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||
const handleLoadLargeRequest = () => {
|
||||
!item?.loading && dispatch(loadLargeRequest({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||
}
|
||||
|
||||
return <StyledWrapper>
|
||||
@@ -44,23 +41,14 @@ const RequestNotLoaded = ({ collection, item }) => {
|
||||
<IconAlertTriangle size={16} className="text-yellow-500" />
|
||||
<span>The request wasn't loaded due to its large size. Please try again with the following options:</span>
|
||||
</div>
|
||||
<div className='flex flex-row mt-6 gap-2 items-center w-full'>
|
||||
<button
|
||||
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
|
||||
onClick={handleLoadRequestViaWorker}
|
||||
>
|
||||
Load in background
|
||||
</button>
|
||||
<p>(Runs in background)</p>
|
||||
</div>
|
||||
<div className='flex flex-row mt-6 items-center gap-2 w-full'>
|
||||
<button
|
||||
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
|
||||
onClick={handleLoadRequest}
|
||||
onClick={handleLoadLargeRequest}
|
||||
>
|
||||
Force load
|
||||
Load Request
|
||||
</button>
|
||||
<p>(May cause the app to freeze temporarily while it runs)</p>
|
||||
<p>(Uses a regex based parsing approach)</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -75,12 +75,15 @@ export default function RunnerResults({ collection }) {
|
||||
|
||||
useEffect(() => {
|
||||
const savedConfiguration = get(collection, 'runnerConfiguration', null);
|
||||
if (savedConfiguration && configureMode) {
|
||||
if (savedConfiguration.selectedRequestItems) {
|
||||
if (savedConfiguration) {
|
||||
if (savedConfiguration.selectedRequestItems && configureMode) {
|
||||
setSelectedRequestItems(savedConfiguration.selectedRequestItems);
|
||||
}
|
||||
if (savedConfiguration.delay !== undefined && delay === null) {
|
||||
setDelay(savedConfiguration.delay);
|
||||
}
|
||||
}
|
||||
}, [collection.runnerConfiguration, configureMode]);
|
||||
}, [collection.runnerConfiguration, configureMode, delay]);
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const runnerInfo = get(collection, 'runnerResult.info', {});
|
||||
@@ -136,9 +139,10 @@ export default function RunnerResults({ collection }) {
|
||||
|
||||
const runCollection = () => {
|
||||
if (configureMode && selectedRequestItems.length > 0) {
|
||||
dispatch(updateRunnerConfiguration(collection.uid, selectedRequestItems, selectedRequestItems));
|
||||
dispatch(updateRunnerConfiguration(collection.uid, selectedRequestItems, selectedRequestItems, delay));
|
||||
dispatch(runCollectionFolder(collection.uid, null, true, Number(delay), tagsEnabled && tags, selectedRequestItems));
|
||||
} else {
|
||||
dispatch(updateRunnerConfiguration(collection.uid, [], [], delay));
|
||||
dispatch(runCollectionFolder(collection.uid, null, true, Number(delay), tagsEnabled && tags));
|
||||
}
|
||||
};
|
||||
@@ -148,12 +152,13 @@ export default function RunnerResults({ collection }) {
|
||||
// Get the saved configuration to determine what to run
|
||||
const savedConfiguration = get(collection, 'runnerConfiguration', null);
|
||||
const savedSelectedItems = savedConfiguration?.selectedRequestItems || [];
|
||||
const savedDelay = savedConfiguration?.delay !== undefined ? savedConfiguration.delay : delay;
|
||||
dispatch(
|
||||
runCollectionFolder(
|
||||
collection.uid,
|
||||
runnerInfo.folderUid,
|
||||
true,
|
||||
Number(delay),
|
||||
Number(savedDelay),
|
||||
tagsEnabled && tags,
|
||||
savedSelectedItems
|
||||
)
|
||||
@@ -168,6 +173,7 @@ export default function RunnerResults({ collection }) {
|
||||
);
|
||||
setSelectedRequestItems([]);
|
||||
setConfigureMode(false);
|
||||
setDelay(null);
|
||||
};
|
||||
|
||||
const cancelExecution = () => {
|
||||
|
||||
@@ -5,10 +5,10 @@ const KeyMapping = {
|
||||
newRequest: { mac: 'command+b', windows: 'ctrl+b', name: 'New Request' },
|
||||
closeTab: { mac: 'command+w', windows: 'ctrl+w', name: 'Close Tab' },
|
||||
openPreferences: { mac: 'command+,', windows: 'ctrl+,', name: 'Open Preferences' },
|
||||
minimizeWindow: {
|
||||
mac: 'command+Shift+Q',
|
||||
windows: 'control+Shift+Q',
|
||||
name: 'Minimize Window'
|
||||
closeBruno: {
|
||||
mac: 'command+Q',
|
||||
windows: 'ctrl+shift+q',
|
||||
name: 'Close Bruno'
|
||||
},
|
||||
switchToPreviousTab: {
|
||||
mac: 'command+pageup',
|
||||
|
||||
@@ -1361,6 +1361,7 @@ export const clearOauth2Cache = (payload) => async (dispatch, getState) => {
|
||||
});
|
||||
};
|
||||
|
||||
// todo: could be removed
|
||||
export const loadRequestViaWorker = ({ collectionUid, pathname }) => (dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
@@ -1368,6 +1369,7 @@ export const loadRequestViaWorker = ({ collectionUid, pathname }) => (dispatch,
|
||||
});
|
||||
};
|
||||
|
||||
// todo: could be removed
|
||||
export const loadRequest = ({ collectionUid, pathname }) => (dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
@@ -1375,6 +1377,13 @@ export const loadRequest = ({ collectionUid, pathname }) => (dispatch, getState)
|
||||
});
|
||||
};
|
||||
|
||||
export const loadLargeRequest = ({ collectionUid, pathname }) => (dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-large-request', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const mountCollection = ({ collectionUid, collectionPathname, brunoConfig }) => (dispatch, getState) => {
|
||||
dispatch(updateCollectionMountStatus({ collectionUid, mountStatus: 'mounting' }));
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@@ -1395,10 +1404,11 @@ export const mountCollection = ({ collectionUid, collectionPathname, brunoConfig
|
||||
});
|
||||
};
|
||||
|
||||
export const updateRunnerConfiguration = (collectionUid, selectedRequestItems, requestItemsOrder) => (dispatch) => {
|
||||
export const updateRunnerConfiguration = (collectionUid, selectedRequestItems, requestItemsOrder, delay) => (dispatch) => {
|
||||
dispatch(_updateRunnerConfiguration({
|
||||
collectionUid,
|
||||
selectedRequestItems,
|
||||
requestItemsOrder
|
||||
requestItemsOrder,
|
||||
delay
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -2291,12 +2291,13 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
},
|
||||
updateRunnerConfiguration: (state, action) => {
|
||||
const { collectionUid, selectedRequestItems, requestItemsOrder } = action.payload;
|
||||
const { collectionUid, selectedRequestItems, requestItemsOrder, delay } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
if (collection) {
|
||||
collection.runnerConfiguration = {
|
||||
selectedRequestItems: selectedRequestItems || [],
|
||||
requestItemsOrder: requestItemsOrder || []
|
||||
requestItemsOrder: requestItemsOrder || [],
|
||||
delay: delay
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -57,7 +57,7 @@ const transformOpenapiRequestItem = (request) => {
|
||||
url: ensureUrl(request.global.server + path),
|
||||
method: request.method.toUpperCase(),
|
||||
auth: {
|
||||
mode: 'none',
|
||||
mode: 'inherit',
|
||||
basic: null,
|
||||
bearer: null,
|
||||
digest: null
|
||||
@@ -419,6 +419,19 @@ export const parseOpenApiCollection = (data) => {
|
||||
uid: uuid(),
|
||||
name: group.name,
|
||||
type: 'folder',
|
||||
root: {
|
||||
request: {
|
||||
auth: {
|
||||
mode: 'inherit',
|
||||
basic: null,
|
||||
bearer: null,
|
||||
digest: null
|
||||
}
|
||||
},
|
||||
meta: {
|
||||
name: group.name
|
||||
}
|
||||
},
|
||||
items: group.requests.map(transformOpenapiRequestItem)
|
||||
};
|
||||
});
|
||||
|
||||
@@ -233,7 +233,7 @@ const circularRefsOutput = {
|
||||
"url": "{{baseUrl}}/",
|
||||
"method": "POST",
|
||||
"auth": {
|
||||
"mode": "none",
|
||||
"mode": "inherit",
|
||||
},
|
||||
"headers": [],
|
||||
"params": [],
|
||||
|
||||
@@ -8,6 +8,13 @@ describe('openapi-collection', () => {
|
||||
expect(brunoCollection).toMatchObject(expectedOutput);
|
||||
});
|
||||
|
||||
it('should set auth mode to inherit when no security is defined in the collection', () => {
|
||||
const brunoCollection = openApiToBruno(openApiCollectionString);
|
||||
|
||||
// The openApiCollectionString has no security defined, so auth mode should be 'inherit'
|
||||
expect(brunoCollection.items[0].items[0].request.auth.mode).toBe('inherit');
|
||||
});
|
||||
|
||||
it('trims whitespace from info.title and uses the trimmed value as the collection name', () => {
|
||||
const openApiWithTitle = `
|
||||
openapi: '3.0.0'
|
||||
@@ -109,6 +116,124 @@ servers:
|
||||
expect(result.name).toBe('Untitled Collection');
|
||||
});
|
||||
|
||||
describe('authentication inheritance', () => {
|
||||
it('should set auth mode to inherit when no security is defined', () => {
|
||||
const openApiWithoutSecurity = `
|
||||
openapi: '3.0.0'
|
||||
info:
|
||||
version: '1.0.0'
|
||||
title: 'API without security'
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
summary: 'Test endpoint'
|
||||
operationId: 'testEndpoint'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
servers:
|
||||
- url: 'https://example.com'
|
||||
`;
|
||||
const result = openApiToBruno(openApiWithoutSecurity);
|
||||
expect(result.items[0].request.auth.mode).toBe('inherit');
|
||||
});
|
||||
|
||||
it('should set auth mode to inherit when no global security schemes exist', () => {
|
||||
const openApiWithEmptySecurity = `
|
||||
openapi: '3.0.0'
|
||||
info:
|
||||
version: '1.0.0'
|
||||
title: 'API with empty security'
|
||||
security: []
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
summary: 'Test endpoint'
|
||||
operationId: 'testEndpoint'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
servers:
|
||||
- url: 'https://example.com'
|
||||
`;
|
||||
const result = openApiToBruno(openApiWithEmptySecurity);
|
||||
expect(result.items[0].request.auth.mode).toBe('inherit');
|
||||
});
|
||||
|
||||
it('should set auth mode to inherit when components.securitySchemes is empty', () => {
|
||||
const openApiWithEmptyComponents = `
|
||||
openapi: '3.0.0'
|
||||
info:
|
||||
version: '1.0.0'
|
||||
title: 'API with empty components'
|
||||
components:
|
||||
securitySchemes: {}
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
summary: 'Test endpoint'
|
||||
operationId: 'testEndpoint'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
servers:
|
||||
- url: 'https://example.com'
|
||||
`;
|
||||
const result = openApiToBruno(openApiWithEmptyComponents);
|
||||
expect(result.items[0].request.auth.mode).toBe('inherit');
|
||||
});
|
||||
|
||||
it('should set auth mode to inherit when operation has empty security array', () => {
|
||||
const openApiWithEmptyOperationSecurity = `
|
||||
openapi: '3.0.0'
|
||||
info:
|
||||
version: '1.0.0'
|
||||
title: 'API with empty operation security'
|
||||
components:
|
||||
securitySchemes:
|
||||
basicAuth:
|
||||
type: http
|
||||
scheme: basic
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
summary: 'Test endpoint'
|
||||
operationId: 'testEndpoint'
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
servers:
|
||||
- url: 'https://example.com'
|
||||
`;
|
||||
const result = openApiToBruno(openApiWithEmptyOperationSecurity);
|
||||
expect(result.items[0].request.auth.mode).toBe('inherit');
|
||||
});
|
||||
|
||||
it('should set auth mode to inherit for folder root when no security is defined', () => {
|
||||
const openApiWithTags = `
|
||||
openapi: '3.0.0'
|
||||
info:
|
||||
version: '1.0.0'
|
||||
title: 'API with tags'
|
||||
paths:
|
||||
/test:
|
||||
get:
|
||||
tags:
|
||||
- TestGroup
|
||||
summary: 'Test endpoint'
|
||||
operationId: 'testEndpoint'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
servers:
|
||||
- url: 'https://example.com'
|
||||
`;
|
||||
const result = openApiToBruno(openApiWithTags);
|
||||
expect(result.items[0].type).toBe('folder');
|
||||
expect(result.items[0].root.request.auth.mode).toBe('inherit');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const openApiCollectionString = `
|
||||
@@ -174,7 +299,7 @@ const expectedOutput = {
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null,
|
||||
"mode": "none",
|
||||
"mode": "inherit",
|
||||
},
|
||||
"body": {
|
||||
"formUrlEncoded": [],
|
||||
|
||||
@@ -20,6 +20,7 @@ const { setBrunoConfig } = require('../store/bruno-config');
|
||||
const EnvironmentSecretsStore = require('../store/env-secrets');
|
||||
const UiStateSnapshot = require('../store/ui-state-snapshot');
|
||||
const { parseBruFileMeta, hydrateRequestWithUuid } = require('../utils/collection');
|
||||
const { parseLargeRequestWithRedaction } = require('../utils/parse');
|
||||
|
||||
const MAX_FILE_SIZE = 2.5 * 1024 * 1024;
|
||||
|
||||
@@ -344,11 +345,17 @@ const addDirectory = async (win, pathname, collectionUid, collectionPath) => {
|
||||
let seq;
|
||||
const folderBruFilePath = path.join(pathname, `folder.bru`);
|
||||
|
||||
if (fs.existsSync(folderBruFilePath)) {
|
||||
let folderBruFileContent = fs.readFileSync(folderBruFilePath, 'utf8');
|
||||
let folderBruData = await parseFolder(folderBruFileContent);
|
||||
name = folderBruData?.meta?.name || name;
|
||||
seq = folderBruData?.meta?.seq;
|
||||
try {
|
||||
if (fs.existsSync(folderBruFilePath)) {
|
||||
let folderBruFileContent = fs.readFileSync(folderBruFilePath, 'utf8');
|
||||
let folderBruData = await parseFolder(folderBruFileContent);
|
||||
name = folderBruData?.meta?.name || name;
|
||||
seq = folderBruData?.meta?.seq;
|
||||
}
|
||||
}
|
||||
catch(error) {
|
||||
console.error('Error occured while parsing folder.bru file!');
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const directory = {
|
||||
@@ -462,10 +469,20 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||
};
|
||||
|
||||
const bru = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = await parseRequest(bru);
|
||||
const fileStats = fs.statSync(pathname);
|
||||
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
if (fileStats.size >= MAX_FILE_SIZE) {
|
||||
const parsedData = await parseLargeRequestWithRedaction(bru);
|
||||
file.data = parsedData;
|
||||
file.size = sizeInMB(fileStats?.size);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
} else {
|
||||
file.data = await parseRequest(bru);
|
||||
file.size = sizeInMB(fileStats?.size);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ if (isDev) {
|
||||
}
|
||||
|
||||
const { format } = require('url');
|
||||
const { BrowserWindow, app, session, Menu, ipcMain } = require('electron');
|
||||
const { BrowserWindow, app, session, Menu, globalShortcut, ipcMain } = require('electron');
|
||||
const { setContentSecurityPolicy } = require('electron-util');
|
||||
|
||||
if (isDev && process.env.ELECTRON_USER_DATA_PATH) {
|
||||
@@ -165,6 +165,19 @@ app.on('ready', async () => {
|
||||
}
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
// Quick fix for Electron issue #29996: https://github.com/electron/electron/issues/29996
|
||||
globalShortcut.register('Ctrl+=', () => {
|
||||
mainWindow.webContents.setZoomLevel(mainWindow.webContents.getZoomLevel() + 1);
|
||||
});
|
||||
|
||||
globalShortcut.register('CommandOrControl+M', () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
|
||||
globalShortcut.register('CommandOrControl+H', () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
let ogSend = mainWindow.webContents.send;
|
||||
|
||||
@@ -19,6 +19,7 @@ const {
|
||||
} = require('@usebruno/filestore');
|
||||
const brunoConverters = require('@usebruno/converters');
|
||||
const { postmanToBruno } = brunoConverters;
|
||||
const { parseLargeRequestWithRedaction } = require('../utils/parse');
|
||||
|
||||
const {
|
||||
writeFile,
|
||||
@@ -1057,6 +1058,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
});
|
||||
|
||||
// todo: could be removed
|
||||
ipcMain.handle('renderer:load-request-via-worker', async (event, { collectionUid, pathname }) => {
|
||||
let fileStats;
|
||||
try {
|
||||
@@ -1094,7 +1096,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
};
|
||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||
const metaJson = parseRequest(parseBruFileMeta(bruContent));
|
||||
const metaJson = parseBruFileMeta(bruContent);
|
||||
file.data = metaJson;
|
||||
file.partial = true;
|
||||
file.loading = false;
|
||||
@@ -1132,6 +1134,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
});
|
||||
|
||||
// todo: could be removed
|
||||
ipcMain.handle('renderer:load-request', async (event, { collectionUid, pathname }) => {
|
||||
let fileStats;
|
||||
try {
|
||||
@@ -1145,7 +1148,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
};
|
||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||
const metaJson = parseRequest(parseBruFileMeta(bruContent));
|
||||
const metaJson = parseBruFileMeta(bruContent);
|
||||
file.data = metaJson;
|
||||
file.loading = true;
|
||||
file.partial = true;
|
||||
@@ -1169,7 +1172,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
};
|
||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||
const metaJson = parseRequest(parseBruFileMeta(bruContent));
|
||||
const metaJson = parseBruFileMeta(bruContent);
|
||||
file.data = metaJson;
|
||||
file.partial = true;
|
||||
file.loading = false;
|
||||
@@ -1181,6 +1184,56 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:load-large-request', async (event, { collectionUid, pathname }) => {
|
||||
let fileStats;
|
||||
if (!hasBruExtension(pathname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
pathname,
|
||||
name: path.basename(pathname)
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
fileStats = fs.statSync(pathname);
|
||||
|
||||
const bruContent = fs.readFileSync(pathname, 'utf8');
|
||||
const metaJson = parseBruFileMeta(bruContent);
|
||||
|
||||
file.data = metaJson;
|
||||
file.partial = false;
|
||||
file.loading = true;
|
||||
file.size = sizeInMB(fileStats?.size);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
await mainWindow.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||
|
||||
try {
|
||||
const parsedData = await parseLargeRequestWithRedaction(bruContent);
|
||||
|
||||
file.data = parsedData;
|
||||
file.loading = false;
|
||||
file.partial = false;
|
||||
file.size = sizeInMB(fileStats?.size);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
await mainWindow.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||
} catch (parseError) {
|
||||
file.data = metaJson;
|
||||
file.partial = true;
|
||||
file.loading = false;
|
||||
file.size = sizeInMB(fileStats?.size);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
await mainWindow.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||
throw parseError;
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:mount-collection', async (event, { collectionUid, collectionPathname, brunoConfig }) => {
|
||||
const {
|
||||
size,
|
||||
|
||||
@@ -252,23 +252,24 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
if (credentialsPlacement === "basic_auth_header") {
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64')}`;
|
||||
}
|
||||
const data = {
|
||||
grant_type: 'authorization_code',
|
||||
code: authorizationCode,
|
||||
redirect_uri: callbackUrl,
|
||||
client_id: clientId,
|
||||
};
|
||||
if (clientSecret && credentialsPlacement !== "basic_auth_header") {
|
||||
if (credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_id = clientId;
|
||||
}
|
||||
if (clientSecret && clientSecret.trim() !== '' && credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_secret = clientSecret;
|
||||
}
|
||||
if (pkce) {
|
||||
data['code_verifier'] = codeVerifier;
|
||||
}
|
||||
if (scope && scope.trim() !== '') {
|
||||
data.scope = scope;
|
||||
}
|
||||
|
||||
axiosRequestConfig.data = qs.stringify(data);
|
||||
axiosRequestConfig.url = url;
|
||||
axiosRequestConfig.responseType = 'arraybuffer';
|
||||
// Apply additional parameters to token request
|
||||
@@ -392,15 +393,6 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
};
|
||||
}
|
||||
|
||||
if (!clientSecret) {
|
||||
return {
|
||||
error: 'Client Secret is required for OAuth2 client credentials flow',
|
||||
credentials: null,
|
||||
url,
|
||||
credentialsId
|
||||
};
|
||||
}
|
||||
|
||||
if (!forceFetch) {
|
||||
const storedCredentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
|
||||
|
||||
@@ -459,14 +451,16 @@ const getOAuth2TokenUsingClientCredentials = async ({ request, collectionUid, fo
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
if (credentialsPlacement === "basic_auth_header") {
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
|
||||
if (credentialsPlacement === "basic_auth_header" && clientSecret && clientSecret.trim() !== '') {
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64')}`;
|
||||
}
|
||||
const data = {
|
||||
grant_type: 'client_credentials',
|
||||
client_id: clientId,
|
||||
};
|
||||
if (clientSecret && credentialsPlacement !== "basic_auth_header") {
|
||||
if (credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_id = clientId;
|
||||
}
|
||||
if (clientSecret && clientSecret.trim() !== '' && credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_secret = clientSecret;
|
||||
}
|
||||
if (scope && scope.trim() !== '') {
|
||||
@@ -604,16 +598,18 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
if (credentialsPlacement === "basic_auth_header") {
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
|
||||
if (credentialsPlacement === "basic_auth_header" && clientSecret && clientSecret.trim() !== '') {
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64')}`;
|
||||
}
|
||||
const data = {
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password,
|
||||
client_id: clientId,
|
||||
};
|
||||
if (clientSecret && credentialsPlacement !== "basic_auth_header") {
|
||||
if (credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_id = clientId;
|
||||
}
|
||||
if (clientSecret && clientSecret.trim() !== '' && credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_secret = clientSecret;
|
||||
}
|
||||
if (scope && scope.trim() !== '') {
|
||||
@@ -638,7 +634,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
|
||||
|
||||
const refreshOauth2Token = async ({ requestCopy, collectionUid, certsAndProxyConfig }) => {
|
||||
const oAuth = get(requestCopy, 'oauth2', {});
|
||||
const { clientId, clientSecret, credentialsId, additionalParameters } = oAuth;
|
||||
const { clientId, clientSecret, credentialsId, credentialsPlacement, additionalParameters } = oAuth;
|
||||
const url = oAuth.refreshTokenUrl ? oAuth.refreshTokenUrl : oAuth.accessTokenUrl;
|
||||
|
||||
const credentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
|
||||
@@ -649,10 +645,12 @@ const refreshOauth2Token = async ({ requestCopy, collectionUid, certsAndProxyCon
|
||||
} else {
|
||||
const data = {
|
||||
grant_type: 'refresh_token',
|
||||
client_id: clientId,
|
||||
refresh_token: credentials.refresh_token,
|
||||
};
|
||||
if (clientSecret) {
|
||||
if (credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_id = clientId;
|
||||
}
|
||||
if (clientSecret && clientSecret.trim() !== '' && credentialsPlacement !== "basic_auth_header") {
|
||||
data.client_secret = clientSecret;
|
||||
}
|
||||
let axiosRequestConfig = {};
|
||||
@@ -661,6 +659,9 @@ const refreshOauth2Token = async ({ requestCopy, collectionUid, certsAndProxyCon
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
if (credentialsPlacement === "basic_auth_header") {
|
||||
axiosRequestConfig.headers['Authorization'] = `Basic ${Buffer.from(`${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`).toString('base64')}`;
|
||||
}
|
||||
axiosRequestConfig.url = url;
|
||||
axiosRequestConfig.responseType = 'arraybuffer';
|
||||
if (additionalParameters?.refresh?.length) {
|
||||
|
||||
42
packages/bruno-electron/src/utils/parse.js
Normal file
42
packages/bruno-electron/src/utils/parse.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { parseRequestAndRedactBody, parseRequestViaWorker } = require('@usebruno/filestore');
|
||||
|
||||
/**
|
||||
* Parses a large BRU request string by redacting body blocks, parsing the remainder,
|
||||
* and then reinserting extracted body content into the parsed structure.
|
||||
* @param {string} bruContent
|
||||
* @returns {Promise<any>} parsed request JSON
|
||||
*/
|
||||
async function parseLargeRequestWithRedaction(bruContent) {
|
||||
const { bruFileStringWithRedactedBody, extractedBodyContent } = parseRequestAndRedactBody(bruContent);
|
||||
const parsedData = await parseRequestViaWorker(bruFileStringWithRedactedBody);
|
||||
|
||||
if (!parsedData.request) {
|
||||
parsedData.request = {};
|
||||
}
|
||||
if (!parsedData.request.body) {
|
||||
parsedData.request.body = {};
|
||||
}
|
||||
|
||||
if (extractedBodyContent.json) {
|
||||
parsedData.request.body.json = extractedBodyContent.json;
|
||||
}
|
||||
if (extractedBodyContent.text) {
|
||||
parsedData.request.body.text = extractedBodyContent.text;
|
||||
}
|
||||
if (extractedBodyContent.xml) {
|
||||
parsedData.request.body.xml = extractedBodyContent.xml;
|
||||
}
|
||||
if (extractedBodyContent.sparql) {
|
||||
parsedData.request.body.sparql = extractedBodyContent.sparql;
|
||||
}
|
||||
if (extractedBodyContent.graphql) {
|
||||
if (!parsedData.request.body.graphql) {
|
||||
parsedData.request.body.graphql = {};
|
||||
}
|
||||
parsedData.request.body.graphql.query = extractedBodyContent.graphql;
|
||||
}
|
||||
|
||||
return parsedData;
|
||||
}
|
||||
|
||||
module.exports = { parseLargeRequestWithRedaction };
|
||||
@@ -58,8 +58,8 @@ export const bruRequestToJson = (data: string | any, parsed: boolean = false): a
|
||||
}
|
||||
|
||||
return transformedJson;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
meta {
|
||||
name: echo request
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://echo.usebruno.com
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
body:json {
|
||||
{
|
||||
"hello": "world"
|
||||
}
|
||||
}
|
||||
|
||||
body:text {
|
||||
This is a text body
|
||||
}
|
||||
body:xml {
|
||||
<xml>
|
||||
<name>John</name>
|
||||
<age>30</age>
|
||||
</xml>
|
||||
}
|
||||
|
||||
body:sparql {
|
||||
SELECT * WHERE {
|
||||
?subject ?predicate ?object .
|
||||
}
|
||||
LIMIT 10
|
||||
}
|
||||
body:graphql {
|
||||
{
|
||||
launchesPast {
|
||||
launch_site {
|
||||
site_name
|
||||
}
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
apikey: secret
|
||||
numbers: +91998877665
|
||||
~message: hello
|
||||
}
|
||||
|
||||
body:multipart-form {
|
||||
apikey: secret
|
||||
numbers: +91998877665
|
||||
~message: hello
|
||||
}
|
||||
body:file {
|
||||
file: @file(path/to/file.json) @contentType(application/json)
|
||||
file: @file(path/to/file.json) @contentType(application/json)
|
||||
~file: @file(path/to/file2.json) @contentType(application/json)
|
||||
}
|
||||
|
||||
body:graphql:vars {
|
||||
{
|
||||
"limit": 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
meta {
|
||||
name: echo request
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://echo.usebruno.com
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
apikey: secret
|
||||
numbers: +91998877665
|
||||
~message: hello
|
||||
}
|
||||
|
||||
body:multipart-form {
|
||||
apikey: secret
|
||||
numbers: +91998877665
|
||||
~message: hello
|
||||
}
|
||||
|
||||
body:file {
|
||||
file: @file(path/to/file.json) @contentType(application/json)
|
||||
file: @file(path/to/file.json) @contentType(application/json)
|
||||
~file: @file(path/to/file2.json) @contentType(application/json)
|
||||
}
|
||||
|
||||
body:graphql:vars {
|
||||
{
|
||||
"limit": 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { bruRequestParseAndRedactBodyData } = require("../utils/request-parse-and-redact-body-data");
|
||||
|
||||
describe("parse and redact body data", () => {
|
||||
it("should redact body blocks from the bru file string", () => {
|
||||
const fixturesPath = `/fixtures/request-parse-and-redact-body-data`;
|
||||
const inputBruString = fs.readFileSync(path.join(__dirname, fixturesPath, './input.bru'), 'utf8');
|
||||
const expectedOutputBruString = fs.readFileSync(path.join(__dirname, fixturesPath, './output.bru'), 'utf8');
|
||||
|
||||
const res = bruRequestParseAndRedactBodyData(inputBruString);
|
||||
expect(res.bruFileStringWithRedactedBody).toBe(expectedOutputBruString);
|
||||
expect(res.extractedBodyContent).toEqual({
|
||||
graphql: `
|
||||
{
|
||||
launchesPast {
|
||||
launch_site {
|
||||
site_name
|
||||
}
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
`.trim(),
|
||||
json: `
|
||||
{
|
||||
"hello": "world"
|
||||
}
|
||||
`.trim(),
|
||||
sparql: `
|
||||
SELECT * WHERE {
|
||||
?subject ?predicate ?object .
|
||||
}
|
||||
LIMIT 10
|
||||
`.trim(),
|
||||
text: `This is a text body`,
|
||||
xml: `
|
||||
<xml>
|
||||
<name>John</name>
|
||||
<age>30</age>
|
||||
</xml>
|
||||
`.trim()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Parses a .bru file and extracts body content while redacting it from the main content
|
||||
* @param {string} bruFileContent - The raw content of the .bru file
|
||||
* @returns {Object} Object containing redacted file content and extracted body data
|
||||
*/
|
||||
export const bruRequestParseAndRedactBodyData = (bruFileContent: string) => {
|
||||
try {
|
||||
// Define the patterns that indicate the start of different body types
|
||||
const bodyTypePatterns = [
|
||||
"body:json {",
|
||||
"body:text {",
|
||||
"body:xml {",
|
||||
"body:sparql {",
|
||||
"body:graphql {"
|
||||
];
|
||||
|
||||
// Normalize line endings to LF
|
||||
bruFileContent = (bruFileContent || '').replace(/\r\n/g, '\n');
|
||||
|
||||
const EOL = `\n`;
|
||||
|
||||
/**
|
||||
* Removes the leading 2-space indentation from each line of a string
|
||||
* @param {string} indentedString - The string with leading spaces to remove
|
||||
* @returns {string} The string with indentation removed
|
||||
*/
|
||||
const removeLeadingIndentation = (indentedString: string) => {
|
||||
if (!indentedString || !indentedString.length) {
|
||||
return indentedString || '';
|
||||
}
|
||||
|
||||
return indentedString
|
||||
.split(EOL)
|
||||
.map((line) => line.replace(/^ /, ''))
|
||||
.join(EOL);
|
||||
};
|
||||
|
||||
// Split the file content into blocks
|
||||
let fileContentBlocks = bruFileContent.split(`${EOL}}${EOL}`);
|
||||
fileContentBlocks = fileContentBlocks.filter(Boolean).map(_ => _.trim());
|
||||
|
||||
// Extract body blocks and their content
|
||||
const extractedBodyBlocks = fileContentBlocks
|
||||
.filter(block => bodyTypePatterns.some(pattern => block.startsWith(pattern)))
|
||||
.reduce((bodyContentMap: Record<string, string>, bodyBlock) => {
|
||||
// Extract the body type (json, text, xml, etc.) from the first line
|
||||
const firstLine = bodyBlock.split(EOL)[0];
|
||||
const bodyType = firstLine.split(`body:`)[1].split(/\s/)[0];
|
||||
|
||||
// Extract the body content (everything between the opening and closing braces)
|
||||
const bodyContentLines = bodyBlock.split(EOL).slice(1);
|
||||
const rawBodyContent = bodyContentLines.join(EOL);
|
||||
|
||||
// Remove indentation from the body content
|
||||
const cleanBodyContent = removeLeadingIndentation(rawBodyContent);
|
||||
|
||||
bodyContentMap[bodyType] = cleanBodyContent;
|
||||
return bodyContentMap;
|
||||
}, {});
|
||||
|
||||
// Filter out body blocks to get the remaining file content
|
||||
const fileContentWithoutBodyBlocks = fileContentBlocks.filter(block =>
|
||||
!bodyTypePatterns.some(pattern => block.startsWith(pattern))
|
||||
);
|
||||
|
||||
return {
|
||||
bruFileStringWithRedactedBody: fileContentWithoutBodyBlocks.join(`${EOL}}${EOL}${EOL}`).concat(`${EOL}}${EOL}`),
|
||||
extractedBodyContent: extractedBodyBlocks
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error parsing and redacting body data:', error);
|
||||
return {
|
||||
bruFileStringWithRedactedBody: bruFileContent,
|
||||
extractedBodyContent: {}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ParsedCollection,
|
||||
ParsedEnvironment
|
||||
} from './types';
|
||||
import { bruRequestParseAndRedactBodyData } from './formats/bru/utils/request-parse-and-redact-body-data';
|
||||
|
||||
export const parseRequest = (content: string, options: ParseOptions = { format: 'bru' }): any => {
|
||||
if (options.format === 'bru') {
|
||||
@@ -23,6 +24,13 @@ export const parseRequest = (content: string, options: ParseOptions = { format:
|
||||
throw new Error(`Unsupported format: ${options.format}`);
|
||||
};
|
||||
|
||||
export const parseRequestAndRedactBody = (content: string, options: ParseOptions = { format: 'bru' }): any => {
|
||||
if (options.format === 'bru') {
|
||||
return bruRequestParseAndRedactBodyData(content);
|
||||
}
|
||||
throw new Error(`Unsupported format: ${options.format}`);
|
||||
};
|
||||
|
||||
export const stringifyRequest = (requestObj: ParsedRequest, options: StringifyOptions = { format: 'bru' }): string => {
|
||||
if (options.format === 'bru') {
|
||||
return jsonRequestToBru(requestObj);
|
||||
|
||||
@@ -10,10 +10,16 @@ get {
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const jar = bru.cookies.jar()
|
||||
|
||||
jar.setCookie("https://testbench-sanity.usebruno.com", "name", "value")
|
||||
}
|
||||
|
||||
tests {
|
||||
const jar = bru.cookies.jar()
|
||||
|
||||
jar.getCookie("https://testbench-sanity.usebruno.com", "__cf_bm", function(error, data) {
|
||||
jar.getCookie("https://testbench-sanity.usebruno.com", "name", function(error, data) {
|
||||
if(error) {
|
||||
console.error("Cookie retrieval error:", error)
|
||||
throw new Error(`Failed to get cookie: ${error.message || error}`)
|
||||
@@ -22,7 +28,7 @@ tests {
|
||||
test("should successfully retrieve cookie data", function() {
|
||||
expect(data).to.have.property('key');
|
||||
expect(data).to.have.property('value');
|
||||
expect(data.key).to.equal("__cf_bm");
|
||||
expect(data.key).to.equal("name");
|
||||
expect(data.value).to.be.a('string');
|
||||
expect(data.value).to.not.be.empty;
|
||||
expect(data.domain).to.include('usebruno.com');
|
||||
|
||||
Reference in New Issue
Block a user