mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-03 01:18:32 +00:00
Compare commits
27 Commits
v0.16.0
...
feature/au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1e330dc5 | ||
|
|
51ee37cf96 | ||
|
|
a6b19605b5 | ||
|
|
7ba471f26a | ||
|
|
f23dcf50a4 | ||
|
|
86cda2cf5a | ||
|
|
00b6e007af | ||
|
|
7313d1b4d7 | ||
|
|
8f803234ce | ||
|
|
76a743b74e | ||
|
|
c623aa0909 | ||
|
|
3eb26834c7 | ||
|
|
64a5852227 | ||
|
|
6471ca74c3 | ||
|
|
f77d955839 | ||
|
|
9947a55b8d | ||
|
|
a71555725c | ||
|
|
c9ec6902a5 | ||
|
|
c9c675e187 | ||
|
|
0517b2685e | ||
|
|
5d01c0a765 | ||
|
|
f3925923c9 | ||
|
|
6facdfd66b | ||
|
|
0f211131b1 | ||
|
|
cd3b8a948e | ||
|
|
f695036721 | ||
|
|
3661fa7df3 |
@@ -1,6 +1,6 @@
|
||||
## Development
|
||||
|
||||
Bruno is deing developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
|
||||
Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
|
||||
|
||||
### Dependencies
|
||||
* NodeJS v18
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.4.0",
|
||||
"@usebruno/schema": "0.5.0",
|
||||
"axios": "^0.26.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.65.2",
|
||||
|
||||
@@ -29,7 +29,7 @@ const BrunoSupport = ({ onClose }) => {
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
|
||||
<IconBrandGithub size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Github</span>
|
||||
<span className="label ml-2">GitHub</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
|
||||
@@ -80,7 +80,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// Ensure the changes caused by this update are not interpretted as
|
||||
// Ensure the changes caused by this update are not interpreted as
|
||||
// user-input changes which could otherwise result in an infinite
|
||||
// event loop.
|
||||
this.ignoreChangeEvent = true;
|
||||
|
||||
@@ -43,7 +43,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
&.border-top {
|
||||
border-top: solid 1px ${(props) => props.theme.dropdown.seperator};
|
||||
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
toast.success('Environment created in collection');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while created the environment'));
|
||||
.catch(() => toast.error('An error occurred while created the environment'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
|
||||
toast.success('Environment deleted successfully');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while deleting the environment'));
|
||||
.catch(() => toast.error('An error occurred while deleting the environment'));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -23,7 +23,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
type: 'CHANGES_SAVED'
|
||||
});
|
||||
})
|
||||
.catch(() => toast.error('An error occured while saving the changes'));
|
||||
.catch(() => toast.error('An error occurred while saving the changes'));
|
||||
};
|
||||
|
||||
const addVariable = () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
toast.success('Environment renamed successfully');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while renaming the environment'));
|
||||
.catch(() => toast.error('An error occurred while renaming the environment'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ModalHeader = ({ title, handleCancel }) => (
|
||||
<div className="bruno-modal-header">
|
||||
{title ? <div className="bruno-modal-heade-title">{title}</div> : null}
|
||||
{title ? <div className="bruno-modal-header-title">{title}</div> : null}
|
||||
{handleCancel ? (
|
||||
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
|
||||
×
|
||||
|
||||
@@ -27,7 +27,7 @@ const Support = () => {
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
|
||||
<IconBrandGithub size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Github</span>
|
||||
<span className="label ml-2">GitHub</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
|
||||
.auth-mode-selector {
|
||||
background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color};
|
||||
border-radius: 3px;
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
|
||||
.label-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.caret {
|
||||
color: rgb(140, 140, 140);
|
||||
fill: rgb(140 140 140);
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { useRef, forwardRef } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { IconCaretDown } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateRequestAuthMode } from 'providers/ReduxStore/slices/collections';
|
||||
import { humanizeRequestAuthMode } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const AuthMode = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none">
|
||||
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onModeChange = (value) => {
|
||||
dispatch(
|
||||
updateRequestAuthMode({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
mode: value
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('basic');
|
||||
}}
|
||||
>
|
||||
Basic Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('bearer');
|
||||
}}
|
||||
>
|
||||
Bearer Token
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('none');
|
||||
}}
|
||||
>
|
||||
No Auth
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default AuthMode;
|
||||
@@ -0,0 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
export default Wrapper;
|
||||
32
packages/bruno-app/src/components/RequestPane/Auth/index.js
Normal file
32
packages/bruno-app/src/components/RequestPane/Auth/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestBody = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
|
||||
|
||||
const onEdit = (value) => {
|
||||
// dispatch(
|
||||
// updateRequestBody({
|
||||
// content: value,
|
||||
// itemUid: item.uid,
|
||||
// collectionUid: collection.uid
|
||||
// })
|
||||
// );
|
||||
};
|
||||
|
||||
if (authMode === 'basic') {
|
||||
return <div>Basic Auth</div>;
|
||||
}
|
||||
|
||||
if (authMode === 'bearer') {
|
||||
return <div>Bearer Token</div>;
|
||||
}
|
||||
|
||||
return <StyledWrapper className="w-full">No Auth</StyledWrapper>;
|
||||
};
|
||||
export default RequestBody;
|
||||
@@ -114,7 +114,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
|
||||
return <div className="pb-4 px-4">An error occured!</div>;
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
@@ -125,7 +125,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex items-center tabs" role="tablist">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('query')} role="tab" onClick={() => selectTab('query')}>
|
||||
Query
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ const useGraphqlSchema = (endpoint, environment) => {
|
||||
.catch((err) => {
|
||||
setIsLoading(false);
|
||||
setError(err);
|
||||
toast.error('Error occured while loading GraphQL Schema');
|
||||
toast.error('Error occurred while loading GraphQL Schema');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import QueryParams from 'components/RequestPane/QueryParams';
|
||||
import RequestHeaders from 'components/RequestPane/RequestHeaders';
|
||||
import RequestBody from 'components/RequestPane/RequestBody';
|
||||
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
|
||||
import Auth from 'components/RequestPane/Auth';
|
||||
import AuthMode from 'components/RequestPane/Auth/AuthMode';
|
||||
import Vars from 'components/RequestPane/Vars';
|
||||
import Assertions from 'components/RequestPane/Assertions';
|
||||
import Script from 'components/RequestPane/Script';
|
||||
@@ -38,6 +40,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
case 'headers': {
|
||||
return <RequestHeaders item={item} collection={collection} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth item={item} collection={collection} />;
|
||||
}
|
||||
case 'vars': {
|
||||
return <Vars item={item} collection={collection} />;
|
||||
}
|
||||
@@ -62,7 +67,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
|
||||
return <div className="pb-4 px-4">An error occured!</div>;
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
@@ -73,7 +78,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex items-center tabs" role="tablist">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
|
||||
Query
|
||||
</div>
|
||||
@@ -83,6 +88,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
|
||||
Auth
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
|
||||
Vars
|
||||
</div>
|
||||
@@ -95,13 +103,16 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
|
||||
Tests
|
||||
</div>
|
||||
{/* Moved to post mvp */}
|
||||
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
|
||||
{focusedTab.requestPaneTab === 'body' ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<RequestBodyMode item={item} collection={collection} />
|
||||
</div>
|
||||
) : null}
|
||||
{focusedTab.requestPaneTab === 'auth' ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<AuthMode item={item} collection={collection} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>
|
||||
{getTabPanel(focusedTab.requestPaneTab)}
|
||||
|
||||
@@ -142,7 +142,7 @@ export default class QueryEditor extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// Ensure the changes caused by this update are not interpretted as
|
||||
// Ensure the changes caused by this update are not interpreted as
|
||||
// user-input changes which could otherwise result in an infinite
|
||||
// event loop.
|
||||
this.ignoreChangeEvent = true;
|
||||
|
||||
@@ -112,7 +112,7 @@ const RequestTabPanel = () => {
|
||||
}
|
||||
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
|
||||
return <div className="pb-4 px-4">An error occured!</div>;
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
border-bottom: 1px solid ${(props) => props.theme.requestTabs.borromBorder};
|
||||
border-bottom: 1px solid ${(props) => props.theme.requestTabs.bottomBorder};
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
|
||||
@@ -76,7 +76,7 @@ const RequestTabs = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Todo: Must support ephermal requests
|
||||
// Todo: Must support ephemeral requests
|
||||
return (
|
||||
<StyledWrapper className={getRootClassname()}>
|
||||
{newRequestModalOpen && (
|
||||
|
||||
@@ -99,7 +99,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex items-center px-3 tabs" role="tablist">
|
||||
<div className="flex flex-wrap items-center px-3 tabs" role="tablist">
|
||||
<div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>
|
||||
Response
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err ? err.message : 'An error occured while cloning the request');
|
||||
toast.error(err ? err.message : 'An error occurred while cloning the request');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ const RemoveCollection = ({ onClose, collection }) => {
|
||||
toast.success('Collection removed');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while removing the collection'));
|
||||
.catch(() => toast.error('An error occurred while removing the collection'));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,7 +19,7 @@ const CreateOrOpenCollection = () => {
|
||||
|
||||
const handleOpenCollection = () => {
|
||||
dispatch(openCollection()).catch(
|
||||
(err) => console.log(err) && toast.error('An error occured while opening the collection')
|
||||
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
|
||||
);
|
||||
};
|
||||
const CreateLink = () => (
|
||||
|
||||
@@ -36,7 +36,7 @@ const CreateCollection = ({ onClose }) => {
|
||||
toast.success('Collection created');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while creating the collection'));
|
||||
.catch(() => toast.error('An error occurred while creating the collection'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const NewFolder = ({ collection, item, onClose }) => {
|
||||
onSubmit: (values) => {
|
||||
dispatch(newFolder(values.folderName, collection.uid, item ? item.uid : null))
|
||||
.then(() => onClose())
|
||||
.catch((err) => toast.error(err ? err.message : 'An error occured while adding the request'));
|
||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ import toast from 'react-hot-toast';
|
||||
import { uuid } from 'utils/common';
|
||||
import Modal from 'components/Modal';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { newEphermalHttpRequest } from 'providers/ReduxStore/slices/collections';
|
||||
import { newEphemeralHttpRequest } from 'providers/ReduxStore/slices/collections';
|
||||
import { newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
|
||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
@@ -34,10 +34,10 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
})
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
if (isEphermal) {
|
||||
if (isEphemeral) {
|
||||
const uid = uuid();
|
||||
dispatch(
|
||||
newEphermalHttpRequest({
|
||||
newEphemeralHttpRequest({
|
||||
uid: uid,
|
||||
requestName: values.requestName,
|
||||
requestType: values.requestType,
|
||||
@@ -56,7 +56,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
);
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => toast.error(err ? err.message : 'An error occured while adding the request'));
|
||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
||||
} else {
|
||||
dispatch(
|
||||
newHttpRequest({
|
||||
@@ -69,7 +69,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
})
|
||||
)
|
||||
.then(() => onClose())
|
||||
.catch((err) => toast.error(err ? err.message : 'An error occured while adding the request'));
|
||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ const TitleBar = () => {
|
||||
|
||||
const handleOpenCollection = () => {
|
||||
dispatch(openCollection()).catch(
|
||||
(err) => console.log(err) && toast.error('An error occured while opening the collection')
|
||||
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ const Sidebar = () => {
|
||||
</GitHubButton>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.16.0</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.16.2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@ class SingleLineEditor extends Component {
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// Ensure the changes caused by this update are not interpretted as
|
||||
// Ensure the changes caused by this update are not interpreted as
|
||||
// user-input changes which could otherwise result in an infinite
|
||||
// event loop.
|
||||
this.ignoreChangeEvent = true;
|
||||
|
||||
@@ -19,7 +19,7 @@ const Welcome = () => {
|
||||
|
||||
const handleOpenCollection = () => {
|
||||
dispatch(openCollection()).catch(
|
||||
(err) => console.log(err) && toast.error('An error occured while opening the collection')
|
||||
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ const Welcome = () => {
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center">
|
||||
<IconBrandGithub size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Github</span>
|
||||
<span className="label ml-2">GitHub</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export const HotkeysProvider = (props) => {
|
||||
};
|
||||
}, [activeTabUid, tabs, saveRequest, collections]);
|
||||
|
||||
// edit environmentss (ctrl/cmd + e)
|
||||
// edit environments (ctrl/cmd + e)
|
||||
useEffect(() => {
|
||||
Mousetrap.bind(['command+e', 'ctrl+e'], (e) => {
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
|
||||
@@ -229,7 +229,7 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
newEphermalHttpRequest: (state, action) => {
|
||||
newEphemeralHttpRequest: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if (collection && collection.items && collection.items.length) {
|
||||
@@ -563,6 +563,20 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
updateRequestAuthMode: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if (collection && collection.items && collection.items.length) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if (item && isItemARequest(item)) {
|
||||
if (!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
item.draft.request.auth.mode = action.payload.mode;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateRequestBodyMode: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@@ -1154,7 +1168,7 @@ export const {
|
||||
requestCancelled,
|
||||
responseReceived,
|
||||
saveRequest,
|
||||
newEphermalHttpRequest,
|
||||
newEphemeralHttpRequest,
|
||||
collectionClicked,
|
||||
collectionFolderClicked,
|
||||
requestUrlChanged,
|
||||
@@ -1170,6 +1184,7 @@ export const {
|
||||
addMultipartFormParam,
|
||||
updateMultipartFormParam,
|
||||
deleteMultipartFormParam,
|
||||
updateRequestAuthMode,
|
||||
updateRequestBodyMode,
|
||||
updateRequestBody,
|
||||
updateRequestGraphqlQuery,
|
||||
|
||||
@@ -70,7 +70,7 @@ const darkTheme = {
|
||||
bg: 'rgb(48, 48, 49)',
|
||||
hoverBg: '#185387',
|
||||
shadow: 'rgb(0 0 0 / 36%) 0px 2px 8px',
|
||||
seperator: '#444',
|
||||
separator: '#444',
|
||||
labelBg: '#4a4949'
|
||||
},
|
||||
|
||||
@@ -174,7 +174,7 @@ const darkTheme = {
|
||||
requestTabs: {
|
||||
color: '#ccc',
|
||||
bg: '#2A2D2F',
|
||||
borromBorder: '#444',
|
||||
bottomBorder: '#444',
|
||||
icon: {
|
||||
color: '#9f9f9f',
|
||||
hoverColor: 'rgb(204, 204, 204)',
|
||||
|
||||
@@ -70,7 +70,7 @@ const lightTheme = {
|
||||
bg: '#fff',
|
||||
hoverBg: '#e9e9e9',
|
||||
shadow: 'rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px',
|
||||
seperator: '#e7e7e7',
|
||||
separator: '#e7e7e7',
|
||||
labelBg: '#f3f3f3'
|
||||
},
|
||||
|
||||
@@ -178,7 +178,7 @@ const lightTheme = {
|
||||
requestTabs: {
|
||||
color: 'rgb(52, 52, 52)',
|
||||
bg: '#f7f7f7',
|
||||
borromBorder: '#efefef',
|
||||
bottomBorder: '#efefef',
|
||||
icon: {
|
||||
color: '#9f9f9f',
|
||||
hoverColor: 'rgb(76 76 76)',
|
||||
|
||||
@@ -445,6 +445,22 @@ export const humanizeRequestBodyMode = (mode) => {
|
||||
return label;
|
||||
};
|
||||
|
||||
export const humanizeRequestAuthMode = (mode) => {
|
||||
let label = 'No Auth';
|
||||
switch (mode) {
|
||||
case 'basic': {
|
||||
label = 'Basic Auth';
|
||||
break;
|
||||
}
|
||||
case 'bearer': {
|
||||
label = 'Bearer Token';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const refreshUidsInItem = (item) => {
|
||||
item.uid = uuid();
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export const getContentType = (headers) => {
|
||||
export const formatResponse = (response) => {
|
||||
let type = getContentType(response.headers);
|
||||
if (type.includes('json')) {
|
||||
return safeStringifyJSON(response.data);
|
||||
return safeStringifyJSON(response.data, true);
|
||||
}
|
||||
if (type.includes('xml')) {
|
||||
return xmlFormat(response.data, { collapseContent: true });
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.10.1
|
||||
|
||||
- fix(#233) Fixed Issue related to content header parsing
|
||||
|
||||
## 0.10.0
|
||||
|
||||
- Support for proxying requests through a proxy server
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- `--output` flag to collect the results of your API tests
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@usebruno/cli",
|
||||
"version": "0.9.0",
|
||||
"license" : "MIT",
|
||||
"version": "0.10.1",
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
"bru": "./bin/bru.js"
|
||||
@@ -21,9 +21,9 @@
|
||||
"package.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@usebruno/js": "0.5.0",
|
||||
"@usebruno/js": "0.6.0",
|
||||
"@usebruno/lang": "0.4.0",
|
||||
"axios": "^1.3.2",
|
||||
"axios": "^1.5.1",
|
||||
"chai": "^4.3.7",
|
||||
"chalk": "^3.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
||||
@@ -165,6 +165,9 @@ const handler = async function (argv) {
|
||||
return;
|
||||
}
|
||||
|
||||
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
|
||||
const brunoConfig = JSON.parse(brunoConfigFile);
|
||||
|
||||
if (filename && filename.length) {
|
||||
const pathExists = await exists(filename);
|
||||
if (!pathExists) {
|
||||
@@ -304,7 +307,8 @@ const handler = async function (argv) {
|
||||
collectionPath,
|
||||
collectionVariables,
|
||||
envVars,
|
||||
processEnvVars
|
||||
processEnvVars,
|
||||
brunoConfig
|
||||
);
|
||||
|
||||
if (result) {
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const { each, forOwn, cloneDeep } = require('lodash');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
let contentType = '';
|
||||
forOwn(headers, (value, key) => {
|
||||
if (key && key.toLowerCase() === 'content-type') {
|
||||
contentType = value;
|
||||
}
|
||||
});
|
||||
|
||||
return contentType;
|
||||
};
|
||||
|
||||
const interpolateEnvVars = (str, processEnvVars) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return str;
|
||||
@@ -55,7 +66,9 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
request.headers[interpolate(key)] = interpolate(value);
|
||||
});
|
||||
|
||||
if (request.headers['content-type'] === 'application/json') {
|
||||
const contentType = getContentType(request.headers);
|
||||
|
||||
if (contentType.includes('json')) {
|
||||
if (typeof request.data === 'object') {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
@@ -69,7 +82,7 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
request.data = interpolate(request.data);
|
||||
}
|
||||
}
|
||||
} else if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
||||
} else if (contentType === 'application/x-www-form-urlencoded') {
|
||||
if (typeof request.data === 'object') {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
@@ -85,6 +98,17 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
param.value = interpolate(param.value);
|
||||
});
|
||||
|
||||
if (request.proxy) {
|
||||
request.proxy.protocol = interpolate(request.proxy.protocol);
|
||||
request.proxy.hostname = interpolate(request.proxy.hostname);
|
||||
request.proxy.port = interpolate(request.proxy.port);
|
||||
|
||||
if (request.proxy.auth) {
|
||||
request.proxy.auth.username = interpolate(request.proxy.auth.username);
|
||||
request.proxy.auth.password = interpolate(request.proxy.auth.password);
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ const runSingleRequest = async function (
|
||||
collectionPath,
|
||||
collectionVariables,
|
||||
envVariables,
|
||||
processEnvVars
|
||||
processEnvVars,
|
||||
brunoConfig
|
||||
) {
|
||||
let request;
|
||||
|
||||
@@ -39,7 +40,14 @@ const runSingleRequest = async function (
|
||||
const preRequestVars = get(bruJson, 'request.vars.req');
|
||||
if (preRequestVars && preRequestVars.length) {
|
||||
const varsRuntime = new VarsRuntime();
|
||||
varsRuntime.runPreRequestVars(preRequestVars, request, envVariables, collectionVariables, collectionPath);
|
||||
varsRuntime.runPreRequestVars(
|
||||
preRequestVars,
|
||||
request,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
// run pre request script
|
||||
@@ -51,10 +59,37 @@ const runSingleRequest = async function (
|
||||
request,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
null,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
// set proxy if enabled
|
||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||
if (proxyEnabled) {
|
||||
const proxyProtocol = get(brunoConfig, 'proxy.protocol');
|
||||
const proxyHostname = get(brunoConfig, 'proxy.hostname');
|
||||
const proxyPort = get(brunoConfig, 'proxy.port');
|
||||
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
||||
|
||||
const proxyConfig = {
|
||||
protocol: proxyProtocol,
|
||||
hostname: proxyHostname,
|
||||
port: proxyPort
|
||||
};
|
||||
if (proxyAuthEnabled) {
|
||||
const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username');
|
||||
const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password');
|
||||
proxyConfig.auth = {
|
||||
username: proxyAuthUsername,
|
||||
password: proxyAuthPassword
|
||||
};
|
||||
}
|
||||
|
||||
request.proxy = proxyConfig;
|
||||
}
|
||||
|
||||
// interpolate variables inside request
|
||||
interpolateVars(request, envVariables, collectionVariables, processEnvVars);
|
||||
|
||||
@@ -102,7 +137,8 @@ const runSingleRequest = async function (
|
||||
response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,7 +152,9 @@ const runSingleRequest = async function (
|
||||
response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
null,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,7 +193,9 @@ const runSingleRequest = async function (
|
||||
response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
null,
|
||||
processEnvVars
|
||||
);
|
||||
testResults = get(result, 'results', []);
|
||||
}
|
||||
@@ -202,7 +242,8 @@ const runSingleRequest = async function (
|
||||
err.response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
@@ -216,7 +257,9 @@ const runSingleRequest = async function (
|
||||
err.response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
null,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,7 +298,9 @@ const runSingleRequest = async function (
|
||||
err.response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
collectionPath,
|
||||
null,
|
||||
processEnvVars
|
||||
);
|
||||
testResults = get(result, 'results', []);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "v0.16.0",
|
||||
"version": "v0.16.2",
|
||||
"name": "bruno",
|
||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||
"homepage": "https://www.usebruno.com",
|
||||
@@ -14,9 +14,9 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@usebruno/js": "0.5.0",
|
||||
"@usebruno/js": "0.6.0",
|
||||
"@usebruno/lang": "0.4.0",
|
||||
"@usebruno/schema": "0.4.0",
|
||||
"@usebruno/schema": "0.5.0",
|
||||
"about-window": "^1.15.2",
|
||||
"axios": "^1.5.1",
|
||||
"chai": "^4.3.7",
|
||||
|
||||
@@ -67,7 +67,7 @@ const openCollection = async (win, watcher, collectionPath, options = {}) => {
|
||||
} catch (err) {
|
||||
if (!options.dontSendDisplayErrors) {
|
||||
win.webContents.send('main:display-error', {
|
||||
error: err.message || 'An error occured while opening the local collection'
|
||||
error: err.message || 'An error occurred while opening the local collection'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const bruToEnvJson = (bru) => {
|
||||
const json = bruToEnvJsonV2(bru);
|
||||
|
||||
// the app env format requires each variable to have a type
|
||||
// this need to be evaulated and safely removed
|
||||
// this need to be evaluated and safely removed
|
||||
// i don't see it being used in schema validation
|
||||
if (json && json.variables && json.variables.length) {
|
||||
each(json.variables, (v) => (v.type = 'text'));
|
||||
@@ -61,6 +61,7 @@ const bruToJson = (bru) => {
|
||||
url: _.get(json, 'http.url'),
|
||||
params: _.get(json, 'query', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
body: _.get(json, 'body', {}),
|
||||
script: _.get(json, 'script', {}),
|
||||
vars: _.get(json, 'vars', {}),
|
||||
@@ -69,6 +70,7 @@ const bruToJson = (bru) => {
|
||||
}
|
||||
};
|
||||
|
||||
transformedJson.request.auth.mode = _.get(json, 'http.auth', 'none');
|
||||
transformedJson.request.body.mode = _.get(json, 'http.body', 'none');
|
||||
|
||||
return transformedJson;
|
||||
@@ -104,10 +106,12 @@ const jsonToBru = (json) => {
|
||||
http: {
|
||||
method: _.lowerCase(_.get(json, 'request.method')),
|
||||
url: _.get(json, 'request.url'),
|
||||
auth: _.get(json, 'request.auth.mode', 'none'),
|
||||
body: _.get(json, 'request.body.mode', 'none')
|
||||
},
|
||||
query: _.get(json, 'request.params', []),
|
||||
headers: _.get(json, 'request.headers', []),
|
||||
auth: _.get(json, 'request.auth', {}),
|
||||
body: _.get(json, 'request.body', {}),
|
||||
script: _.get(json, 'request.script', {}),
|
||||
vars: {
|
||||
|
||||
@@ -165,6 +165,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
});
|
||||
}
|
||||
|
||||
// proxy configuration
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||
if (proxyEnabled) {
|
||||
@@ -597,6 +598,32 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
});
|
||||
}
|
||||
|
||||
// proxy configuration
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||
if (proxyEnabled) {
|
||||
const proxyProtocol = get(brunoConfig, 'proxy.protocol');
|
||||
const proxyHostname = get(brunoConfig, 'proxy.hostname');
|
||||
const proxyPort = get(brunoConfig, 'proxy.port');
|
||||
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
||||
|
||||
const proxyConfig = {
|
||||
protocol: proxyProtocol,
|
||||
hostname: proxyHostname,
|
||||
port: proxyPort
|
||||
};
|
||||
if (proxyAuthEnabled) {
|
||||
const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username');
|
||||
const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password');
|
||||
proxyConfig.auth = {
|
||||
username: proxyAuthUsername,
|
||||
password: proxyAuthPassword
|
||||
};
|
||||
}
|
||||
|
||||
request.proxy = proxyConfig;
|
||||
}
|
||||
|
||||
// interpolate variables inside request
|
||||
interpolateVars(request, envVars, collectionVariables, processEnvVars);
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const { each, forOwn, cloneDeep } = require('lodash');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
let contentType = '';
|
||||
forOwn(headers, (value, key) => {
|
||||
if (key && key.toLowerCase() === 'content-type') {
|
||||
contentType = value;
|
||||
}
|
||||
});
|
||||
|
||||
return contentType;
|
||||
};
|
||||
|
||||
const interpolateEnvVars = (str, processEnvVars) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return str;
|
||||
@@ -55,7 +66,9 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
request.headers[interpolate(key)] = interpolate(value);
|
||||
});
|
||||
|
||||
if (request.headers['content-type'] === 'application/json') {
|
||||
const contentType = getContentType(request.headers);
|
||||
|
||||
if (contentType.includes('json')) {
|
||||
if (typeof request.data === 'object') {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
@@ -69,7 +82,7 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
request.data = interpolate(request.data);
|
||||
}
|
||||
}
|
||||
} else if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
||||
} else if (contentType === 'application/x-www-form-urlencoded') {
|
||||
if (typeof request.data === 'object') {
|
||||
try {
|
||||
let parsed = JSON.stringify(request.data);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@usebruno/js",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
|
||||
@@ -11,7 +11,7 @@ const JS_KEYWORDS = `
|
||||
.filter((word) => word.length > 0);
|
||||
|
||||
/**
|
||||
* Creates a function from a Javascript expression
|
||||
* Creates a function from a JavaScript expression
|
||||
*
|
||||
* When the function is called, the variables used in this expression are picked up from the context
|
||||
*
|
||||
@@ -119,7 +119,7 @@ const createResponseParser = (response = {}) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Objects that are created inside vm2 execution context result in an serilaization error when sent to the renderer process
|
||||
* Objects that are created inside vm2 execution context result in an serialization error when sent to the renderer process
|
||||
* Error sending from webFrameMain: Error: Failed to serialize arguments
|
||||
* at s.send (node:electron/js2c/browser_init:169:631)
|
||||
* at g.send (node:electron/js2c/browser_init:165:2156)
|
||||
|
||||
@@ -66,7 +66,7 @@ const bodyXmlTag = between(bodyXmlBegin)(bodyEnd)(everyCharUntil(bodyEnd)).map((
|
||||
* We have deprecated form-url-encoded type in body tag, it was a misspelling on my part
|
||||
* The new type is form-urlencoded
|
||||
*
|
||||
* Very few peope would have used this. I launched this to the public on 22 Jan 2023
|
||||
* Very few people would have used this. I launched this to the public on 22 Jan 2023
|
||||
* And I am making the change on 23 Jan 2023
|
||||
*
|
||||
* This deprecated tag can be removed on 1 April 2023
|
||||
|
||||
@@ -22,7 +22,8 @@ const { outdentString } = require('../../v1/src/utils');
|
||||
*
|
||||
*/
|
||||
const grammar = ohm.grammar(`Bru {
|
||||
BruFile = (meta | http | query | headers | bodies | varsandassert | script | tests | docs)*
|
||||
BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)*
|
||||
auths = authbasic | authbearer
|
||||
bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms | body
|
||||
bodyforms = bodyformurlencoded | bodymultipart
|
||||
|
||||
@@ -75,6 +76,9 @@ const grammar = ohm.grammar(`Bru {
|
||||
varsres = "vars:post-response" dictionary
|
||||
assert = "assert" assertdictionary
|
||||
|
||||
authbasic = "auth:basic" dictionary
|
||||
authbearer = "auth:bearer" dictionary
|
||||
|
||||
body = "body" st* "{" nl* textblock tagend
|
||||
bodyjson = "body:json" st* "{" nl* textblock tagend
|
||||
bodytext = "body:text" st* "{" nl* textblock tagend
|
||||
@@ -92,13 +96,21 @@ const grammar = ohm.grammar(`Bru {
|
||||
docs = "docs" st* "{" nl* textblock tagend
|
||||
}`);
|
||||
|
||||
const mapPairListToKeyValPairs = (pairList = []) => {
|
||||
const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
|
||||
if (!pairList.length) {
|
||||
return [];
|
||||
}
|
||||
return _.map(pairList[0], (pair) => {
|
||||
let name = _.keys(pair)[0];
|
||||
let value = pair[name];
|
||||
|
||||
if (!parseEnabled) {
|
||||
return {
|
||||
name,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
let enabled = true;
|
||||
if (name && name.length && name.charAt(0) === '~') {
|
||||
name = name.slice(1);
|
||||
@@ -282,6 +294,33 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
headers: mapPairListToKeyValPairs(dictionary.ast)
|
||||
};
|
||||
},
|
||||
authbasic(_1, dictionary) {
|
||||
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||
const usernameKey = _.find(auth, { name: 'username' });
|
||||
const passwordKey = _.find(auth, { name: 'password' });
|
||||
const username = usernameKey ? usernameKey.value : '';
|
||||
const password = passwordKey ? passwordKey.value : '';
|
||||
return {
|
||||
auth: {
|
||||
basic: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
authbearer(_1, dictionary) {
|
||||
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||
const tokenKey = _.find(auth, { name: 'token' });
|
||||
const token = tokenKey ? tokenKey.value : '';
|
||||
return {
|
||||
auth: {
|
||||
bearer: {
|
||||
token
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
bodyformurlencoded(_1, dictionary) {
|
||||
return {
|
||||
body: {
|
||||
|
||||
@@ -13,7 +13,7 @@ const stripLastLine = (text) => {
|
||||
};
|
||||
|
||||
const jsonToBru = (json) => {
|
||||
const { meta, http, query, headers, body, script, tests, vars, assertions, docs } = json;
|
||||
const { meta, http, query, headers, auth, body, script, tests, vars, assertions, docs } = json;
|
||||
|
||||
let bru = '';
|
||||
|
||||
@@ -82,6 +82,23 @@ const jsonToBru = (json) => {
|
||||
bru += '\n}\n\n';
|
||||
}
|
||||
|
||||
if (auth && auth.basic) {
|
||||
bru += `auth:basic {
|
||||
${indentString(`username: ${auth.basic.username}`)}
|
||||
${indentString(`password: ${auth.basic.password}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
if (auth && auth.bearer) {
|
||||
bru += `auth:bearer {
|
||||
${indentString(`token: ${auth.bearer.token}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
if (body && body.json && body.json.length) {
|
||||
bru += `body:json {
|
||||
${indentString(body.json)}
|
||||
|
||||
@@ -21,6 +21,15 @@ headers {
|
||||
~transaction-id: {{transactionId}}
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: john
|
||||
password: secret
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: 123
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"hello": "world"
|
||||
|
||||
@@ -43,6 +43,15 @@
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"basic": {
|
||||
"username": "john",
|
||||
"password": "secret"
|
||||
},
|
||||
"bearer": {
|
||||
"token": "123"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"json": "{\n \"hello\": \"world\"\n}",
|
||||
"text": "This is a text body",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@usebruno/schema",
|
||||
"version": "0.4.0",
|
||||
"license" : "MIT",
|
||||
"version": "0.5.0",
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
"src",
|
||||
|
||||
@@ -69,6 +69,27 @@ const requestBodySchema = Yup.object({
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
const authBasicSchema = Yup.object({
|
||||
username: Yup.string().nullable(),
|
||||
password: Yup.string().nullable()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
const authBearerSchema = Yup.object({
|
||||
token: Yup.string().nullable()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
const authSchema = Yup.object({
|
||||
mode: Yup.string().oneOf(['none', 'basic', 'bearer']).required('mode is required'),
|
||||
basic: authBasicSchema.nullable(),
|
||||
bearer: authBearerSchema.nullable()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
// Right now, the request schema is very tightly coupled with http request
|
||||
// As we introduce more request types in the future, we will improve the definition to support
|
||||
// schema structure based on other request type
|
||||
@@ -77,6 +98,7 @@ const requestSchema = Yup.object({
|
||||
method: requestMethodSchema,
|
||||
headers: Yup.array().of(keyValueSchema).required('headers are required'),
|
||||
params: Yup.array().of(keyValueSchema).required('params are required'),
|
||||
auth: authSchema,
|
||||
body: requestBodySchema,
|
||||
script: Yup.object({
|
||||
req: Yup.string().nullable(),
|
||||
|
||||
@@ -10,7 +10,7 @@ exports.HomePage = class HomePage {
|
||||
|
||||
// sample collection
|
||||
this.loadSampleCollectionSuccessToast = page.getByText('Sample Collection loaded successfully');
|
||||
this.sampeCollectionSelector = page.locator('#sidebar-collection-name');
|
||||
this.sampleCollectionSelector = page.locator('#sidebar-collection-name');
|
||||
this.getUsersSelector = page.getByText('Users');
|
||||
this.getSingleUserSelector = page.getByText('Single User');
|
||||
this.getUserNotFoundSelector = page.getByText('User Not Found');
|
||||
@@ -43,7 +43,7 @@ exports.HomePage = class HomePage {
|
||||
}
|
||||
|
||||
async getUsers() {
|
||||
await this.sampeCollectionSelector.click();
|
||||
await this.sampleCollectionSelector.click();
|
||||
await this.getUsersSelector.click();
|
||||
await this.sendRequestButton.click();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user