mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-07 08:34:15 +01:00
Feat/safe mode quickjs (#2848)
Safe Mode Sandbox using QuickJS Co-authored-by: Anoop M D <anoop.md1421@gmail.com> Co-authored-by: lohit <lohit.jiddimani@gmail.com>
This commit is contained in:
parent
3ad4eda861
commit
753a576c3c
13
.github/workflows/tests.yml
vendored
13
.github/workflows/tests.yml
vendored
@ -25,8 +25,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm run build --workspace=packages/bruno-common
|
npm run build --workspace=packages/bruno-common
|
||||||
npm run build --workspace=packages/bruno-query
|
npm run build --workspace=packages/bruno-query
|
||||||
|
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||||
|
|
||||||
|
# tests
|
||||||
|
- name: Test Package bruno-js
|
||||||
|
run: npm run test --workspace=packages/bruno-js
|
||||||
|
- name: Test Package bruno-cli
|
||||||
|
run: npm run test --workspace=packages/bruno-cli
|
||||||
|
|
||||||
# test
|
|
||||||
- name: Test Package bruno-query
|
- name: Test Package bruno-query
|
||||||
run: npm run test --workspace=packages/bruno-query
|
run: npm run test --workspace=packages/bruno-query
|
||||||
- name: Test Package bruno-lang
|
- name: Test Package bruno-lang
|
||||||
@ -35,12 +41,8 @@ jobs:
|
|||||||
run: npm run test --workspace=packages/bruno-schema
|
run: npm run test --workspace=packages/bruno-schema
|
||||||
- name: Test Package bruno-app
|
- name: Test Package bruno-app
|
||||||
run: npm run test --workspace=packages/bruno-app
|
run: npm run test --workspace=packages/bruno-app
|
||||||
- name: Test Package bruno-js
|
|
||||||
run: npm run test --workspace=packages/bruno-js
|
|
||||||
- name: Test Package bruno-common
|
- name: Test Package bruno-common
|
||||||
run: npm run test --workspace=packages/bruno-common
|
run: npm run test --workspace=packages/bruno-common
|
||||||
- name: Test Package bruno-cli
|
|
||||||
run: npm run test --workspace=packages/bruno-cli
|
|
||||||
- name: Test Package bruno-electron
|
- name: Test Package bruno-electron
|
||||||
run: npm run test --workspace=packages/bruno-electron
|
run: npm run test --workspace=packages/bruno-electron
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm run build --workspace=packages/bruno-query
|
npm run build --workspace=packages/bruno-query
|
||||||
npm run build --workspace=packages/bruno-common
|
npm run build --workspace=packages/bruno-common
|
||||||
|
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
|
@ -57,6 +57,9 @@ npm run build:graphql-docs
|
|||||||
npm run build:bruno-query
|
npm run build:bruno-query
|
||||||
npm run build:bruno-common
|
npm run build:bruno-common
|
||||||
|
|
||||||
|
# bundle js sandbox libraries
|
||||||
|
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||||
|
|
||||||
# run next app (terminal 1)
|
# run next app (terminal 1)
|
||||||
npm run dev:web
|
npm run dev:web
|
||||||
|
|
||||||
|
27086
package-lock.json
generated
27086
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,6 @@
|
|||||||
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"rollup": "3.2.5"
|
"rollup": "3.2.5"
|
||||||
},
|
},
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const ModalHeader = ({ title, handleCancel, customHeader }) => (
|
const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
|
||||||
<div className="bruno-modal-header">
|
<div className="bruno-modal-header">
|
||||||
{customHeader ? customHeader : <>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>}
|
{customHeader ? customHeader : <>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>}
|
||||||
{handleCancel ? (
|
{handleCancel && !hideClose ? (
|
||||||
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
|
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
|
||||||
×
|
×
|
||||||
</div>
|
</div>
|
||||||
@ -63,6 +63,7 @@ const Modal = ({
|
|||||||
confirmDisabled,
|
confirmDisabled,
|
||||||
hideCancel,
|
hideCancel,
|
||||||
hideFooter,
|
hideFooter,
|
||||||
|
hideClose,
|
||||||
disableCloseOnOutsideClick,
|
disableCloseOnOutsideClick,
|
||||||
disableEscapeKey,
|
disableEscapeKey,
|
||||||
onClick,
|
onClick,
|
||||||
@ -100,7 +101,12 @@ const Modal = ({
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
||||||
<div className={`bruno-modal-card modal-${size}`}>
|
<div className={`bruno-modal-card modal-${size}`}>
|
||||||
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} customHeader={customHeader} />
|
<ModalHeader
|
||||||
|
title={title}
|
||||||
|
hideClose={hideClose}
|
||||||
|
handleCancel={() => closeModal({ type: 'icon' })}
|
||||||
|
customHeader={customHeader}
|
||||||
|
/>
|
||||||
<ModalContent>{children}</ModalContent>
|
<ModalContent>{children}</ModalContent>
|
||||||
<ModalFooter
|
<ModalFooter
|
||||||
confirmText={confirmText}
|
confirmText={confirmText}
|
||||||
|
@ -18,6 +18,7 @@ import CollectionSettings from 'components/CollectionSettings';
|
|||||||
import { DocExplorer } from '@usebruno/graphql-docs';
|
import { DocExplorer } from '@usebruno/graphql-docs';
|
||||||
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import SecuritySettings from 'components/SecuritySettings';
|
||||||
import FolderSettings from 'components/FolderSettings';
|
import FolderSettings from 'components/FolderSettings';
|
||||||
|
|
||||||
const MIN_LEFT_PANE_WIDTH = 300;
|
const MIN_LEFT_PANE_WIDTH = 300;
|
||||||
@ -137,6 +138,10 @@ const RequestTabPanel = () => {
|
|||||||
return <FolderSettings collection={collection} folder={folder} />;
|
return <FolderSettings collection={collection} folder={folder} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (focusedTab.type === 'security-settings') {
|
||||||
|
return <SecuritySettings collection={collection} />;
|
||||||
|
}
|
||||||
|
|
||||||
const item = findItemInCollection(collection, activeTabUid);
|
const item = findItemInCollection(collection, activeTabUid);
|
||||||
if (!item || !item.uid) {
|
if (!item || !item.uid) {
|
||||||
return <RequestNotFound itemUid={activeTabUid} />;
|
return <RequestNotFound itemUid={activeTabUid} />;
|
||||||
|
@ -5,6 +5,7 @@ import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
|||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
|
||||||
|
|
||||||
const CollectionToolBar = ({ collection }) => {
|
const CollectionToolBar = ({ collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -47,6 +48,9 @@ const CollectionToolBar = ({ collection }) => {
|
|||||||
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 items-center justify-end">
|
<div className="flex flex-1 items-center justify-end">
|
||||||
|
<span className="mr-2">
|
||||||
|
<JsSandboxMode collection={collection} />
|
||||||
|
</span>
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconVariable, IconSettings, IconRun, IconFolder } from '@tabler/icons';
|
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
|
||||||
|
|
||||||
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
||||||
const getTabInfo = (type, tabName) => {
|
const getTabInfo = (type, tabName) => {
|
||||||
@ -12,6 +12,14 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'security-settings': {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IconShieldLock size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
|
<span className="ml-1">Security</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'folder-settings': {
|
case 'folder-settings': {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center flex-nowrap overflow-hidden">
|
<div className="flex items-center flex-nowrap overflow-hidden">
|
||||||
|
@ -66,7 +66,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
||||||
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner'].includes(tab.type)) {
|
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||||
{tab.type === 'folder-settings' ? (
|
{tab.type === 'folder-settings' ? (
|
||||||
|
@ -3,6 +3,7 @@ import styled from 'styled-components';
|
|||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: calc(100% - 0.75rem);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-color: ${(props) => props.theme.requestTabPanel.responseOverlayBg};
|
background-color: ${(props) => props.theme.requestTabPanel.responseOverlayBg};
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ const ResponseLoadingOverlay = ({ item, collection }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="px-3 w-full">
|
<StyledWrapper className="w-full">
|
||||||
<div className="overlay">
|
<div className="overlay">
|
||||||
<div style={{ marginBottom: 15, fontSize: 26 }}>
|
<div style={{ marginBottom: 15, fontSize: 26 }}>
|
||||||
<div style={{ display: 'inline-block', fontSize: 20, marginLeft: 5, marginRight: 5 }}>
|
<div style={{ display: 'inline-block', fontSize: 20, marginLeft: 5, marginRight: 5 }}>
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
.safe-mode {
|
||||||
|
padding: 0.15rem 0.3rem;
|
||||||
|
color: ${(props) => props.theme.colors.text.green};
|
||||||
|
border: solid 1px ${(props) => props.theme.colors.text.green} !important;
|
||||||
|
}
|
||||||
|
.developer-mode {
|
||||||
|
padding: 0.15rem 0.3rem;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
border: solid 1px ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,45 @@
|
|||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { IconShieldLock } from '@tabler/icons';
|
||||||
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
import { uuid } from 'utils/common/index';
|
||||||
|
import JsSandboxModeModal from '../JsSandboxModeModal';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const JsSandboxMode = ({ collection }) => {
|
||||||
|
const jsSandboxMode = collection?.securityConfig?.jsSandboxMode;
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const viewSecuritySettings = () => {
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: uuid(),
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
type: 'security-settings'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className='flex'>
|
||||||
|
{jsSandboxMode === 'safe' && (
|
||||||
|
<div
|
||||||
|
className="flex items-center border rounded-md text-xs cursor-pointer safe-mode"
|
||||||
|
onClick={viewSecuritySettings}
|
||||||
|
>
|
||||||
|
Safe Mode
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{jsSandboxMode === 'developer' && (
|
||||||
|
<div
|
||||||
|
className="flex items-center border rounded-md text-xs cursor-pointer developer-mode"
|
||||||
|
onClick={viewSecuritySettings}
|
||||||
|
>
|
||||||
|
Developer Mode
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!jsSandboxMode ? <JsSandboxModeModal collection={collection} /> : null}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JsSandboxMode;
|
@ -0,0 +1,22 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
|
span.beta-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1rem 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
color: ${(props) => props.theme.colors.text.green};
|
||||||
|
border: solid 1px ${(props) => props.theme.colors.text.green} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.developer-mode-warning {
|
||||||
|
font-weight: 400;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,99 @@
|
|||||||
|
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Portal from 'components/Portal';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const JsSandboxModeModal = ({ collection, onClose }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [jsSandboxMode, setJsSandboxMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setJsSandboxMode(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
dispatch(
|
||||||
|
saveCollectionSecurityConfig(collection?.uid, {
|
||||||
|
jsSandboxMode: jsSandboxMode
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Sandbox mode updated successfully');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error('Failed to update sandbox mode'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'JavaScript Sandbox'}
|
||||||
|
confirmText="Save"
|
||||||
|
handleConfirm={handleSave}
|
||||||
|
hideCancel={true}
|
||||||
|
hideClose={true}
|
||||||
|
disableCloseOnOutsideClick={true}
|
||||||
|
disableEscapeKey={true}
|
||||||
|
>
|
||||||
|
<StyledWrapper>
|
||||||
|
<div>
|
||||||
|
The collection might include JavaScript code in Variables, Scripts, Tests, and Assertions.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='text-muted mt-6'>
|
||||||
|
Please choose the security level for the JavaScript code execution.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col mt-4">
|
||||||
|
<label htmlFor="safe" className="flex flex-row items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="safe"
|
||||||
|
name="jsSandboxMode"
|
||||||
|
value="safe"
|
||||||
|
checked={jsSandboxMode === 'safe'}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
<span className={jsSandboxMode === 'safe' ? 'font-medium' : 'font-normal'}>
|
||||||
|
Safe Mode
|
||||||
|
</span>
|
||||||
|
<span className='beta-tag'>BETA</span>
|
||||||
|
</label>
|
||||||
|
<p className='text-sm text-muted mt-1'>
|
||||||
|
JavaScript code is executed in a secure sandbox and cannot excess your filesystem or execute system commands.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<label htmlFor="developer" className="flex flex-row gap-2 mt-6 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="developer"
|
||||||
|
name="jsSandboxMode"
|
||||||
|
value="developer"
|
||||||
|
checked={jsSandboxMode === 'developer'}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
<span className={jsSandboxMode === 'developer' ? 'font-medium' : 'font-normal'}>
|
||||||
|
Developer Mode
|
||||||
|
<span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p className='text-sm text-muted mt-1'>
|
||||||
|
JavaScript code has access to the filesystem, can execute system commands and access sensitive information.
|
||||||
|
</p>
|
||||||
|
<small className='text-muted mt-6'>
|
||||||
|
* SAFE mode has been introduced v1.25 onwards and is in beta. Please report any issues on github.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JsSandboxModeModal;
|
@ -0,0 +1,22 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
|
span.beta-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1rem 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
color: ${(props) => props.theme.colors.text.green};
|
||||||
|
border: solid 1px ${(props) => props.theme.colors.text.green} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.developer-mode-warning {
|
||||||
|
font-weight: 400;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
86
packages/bruno-app/src/components/SecuritySettings/index.js
Normal file
86
packages/bruno-app/src/components/SecuritySettings/index.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
const SecuritySettings = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [jsSandboxMode, setJsSandboxMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setJsSandboxMode(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
dispatch(
|
||||||
|
saveCollectionSecurityConfig(collection?.uid, {
|
||||||
|
jsSandboxMode: jsSandboxMode
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Sandbox mode updated successfully');
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error('Failed to update sandbox mode'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||||
|
<div className='font-semibold mt-2'>JavaScript Sandbox</div>
|
||||||
|
|
||||||
|
<div className='mt-4'>
|
||||||
|
The collection might include JavaScript code in Variables, Scripts, Tests, and Assertions.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col mt-4">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="safe" className="flex flex-row items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="safe"
|
||||||
|
name="jsSandboxMode"
|
||||||
|
value="safe"
|
||||||
|
checked={jsSandboxMode === 'safe'}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
<span className={jsSandboxMode === 'safe' ? 'font-medium' : 'font-normal'}>
|
||||||
|
Safe Mode
|
||||||
|
</span>
|
||||||
|
<span className='beta-tag'>BETA</span>
|
||||||
|
</label>
|
||||||
|
<p className='text-sm text-muted mt-1'>
|
||||||
|
JavaScript code is executed in a secure sandbox and cannot excess your filesystem or execute system commands.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<label htmlFor="developer" className="flex flex-row gap-2 mt-6 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="developer"
|
||||||
|
name="jsSandboxMode"
|
||||||
|
value="developer"
|
||||||
|
checked={jsSandboxMode === 'developer'}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
<span className={jsSandboxMode === 'developer' ? 'font-medium' : 'font-normal'}>
|
||||||
|
Developer Mode
|
||||||
|
<span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p className='text-sm text-muted mt-1'>
|
||||||
|
JavaScript code has access to the filesystem, can execute system commands and access sensitive information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit mt-6">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<small className='text-muted mt-6'>
|
||||||
|
* SAFE mode has been introduced v1.25 onwards and is in beta. Please report any issues on github.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecuritySettings;
|
@ -115,7 +115,7 @@ const Collections = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-col overflow-y-auto absolute top-32 bottom-10 left-0 right-0">
|
<div className="mt-4 flex flex-col overflow-hidden hover:overflow-y-auto absolute top-32 bottom-10 left-0 right-0">
|
||||||
{collections && collections.length
|
{collections && collections.length
|
||||||
? collections.map((c) => {
|
? collections.map((c) => {
|
||||||
return (
|
return (
|
||||||
|
@ -33,7 +33,8 @@ import {
|
|||||||
requestCancelled,
|
requestCancelled,
|
||||||
resetRunResults,
|
resetRunResults,
|
||||||
responseReceived,
|
responseReceived,
|
||||||
updateLastAction
|
updateLastAction,
|
||||||
|
setCollectionSecurityConfig
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
import { each } from 'lodash';
|
import { each } from 'lodash';
|
||||||
@ -1051,11 +1052,13 @@ export const openCollectionEvent = (uid, pathname, brunoConfig) => (dispatch, ge
|
|||||||
};
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
collectionSchema
|
ipcRenderer.invoke('renderer:get-collection-security-config', pathname).then((securityConfig) => {
|
||||||
.validate(collection)
|
collectionSchema
|
||||||
.then(() => dispatch(_createCollection(collection)))
|
.validate(collection)
|
||||||
.then(resolve)
|
.then(() => dispatch(_createCollection({ ...collection, securityConfig })))
|
||||||
.catch(reject);
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1120,3 +1123,19 @@ export const importCollection = (collection, collectionLocation) => (dispatch, g
|
|||||||
ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation).then(resolve).catch(reject);
|
ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation).then(resolve).catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const saveCollectionSecurityConfig = (collectionUid, securityConfig) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
const state = getState();
|
||||||
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:save-collection-security-config', collection?.pathname, securityConfig)
|
||||||
|
.then(async () => {
|
||||||
|
await dispatch(setCollectionSecurityConfig({ collectionUid, securityConfig }));
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -33,7 +33,6 @@ export const collectionsSlice = createSlice({
|
|||||||
const collection = action.payload;
|
const collection = action.payload;
|
||||||
|
|
||||||
collection.settingsSelectedTab = 'headers';
|
collection.settingsSelectedTab = 'headers';
|
||||||
|
|
||||||
collection.folderLevelSettingsSelectedTab = {};
|
collection.folderLevelSettingsSelectedTab = {};
|
||||||
|
|
||||||
// TODO: move this to use the nextAction approach
|
// TODO: move this to use the nextAction approach
|
||||||
@ -51,6 +50,12 @@ export const collectionsSlice = createSlice({
|
|||||||
state.collections.push(collection);
|
state.collections.push(collection);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setCollectionSecurityConfig: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
if (collection) {
|
||||||
|
collection.securityConfig = action.payload.securityConfig;
|
||||||
|
}
|
||||||
|
},
|
||||||
brunoConfigUpdateEvent: (state, action) => {
|
brunoConfigUpdateEvent: (state, action) => {
|
||||||
const { collectionUid, brunoConfig } = action.payload;
|
const { collectionUid, brunoConfig } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
@ -1622,6 +1627,7 @@ export const collectionsSlice = createSlice({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
createCollection,
|
createCollection,
|
||||||
|
setCollectionSecurityConfig,
|
||||||
brunoConfigUpdateEvent,
|
brunoConfigUpdateEvent,
|
||||||
renameCollection,
|
renameCollection,
|
||||||
removeCollection,
|
removeCollection,
|
||||||
|
@ -24,7 +24,9 @@ export const tabsSlice = createSlice({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['variables', 'collection-settings', 'collection-runner'].includes(action.payload.type)) {
|
if (
|
||||||
|
['variables', 'collection-settings', 'collection-runner', 'security-settings'].includes(action.payload.type)
|
||||||
|
) {
|
||||||
const tab = tabTypeAlreadyExists(state.tabs, action.payload.collectionUid, action.payload.type);
|
const tab = tabTypeAlreadyExists(state.tabs, action.payload.collectionUid, action.payload.type);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
state.activeTabUid = tab.uid;
|
state.activeTabUid = tab.uid;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"url": "git+https://github.com/usebruno/bruno.git"
|
"url": "git+https://github.com/usebruno/bruno.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest"
|
"test": "node --experimental-vm-modules $(npx --no-install which jest)"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
|
@ -190,6 +190,10 @@ const getFolderRoot = (dir) => {
|
|||||||
return collectionBruToJson(content);
|
return collectionBruToJson(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getJsSandboxRuntime = (sandbox) => {
|
||||||
|
return sandbox === 'safe' ? 'quickjs' : 'vm2';
|
||||||
|
};
|
||||||
|
|
||||||
const builder = async (yargs) => {
|
const builder = async (yargs) => {
|
||||||
yargs
|
yargs
|
||||||
.option('r', {
|
.option('r', {
|
||||||
@ -215,6 +219,11 @@ const builder = async (yargs) => {
|
|||||||
describe: 'Overwrite a single environment variable, multiple usages possible',
|
describe: 'Overwrite a single environment variable, multiple usages possible',
|
||||||
type: 'string'
|
type: 'string'
|
||||||
})
|
})
|
||||||
|
.option('sandbox', {
|
||||||
|
describe: 'Javscript sandbox to use; available sandboxes are "developer" (default) or "safe"',
|
||||||
|
default: 'developer',
|
||||||
|
type: 'string'
|
||||||
|
})
|
||||||
.option('output', {
|
.option('output', {
|
||||||
alias: 'o',
|
alias: 'o',
|
||||||
describe: 'Path to write file results to',
|
describe: 'Path to write file results to',
|
||||||
@ -282,6 +291,7 @@ const handler = async function (argv) {
|
|||||||
r: recursive,
|
r: recursive,
|
||||||
output: outputPath,
|
output: outputPath,
|
||||||
format,
|
format,
|
||||||
|
sandbox,
|
||||||
testsOnly,
|
testsOnly,
|
||||||
bail
|
bail
|
||||||
} = argv;
|
} = argv;
|
||||||
@ -451,6 +461,7 @@ const handler = async function (argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runtime = getJsSandboxRuntime(sandbox);
|
||||||
let currentRequestIndex = 0;
|
let currentRequestIndex = 0;
|
||||||
let nJumps = 0; // count the number of jumps to avoid infinite loops
|
let nJumps = 0; // count the number of jumps to avoid infinite loops
|
||||||
while (currentRequestIndex < bruJsons.length) {
|
while (currentRequestIndex < bruJsons.length) {
|
||||||
@ -466,7 +477,8 @@ const handler = async function (argv) {
|
|||||||
envVars,
|
envVars,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
brunoConfig,
|
brunoConfig,
|
||||||
collectionRoot
|
collectionRoot,
|
||||||
|
runtime
|
||||||
);
|
);
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
|
@ -21,6 +21,10 @@ const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||||
|
|
||||||
|
const onConsoleLog = (type, args) => {
|
||||||
|
console[type](...args);
|
||||||
|
};
|
||||||
|
|
||||||
const runSingleRequest = async function (
|
const runSingleRequest = async function (
|
||||||
filename,
|
filename,
|
||||||
bruJson,
|
bruJson,
|
||||||
@ -29,7 +33,8 @@ const runSingleRequest = async function (
|
|||||||
envVariables,
|
envVariables,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
brunoConfig,
|
brunoConfig,
|
||||||
collectionRoot
|
collectionRoot,
|
||||||
|
runtime
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let request;
|
let request;
|
||||||
@ -38,6 +43,7 @@ const runSingleRequest = async function (
|
|||||||
request = prepareRequest(bruJson.request, collectionRoot);
|
request = prepareRequest(bruJson.request, collectionRoot);
|
||||||
|
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
scriptingConfig.runtime = runtime;
|
||||||
|
|
||||||
// make axios work in node using form data
|
// make axios work in node using form data
|
||||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||||
@ -57,7 +63,7 @@ const runSingleRequest = async function (
|
|||||||
// run pre-request vars
|
// run pre-request vars
|
||||||
const preRequestVars = get(bruJson, 'request.vars.req');
|
const preRequestVars = get(bruJson, 'request.vars.req');
|
||||||
if (preRequestVars?.length) {
|
if (preRequestVars?.length) {
|
||||||
const varsRuntime = new VarsRuntime();
|
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
varsRuntime.runPreRequestVars(
|
varsRuntime.runPreRequestVars(
|
||||||
preRequestVars,
|
preRequestVars,
|
||||||
request,
|
request,
|
||||||
@ -74,14 +80,14 @@ const runSingleRequest = async function (
|
|||||||
get(bruJson, 'request.script.req')
|
get(bruJson, 'request.script.req')
|
||||||
]).join(os.EOL);
|
]).join(os.EOL);
|
||||||
if (requestScriptFile?.length) {
|
if (requestScriptFile?.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const result = await scriptRuntime.runRequestScript(
|
const result = await scriptRuntime.runRequestScript(
|
||||||
decomment(requestScriptFile),
|
decomment(requestScriptFile),
|
||||||
request,
|
request,
|
||||||
envVariables,
|
envVariables,
|
||||||
runtimeVariables,
|
runtimeVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
null,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
scriptingConfig
|
scriptingConfig
|
||||||
);
|
);
|
||||||
@ -276,7 +282,7 @@ const runSingleRequest = async function (
|
|||||||
// run post-response vars
|
// run post-response vars
|
||||||
const postResponseVars = get(bruJson, 'request.vars.res');
|
const postResponseVars = get(bruJson, 'request.vars.res');
|
||||||
if (postResponseVars?.length) {
|
if (postResponseVars?.length) {
|
||||||
const varsRuntime = new VarsRuntime();
|
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
varsRuntime.runPostResponseVars(
|
varsRuntime.runPostResponseVars(
|
||||||
postResponseVars,
|
postResponseVars,
|
||||||
request,
|
request,
|
||||||
@ -294,7 +300,7 @@ const runSingleRequest = async function (
|
|||||||
get(bruJson, 'request.script.res')
|
get(bruJson, 'request.script.res')
|
||||||
]).join(os.EOL);
|
]).join(os.EOL);
|
||||||
if (responseScriptFile?.length) {
|
if (responseScriptFile?.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const result = await scriptRuntime.runResponseScript(
|
const result = await scriptRuntime.runResponseScript(
|
||||||
decomment(responseScriptFile),
|
decomment(responseScriptFile),
|
||||||
request,
|
request,
|
||||||
@ -315,7 +321,7 @@ const runSingleRequest = async function (
|
|||||||
let assertionResults = [];
|
let assertionResults = [];
|
||||||
const assertions = get(bruJson, 'request.assertions');
|
const assertions = get(bruJson, 'request.assertions');
|
||||||
if (assertions) {
|
if (assertions) {
|
||||||
const assertRuntime = new AssertRuntime();
|
const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
assertionResults = assertRuntime.runAssertions(
|
assertionResults = assertRuntime.runAssertions(
|
||||||
assertions,
|
assertions,
|
||||||
request,
|
request,
|
||||||
@ -339,7 +345,7 @@ const runSingleRequest = async function (
|
|||||||
let testResults = [];
|
let testResults = [];
|
||||||
const testFile = compact([get(collectionRoot, 'request.tests'), get(bruJson, 'request.tests')]).join(os.EOL);
|
const testFile = compact([get(collectionRoot, 'request.tests'), get(bruJson, 'request.tests')]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const result = await testRuntime.runTests(
|
const result = await testRuntime.runTests(
|
||||||
decomment(testFile),
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"dist:rpm": "electron-builder --linux rpm --config electron-builder-config.js",
|
"dist:rpm": "electron-builder --linux rpm --config electron-builder-config.js",
|
||||||
"dist:snap": "electron-builder --linux snap --config electron-builder-config.js",
|
"dist:snap": "electron-builder --linux snap --config electron-builder-config.js",
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"test": "jest"
|
"test": "node --experimental-vm-modules $(npx --no-install which jest)"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"modulePaths": ["node_modules"]
|
"modulePaths": ["node_modules"]
|
||||||
@ -65,7 +65,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "31.2.1",
|
"electron": "31.2.1",
|
||||||
"electron-builder": "23.0.2",
|
"electron-builder": "23.0.2"
|
||||||
"electron-icon-maker": "^0.0.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,10 @@ const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON
|
|||||||
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
|
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
|
||||||
const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies');
|
const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies');
|
||||||
const EnvironmentSecretsStore = require('../store/env-secrets');
|
const EnvironmentSecretsStore = require('../store/env-secrets');
|
||||||
|
const CollectionSecurityStore = require('../store/collection-security');
|
||||||
|
|
||||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||||
|
const collectionSecurityStore = new CollectionSecurityStore();
|
||||||
|
|
||||||
const envHasSecrets = (environment = {}) => {
|
const envHasSecrets = (environment = {}) => {
|
||||||
const secrets = _.filter(environment.variables, (v) => v.secret);
|
const secrets = _.filter(environment.variables, (v) => v.secret);
|
||||||
@ -665,6 +667,24 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:save-collection-security-config', async (event, collectionPath, securityConfig) => {
|
||||||
|
try {
|
||||||
|
collectionSecurityStore.setSecurityConfigForCollection(collectionPath, {
|
||||||
|
jsSandboxMode: securityConfig.jsSandboxMode
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:get-collection-security-config', async (event, collectionPath) => {
|
||||||
|
try {
|
||||||
|
return collectionSecurityStore.getSecurityConfigForCollection(collectionPath);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
|
@ -81,6 +81,11 @@ const getEnvVars = (environment = {}) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getJsSandboxRuntime = (collection) => {
|
||||||
|
const securityConfig = get(collection, 'securityConfig', {});
|
||||||
|
return securityConfig.jsSandboxMode === 'safe' ? 'quickjs' : 'vm2';
|
||||||
|
};
|
||||||
|
|
||||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||||
|
|
||||||
const configureRequest = async (
|
const configureRequest = async (
|
||||||
@ -315,7 +320,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
// run pre-request vars
|
// run pre-request vars
|
||||||
const preRequestVars = get(request, 'vars.req', []);
|
const preRequestVars = get(request, 'vars.req', []);
|
||||||
if (preRequestVars?.length) {
|
if (preRequestVars?.length) {
|
||||||
const varsRuntime = new VarsRuntime();
|
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
varsRuntime.runPreRequestVars(
|
varsRuntime.runPreRequestVars(
|
||||||
preRequestVars,
|
preRequestVars,
|
||||||
request,
|
request,
|
||||||
@ -330,7 +335,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
let scriptResult;
|
let scriptResult;
|
||||||
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL);
|
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL);
|
||||||
if (requestScript?.length) {
|
if (requestScript?.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
scriptResult = await scriptRuntime.runRequestScript(
|
scriptResult = await scriptRuntime.runRequestScript(
|
||||||
decomment(requestScript),
|
decomment(requestScript),
|
||||||
request,
|
request,
|
||||||
@ -382,7 +387,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
// run post-response vars
|
// run post-response vars
|
||||||
const postResponseVars = get(request, 'vars.res', []);
|
const postResponseVars = get(request, 'vars.res', []);
|
||||||
if (postResponseVars?.length) {
|
if (postResponseVars?.length) {
|
||||||
const varsRuntime = new VarsRuntime();
|
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const result = varsRuntime.runPostResponseVars(
|
const result = varsRuntime.runPostResponseVars(
|
||||||
postResponseVars,
|
postResponseVars,
|
||||||
request,
|
request,
|
||||||
@ -416,7 +421,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
|
|
||||||
let scriptResult;
|
let scriptResult;
|
||||||
if (responseScript?.length) {
|
if (responseScript?.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
scriptResult = await scriptRuntime.runResponseScript(
|
scriptResult = await scriptRuntime.runResponseScript(
|
||||||
decomment(responseScript),
|
decomment(responseScript),
|
||||||
request,
|
request,
|
||||||
@ -460,6 +465,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
scriptingConfig.runtime = getJsSandboxRuntime(collection);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@ -575,7 +581,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
// run assertions
|
// run assertions
|
||||||
const assertions = get(request, 'assertions');
|
const assertions = get(request, 'assertions');
|
||||||
if (assertions) {
|
if (assertions) {
|
||||||
const assertRuntime = new AssertRuntime();
|
const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const results = assertRuntime.runAssertions(
|
const results = assertRuntime.runAssertions(
|
||||||
assertions,
|
assertions,
|
||||||
request,
|
request,
|
||||||
@ -603,7 +609,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
]).join(os.EOL);
|
]).join(os.EOL);
|
||||||
|
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
decomment(testFile),
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
@ -661,6 +667,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
scriptingConfig.runtime = getJsSandboxRuntime(collection);
|
||||||
|
|
||||||
await runPreRequest(
|
await runPreRequest(
|
||||||
request,
|
request,
|
||||||
@ -766,6 +773,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
const brunoConfig = getBrunoConfig(collection.uid);
|
const brunoConfig = getBrunoConfig(collection.uid);
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
scriptingConfig.runtime = getJsSandboxRuntime(collection);
|
||||||
|
|
||||||
await runPreRequest(
|
await runPreRequest(
|
||||||
request,
|
request,
|
||||||
@ -832,6 +840,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const cancelTokenUid = uuid();
|
const cancelTokenUid = uuid();
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
scriptingConfig.runtime = getJsSandboxRuntime(collection);
|
||||||
const collectionRoot = get(collection, 'root', {});
|
const collectionRoot = get(collection, 'root', {});
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
@ -1028,7 +1037,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
// run assertions
|
// run assertions
|
||||||
const assertions = get(item, 'request.assertions');
|
const assertions = get(item, 'request.assertions');
|
||||||
if (assertions) {
|
if (assertions) {
|
||||||
const assertRuntime = new AssertRuntime();
|
const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const results = assertRuntime.runAssertions(
|
const results = assertRuntime.runAssertions(
|
||||||
assertions,
|
assertions,
|
||||||
request,
|
request,
|
||||||
@ -1055,7 +1064,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
]).join(os.EOL);
|
]).join(os.EOL);
|
||||||
|
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime });
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
decomment(testFile),
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
|
39
packages/bruno-electron/src/store/collection-security.js
Normal file
39
packages/bruno-electron/src/store/collection-security.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const Store = require('electron-store');
|
||||||
|
|
||||||
|
class CollectionSecurityStore {
|
||||||
|
constructor() {
|
||||||
|
this.store = new Store({
|
||||||
|
name: 'collection-security',
|
||||||
|
clearInvalidConfig: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setSecurityConfigForCollection(collectionPathname, securityConfig) {
|
||||||
|
const collections = this.store.get('collections') || [];
|
||||||
|
const collection = _.find(collections, (c) => c.path === collectionPathname);
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
collections.push({
|
||||||
|
path: collectionPathname,
|
||||||
|
securityConfig: {
|
||||||
|
jsSandboxMode: securityConfig.jsSandboxMode
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store.set('collections', collections);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.securityConfig = securityConfig || {};
|
||||||
|
this.store.set('collections', collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecurityConfigForCollection(collectionPathname) {
|
||||||
|
const collections = this.store.get('collections') || [];
|
||||||
|
const collection = _.find(collections, (c) => c.path === collectionPathname);
|
||||||
|
return collection?.securityConfig || {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CollectionSecurityStore;
|
@ -86,7 +86,11 @@ function decryptString(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (algo === ELECTRONSAFESTORAGE_ALGO) {
|
if (algo === ELECTRONSAFESTORAGE_ALGO) {
|
||||||
return safeStorageDecrypt(encryptedString);
|
if (safeStorage && safeStorage.isEncryptionAvailable()) {
|
||||||
|
return safeStorageDecrypt(encryptedString);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (algo === AES256_ALGO) {
|
if (algo === AES256_ALGO) {
|
||||||
|
1
packages/bruno-js/.gitignore
vendored
Normal file
1
packages/bruno-js/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/sandbox/bundle-browser-rollup.js
|
@ -11,7 +11,8 @@
|
|||||||
"@n8n/vm2": "^3.9.23"
|
"@n8n/vm2": "^3.9.23"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --testPathIgnorePatterns test.js"
|
"test": "node --experimental-vm-modules $(npx --no-install which jest) --testPathIgnorePatterns test.js",
|
||||||
|
"sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/common": "0.1.0",
|
"@usebruno/common": "0.1.0",
|
||||||
@ -24,12 +25,29 @@
|
|||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-string": "^1.5.0",
|
"chai-string": "^1.5.0",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
"crypto-js-3.1.9-1": "npm:crypto-js@^3.1.9-1",
|
||||||
"json-query": "^2.2.2",
|
"json-query": "^2.2.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-fetch": "2.*",
|
"node-fetch": "^2.7.0",
|
||||||
"node-vault": "^0.10.2",
|
"node-vault": "^0.10.2",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"quickjs-emscripten": "^0.29.2",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
"rollup": "3.2.5",
|
||||||
|
"rollup-plugin-node-builtins": "^2.1.2",
|
||||||
|
"rollup-plugin-node-globals": "^1.4.0",
|
||||||
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"stream": "^0.0.2",
|
||||||
|
"terser": "^5.31.1",
|
||||||
|
"uglify-js": "^3.18.0",
|
||||||
|
"util": "^0.12.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,10 @@ class Bru {
|
|||||||
setNextRequest(nextRequest) {
|
setNextRequest(nextRequest) {
|
||||||
this.nextRequest = nextRequest;
|
this.nextRequest = nextRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Bru;
|
module.exports = Bru;
|
||||||
|
@ -5,6 +5,7 @@ const Bru = require('../bru');
|
|||||||
const BrunoRequest = require('../bruno-request');
|
const BrunoRequest = require('../bruno-request');
|
||||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||||
const { interpolateString } = require('../interpolate-string');
|
const { interpolateString } = require('../interpolate-string');
|
||||||
|
const { executeQuickJsVm } = require('../sandbox/quickjs');
|
||||||
|
|
||||||
const { expect } = chai;
|
const { expect } = chai;
|
||||||
chai.use(require('chai-string'));
|
chai.use(require('chai-string'));
|
||||||
@ -161,7 +162,31 @@ const isUnaryOperator = (operator) => {
|
|||||||
return unaryOperators.includes(operator);
|
return unaryOperators.includes(operator);
|
||||||
};
|
};
|
||||||
|
|
||||||
const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
|
||||||
|
if (runtime === 'quickjs') {
|
||||||
|
return executeQuickJsVm({
|
||||||
|
script: literal,
|
||||||
|
context,
|
||||||
|
scriptType: 'template-literal'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluateJsTemplateLiteral(literal, context);
|
||||||
|
};
|
||||||
|
|
||||||
|
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime) => {
|
||||||
|
if (runtime === 'quickjs') {
|
||||||
|
return executeQuickJsVm({
|
||||||
|
script: expr,
|
||||||
|
context,
|
||||||
|
scriptType: 'expression'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluateJsExpression(expr, context);
|
||||||
|
};
|
||||||
|
|
||||||
|
const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
|
||||||
if (isUnaryOperator(operator)) {
|
if (isUnaryOperator(operator)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -181,13 +206,17 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
|||||||
|
|
||||||
return rhsOperand
|
return rhsOperand
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
.map((v) =>
|
||||||
|
evaluateJsTemplateLiteralBasedOnRuntime(interpolateString(v.trim(), interpolationContext), context, runtime)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operator === 'between') {
|
if (operator === 'between') {
|
||||||
const [lhs, rhs] = rhsOperand
|
const [lhs, rhs] = rhsOperand
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
.map((v) =>
|
||||||
|
evaluateJsTemplateLiteralBasedOnRuntime(interpolateString(v.trim(), interpolationContext), context, runtime)
|
||||||
|
);
|
||||||
return [lhs, rhs];
|
return [lhs, rhs];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,10 +229,14 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
|||||||
return interpolateString(rhsOperand, interpolationContext);
|
return interpolateString(rhsOperand, interpolationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
return evaluateJsTemplateLiteral(interpolateString(rhsOperand, interpolationContext), context);
|
return evaluateJsTemplateLiteralBasedOnRuntime(interpolateString(rhsOperand, interpolationContext), context, runtime);
|
||||||
};
|
};
|
||||||
|
|
||||||
class AssertRuntime {
|
class AssertRuntime {
|
||||||
|
constructor(props) {
|
||||||
|
this.runtime = props?.runtime || 'vm2';
|
||||||
|
}
|
||||||
|
|
||||||
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
||||||
const requestVariables = request?.requestVariables || {};
|
const requestVariables = request?.requestVariables || {};
|
||||||
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
|
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
|
||||||
@ -238,8 +271,8 @@ class AssertRuntime {
|
|||||||
const { operator, value: rhsOperand } = parseAssertionOperator(rhsExpr);
|
const { operator, value: rhsOperand } = parseAssertionOperator(rhsExpr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lhs = evaluateJsExpression(lhsExpr, context);
|
const lhs = evaluateJsExpressionBasedOnRuntime(lhsExpr, context, this.runtime);
|
||||||
const rhs = evaluateRhsOperand(rhsOperand, operator, context);
|
const rhs = evaluateRhsOperand(rhsOperand, operator, context, this.runtime);
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case 'eq':
|
case 'eq':
|
||||||
|
@ -28,9 +28,12 @@ const fetch = require('node-fetch');
|
|||||||
const chai = require('chai');
|
const chai = require('chai');
|
||||||
const CryptoJS = require('crypto-js');
|
const CryptoJS = require('crypto-js');
|
||||||
const NodeVault = require('node-vault');
|
const NodeVault = require('node-vault');
|
||||||
|
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||||
|
|
||||||
class ScriptRuntime {
|
class ScriptRuntime {
|
||||||
constructor() {}
|
constructor(props) {
|
||||||
|
this.runtime = props?.runtime || 'vm2';
|
||||||
|
}
|
||||||
|
|
||||||
// This approach is getting out of hand
|
// This approach is getting out of hand
|
||||||
// Need to refactor this to use a single arg (object) instead of 7
|
// Need to refactor this to use a single arg (object) instead of 7
|
||||||
@ -86,6 +89,22 @@ class ScriptRuntime {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.runtime === 'quickjs') {
|
||||||
|
await executeQuickJsVmAsync({
|
||||||
|
script: script,
|
||||||
|
context: context,
|
||||||
|
collectionPath
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
request,
|
||||||
|
envVariables: cleanJson(envVariables),
|
||||||
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
nextRequestName: bru.nextRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// default runtime is vm2
|
||||||
const vm = new NodeVM({
|
const vm = new NodeVM({
|
||||||
sandbox: context,
|
sandbox: context,
|
||||||
require: {
|
require: {
|
||||||
@ -123,6 +142,7 @@ class ScriptRuntime {
|
|||||||
});
|
});
|
||||||
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
|
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
|
||||||
await asyncVM();
|
await asyncVM();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request,
|
request,
|
||||||
envVariables: cleanJson(envVariables),
|
envVariables: cleanJson(envVariables),
|
||||||
@ -176,10 +196,27 @@ class ScriptRuntime {
|
|||||||
log: customLogger('log'),
|
log: customLogger('log'),
|
||||||
info: customLogger('info'),
|
info: customLogger('info'),
|
||||||
warn: customLogger('warn'),
|
warn: customLogger('warn'),
|
||||||
error: customLogger('error')
|
error: customLogger('error'),
|
||||||
|
debug: customLogger('debug')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.runtime === 'quickjs') {
|
||||||
|
await executeQuickJsVmAsync({
|
||||||
|
script: script,
|
||||||
|
context: context,
|
||||||
|
collectionPath
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
response,
|
||||||
|
envVariables: cleanJson(envVariables),
|
||||||
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
nextRequestName: bru.nextRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// default runtime is vm2
|
||||||
const vm = new NodeVM({
|
const vm = new NodeVM({
|
||||||
sandbox: context,
|
sandbox: context,
|
||||||
require: {
|
require: {
|
||||||
|
@ -15,7 +15,7 @@ const BrunoRequest = require('../bruno-request');
|
|||||||
const BrunoResponse = require('../bruno-response');
|
const BrunoResponse = require('../bruno-response');
|
||||||
const Test = require('../test');
|
const Test = require('../test');
|
||||||
const TestResults = require('../test-results');
|
const TestResults = require('../test-results');
|
||||||
const { cleanJson } = require('../utils');
|
const { cleanJson, appendAwaitToTestFunc } = require('../utils');
|
||||||
|
|
||||||
// Inbuilt Library Support
|
// Inbuilt Library Support
|
||||||
const ajv = require('ajv');
|
const ajv = require('ajv');
|
||||||
@ -30,9 +30,12 @@ const axios = require('axios');
|
|||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const CryptoJS = require('crypto-js');
|
const CryptoJS = require('crypto-js');
|
||||||
const NodeVault = require('node-vault');
|
const NodeVault = require('node-vault');
|
||||||
|
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||||
|
|
||||||
class TestRuntime {
|
class TestRuntime {
|
||||||
constructor() {}
|
constructor(props) {
|
||||||
|
this.runtime = props?.runtime || 'vm2';
|
||||||
|
}
|
||||||
|
|
||||||
async runTests(
|
async runTests(
|
||||||
testsFile,
|
testsFile,
|
||||||
@ -81,6 +84,9 @@ class TestRuntime {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add 'await' prefix to the test function calls
|
||||||
|
testsFile = appendAwaitToTestFunc(testsFile);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
test,
|
test,
|
||||||
bru,
|
bru,
|
||||||
@ -101,48 +107,56 @@ class TestRuntime {
|
|||||||
log: customLogger('log'),
|
log: customLogger('log'),
|
||||||
info: customLogger('info'),
|
info: customLogger('info'),
|
||||||
warn: customLogger('warn'),
|
warn: customLogger('warn'),
|
||||||
|
debug: customLogger('debug'),
|
||||||
error: customLogger('error')
|
error: customLogger('error')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const vm = new NodeVM({
|
if (this.runtime === 'quickjs') {
|
||||||
sandbox: context,
|
await executeQuickJsVmAsync({
|
||||||
require: {
|
script: testsFile,
|
||||||
context: 'sandbox',
|
context: context
|
||||||
external: true,
|
});
|
||||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
} else {
|
||||||
mock: {
|
// default runtime is vm2
|
||||||
// node libs
|
const vm = new NodeVM({
|
||||||
path,
|
sandbox: context,
|
||||||
stream,
|
require: {
|
||||||
util,
|
context: 'sandbox',
|
||||||
url,
|
external: true,
|
||||||
http,
|
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||||
https,
|
mock: {
|
||||||
punycode,
|
// node libs
|
||||||
zlib,
|
path,
|
||||||
// 3rd party libs
|
stream,
|
||||||
ajv,
|
util,
|
||||||
'ajv-formats': addFormats,
|
url,
|
||||||
btoa,
|
http,
|
||||||
atob,
|
https,
|
||||||
lodash,
|
punycode,
|
||||||
moment,
|
zlib,
|
||||||
uuid,
|
// 3rd party libs
|
||||||
nanoid,
|
ajv,
|
||||||
axios,
|
'ajv-formats': addFormats,
|
||||||
chai,
|
btoa,
|
||||||
'node-fetch': fetch,
|
atob,
|
||||||
'crypto-js': CryptoJS,
|
lodash,
|
||||||
...whitelistedModules,
|
moment,
|
||||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
uuid,
|
||||||
'node-vault': NodeVault
|
nanoid,
|
||||||
|
axios,
|
||||||
|
chai,
|
||||||
|
'node-fetch': fetch,
|
||||||
|
'crypto-js': CryptoJS,
|
||||||
|
...whitelistedModules,
|
||||||
|
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||||
|
'node-vault': NodeVault
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
|
||||||
|
await asyncVM();
|
||||||
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
|
}
|
||||||
await asyncVM();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request,
|
request,
|
||||||
|
@ -3,7 +3,38 @@ const Bru = require('../bru');
|
|||||||
const BrunoRequest = require('../bruno-request');
|
const BrunoRequest = require('../bruno-request');
|
||||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||||
|
|
||||||
|
const { executeQuickJsVm } = require('../sandbox/quickjs');
|
||||||
|
|
||||||
|
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
|
||||||
|
if (runtime === 'quickjs') {
|
||||||
|
return executeQuickJsVm({
|
||||||
|
script: literal,
|
||||||
|
context,
|
||||||
|
scriptType: 'template-literal'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluateJsTemplateLiteral(literal, context);
|
||||||
|
};
|
||||||
|
|
||||||
|
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
|
||||||
|
if (runtime === 'quickjs') {
|
||||||
|
return executeQuickJsVm({
|
||||||
|
script: expr,
|
||||||
|
context,
|
||||||
|
scriptType: 'expression'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluateJsExpression(expr, context);
|
||||||
|
};
|
||||||
|
|
||||||
class VarsRuntime {
|
class VarsRuntime {
|
||||||
|
constructor(props) {
|
||||||
|
this.runtime = props?.runtime || 'vm2';
|
||||||
|
this.mode = props?.mode || 'developer';
|
||||||
|
}
|
||||||
|
|
||||||
runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) {
|
runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) {
|
||||||
if (!request?.requestVariables) {
|
if (!request?.requestVariables) {
|
||||||
request.requestVariables = {};
|
request.requestVariables = {};
|
||||||
@ -28,7 +59,7 @@ class VarsRuntime {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_.each(enabledVars, (v) => {
|
_.each(enabledVars, (v) => {
|
||||||
const value = evaluateJsTemplateLiteral(v.value, context);
|
const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime);
|
||||||
request?.requestVariables && (request.requestVariables[v.name] = value);
|
request?.requestVariables && (request.requestVariables[v.name] = value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -59,7 +90,7 @@ class VarsRuntime {
|
|||||||
const errors = new Map();
|
const errors = new Map();
|
||||||
_.each(enabledVars, (v) => {
|
_.each(enabledVars, (v) => {
|
||||||
try {
|
try {
|
||||||
const value = evaluateJsExpression(v.value, context);
|
const value = evaluateJsExpressionBasedOnRuntime(v.value, context, this.runtime);
|
||||||
bru.setVar(v.name, value);
|
bru.setVar(v.name, value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errors.set(v.name, error);
|
errors.set(v.name, error);
|
||||||
|
88
packages/bruno-js/src/sandbox/bundle-libraries.js
Normal file
88
packages/bruno-js/src/sandbox/bundle-libraries.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
const rollup = require('rollup');
|
||||||
|
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||||
|
const commonjs = require('@rollup/plugin-commonjs');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { terser } = require('rollup-plugin-terser');
|
||||||
|
|
||||||
|
const bundleLibraries = async () => {
|
||||||
|
const codeScript = `
|
||||||
|
import { expect, assert } from 'chai';
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
import moment from "moment";
|
||||||
|
import btoa from "btoa";
|
||||||
|
import atob from "atob";
|
||||||
|
import * as CryptoJS from "crypto-js-3.1.9-1";
|
||||||
|
globalThis.expect = expect;
|
||||||
|
globalThis.assert = assert;
|
||||||
|
globalThis.moment = moment;
|
||||||
|
globalThis.btoa = btoa;
|
||||||
|
globalThis.atob = atob;
|
||||||
|
globalThis.Buffer = Buffer;
|
||||||
|
globalThis.CryptoJS = CryptoJS;
|
||||||
|
globalThis.requireObject = {
|
||||||
|
...(globalThis.requireObject || {}),
|
||||||
|
'chai': { expect, assert },
|
||||||
|
'moment': moment,
|
||||||
|
'buffer': { Buffer },
|
||||||
|
'btoa': btoa,
|
||||||
|
'atob': atob,
|
||||||
|
'crypto-js': CryptoJS
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
input: {
|
||||||
|
input: 'inline-code',
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'inline-code-plugin',
|
||||||
|
resolveId(id) {
|
||||||
|
if (id === 'inline-code') {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
load(id) {
|
||||||
|
if (id === 'inline-code') {
|
||||||
|
return codeScript;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodeResolve({
|
||||||
|
preferBuiltins: false,
|
||||||
|
browser: false
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
terser()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
file: './src/sandbox/bundle-browser-rollup.js',
|
||||||
|
format: 'iife',
|
||||||
|
name: 'MyBundle'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bundle = await rollup.rollup(config.input);
|
||||||
|
const { output } = await bundle.generate(config.output);
|
||||||
|
fs.writeFileSync(
|
||||||
|
'./src/sandbox/bundle-browser-rollup.js',
|
||||||
|
`
|
||||||
|
const getBundledCode = () => {
|
||||||
|
return function(){
|
||||||
|
${output?.map((o) => o.code).join('\n')}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
module.exports = getBundledCode;
|
||||||
|
`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error while bundling:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bundleLibraries();
|
||||||
|
|
||||||
|
module.exports = bundleLibraries;
|
153
packages/bruno-js/src/sandbox/quickjs/index.js
Normal file
153
packages/bruno-js/src/sandbox/quickjs/index.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
const addBruShimToContext = require('./shims/bru');
|
||||||
|
const addBrunoRequestShimToContext = require('./shims/bruno-request');
|
||||||
|
const addConsoleShimToContext = require('./shims/console');
|
||||||
|
const addBrunoResponseShimToContext = require('./shims/bruno-response');
|
||||||
|
const addTestShimToContext = require('./shims/test');
|
||||||
|
const addLibraryShimsToContext = require('./shims/lib');
|
||||||
|
const addLocalModuleLoaderShimToContext = require('./shims/local-module');
|
||||||
|
const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscripten');
|
||||||
|
|
||||||
|
// execute `npm run sandbox:bundle-libraries` if the below file doesn't exist
|
||||||
|
const getBundledCode = require('../bundle-browser-rollup');
|
||||||
|
const addPathShimToContext = require('./shims/lib/path');
|
||||||
|
|
||||||
|
let QuickJSSyncContext;
|
||||||
|
const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
|
||||||
|
const getContext = (opts) => loader().then((mod) => (QuickJSSyncContext = mod.newContext(opts)));
|
||||||
|
getContext();
|
||||||
|
|
||||||
|
const toNumber = (value) => {
|
||||||
|
const num = Number(value);
|
||||||
|
return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeQuickJsVm = ({ script: externalScript, context: externalContext, scriptType = 'template-literal' }) => {
|
||||||
|
if (!isNaN(Number(externalScript))) {
|
||||||
|
return Number(externalScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vm = QuickJSSyncContext;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { bru, req, res } = externalContext;
|
||||||
|
|
||||||
|
bru && addBruShimToContext(vm, bru);
|
||||||
|
req && addBrunoRequestShimToContext(vm, req);
|
||||||
|
res && addBrunoResponseShimToContext(vm, res);
|
||||||
|
|
||||||
|
const templateLiteralText = `\`${externalScript}\`;`;
|
||||||
|
const jsExpressionText = `${externalScript};`;
|
||||||
|
|
||||||
|
let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText;
|
||||||
|
|
||||||
|
const result = vm.evalCode(scriptText);
|
||||||
|
if (result.error) {
|
||||||
|
let e = vm.dump(result.error);
|
||||||
|
result.error.dispose();
|
||||||
|
return e;
|
||||||
|
} else {
|
||||||
|
let v = vm.dump(result.value);
|
||||||
|
let vString = v.toString();
|
||||||
|
result.value.dispose();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error executing the script!', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeQuickJsVmAsync = async ({ script: externalScript, context: externalContext, collectionPath }) => {
|
||||||
|
if (!isNaN(Number(externalScript))) {
|
||||||
|
return toNumber(externalScript);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const module = await newQuickJSWASMModule();
|
||||||
|
const vm = module.newContext();
|
||||||
|
|
||||||
|
const bundledCode = getBundledCode?.toString() || '';
|
||||||
|
const moduleLoaderCode = function () {
|
||||||
|
return `
|
||||||
|
globalThis.require = (mod) => {
|
||||||
|
let lib = globalThis.requireObject[mod];
|
||||||
|
let isModuleAPath = (module) => (module?.startsWith('.') || module?.startsWith?.(bru.cwd()))
|
||||||
|
if (lib) {
|
||||||
|
return lib;
|
||||||
|
}
|
||||||
|
else if (isModuleAPath(mod)) {
|
||||||
|
// fetch local module
|
||||||
|
let localModuleCode = globalThis.__brunoLoadLocalModule(mod);
|
||||||
|
|
||||||
|
// compile local module as iife
|
||||||
|
(function (){
|
||||||
|
const initModuleExportsCode = "const module = { exports: {} };"
|
||||||
|
const copyModuleExportsCode = "\\n;globalThis.requireObject[mod] = module.exports;";
|
||||||
|
const patchedRequire = ${`
|
||||||
|
"\\n;" +
|
||||||
|
"let require = (subModule) => isModuleAPath(subModule) ? globalThis.require(path.resolve(bru.cwd(), mod, '..', subModule)) : globalThis.require(subModule)" +
|
||||||
|
"\\n;"
|
||||||
|
`}
|
||||||
|
eval(initModuleExportsCode + patchedRequire + localModuleCode + copyModuleExportsCode);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// resolve module
|
||||||
|
return globalThis.requireObject[mod];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.evalCode(
|
||||||
|
`
|
||||||
|
(${bundledCode})()
|
||||||
|
${moduleLoaderCode()}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext;
|
||||||
|
|
||||||
|
bru && addBruShimToContext(vm, bru);
|
||||||
|
req && addBrunoRequestShimToContext(vm, req);
|
||||||
|
res && addBrunoResponseShimToContext(vm, res);
|
||||||
|
consoleFn && addConsoleShimToContext(vm, consoleFn);
|
||||||
|
addLocalModuleLoaderShimToContext(vm, collectionPath);
|
||||||
|
addPathShimToContext(vm);
|
||||||
|
|
||||||
|
await addLibraryShimsToContext(vm);
|
||||||
|
|
||||||
|
test && __brunoTestResults && addTestShimToContext(vm, __brunoTestResults);
|
||||||
|
|
||||||
|
const script = `
|
||||||
|
(async () => {
|
||||||
|
const setTimeout = async(fn, timer) => {
|
||||||
|
v = await bru.sleep(timer);
|
||||||
|
fn.apply();
|
||||||
|
}
|
||||||
|
await bru.sleep(0);
|
||||||
|
try {
|
||||||
|
${externalScript}
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console?.debug?.('quick-js:execution-end:with-error', error?.message);
|
||||||
|
}
|
||||||
|
return 'done';
|
||||||
|
})()
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = vm.evalCode(script);
|
||||||
|
const promiseHandle = vm.unwrapResult(result);
|
||||||
|
const resolvedResult = await vm.resolvePromise(promiseHandle);
|
||||||
|
promiseHandle.dispose();
|
||||||
|
const resolvedHandle = vm.unwrapResult(resolvedResult);
|
||||||
|
resolvedHandle.dispose();
|
||||||
|
// vm.dispose();
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error executing the script!', error);
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
executeQuickJsVm,
|
||||||
|
executeQuickJsVmAsync
|
||||||
|
};
|
81
packages/bruno-js/src/sandbox/quickjs/shims/bru.js
Normal file
81
packages/bruno-js/src/sandbox/quickjs/shims/bru.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
const { marshallToVm } = require('../utils');
|
||||||
|
|
||||||
|
const addBruShimToContext = (vm, bru) => {
|
||||||
|
const bruObject = vm.newObject();
|
||||||
|
|
||||||
|
let cwd = vm.newFunction('cwd', function () {
|
||||||
|
return marshallToVm(bru.cwd(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'cwd', cwd);
|
||||||
|
cwd.dispose();
|
||||||
|
|
||||||
|
let getEnvName = vm.newFunction('getEnvName', function () {
|
||||||
|
return marshallToVm(bru.getEnvName(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'getEnvName', getEnvName);
|
||||||
|
getEnvName.dispose();
|
||||||
|
|
||||||
|
let getProcessEnv = vm.newFunction('getProcessEnv', function (key) {
|
||||||
|
return marshallToVm(bru.getProcessEnv(vm.dump(key)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'getProcessEnv', getProcessEnv);
|
||||||
|
getProcessEnv.dispose();
|
||||||
|
|
||||||
|
let getEnvVar = vm.newFunction('getEnvVar', function (key) {
|
||||||
|
return marshallToVm(bru.getEnvVar(vm.dump(key)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'getEnvVar', getEnvVar);
|
||||||
|
getEnvVar.dispose();
|
||||||
|
|
||||||
|
let setEnvVar = vm.newFunction('setEnvVar', function (key, value) {
|
||||||
|
bru.setEnvVar(vm.dump(key), vm.dump(value));
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
||||||
|
setEnvVar.dispose();
|
||||||
|
|
||||||
|
let getVar = vm.newFunction('getVar', function (key) {
|
||||||
|
return marshallToVm(bru.getVar(vm.dump(key)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'getVar', getVar);
|
||||||
|
getVar.dispose();
|
||||||
|
|
||||||
|
let setVar = vm.newFunction('setVar', function (key, value) {
|
||||||
|
bru.setVar(vm.dump(key), vm.dump(value));
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'setVar', setVar);
|
||||||
|
setVar.dispose();
|
||||||
|
|
||||||
|
let setNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
|
||||||
|
bru.setNextRequest(vm.dump(nextRequest));
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'setNextRequest', setNextRequest);
|
||||||
|
setNextRequest.dispose();
|
||||||
|
|
||||||
|
let visualize = vm.newFunction('visualize', function (htmlString) {
|
||||||
|
bru.visualize(vm.dump(htmlString));
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'visualize', visualize);
|
||||||
|
visualize.dispose();
|
||||||
|
|
||||||
|
let getSecretVar = vm.newFunction('getSecretVar', function (key) {
|
||||||
|
return marshallToVm(bru.getSecretVar(vm.dump(key)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'getSecretVar', getSecretVar);
|
||||||
|
getSecretVar.dispose();
|
||||||
|
|
||||||
|
const sleep = vm.newFunction('sleep', (timer) => {
|
||||||
|
const t = vm.getString(timer);
|
||||||
|
const promise = vm.newPromise();
|
||||||
|
setTimeout(() => {
|
||||||
|
promise.resolve(vm.newString('slept'));
|
||||||
|
}, t);
|
||||||
|
promise.settled.then(vm.runtime.executePendingJobs);
|
||||||
|
return promise.handle;
|
||||||
|
});
|
||||||
|
sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
|
||||||
|
|
||||||
|
vm.setProp(vm.global, 'bru', bruObject);
|
||||||
|
bruObject.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addBruShimToContext;
|
112
packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js
Normal file
112
packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
const { marshallToVm } = require('../utils');
|
||||||
|
|
||||||
|
const addBrunoRequestShimToContext = (vm, req) => {
|
||||||
|
const reqObject = vm.newObject();
|
||||||
|
|
||||||
|
const url = marshallToVm(req.getUrl(), vm);
|
||||||
|
const method = marshallToVm(req.getMethod(), vm);
|
||||||
|
const headers = marshallToVm(req.getHeaders(), vm);
|
||||||
|
const body = marshallToVm(req.getBody(), vm);
|
||||||
|
const timeout = marshallToVm(req.getTimeout(), vm);
|
||||||
|
|
||||||
|
vm.setProp(reqObject, 'url', url);
|
||||||
|
vm.setProp(reqObject, 'method', method);
|
||||||
|
vm.setProp(reqObject, 'headers', headers);
|
||||||
|
vm.setProp(reqObject, 'body', body);
|
||||||
|
vm.setProp(reqObject, 'timeout', timeout);
|
||||||
|
|
||||||
|
url.dispose();
|
||||||
|
method.dispose();
|
||||||
|
headers.dispose();
|
||||||
|
body.dispose();
|
||||||
|
timeout.dispose();
|
||||||
|
|
||||||
|
let getUrl = vm.newFunction('getUrl', function () {
|
||||||
|
return marshallToVm(req.getUrl(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getUrl', getUrl);
|
||||||
|
getUrl.dispose();
|
||||||
|
|
||||||
|
let setUrl = vm.newFunction('setUrl', function (url) {
|
||||||
|
req.setUrl(vm.dump(url));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setUrl', setUrl);
|
||||||
|
setUrl.dispose();
|
||||||
|
|
||||||
|
let getMethod = vm.newFunction('getMethod', function () {
|
||||||
|
return marshallToVm(req.getMethod(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getMethod', getMethod);
|
||||||
|
getMethod.dispose();
|
||||||
|
|
||||||
|
let getAuthMode = vm.newFunction('getAuthMode', function () {
|
||||||
|
return marshallToVm(req.getAuthMode(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getAuthMode', getAuthMode);
|
||||||
|
getAuthMode.dispose();
|
||||||
|
|
||||||
|
let setMethod = vm.newFunction('setMethod', function (method) {
|
||||||
|
req.setMethod(vm.dump(method));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setMethod', setMethod);
|
||||||
|
setMethod.dispose();
|
||||||
|
|
||||||
|
let getHeaders = vm.newFunction('getHeaders', function () {
|
||||||
|
return marshallToVm(req.getHeaders(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getHeaders', getHeaders);
|
||||||
|
getHeaders.dispose();
|
||||||
|
|
||||||
|
let setHeaders = vm.newFunction('setHeaders', function (headers) {
|
||||||
|
req.setHeaders(vm.dump(headers));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setHeaders', setHeaders);
|
||||||
|
setHeaders.dispose();
|
||||||
|
|
||||||
|
let getHeader = vm.newFunction('getHeader', function (name) {
|
||||||
|
return marshallToVm(req.getHeader(vm.dump(name)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getHeader', getHeader);
|
||||||
|
getHeader.dispose();
|
||||||
|
|
||||||
|
let setHeader = vm.newFunction('setHeader', function (name, value) {
|
||||||
|
req.setHeader(vm.dump(name), vm.dump(value));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setHeader', setHeader);
|
||||||
|
setHeader.dispose();
|
||||||
|
|
||||||
|
let getBody = vm.newFunction('getBody', function () {
|
||||||
|
return marshallToVm(req.getBody(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getBody', getBody);
|
||||||
|
getBody.dispose();
|
||||||
|
|
||||||
|
let setBody = vm.newFunction('setBody', function (data) {
|
||||||
|
req.setBody(vm.dump(data));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setBody', setBody);
|
||||||
|
setBody.dispose();
|
||||||
|
|
||||||
|
let setMaxRedirects = vm.newFunction('setMaxRedirects', function (maxRedirects) {
|
||||||
|
req.setMaxRedirects(vm.dump(maxRedirects));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setMaxRedirects', setMaxRedirects);
|
||||||
|
setMaxRedirects.dispose();
|
||||||
|
|
||||||
|
let getTimeout = vm.newFunction('getTimeout', function () {
|
||||||
|
return marshallToVm(req.getTimeout(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'getTimeout', getTimeout);
|
||||||
|
getTimeout.dispose();
|
||||||
|
|
||||||
|
let setTimeout = vm.newFunction('setTimeout', function (timeout) {
|
||||||
|
req.setTimeout(vm.dump(timeout));
|
||||||
|
});
|
||||||
|
vm.setProp(reqObject, 'setTimeout', setTimeout);
|
||||||
|
setTimeout.dispose();
|
||||||
|
|
||||||
|
vm.setProp(vm.global, 'req', reqObject);
|
||||||
|
reqObject.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addBrunoRequestShimToContext;
|
@ -0,0 +1,55 @@
|
|||||||
|
const { marshallToVm } = require('../utils');
|
||||||
|
|
||||||
|
const addBrunoResponseShimToContext = (vm, res) => {
|
||||||
|
const resObject = vm.newObject();
|
||||||
|
|
||||||
|
const status = marshallToVm(res?.status, vm);
|
||||||
|
const headers = marshallToVm(res?.headers, vm);
|
||||||
|
const body = marshallToVm(res?.body, vm);
|
||||||
|
const responseTime = marshallToVm(res?.responseTime, vm);
|
||||||
|
|
||||||
|
vm.setProp(resObject, 'status', status);
|
||||||
|
vm.setProp(resObject, 'headers', headers);
|
||||||
|
vm.setProp(resObject, 'body', body);
|
||||||
|
vm.setProp(resObject, 'responseTime', responseTime);
|
||||||
|
|
||||||
|
status.dispose();
|
||||||
|
headers.dispose();
|
||||||
|
body.dispose();
|
||||||
|
responseTime.dispose();
|
||||||
|
|
||||||
|
let getStatus = vm.newFunction('getStatus', function () {
|
||||||
|
return marshallToVm(res.getStatus(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(resObject, 'getStatus', getStatus);
|
||||||
|
getStatus.dispose();
|
||||||
|
|
||||||
|
let getHeader = vm.newFunction('getHeader', function (name) {
|
||||||
|
return marshallToVm(res.getHeader(vm.dump(name)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(resObject, 'getHeader', getHeader);
|
||||||
|
getHeader.dispose();
|
||||||
|
|
||||||
|
let getHeaders = vm.newFunction('getHeaders', function () {
|
||||||
|
return marshallToVm(res.getHeaders(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(resObject, 'getHeaders', getHeaders);
|
||||||
|
getHeaders.dispose();
|
||||||
|
|
||||||
|
let getBody = vm.newFunction('getBody', function () {
|
||||||
|
return marshallToVm(res.getBody(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(resObject, 'getBody', getBody);
|
||||||
|
getBody.dispose();
|
||||||
|
|
||||||
|
let getResponseTime = vm.newFunction('getResponseTime', function () {
|
||||||
|
return marshallToVm(res.getResponseTime(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(resObject, 'getResponseTime', getResponseTime);
|
||||||
|
getResponseTime.dispose();
|
||||||
|
|
||||||
|
vm.setProp(vm.global, 'res', resObject);
|
||||||
|
resObject.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addBrunoResponseShimToContext;
|
46
packages/bruno-js/src/sandbox/quickjs/shims/console.js
Normal file
46
packages/bruno-js/src/sandbox/quickjs/shims/console.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const addConsoleShimToContext = (vm, console) => {
|
||||||
|
if (!console) return;
|
||||||
|
|
||||||
|
const consoleHandle = vm.newObject();
|
||||||
|
|
||||||
|
const logHandle = vm.newFunction('log', (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
console?.log?.(...nativeArgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
const debugHandle = vm.newFunction('debug', (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
console?.debug?.(...nativeArgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
const infoHandle = vm.newFunction('info', (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
console?.info?.(...nativeArgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
const warnHandle = vm.newFunction('warn', (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
console?.warn?.(...nativeArgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorHandle = vm.newFunction('error', (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
console?.error?.(...nativeArgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.setProp(consoleHandle, 'log', logHandle);
|
||||||
|
vm.setProp(consoleHandle, 'debug', debugHandle);
|
||||||
|
vm.setProp(consoleHandle, 'info', infoHandle);
|
||||||
|
vm.setProp(consoleHandle, 'warn', warnHandle);
|
||||||
|
vm.setProp(consoleHandle, 'error', errorHandle);
|
||||||
|
|
||||||
|
vm.setProp(vm.global, 'console', consoleHandle);
|
||||||
|
consoleHandle.dispose();
|
||||||
|
logHandle.dispose();
|
||||||
|
debugHandle.dispose();
|
||||||
|
infoHandle.dispose();
|
||||||
|
warnHandle.dispose();
|
||||||
|
errorHandle.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addConsoleShimToContext;
|
72
packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js
Normal file
72
packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const { cleanJson } = require('../../../../utils');
|
||||||
|
const { marshallToVm } = require('../../utils');
|
||||||
|
|
||||||
|
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
||||||
|
|
||||||
|
const addAxiosShimToContext = async (vm) => {
|
||||||
|
methods?.forEach((method) => {
|
||||||
|
const axiosHandle = vm.newFunction(method, (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
const promise = vm.newPromise();
|
||||||
|
axios[method](...nativeArgs)
|
||||||
|
.then((response) => {
|
||||||
|
const { status, headers, data } = response || {};
|
||||||
|
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
promise.resolve(
|
||||||
|
marshallToVm(
|
||||||
|
cleanJson({
|
||||||
|
message: err.message
|
||||||
|
}),
|
||||||
|
vm
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
promise.settled.then(vm.runtime.executePendingJobs);
|
||||||
|
return promise.handle;
|
||||||
|
});
|
||||||
|
axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios__${method}`, handle));
|
||||||
|
});
|
||||||
|
|
||||||
|
const axiosHandle = vm.newFunction('axios', (...args) => {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
const promise = vm.newPromise();
|
||||||
|
axios(...nativeArgs)
|
||||||
|
.then((response) => {
|
||||||
|
const { status, headers, data } = response || {};
|
||||||
|
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
promise.resolve(
|
||||||
|
marshallToVm(
|
||||||
|
cleanJson({
|
||||||
|
message: err.message
|
||||||
|
}),
|
||||||
|
vm
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
promise.settled.then(vm.runtime.executePendingJobs);
|
||||||
|
return promise.handle;
|
||||||
|
});
|
||||||
|
axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios`, handle));
|
||||||
|
|
||||||
|
vm.evalCode(
|
||||||
|
`
|
||||||
|
globalThis.axios = __bruno__axios;
|
||||||
|
${methods
|
||||||
|
?.map((method) => {
|
||||||
|
return `globalThis.axios.${method} = __bruno__axios__${method};`;
|
||||||
|
})
|
||||||
|
?.join('\n')}
|
||||||
|
globalThis.requireObject = {
|
||||||
|
...globalThis.requireObject,
|
||||||
|
axios: globalThis.axios,
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addAxiosShimToContext;
|
13
packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js
Normal file
13
packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const addAxiosShimToContext = require('./axios');
|
||||||
|
const addNanoidShimToContext = require('./nanoid');
|
||||||
|
const addPathShimToContext = require('./path');
|
||||||
|
const addUuidShimToContext = require('./uuid');
|
||||||
|
|
||||||
|
const addLibraryShimsToContext = async (vm) => {
|
||||||
|
await addNanoidShimToContext(vm);
|
||||||
|
await addAxiosShimToContext(vm);
|
||||||
|
await addUuidShimToContext(vm);
|
||||||
|
await addPathShimToContext(vm);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addLibraryShimsToContext;
|
24
packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js
Normal file
24
packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const { nanoid } = require('nanoid');
|
||||||
|
const { marshallToVm } = require('../../utils');
|
||||||
|
|
||||||
|
const addNanoidShimToContext = async (vm) => {
|
||||||
|
let _nanoid = vm.newFunction('nanoid', function () {
|
||||||
|
let v = nanoid();
|
||||||
|
return marshallToVm(v, vm);
|
||||||
|
});
|
||||||
|
vm.setProp(vm.global, '__bruno__nanoid', _nanoid);
|
||||||
|
_nanoid.dispose();
|
||||||
|
|
||||||
|
vm.evalCode(
|
||||||
|
`
|
||||||
|
globalThis.nanoid = {};
|
||||||
|
globalThis.nanoid.nanoid = globalThis.__bruno__nanoid;
|
||||||
|
globalThis.requireObject = {
|
||||||
|
...globalThis.requireObject,
|
||||||
|
'nanoid': globalThis.nanoid
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addNanoidShimToContext;
|
28
packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js
Normal file
28
packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { marshallToVm } = require('../../utils');
|
||||||
|
|
||||||
|
const fns = ['resolve'];
|
||||||
|
|
||||||
|
const addPathShimToContext = async (vm) => {
|
||||||
|
fns.forEach((fn) => {
|
||||||
|
let fnHandle = vm.newFunction(fn, function (...args) {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
return marshallToVm(path[fn](...nativeArgs), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(vm.global, `__bruno__path__${fn}`, fnHandle);
|
||||||
|
fnHandle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.evalCode(
|
||||||
|
`
|
||||||
|
globalThis.path = {};
|
||||||
|
${fns?.map((fn, idx) => `globalThis.path.${fn} = __bruno__path__${fn}`).join('\n')}
|
||||||
|
globalThis.requireObject = {
|
||||||
|
...(globalThis.requireObject || {}),
|
||||||
|
path: globalThis.path,
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addPathShimToContext;
|
30
packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js
Normal file
30
packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const uuid = require('uuid');
|
||||||
|
const { marshallToVm } = require('../../utils');
|
||||||
|
|
||||||
|
const fns = ['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate'];
|
||||||
|
|
||||||
|
const addUuidShimToContext = async (vm) => {
|
||||||
|
fns.forEach((fn) => {
|
||||||
|
let fnHandle = vm.newFunction(fn, function (...args) {
|
||||||
|
const nativeArgs = args.map(vm.dump);
|
||||||
|
return marshallToVm(uuid[fn](...nativeArgs), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(vm.global, `__bruno__uuid__${fn}`, fnHandle);
|
||||||
|
fnHandle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.evalCode(
|
||||||
|
`
|
||||||
|
globalThis.uuid = {};
|
||||||
|
${['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']
|
||||||
|
?.map((fn, idx) => `globalThis.uuid.${fn} = __bruno__uuid__${fn}`)
|
||||||
|
.join('\n')}
|
||||||
|
globalThis.requireObject = {
|
||||||
|
...globalThis.requireObject,
|
||||||
|
uuid: globalThis.uuid,
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addUuidShimToContext;
|
31
packages/bruno-js/src/sandbox/quickjs/shims/local-module.js
Normal file
31
packages/bruno-js/src/sandbox/quickjs/shims/local-module.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { marshallToVm } = require('../utils');
|
||||||
|
|
||||||
|
const addLocalModuleLoaderShimToContext = (vm, collectionPath) => {
|
||||||
|
let loadLocalModuleHandle = vm.newFunction('loadLocalModule', function (module) {
|
||||||
|
const filename = vm.dump(module);
|
||||||
|
|
||||||
|
// Check if the filename has an extension
|
||||||
|
const hasExtension = path.extname(filename) !== '';
|
||||||
|
const resolvedFilename = hasExtension ? filename : `${filename}.js`;
|
||||||
|
|
||||||
|
// Resolve the file path and check if it's within the collectionPath
|
||||||
|
const filePath = path.resolve(collectionPath, resolvedFilename);
|
||||||
|
const relativePath = path.relative(collectionPath, filePath);
|
||||||
|
|
||||||
|
// Ensure the resolved file path is inside the collectionPath
|
||||||
|
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
||||||
|
throw new Error('Access to files outside of the collectionPath is not allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = fs.readFileSync(filePath).toString();
|
||||||
|
|
||||||
|
return marshallToVm(code, vm);
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.setProp(vm.global, '__brunoLoadLocalModule', loadLocalModuleHandle);
|
||||||
|
loadLocalModuleHandle.dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addLocalModuleLoaderShimToContext;
|
63
packages/bruno-js/src/sandbox/quickjs/shims/test.js
Normal file
63
packages/bruno-js/src/sandbox/quickjs/shims/test.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
const { marshallToVm } = require('../utils');
|
||||||
|
|
||||||
|
const addBruShimToContext = (vm, __brunoTestResults) => {
|
||||||
|
let addResult = vm.newFunction('addResult', function (v) {
|
||||||
|
__brunoTestResults.addResult(vm.dump(v));
|
||||||
|
});
|
||||||
|
vm.setProp(vm.global, '__bruno__addResult', addResult);
|
||||||
|
addResult.dispose();
|
||||||
|
|
||||||
|
let getResults = vm.newFunction('getResults', function () {
|
||||||
|
return marshallToVm(__brunoTestResults.getResults(), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(vm.global, '__bruno__getResults', getResults);
|
||||||
|
getResults.dispose();
|
||||||
|
|
||||||
|
vm.evalCode(
|
||||||
|
`
|
||||||
|
globalThis.expect = require('chai').expect;
|
||||||
|
globalThis.assert = require('chai').assert;
|
||||||
|
|
||||||
|
globalThis.__brunoTestResults = {
|
||||||
|
addResult: globalThis.__bruno__addResult,
|
||||||
|
getResults: globalThis.__bruno__getResults,
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error {
|
||||||
|
constructor(message, props, ssf) {
|
||||||
|
super(message);
|
||||||
|
this.name = "AssertionError";
|
||||||
|
Object.assign(this, props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.Test = (__brunoTestResults) => async (description, callback) => {
|
||||||
|
try {
|
||||||
|
await callback();
|
||||||
|
__brunoTestResults.addResult({ description, status: "pass" });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DummyChaiAssertionError) {
|
||||||
|
const { message, actual, expected } = error;
|
||||||
|
__brunoTestResults.addResult({
|
||||||
|
description,
|
||||||
|
status: "fail",
|
||||||
|
error: message,
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
globalThis.__bruno__addResult({
|
||||||
|
description,
|
||||||
|
status: "fail",
|
||||||
|
error: error.message || "An unexpected error occurred.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.test = Test(__brunoTestResults);
|
||||||
|
`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addBruShimToContext;
|
33
packages/bruno-js/src/sandbox/quickjs/utils/index.js
Normal file
33
packages/bruno-js/src/sandbox/quickjs/utils/index.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const marshallToVm = (value, vm) => {
|
||||||
|
if (value === undefined) {
|
||||||
|
return vm.undefined;
|
||||||
|
}
|
||||||
|
if (value === null) {
|
||||||
|
return vm.null;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return vm.newString(value);
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
return vm.newNumber(value);
|
||||||
|
} else if (typeof value === 'boolean') {
|
||||||
|
return vm.newBoolean(value);
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const arr = vm.newArray();
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
vm.setProp(arr, i, marshallToVm(value[i], vm));
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
} else {
|
||||||
|
const obj = vm.newObject();
|
||||||
|
for (const key in value) {
|
||||||
|
vm.setProp(obj, key, marshallToVm(value[key], vm));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
marshallToVm
|
||||||
|
};
|
@ -142,10 +142,15 @@ const cleanJson = (data) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appendAwaitToTestFunc = (str) => {
|
||||||
|
return str.replace(/(?<!\.\s*)(?<!await\s)(test\()/g, 'await $1');
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
evaluateJsExpression,
|
evaluateJsExpression,
|
||||||
evaluateJsTemplateLiteral,
|
evaluateJsTemplateLiteral,
|
||||||
createResponseParser,
|
createResponseParser,
|
||||||
internalExpressionCache,
|
internalExpressionCache,
|
||||||
cleanJson
|
cleanJson,
|
||||||
|
appendAwaitToTestFunc
|
||||||
};
|
};
|
||||||
|
@ -35,7 +35,7 @@ describe('runtime', () => {
|
|||||||
})
|
})
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const runtime = new TestRuntime();
|
const runtime = new TestRuntime({ runtime: 'vm2' });
|
||||||
const result = await runtime.runTests(
|
const result = await runtime.runTests(
|
||||||
testFile,
|
testFile,
|
||||||
{ ...baseRequest },
|
{ ...baseRequest },
|
||||||
@ -71,7 +71,7 @@ describe('runtime', () => {
|
|||||||
})
|
})
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const runtime = new TestRuntime();
|
const runtime = new TestRuntime({ runtime: 'vm2' });
|
||||||
const result = await runtime.runTests(
|
const result = await runtime.runTests(
|
||||||
testFile,
|
testFile,
|
||||||
{ ...baseRequest },
|
{ ...baseRequest },
|
||||||
@ -114,7 +114,7 @@ describe('runtime', () => {
|
|||||||
bru.setVar('validation', validate(new Date().toISOString()))
|
bru.setVar('validation', validate(new Date().toISOString()))
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const runtime = new ScriptRuntime();
|
const runtime = new ScriptRuntime({ runtime: 'vm2' });
|
||||||
const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env);
|
const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env);
|
||||||
expect(result.runtimeVariables.validation).toBeTruthy();
|
expect(result.runtimeVariables.validation).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -160,7 +160,7 @@ describe('runtime', () => {
|
|||||||
bru.setVar('validation', validate(new Date().toISOString()))
|
bru.setVar('validation', validate(new Date().toISOString()))
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const runtime = new ScriptRuntime();
|
const runtime = new ScriptRuntime({ runtime: 'vm2' });
|
||||||
const result = await runtime.runResponseScript(
|
const result = await runtime.runResponseScript(
|
||||||
script,
|
script,
|
||||||
{ ...baseRequest },
|
{ ...baseRequest },
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
const { describe, it, expect } = require('@jest/globals');
|
const { describe, it, expect } = require('@jest/globals');
|
||||||
const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser } = require('../src/utils');
|
const {
|
||||||
|
evaluateJsExpression,
|
||||||
|
internalExpressionCache: cache,
|
||||||
|
createResponseParser,
|
||||||
|
appendAwaitToTestFunc
|
||||||
|
} = require('../src/utils');
|
||||||
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
describe('expression evaluation', () => {
|
describe('expression evaluation', () => {
|
||||||
@ -137,4 +142,70 @@ describe('utils', () => {
|
|||||||
expect(value).toBe(20);
|
expect(value).toBe(20);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('appendAwaitToTestFunc function', () => {
|
||||||
|
it('example 1', () => {
|
||||||
|
const inputTestsString = `
|
||||||
|
test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
const ouutputTestsString = appendAwaitToTestFunc(inputTestsString);
|
||||||
|
expect(ouutputTestsString).toBe(`
|
||||||
|
await test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('example 2', () => {
|
||||||
|
const inputTestsString = `
|
||||||
|
await test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
const ouutputTestsString = appendAwaitToTestFunc(inputTestsString);
|
||||||
|
expect(ouutputTestsString).toBe(`
|
||||||
|
await test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: echo json
|
name: echo json
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: echo plaintext
|
name: echo plaintext
|
||||||
type: http
|
type: http
|
||||||
seq: 2
|
seq: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: echo xml parsed
|
name: echo xml parsed
|
||||||
type: http
|
type: http
|
||||||
seq: 3
|
seq: 4
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
meta {
|
meta {
|
||||||
name: echo xml raw
|
name: echo xml raw
|
||||||
type: http
|
type: http
|
||||||
seq: 4
|
seq: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
|
@ -5,4 +5,6 @@ vars {
|
|||||||
env.var1: envVar1
|
env.var1: envVar1
|
||||||
env-var2: envVar2
|
env-var2: envVar2
|
||||||
bark: {{process.env.PROC_ENV_VAR}}
|
bark: {{process.env.PROC_ENV_VAR}}
|
||||||
|
foo: bar
|
||||||
|
testSetEnvVar: bruno-29653
|
||||||
}
|
}
|
||||||
|
5
packages/bruno-tests/collection/lib/constants.js
Normal file
5
packages/bruno-tests/collection/lib/constants.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const PI = 3.14;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
PI
|
||||||
|
};
|
@ -1,5 +1,9 @@
|
|||||||
|
const { PI } = require('./constants');
|
||||||
|
|
||||||
const sum = (a, b) => a + b;
|
const sum = (a, b) => a + b;
|
||||||
|
const areaOfCircle = (radius) => PI * radius * radius;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sum
|
sum,
|
||||||
|
areaOfCircle
|
||||||
};
|
};
|
||||||
|
@ -9,52 +9,3 @@ get {
|
|||||||
body: none
|
body: none
|
||||||
auth: none
|
auth: none
|
||||||
}
|
}
|
||||||
|
|
||||||
auth:awsv4 {
|
|
||||||
accessKeyId: a
|
|
||||||
secretAccessKey: b
|
|
||||||
sessionToken: c
|
|
||||||
service: d
|
|
||||||
region: e
|
|
||||||
profileName: f
|
|
||||||
}
|
|
||||||
|
|
||||||
vars:pre-request {
|
|
||||||
m4: true
|
|
||||||
pong: pong
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
res.responseTime: lte 2000
|
|
||||||
res.body: eq {{pong}}
|
|
||||||
}
|
|
||||||
|
|
||||||
tests {
|
|
||||||
test("should ping pong", function() {
|
|
||||||
const data = res.getBody();
|
|
||||||
expect(data).to.equal(bru.getRequestVar("pong"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
# API Documentation
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Welcome to the API documentation for [Your API Name]. This document provides instructions on how to make requests to the API and covers available authentication methods.
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
Before making requests to the API, you need to authenticate your application. [Your API Name] supports the following authentication methods:
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
To use API key authentication, include your API key in the request headers as follows:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/endpoint
|
|
||||||
Host: api.example.com
|
|
||||||
Authorization: Bearer YOUR_API_KEY
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
meta {
|
||||||
|
name: getEnvName
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
const envName = bru.getEnvName();
|
||||||
|
bru.setVar("testEnvName", envName);
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("should get env name in scripts", function() {
|
||||||
|
const testEnvName = bru.getVar("testEnvName");
|
||||||
|
expect(testEnvName).to.equal("Prod");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
meta {
|
||||||
|
name: getEnvVar
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("should get env var in scripts", function() {
|
||||||
|
const host = bru.getEnvVar("host")
|
||||||
|
expect(host).to.equal("https://testbench-sanity.usebruno.com");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
meta {
|
||||||
|
name: getProcessEnv
|
||||||
|
type: http
|
||||||
|
seq: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("bru.getProcessEnv()", function() {
|
||||||
|
const v = bru.getProcessEnv("PROC_ENV_VAR");
|
||||||
|
expect(v).to.equal("woof");
|
||||||
|
});
|
||||||
|
}
|
19
packages/bruno-tests/collection/scripting/api/bru/getVar.bru
Normal file
19
packages/bruno-tests/collection/scripting/api/bru/getVar.bru
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
meta {
|
||||||
|
name: getVar
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("should get var in scripts", function() {
|
||||||
|
const testSetVar = bru.getVar("testSetVar");
|
||||||
|
expect(testSetVar).to.equal("bruno-test-87267");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
meta {
|
||||||
|
name: setEnvVar
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
bru.setEnvVar("testSetEnvVar", "bruno-29653")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("should set env var in scripts", function() {
|
||||||
|
const testSetEnvVar = bru.getEnvVar("testSetEnvVar")
|
||||||
|
expect(testSetEnvVar).to.equal("bruno-29653");
|
||||||
|
});
|
||||||
|
}
|
22
packages/bruno-tests/collection/scripting/api/bru/setVar.bru
Normal file
22
packages/bruno-tests/collection/scripting/api/bru/setVar.bru
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: setVar
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
bru.setVar("testSetVar", "bruno-test-87267")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("should get var in scripts", function() {
|
||||||
|
const testSetVar = bru.getVar("testSetVar");
|
||||||
|
expect(testSetVar).to.equal("bruno-test-87267");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
meta {
|
||||||
|
name: getBody
|
||||||
|
type: http
|
||||||
|
seq: 9
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: asd
|
||||||
|
password: j
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token:
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"hello": "bruno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.getBody()", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
meta {
|
||||||
|
name: getHeader
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
bruno: is-awesome
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.getHeader(name)", function() {
|
||||||
|
const h = req.getHeader('bruno');
|
||||||
|
expect(h).to.equal("is-awesome");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
meta {
|
||||||
|
name: getHeaders
|
||||||
|
type: http
|
||||||
|
seq: 7
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
bruno: is-awesome
|
||||||
|
della: is-beautiful
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.getHeaders()", function() {
|
||||||
|
const h = req.getHeaders();
|
||||||
|
expect(h.bruno).to.equal("is-awesome");
|
||||||
|
expect(h.della).to.equal("is-beautiful");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
meta {
|
||||||
|
name: getMethod
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.getMethod()()", function() {
|
||||||
|
const method = req.getMethod();
|
||||||
|
expect(method).to.equal("GET");
|
||||||
|
});
|
||||||
|
}
|
23
packages/bruno-tests/collection/scripting/api/req/getUrl.bru
Normal file
23
packages/bruno-tests/collection/scripting/api/req/getUrl.bru
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
meta {
|
||||||
|
name: getUrl
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.getUrl()", function() {
|
||||||
|
const url = req.getUrl();
|
||||||
|
expect(url).to.equal("https://testbench-sanity.usebruno.com/ping");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
meta {
|
||||||
|
name: setBody
|
||||||
|
type: http
|
||||||
|
seq: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: asd
|
||||||
|
password: j
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token:
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"hello": "bruno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
req.setBody({
|
||||||
|
"bruno": "is awesome"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.setBody()", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data).to.eql({
|
||||||
|
"bruno": "is awesome"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
meta {
|
||||||
|
name: setHeader
|
||||||
|
type: http
|
||||||
|
seq: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
bruno: is-awesome
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
req.setHeader('bruno', 'is-the-future');
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.setHeader(name)", function() {
|
||||||
|
const h = req.getHeader('bruno');
|
||||||
|
expect(h).to.equal("is-the-future");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
meta {
|
||||||
|
name: setHeaders
|
||||||
|
type: http
|
||||||
|
seq: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
bruno: is-awesome
|
||||||
|
della: is-beautiful
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
req.setHeaders({
|
||||||
|
"content-type": "application/text",
|
||||||
|
"transaction-id": "foobar"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.setHeaders()", function() {
|
||||||
|
const h = req.getHeaders();
|
||||||
|
expect(h['content-type']).to.equal("application/text");
|
||||||
|
expect(h['transaction-id']).to.equal("foobar");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
meta {
|
||||||
|
name: setMethod
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
req.setMethod("GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.setMethod()()", function() {
|
||||||
|
const method = req.getMethod();
|
||||||
|
expect(method).to.equal("GET");
|
||||||
|
});
|
||||||
|
}
|
28
packages/bruno-tests/collection/scripting/api/req/setUrl.bru
Normal file
28
packages/bruno-tests/collection/scripting/api/req/setUrl.bru
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
meta {
|
||||||
|
name: setUrl
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping/invalid
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
req.setUrl("https://testbench-sanity.usebruno.com/ping");
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.setUrl()", function() {
|
||||||
|
const url = req.getUrl();
|
||||||
|
expect(url).to.equal("https://testbench-sanity.usebruno.com/ping");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
meta {
|
||||||
|
name: getBody
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: asd
|
||||||
|
password: j
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token:
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"hello": "bruno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("res.getBody()", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
meta {
|
||||||
|
name: getHeader
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: asd
|
||||||
|
password: j
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token:
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"hello": "bruno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("res.getHeader(name)", function() {
|
||||||
|
const server = res.getHeader('x-powered-by');
|
||||||
|
expect(server).to.eql('Express');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
meta {
|
||||||
|
name: getHeaders
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: asd
|
||||||
|
password: j
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token:
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"hello": "bruno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("res.getHeaders(name)", function() {
|
||||||
|
const h = res.getHeaders();
|
||||||
|
expect(h['x-powered-by']).to.eql('Express');
|
||||||
|
expect(h['content-length']).to.eql('17');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
meta {
|
||||||
|
name: getResponseTime
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: asd
|
||||||
|
password: j
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token:
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"hello": "bruno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("res.getResponseTime()", function() {
|
||||||
|
const responseTime = res.getResponseTime();
|
||||||
|
expect(typeof responseTime).to.eql("number");
|
||||||
|
expect(responseTime > 0).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
meta {
|
||||||
|
name: getStatus
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: eq pong
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("res.getStatus()", function() {
|
||||||
|
const status = res.getStatus()
|
||||||
|
expect(status).to.equal(200);
|
||||||
|
});
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: get-env-name
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{host}}/ping
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
auth:awsv4 {
|
|
||||||
accessKeyId: a
|
|
||||||
secretAccessKey: b
|
|
||||||
sessionToken: c
|
|
||||||
service: d
|
|
||||||
region: e
|
|
||||||
profileName: f
|
|
||||||
}
|
|
||||||
|
|
||||||
script:pre-request {
|
|
||||||
const envName = bru.getEnvName();
|
|
||||||
bru.setVar("testEnvName", envName);
|
|
||||||
}
|
|
||||||
|
|
||||||
tests {
|
|
||||||
test("should get env name in scripts", function() {
|
|
||||||
const testEnvName = bru.getVar("testEnvName");
|
|
||||||
expect(testEnvName).to.equal("Prod");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
# API Documentation
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Welcome to the API documentation for [Your API Name]. This document provides instructions on how to make requests to the API and covers available authentication methods.
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
Before making requests to the API, you need to authenticate your application. [Your API Name] supports the following authentication methods:
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
To use API key authentication, include your API key in the request headers as follows:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/endpoint
|
|
||||||
Host: api.example.com
|
|
||||||
Authorization: Bearer YOUR_API_KEY
|
|
||||||
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: get-env-var
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{host}}/ping
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
auth:awsv4 {
|
|
||||||
accessKeyId: a
|
|
||||||
secretAccessKey: b
|
|
||||||
sessionToken: c
|
|
||||||
service: d
|
|
||||||
region: e
|
|
||||||
profileName: f
|
|
||||||
}
|
|
||||||
|
|
||||||
tests {
|
|
||||||
test("should get env var in scripts", function() {
|
|
||||||
const host = bru.getEnvVar("host")
|
|
||||||
expect(host).to.equal("https://testbench-sanity.usebruno.com");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
# API Documentation
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Welcome to the API documentation for [Your API Name]. This document provides instructions on how to make requests to the API and covers available authentication methods.
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
Before making requests to the API, you need to authenticate your application. [Your API Name] supports the following authentication methods:
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
To use API key authentication, include your API key in the request headers as follows:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/endpoint
|
|
||||||
Host: api.example.com
|
|
||||||
Authorization: Bearer YOUR_API_KEY
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,34 @@
|
|||||||
|
meta {
|
||||||
|
name: axios-pre-req-script
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
const url = "https://testbench-sanity.usebruno.com/api/echo/json";
|
||||||
|
const response = await axios.post(url, {
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
|
||||||
|
req.setBody(response.data);
|
||||||
|
req.setMethod("POST");
|
||||||
|
req.setUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("req.getBody()", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data).to.eql({
|
||||||
|
"hello": "bruno"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
meta {
|
||||||
|
name: crypto-js-pre-request-script
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
var CryptoJS = require("crypto-js");
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
|
||||||
|
var originalText = bytes.toString(CryptoJS.enc.Utf8);
|
||||||
|
|
||||||
|
bru.setVar('crypto-test-message', originalText);
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("crypto message", function() {
|
||||||
|
const data = bru.getVar('crypto-test-message');
|
||||||
|
bru.setVar('crypto-test-message', null);
|
||||||
|
expect(data).to.eql('my message');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
meta {
|
||||||
|
name: nanoid
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
const { nanoid } = require("nanoid");
|
||||||
|
|
||||||
|
bru.setVar("nanoid-test-id", nanoid());
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("nanoid var", function() {
|
||||||
|
const id = bru.getVar('nanoid-test-id');
|
||||||
|
let isValidNanoid = /^[a-zA-Z0-9_-]{21}$/.test(id)
|
||||||
|
bru.setVar('nanoid-test-id', null);
|
||||||
|
expect(isValidNanoid).to.eql(true);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
meta {
|
||||||
|
name: uuid
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
const { v4 } = require("uuid");
|
||||||
|
|
||||||
|
bru.setVar("uuid-test-id", v4());
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("uuid var", function() {
|
||||||
|
const id = bru.getVar('uuid-test-id');
|
||||||
|
let isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);
|
||||||
|
bru.setVar('uuid-test-id', null);
|
||||||
|
expect(isValidUuid).to.eql(true);
|
||||||
|
});
|
||||||
|
}
|
32
packages/bruno-tests/collection/scripting/js/setTimeout.bru
Normal file
32
packages/bruno-tests/collection/scripting/js/setTimeout.bru
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
meta {
|
||||||
|
name: setTimeout
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/ping
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
bru.setVar("test-js-set-timeout", "");
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
bru.setVar("test-js-set-timeout", "bruno");
|
||||||
|
resolve();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const v = bru.getVar("test-js-set-timeout");
|
||||||
|
bru.setVar("test-js-set-timeout", v + "-is-awesome");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("setTimeout()", function() {
|
||||||
|
const v = bru.getVar("test-js-set-timeout")
|
||||||
|
expect(v).to.eql("bruno-is-awesome");
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
meta {
|
||||||
|
name: sum (without js extn)
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{host}}/api/echo/json
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"a": 1,
|
||||||
|
"b": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
}
|
||||||
|
|
||||||
|
script:pre-request {
|
||||||
|
const math = require("./lib/math");
|
||||||
|
console.log(math, 'math');
|
||||||
|
|
||||||
|
const body = req.getBody();
|
||||||
|
body.sum = math.sum(body.a, body.b);
|
||||||
|
body.areaOfCircle = math.areaOfCircle(2);
|
||||||
|
|
||||||
|
req.setBody(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
"sum": 3,
|
||||||
|
"areaOfCircle": 12.56
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -22,10 +22,9 @@ assert {
|
|||||||
}
|
}
|
||||||
|
|
||||||
script:pre-request {
|
script:pre-request {
|
||||||
const math = require("./lib/math");
|
const math = require("./lib/math.js");
|
||||||
|
|
||||||
const body = req.getBody();
|
const body = req.getBody();
|
||||||
body.sum = body.a + body.b;
|
body.sum = math.sum(body.a, body.b);
|
||||||
|
|
||||||
req.setBody(body);
|
req.setBody(body);
|
||||||
}
|
}
|
||||||
@ -39,4 +38,22 @@ tests {
|
|||||||
"sum": 3
|
"sum": 3
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
"sum": 3
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return json", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(res.getBody()).to.eql({
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
"sum": 3
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: set-env-var
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{host}}/ping
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
auth:awsv4 {
|
|
||||||
accessKeyId: a
|
|
||||||
secretAccessKey: b
|
|
||||||
sessionToken: c
|
|
||||||
service: d
|
|
||||||
region: e
|
|
||||||
profileName: f
|
|
||||||
}
|
|
||||||
|
|
||||||
script:post-response {
|
|
||||||
bru.setEnvVar("testSetEnvVar", "bruno-29653")
|
|
||||||
}
|
|
||||||
|
|
||||||
tests {
|
|
||||||
test("should set env var in scripts", function() {
|
|
||||||
const testSetEnvVar = bru.getEnvVar("testSetEnvVar")
|
|
||||||
console.log(testSetEnvVar);
|
|
||||||
expect(testSetEnvVar).to.equal("bruno-29653");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
# API Documentation
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Welcome to the API documentation for [Your API Name]. This document provides instructions on how to make requests to the API and covers available authentication methods.
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
Before making requests to the API, you need to authenticate your application. [Your API Name] supports the following authentication methods:
|
|
||||||
|
|
||||||
### API Key
|
|
||||||
|
|
||||||
To use API key authentication, include your API key in the request headers as follows:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/endpoint
|
|
||||||
Host: api.example.com
|
|
||||||
Authorization: Bearer YOUR_API_KEY
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user