mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-24 17:03:47 +01:00
Feat/improved path params (#2357)
* feat: path parameters (#484) * add path parameters on bruno-app * add path parameters on bruno-cli * fix bruno-schema testing * fix generate request code not replace path parameter value --------- Co-authored-by: game5413 <febryanph10@gmail.com> Co-authored-by: Anoop M D <anoop.md1421@gmail.com> * feat: Refactor request parameter handling - Update prepare-request.js to filter and rename 'paths' to 'params' with type 'path' - Remove 'paths' from export.js and interpolate-vars.js - Update bru.js to use 'params' instead of 'path' - Update requestSchema in index.js to use 'keyValueWithTypeSchema' for 'params' Co-authored-by: game5413 <febryanph10@gmail.com> Co-authored-by: Anoop M D <anoop.md1421@gmail.com> * feat: Refactor request parameter handling * refactor: changes form the review * refactor: Refactor transformItemsInCollection handling * refactor: Refactor improved export/import functionalities * refactor: Remove console.log statement in bruToJson.js --------- Co-authored-by: game5413 <37659721+game5413@users.noreply.github.com> Co-authored-by: game5413 <febryanph10@gmail.com> Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
This commit is contained in:
parent
77b1e6d738
commit
abfd14a306
@ -33,7 +33,7 @@ Bruno jest rozwijane jako aplikacja desktopowa. Musisz załadować aplikację, u
|
||||
|
||||
### Lokalny Rozwój
|
||||
|
||||
```bash
|
||||
````bash
|
||||
# użyj wersji nodejs 18
|
||||
nvm use
|
||||
|
||||
@ -66,7 +66,7 @@ done
|
||||
# Usuń package-lock w podkatalogach
|
||||
find . -type f -name "package-lock.json" -delete
|
||||
|
||||
```
|
||||
````
|
||||
|
||||
### Testowanie
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import find from 'lodash/find';
|
||||
import classnames from 'classnames';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||
@ -14,7 +13,7 @@ import Assertions from 'components/RequestPane/Assertions';
|
||||
import Script from 'components/RequestPane/Script';
|
||||
import Tests from 'components/RequestPane/Tests';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { get } from 'lodash';
|
||||
import { find, get } from 'lodash';
|
||||
import Documentation from 'components/Documentation/index';
|
||||
|
||||
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
@ -81,6 +80,8 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const isMultipleContentTab = ['params', 'script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab);
|
||||
|
||||
// get the length of active params, headers, asserts and vars
|
||||
const params = item.draft ? get(item, 'draft.request.params', []) : get(item, 'request.params', []);
|
||||
const headers = item.draft ? get(item, 'draft.request.headers', []) : get(item, 'request.headers', []);
|
||||
@ -99,7 +100,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
|
||||
Query
|
||||
Params
|
||||
{activeParamsLength > 0 && <sup className="ml-1 font-medium">{activeParamsLength}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
|
||||
@ -136,9 +137,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
) : null}
|
||||
</div>
|
||||
<section
|
||||
className={`flex w-full ${
|
||||
['script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'
|
||||
}`}
|
||||
className={classnames('flex w-full', {
|
||||
'mt-5': !isMultipleContentTab
|
||||
})}
|
||||
>
|
||||
{getTabPanel(focusedTab.requestPaneTab)}
|
||||
</section>
|
||||
|
@ -1,6 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
div.title {
|
||||
color: var(--color-tab-inactive);
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
@ -1,12 +1,18 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import has from 'lodash/has';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { addQueryParam, updateQueryParam, deleteQueryParam } from 'providers/ReduxStore/slices/collections';
|
||||
import {
|
||||
addQueryParam,
|
||||
deleteQueryParam,
|
||||
updatePathParam,
|
||||
updateQueryParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
@ -14,8 +20,10 @@ const QueryParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const params = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params');
|
||||
const queryParams = params.filter((param) => param.type === 'query');
|
||||
const pathParams = params.filter((param) => param.type === 'path');
|
||||
|
||||
const handleAddParam = () => {
|
||||
const handleAddQueryParam = () => {
|
||||
dispatch(
|
||||
addQueryParam({
|
||||
itemUid: item.uid,
|
||||
@ -26,24 +34,39 @@ const QueryParams = ({ item, collection }) => {
|
||||
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
|
||||
const handleValueChange = (data, type, value) => {
|
||||
const _data = cloneDeep(data);
|
||||
|
||||
if (!has(_data, type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_data[type] = value;
|
||||
|
||||
return _data;
|
||||
};
|
||||
|
||||
const handleQueryParamChange = (e, data, type) => {
|
||||
let value;
|
||||
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
param.name = e.target.value;
|
||||
value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
param.value = e.target.value;
|
||||
value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
value = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const param = handleValueChange(data, type, value);
|
||||
|
||||
dispatch(
|
||||
updateQueryParam({
|
||||
param,
|
||||
@ -53,7 +76,21 @@ const QueryParams = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveParam = (param) => {
|
||||
const handlePathParamChange = (e, data) => {
|
||||
let value = e.target.value;
|
||||
|
||||
const path = handleValueChange(data, 'value', value);
|
||||
|
||||
dispatch(
|
||||
updatePathParam({
|
||||
path,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveQueryParam = (param) => {
|
||||
dispatch(
|
||||
deleteQueryParam({
|
||||
paramUid: param.uid,
|
||||
@ -64,7 +101,9 @@ const QueryParams = ({ item, collection }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Query</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -74,8 +113,8 @@ const QueryParams = ({ item, collection }) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
{queryParams && queryParams.length
|
||||
? queryParams.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
@ -87,7 +126,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
onChange={(e) => handleQueryParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
@ -96,7 +135,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
handleQueryParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
@ -117,9 +156,9 @@ const QueryParams = ({ item, collection }) => {
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
onChange={(e) => handleQueryParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParam(param)}>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveQueryParam(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
@ -130,9 +169,60 @@ const QueryParams = ({ item, collection }) => {
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddParam}>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddQueryParam}>
|
||||
+ <span>Add Param</span>
|
||||
</button>
|
||||
<div className="mb-1 title text-xs">Path</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pathParams && pathParams.length
|
||||
? pathParams.map((path, index) => {
|
||||
return (
|
||||
<tr key={path.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={path.name}
|
||||
className="mousetrap"
|
||||
readOnly={true}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={path.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handlePathParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
path
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -2,8 +2,8 @@ import Modal from 'components/Modal/index';
|
||||
import { useState } from 'react';
|
||||
import CodeView from './CodeView';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { isValidUrl } from 'utils/url/index';
|
||||
import get from 'lodash/get';
|
||||
import { isValidUrl } from 'utils/url';
|
||||
import { find, get } from 'lodash';
|
||||
import { findEnvironmentInCollection } from 'utils/collections';
|
||||
|
||||
// Todo: Fix this
|
||||
@ -27,6 +27,44 @@ const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) =
|
||||
});
|
||||
};
|
||||
|
||||
const joinPathUrl = (url, params) => {
|
||||
const processPaths = (uri, paths) => {
|
||||
return uri
|
||||
.split('/')
|
||||
.map((segment) => {
|
||||
if (segment.startsWith(':')) {
|
||||
const paramName = segment.slice(1);
|
||||
const param = paths.find((p) => p.name === paramName && p.type === 'path' && p.enabled);
|
||||
return param ? param.value : segment;
|
||||
}
|
||||
return segment;
|
||||
})
|
||||
.join('/');
|
||||
};
|
||||
|
||||
const processQueryParams = (search, params) => {
|
||||
const queryParams = new URLSearchParams(search);
|
||||
params
|
||||
.filter((p) => p.type === 'query' && p.enabled)
|
||||
.forEach((param) => {
|
||||
queryParams.set(param.name, param.value);
|
||||
});
|
||||
return queryParams.toString();
|
||||
};
|
||||
|
||||
let uri;
|
||||
try {
|
||||
uri = new URL(url);
|
||||
} catch (error) {
|
||||
uri = new URL(`http://${url}`);
|
||||
}
|
||||
|
||||
const basePath = processPaths(uri.pathname, params);
|
||||
const queryString = processQueryParams(uri.search, params);
|
||||
|
||||
return `${uri.origin}${basePath}${queryString ? `?${queryString}` : ''}`;
|
||||
};
|
||||
|
||||
const languages = [
|
||||
{
|
||||
name: 'HTTP',
|
||||
@ -76,7 +114,10 @@ const languages = [
|
||||
];
|
||||
|
||||
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
||||
const url = get(item, 'draft.request.url') !== undefined ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||
const url = joinPathUrl(
|
||||
get(item, 'draft.request.url') !== undefined ? get(item, 'draft.request.url') : get(item, 'request.url'),
|
||||
get(item, 'draft.request.params') !== undefined ? get(item, 'draft.request.params') : get(item, 'request.params')
|
||||
);
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
let envVars = {};
|
||||
if (environment) {
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { uuid } from 'utils/common';
|
||||
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, debounce } from 'lodash';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import concat from 'lodash/concat';
|
||||
import each from 'lodash/each';
|
||||
import filter from 'lodash/filter';
|
||||
import find from 'lodash/find';
|
||||
import forOwn from 'lodash/forOwn';
|
||||
import get from 'lodash/get';
|
||||
import map from 'lodash/map';
|
||||
import set from 'lodash/set';
|
||||
import {
|
||||
addDepth,
|
||||
areItemsTheSameExceptSeqUpdate,
|
||||
@ -21,9 +14,9 @@ import {
|
||||
findItemInCollectionByPathname,
|
||||
isItemARequest
|
||||
} from 'utils/collections';
|
||||
import { uuid } from 'utils/common';
|
||||
import { PATH_SEPARATOR, getDirectoryName, getSubdirectoriesFromRoot } from 'utils/common/platform';
|
||||
import { parseQueryParams, splitOnFirst, stringifyQueryParams } from 'utils/url';
|
||||
import { parsePathParams, parseQueryParams, splitOnFirst, stringifyQueryParams } from 'utils/url';
|
||||
import { getDirectoryName, getSubdirectoriesFromRoot, PATH_SEPARATOR } from 'utils/common/platform';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const initialState = {
|
||||
collections: [],
|
||||
@ -294,10 +287,35 @@ export const collectionsSlice = createSlice({
|
||||
|
||||
if (collection && collection.items && collection.items.length) {
|
||||
const parts = splitOnFirst(action.payload.requestUrl, '?');
|
||||
const params = parseQueryParams(parts[1]);
|
||||
each(params, (urlParam) => {
|
||||
urlParam.enabled = true;
|
||||
});
|
||||
const queryParams = parseQueryParams(parts[1]);
|
||||
|
||||
let pathParams = [];
|
||||
try {
|
||||
pathParams = parsePathParams(parts[0]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(err.message);
|
||||
}
|
||||
|
||||
const queryParamObjects = queryParams.map((param) => ({
|
||||
uid: uuid(),
|
||||
name: param.key,
|
||||
value: param.value,
|
||||
description: '',
|
||||
type: 'query',
|
||||
enabled: true
|
||||
}));
|
||||
|
||||
const pathParamObjects = pathParams.map((param) => ({
|
||||
uid: uuid(),
|
||||
name: param.key,
|
||||
value: param.value,
|
||||
description: '',
|
||||
type: 'path',
|
||||
enabled: true
|
||||
}));
|
||||
|
||||
const params = [...queryParamObjects, ...pathParamObjects];
|
||||
|
||||
const item = {
|
||||
uid: action.payload.uid,
|
||||
@ -351,14 +369,26 @@ export const collectionsSlice = createSlice({
|
||||
|
||||
const parts = splitOnFirst(item.draft.request.url, '?');
|
||||
const urlParams = parseQueryParams(parts[1]);
|
||||
let urlPaths = [];
|
||||
|
||||
try {
|
||||
urlPaths = parsePathParams(parts[0]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error(err.message);
|
||||
}
|
||||
|
||||
const disabledParams = filter(item.draft.request.params, (p) => !p.enabled);
|
||||
let enabledParams = filter(item.draft.request.params, (p) => p.enabled);
|
||||
let enabledParams = filter(item.draft.request.params, (p) => p.enabled && p.type === 'query');
|
||||
let oldPaths = filter(item.draft.request.params, (p) => p.enabled && p.type === 'path');
|
||||
let newPaths = [];
|
||||
|
||||
// try and connect as much as old params uid's as possible
|
||||
each(urlParams, (urlParam) => {
|
||||
const existingParam = find(enabledParams, (p) => p.name === urlParam.name || p.value === urlParam.value);
|
||||
urlParam.uid = existingParam ? existingParam.uid : uuid();
|
||||
urlParam.enabled = true;
|
||||
urlParam.type = 'query';
|
||||
|
||||
// once found, remove it - trying our best here to accommodate duplicate query params
|
||||
if (existingParam) {
|
||||
@ -366,10 +396,27 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
// filter the newest path param and compare with previous data that already inserted
|
||||
newPaths = filter(urlPaths, (urlPath) => {
|
||||
const existingPath = find(oldPaths, (p) => p.name === urlPath.name);
|
||||
if (existingPath) {
|
||||
return false;
|
||||
}
|
||||
urlPath.uid = uuid();
|
||||
urlPath.enabled = true;
|
||||
urlPath.type = 'path';
|
||||
return true;
|
||||
});
|
||||
|
||||
// remove path param that not used or deleted when typing url
|
||||
oldPaths = filter(oldPaths, (urlPath) => {
|
||||
return find(urlPaths, (p) => p.name === urlPath.name);
|
||||
});
|
||||
|
||||
// ultimately params get replaced with params in url + the disabled ones that existed prior
|
||||
// the query params are the source of truth, the url in the queryurl input gets constructed using these params
|
||||
// we however are also storing the full url (with params) in the url itself
|
||||
item.draft.request.params = concat(urlParams, disabledParams);
|
||||
item.draft.request.params = concat(urlParams, newPaths, disabledParams, oldPaths);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -426,6 +473,7 @@ export const collectionsSlice = createSlice({
|
||||
name: '',
|
||||
value: '',
|
||||
description: '',
|
||||
type: 'query',
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
@ -441,16 +489,20 @@ export const collectionsSlice = createSlice({
|
||||
if (!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
const param = find(item.draft.request.params, (h) => h.uid === action.payload.param.uid);
|
||||
if (param) {
|
||||
param.name = action.payload.param.name;
|
||||
param.value = action.payload.param.value;
|
||||
param.description = action.payload.param.description;
|
||||
param.enabled = action.payload.param.enabled;
|
||||
const queryParam = find(
|
||||
item.draft.request.params,
|
||||
(h) => h.uid === action.payload.param.uid && h.type === 'query'
|
||||
);
|
||||
if (queryParam) {
|
||||
queryParam.name = action.payload.param.name;
|
||||
queryParam.value = action.payload.param.value;
|
||||
queryParam.enabled = action.payload.param.enabled;
|
||||
|
||||
// update request url
|
||||
const parts = splitOnFirst(item.draft.request.url, '?');
|
||||
const query = stringifyQueryParams(filter(item.draft.request.params, (p) => p.enabled));
|
||||
const query = stringifyQueryParams(
|
||||
filter(item.draft.request.params, (p) => p.enabled && p.type === 'query')
|
||||
);
|
||||
|
||||
// if no query is found, then strip the query params in url
|
||||
if (!query || !query.length) {
|
||||
@ -486,7 +538,7 @@ export const collectionsSlice = createSlice({
|
||||
|
||||
// update request url
|
||||
const parts = splitOnFirst(item.draft.request.url, '?');
|
||||
const query = stringifyQueryParams(filter(item.draft.request.params, (p) => p.enabled));
|
||||
const query = stringifyQueryParams(filter(item.draft.request.params, (p) => p.enabled && p.type === 'query'));
|
||||
if (query && query.length) {
|
||||
item.draft.request.url = parts[0] + '?' + query;
|
||||
} else {
|
||||
@ -495,6 +547,26 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
updatePathParam: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
if (collection) {
|
||||
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||
|
||||
if (item && isItemARequest(item)) {
|
||||
if (!item.draft) {
|
||||
item.draft = cloneDeep(item);
|
||||
}
|
||||
|
||||
const param = find(item.draft.request.params, (p) => p.uid === action.payload.path.uid && p.type === 'path');
|
||||
|
||||
if (param) {
|
||||
param.name = action.payload.path.name;
|
||||
param.value = action.payload.path.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
addRequestHeader: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@ -1418,6 +1490,7 @@ export const {
|
||||
addQueryParam,
|
||||
updateQueryParam,
|
||||
deleteQueryParam,
|
||||
updatePathParam,
|
||||
addRequestHeader,
|
||||
updateRequestHeader,
|
||||
deleteRequestHeader,
|
||||
|
@ -24,7 +24,7 @@ const createHeaders = (headers) => {
|
||||
|
||||
const createQuery = (queryParams = []) => {
|
||||
return queryParams
|
||||
.filter((param) => param.enabled)
|
||||
.filter((param) => param.enabled && param.type === 'query')
|
||||
.map((param) => ({
|
||||
name: param.name,
|
||||
value: param.value
|
||||
|
@ -29,9 +29,6 @@ export const deleteUidsInItems = (items) => {
|
||||
export const transformItem = (items = []) => {
|
||||
each(items, (item) => {
|
||||
if (['http-request', 'graphql-request'].includes(item.type)) {
|
||||
item.request.query = item.request.params;
|
||||
delete item.request.params;
|
||||
|
||||
if (item.type === 'graphql-request') {
|
||||
item.type = 'graphql';
|
||||
}
|
||||
|
@ -228,13 +228,14 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
||||
});
|
||||
};
|
||||
|
||||
const copyQueryParams = (params) => {
|
||||
const copyParams = (params) => {
|
||||
return map(params, (param) => {
|
||||
return {
|
||||
uid: param.uid,
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: param.type,
|
||||
enabled: param.enabled
|
||||
};
|
||||
});
|
||||
@ -283,7 +284,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
||||
url: si.request.url,
|
||||
method: si.request.method,
|
||||
headers: copyHeaders(si.request.headers),
|
||||
params: copyQueryParams(si.request.params),
|
||||
params: copyParams(si.request.params),
|
||||
body: {
|
||||
mode: si.request.body.mode,
|
||||
json: si.request.body.json,
|
||||
@ -441,6 +442,7 @@ export const transformRequestToSaveToFilesystem = (item) => {
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: param.type,
|
||||
enabled: param.enabled
|
||||
});
|
||||
});
|
||||
|
@ -171,11 +171,43 @@ export const exportCollection = (collection) => {
|
||||
}
|
||||
};
|
||||
|
||||
const generateHost = (url) => {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
return hostname.split('.');
|
||||
} catch (error) {
|
||||
console.error(`Invalid URL: ${url}`, error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const generatePathParams = (params) => {
|
||||
return params.filter((param) => param.type === 'path').map((param) => `:${param.name}`);
|
||||
};
|
||||
|
||||
const generateQueryParams = (params) => {
|
||||
return params
|
||||
.filter((param) => param.type === 'query')
|
||||
.map(({ name, value, description }) => ({ key: name, value, description }));
|
||||
};
|
||||
|
||||
const generateVariables = (params) => {
|
||||
return params
|
||||
.filter((param) => param.type === 'path')
|
||||
.map(({ name, value, description }) => ({ key: name, value, description }));
|
||||
};
|
||||
|
||||
const generateRequestSection = (itemRequest) => {
|
||||
const requestObject = {
|
||||
method: itemRequest.method,
|
||||
header: generateHeaders(itemRequest.headers),
|
||||
url: itemRequest.url,
|
||||
url: {
|
||||
raw: itemRequest.url,
|
||||
host: generateHost(itemRequest.url),
|
||||
path: generatePathParams(itemRequest.params),
|
||||
query: generateQueryParams(itemRequest.params),
|
||||
variable: generateVariables(itemRequest.params)
|
||||
},
|
||||
auth: generateAuth(itemRequest.auth)
|
||||
};
|
||||
|
||||
|
@ -29,7 +29,6 @@ export const updateUidsInCollection = (_collection) => {
|
||||
item.uid = uuid();
|
||||
|
||||
each(get(item, 'request.headers'), (header) => (header.uid = uuid()));
|
||||
each(get(item, 'request.query'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.params'), (param) => (param.uid = uuid()));
|
||||
each(get(item, 'request.vars.req'), (v) => (v.uid = uuid()));
|
||||
each(get(item, 'request.vars.res'), (v) => (v.uid = uuid()));
|
||||
@ -66,8 +65,13 @@ export const transformItemsInCollection = (collection) => {
|
||||
|
||||
if (['http', 'graphql'].includes(item.type)) {
|
||||
item.type = `${item.type}-request`;
|
||||
|
||||
if (item.request.query) {
|
||||
item.request.params = item.request.query;
|
||||
item.request.params = item.request.query.map((queryItem) => ({
|
||||
...queryItem,
|
||||
type: 'query',
|
||||
uid: queryItem.uid || uuid()
|
||||
}));
|
||||
}
|
||||
|
||||
delete item.request.query;
|
||||
|
@ -112,10 +112,22 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: 'query',
|
||||
enabled: !param.disabled
|
||||
});
|
||||
});
|
||||
|
||||
each(request.pathParameters, (param) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
description: '',
|
||||
type: 'path',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
|
||||
const authType = get(request, 'authentication.type', '');
|
||||
|
||||
if (authType === 'basic') {
|
||||
|
@ -92,7 +92,17 @@ const transformOpenapiRequestItem = (request) => {
|
||||
name: param.name,
|
||||
value: '',
|
||||
description: param.description || '',
|
||||
enabled: param.required
|
||||
enabled: param.required,
|
||||
type: 'query'
|
||||
});
|
||||
} else if (param.in === 'path') {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.name,
|
||||
value: '',
|
||||
description: param.description || '',
|
||||
enabled: param.required,
|
||||
type: 'path'
|
||||
});
|
||||
} else if (param.in === 'header') {
|
||||
brunoRequestItem.request.headers.push({
|
||||
|
@ -275,10 +275,22 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
||||
name: param.key,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: 'query',
|
||||
enabled: !param.disabled
|
||||
});
|
||||
});
|
||||
|
||||
each(get(i, 'request.url.variable'), (param) => {
|
||||
brunoRequestItem.request.params.push({
|
||||
uid: uuid(),
|
||||
name: param.key,
|
||||
value: param.value,
|
||||
description: param.description,
|
||||
type: 'path',
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
|
||||
brunoParent.items.push(brunoRequestItem);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import isEmpty from 'lodash/isEmpty';
|
||||
import trim from 'lodash/trim';
|
||||
import each from 'lodash/each';
|
||||
import filter from 'lodash/filter';
|
||||
import find from 'lodash/find';
|
||||
|
||||
const hasLength = (str) => {
|
||||
if (!str || !str.length) {
|
||||
@ -26,6 +27,41 @@ export const parseQueryParams = (query) => {
|
||||
return filter(params, (p) => hasLength(p.name));
|
||||
};
|
||||
|
||||
export const parsePathParams = (url) => {
|
||||
let uri = url.slice();
|
||||
|
||||
if (!uri || !uri.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!uri.startsWith('http://') && !uri.startsWith('https://')) {
|
||||
uri = `http://${uri}`;
|
||||
}
|
||||
|
||||
try {
|
||||
uri = new URL(uri);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
let paths = uri.pathname.split('/');
|
||||
|
||||
paths = paths.reduce((acc, path) => {
|
||||
if (path !== '' && path[0] === ':') {
|
||||
let name = path.slice(1, path.length);
|
||||
if (name) {
|
||||
let isExist = find(acc, (path) => path.name === name);
|
||||
if (!isExist) {
|
||||
acc.push({ name: path.slice(1, path.length), value: '' });
|
||||
}
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return paths;
|
||||
};
|
||||
|
||||
export const stringifyQueryParams = (params) => {
|
||||
if (!params || isEmpty(params)) {
|
||||
return '';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { parseQueryParams, splitOnFirst } from './index';
|
||||
import { parseQueryParams, splitOnFirst, parsePathParams } from './index';
|
||||
|
||||
describe('Url Utils - parseQueryParams', () => {
|
||||
it('should parse query - case 1', () => {
|
||||
@ -51,6 +51,51 @@ describe('Url Utils - parseQueryParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Url Utils - parsePathParams', () => {
|
||||
it('should parse path - case 1', () => {
|
||||
const params = parsePathParams('www.example.com');
|
||||
expect(params).toEqual([]);
|
||||
});
|
||||
|
||||
it('should parse path - case 2', () => {
|
||||
const params = parsePathParams('http://www.example.com');
|
||||
expect(params).toEqual([]);
|
||||
});
|
||||
|
||||
it('should parse path - case 3', () => {
|
||||
const params = parsePathParams('https://www.example.com');
|
||||
expect(params).toEqual([]);
|
||||
});
|
||||
|
||||
it('should parse path - case 4', () => {
|
||||
const params = parsePathParams('https://www.example.com/users/:id');
|
||||
expect(params).toEqual([{ name: 'id', value: '' }]);
|
||||
});
|
||||
|
||||
it('should parse path - case 5', () => {
|
||||
const params = parsePathParams('https://www.example.com/users/:id/');
|
||||
expect(params).toEqual([{ name: 'id', value: '' }]);
|
||||
});
|
||||
|
||||
it('should parse path - case 6', () => {
|
||||
const params = parsePathParams('https://www.example.com/users/:id/:');
|
||||
expect(params).toEqual([{ name: 'id', value: '' }]);
|
||||
});
|
||||
|
||||
it('should parse path - case 7', () => {
|
||||
const params = parsePathParams('https://www.example.com/users/:id/posts/:id');
|
||||
expect(params).toEqual([{ name: 'id', value: '' }]);
|
||||
});
|
||||
|
||||
it('should parse path - case 8', () => {
|
||||
const params = parsePathParams('https://www.example.com/users/:id/posts/:postId');
|
||||
expect(params).toEqual([
|
||||
{ name: 'id', value: '' },
|
||||
{ name: 'postId', value: '' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Url Utils - splitOnFirst', () => {
|
||||
it('should split on first - case 1', () => {
|
||||
const params = splitOnFirst('a', '=');
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { interpolate } = require('@usebruno/common');
|
||||
const { each, forOwn, cloneDeep } = require('lodash');
|
||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
let contentType = '';
|
||||
@ -86,6 +86,36 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
param.value = _interpolate(param.value);
|
||||
});
|
||||
|
||||
if (request.params.length) {
|
||||
let url = request.url;
|
||||
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
|
||||
try {
|
||||
url = new URL(url);
|
||||
} catch (e) {
|
||||
throw { message: 'Invalid URL format', originalError: e.message };
|
||||
}
|
||||
|
||||
const urlPaths = url.pathname
|
||||
.split('/')
|
||||
.filter((path) => path !== '')
|
||||
.map((path) => {
|
||||
if (path[0] !== ':') {
|
||||
return '/' + path;
|
||||
} else {
|
||||
const name = path.slice(1);
|
||||
const existingPathParam = request.params.find((param) => param.type === 'path' && param.name === name);
|
||||
return existingPathParam ? '/' + existingPathParam.value : '';
|
||||
}
|
||||
})
|
||||
.join('');
|
||||
|
||||
request.url = url.origin + urlPaths + url.search;
|
||||
}
|
||||
|
||||
if (request.proxy) {
|
||||
request.proxy.protocol = _interpolate(request.proxy.protocol);
|
||||
request.proxy.hostname = _interpolate(request.proxy.hostname);
|
||||
|
@ -29,7 +29,8 @@ const prepareRequest = (request, collectionRoot) => {
|
||||
let axiosRequest = {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
headers: headers
|
||||
headers: headers,
|
||||
paths: request.paths
|
||||
};
|
||||
|
||||
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||
|
@ -13,7 +13,7 @@ const collectionBruToJson = (bru) => {
|
||||
|
||||
const transformedJson = {
|
||||
request: {
|
||||
params: _.get(json, 'query', []),
|
||||
params: _.get(json, 'params', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
script: _.get(json, 'script', {}),
|
||||
@ -60,7 +60,7 @@ const bruToJson = (bru) => {
|
||||
method: _.upperCase(_.get(json, 'http.method')),
|
||||
url: _.get(json, 'http.url'),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
params: _.get(json, 'query', []),
|
||||
params: _.get(json, 'params', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
body: _.get(json, 'body', {}),
|
||||
vars: _.get(json, 'vars', []),
|
||||
|
@ -14,7 +14,7 @@ const collectionBruToJson = (bru) => {
|
||||
|
||||
const transformedJson = {
|
||||
request: {
|
||||
params: _.get(json, 'query', []),
|
||||
params: _.get(json, 'params', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
script: _.get(json, 'script', {}),
|
||||
@ -33,7 +33,7 @@ const collectionBruToJson = (bru) => {
|
||||
const jsonToCollectionBru = (json) => {
|
||||
try {
|
||||
const collectionBruJson = {
|
||||
query: _.get(json, 'request.params', []),
|
||||
params: _.get(json, 'request.params', []),
|
||||
headers: _.get(json, 'request.headers', []),
|
||||
auth: _.get(json, 'request.auth', {}),
|
||||
script: {
|
||||
@ -111,7 +111,7 @@ const bruToJson = (bru) => {
|
||||
request: {
|
||||
method: _.upperCase(_.get(json, 'http.method')),
|
||||
url: _.get(json, 'http.url'),
|
||||
params: _.get(json, 'query', []),
|
||||
params: _.get(json, 'params', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
body: _.get(json, 'body', {}),
|
||||
@ -162,7 +162,7 @@ const jsonToBru = (json) => {
|
||||
auth: _.get(json, 'request.auth.mode', 'none'),
|
||||
body: _.get(json, 'request.body.mode', 'none')
|
||||
},
|
||||
query: _.get(json, 'request.params', []),
|
||||
params: _.get(json, 'request.params', []),
|
||||
headers: _.get(json, 'request.headers', []),
|
||||
auth: _.get(json, 'request.auth', {}),
|
||||
body: _.get(json, 'request.body', {}),
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { interpolate } = require('@usebruno/common');
|
||||
const { each, forOwn, cloneDeep } = require('lodash');
|
||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
let contentType = '';
|
||||
@ -86,6 +86,36 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
param.value = _interpolate(param.value);
|
||||
});
|
||||
|
||||
if (request.params.length) {
|
||||
let url = request.url;
|
||||
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
|
||||
try {
|
||||
url = new URL(url);
|
||||
} catch (e) {
|
||||
throw { message: 'Invalid URL format', originalError: e.message };
|
||||
}
|
||||
|
||||
const urlPaths = url.pathname
|
||||
.split('/')
|
||||
.filter((path) => path !== '')
|
||||
.map((path) => {
|
||||
if (path[0] !== ':') {
|
||||
return '/' + path;
|
||||
} else {
|
||||
const name = path.slice(1);
|
||||
const existingPathParam = request.params.find((param) => param.type === 'path' && param.name === name);
|
||||
return existingPathParam ? '/' + existingPathParam.value : '';
|
||||
}
|
||||
})
|
||||
.join('');
|
||||
|
||||
request.url = url.origin + urlPaths + url.search;
|
||||
}
|
||||
|
||||
if (request.proxy) {
|
||||
request.proxy.protocol = _interpolate(request.proxy.protocol);
|
||||
request.proxy.hostname = _interpolate(request.proxy.hostname);
|
||||
|
@ -161,6 +161,7 @@ const prepareRequest = (request, collectionRoot, collectionPath) => {
|
||||
method: request.method,
|
||||
url,
|
||||
headers,
|
||||
params: request.params.filter((param) => param.type === 'path'),
|
||||
responseType: 'arraybuffer'
|
||||
};
|
||||
|
||||
|
@ -22,10 +22,11 @@ const { outdentString } = require('../../v1/src/utils');
|
||||
*
|
||||
*/
|
||||
const grammar = ohm.grammar(`Bru {
|
||||
BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)*
|
||||
BruFile = (meta | http | query | params | headers | auths | bodies | varsandassert | script | tests | docs)*
|
||||
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2
|
||||
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
|
||||
bodyforms = bodyformurlencoded | bodymultipart
|
||||
params = paramspath | paramsquery
|
||||
|
||||
nl = "\\r"? "\\n"
|
||||
st = " " | "\\t"
|
||||
@ -74,6 +75,8 @@ const grammar = ohm.grammar(`Bru {
|
||||
headers = "headers" dictionary
|
||||
|
||||
query = "query" dictionary
|
||||
paramspath = "params:path" dictionary
|
||||
paramsquery = "params:query" dictionary
|
||||
|
||||
varsandassert = varsreq | varsres | assert
|
||||
varsreq = "vars:pre-request" dictionary
|
||||
@ -133,6 +136,28 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
|
||||
});
|
||||
};
|
||||
|
||||
const mapPairListToKeyValPairsWithType = (pairList = [], type) => {
|
||||
if (!pairList.length) {
|
||||
return [];
|
||||
}
|
||||
return _.map(pairList[0], (pair) => {
|
||||
let name = _.keys(pair)[0];
|
||||
let value = pair[name];
|
||||
let enabled = true;
|
||||
if (name && name.length && name.charAt(0) === '~') {
|
||||
name = name.slice(1);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
value,
|
||||
enabled,
|
||||
type
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const mapPairListToKeyValPairsMultipart = (pairList = [], parseEnabled = true) => {
|
||||
const pairs = mapPairListToKeyValPairs(pairList, parseEnabled);
|
||||
|
||||
@ -321,7 +346,17 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
},
|
||||
query(_1, dictionary) {
|
||||
return {
|
||||
query: mapPairListToKeyValPairs(dictionary.ast)
|
||||
params: mapPairListToKeyValPairsWithType(dictionary.ast, 'query')
|
||||
};
|
||||
},
|
||||
paramspath(_1, dictionary) {
|
||||
return {
|
||||
params: mapPairListToKeyValPairsWithType(dictionary.ast, 'path')
|
||||
};
|
||||
},
|
||||
paramsquery(_1, dictionary) {
|
||||
return {
|
||||
params: mapPairListToKeyValPairsWithType(dictionary.ast, 'query')
|
||||
};
|
||||
},
|
||||
headers(_1, dictionary) {
|
||||
|
@ -30,7 +30,7 @@ const getValueString = (value) => {
|
||||
};
|
||||
|
||||
const jsonToBru = (json) => {
|
||||
const { meta, http, query, headers, auth, body, script, tests, vars, assertions, docs } = json;
|
||||
const { meta, http, params, headers, auth, body, script, tests, vars, assertions, docs } = json;
|
||||
|
||||
let bru = '';
|
||||
|
||||
@ -62,19 +62,23 @@ const jsonToBru = (json) => {
|
||||
`;
|
||||
}
|
||||
|
||||
if (query && query.length) {
|
||||
bru += 'query {';
|
||||
if (enabled(query).length) {
|
||||
if (params && params.length) {
|
||||
const queryParams = params.filter((param) => param.type === 'query');
|
||||
const pathParams = params.filter((param) => param.type === 'path');
|
||||
|
||||
if (queryParams.length) {
|
||||
bru += 'params:query {';
|
||||
if (enabled(queryParams).length) {
|
||||
bru += `\n${indentString(
|
||||
enabled(query)
|
||||
enabled(queryParams)
|
||||
.map((item) => `${item.name}: ${item.value}`)
|
||||
.join('\n')
|
||||
)}`;
|
||||
}
|
||||
|
||||
if (disabled(query).length) {
|
||||
if (disabled(queryParams).length) {
|
||||
bru += `\n${indentString(
|
||||
disabled(query)
|
||||
disabled(queryParams)
|
||||
.map((item) => `~${item.name}: ${item.value}`)
|
||||
.join('\n')
|
||||
)}`;
|
||||
@ -83,6 +87,15 @@ const jsonToBru = (json) => {
|
||||
bru += '\n}\n\n';
|
||||
}
|
||||
|
||||
if (pathParams.length) {
|
||||
bru += 'params:path {';
|
||||
|
||||
bru += `\n${indentString(pathParams.map((item) => `${item.name}: ${item.value}`).join('\n'))}`;
|
||||
|
||||
bru += '\n}\n\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (headers && headers.length) {
|
||||
bru += 'headers {';
|
||||
if (enabled(headers).length) {
|
||||
|
@ -185,6 +185,17 @@ const authSchema = Yup.object({
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
const keyValueWithTypeSchema = Yup.object({
|
||||
uid: uidSchema,
|
||||
name: Yup.string().nullable(),
|
||||
value: Yup.string().nullable(),
|
||||
description: Yup.string().nullable(),
|
||||
type: Yup.string().oneOf(['query', 'path']).required('type is required'),
|
||||
enabled: Yup.boolean()
|
||||
})
|
||||
.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
|
||||
@ -192,7 +203,7 @@ const requestSchema = Yup.object({
|
||||
url: requestUrlSchema,
|
||||
method: requestMethodSchema,
|
||||
headers: Yup.array().of(keyValueSchema).required('headers are required'),
|
||||
params: Yup.array().of(keyValueSchema).required('params are required'),
|
||||
params: Yup.array().of(keyValueWithTypeSchema).required('params are required'),
|
||||
auth: authSchema,
|
||||
body: requestBodySchema,
|
||||
script: Yup.object({
|
||||
|
@ -59,6 +59,7 @@ describe('Collection Schema Validation', () => {
|
||||
method: 'GET',
|
||||
headers: [],
|
||||
params: [],
|
||||
paths: [],
|
||||
body: {
|
||||
mode: 'none'
|
||||
}
|
||||
@ -116,6 +117,7 @@ describe('Collection Schema Validation', () => {
|
||||
method: 'GET',
|
||||
headers: [],
|
||||
params: [],
|
||||
paths: [],
|
||||
body: {
|
||||
mode: 'none'
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ describe('Request Schema Validation', () => {
|
||||
method: 'GET',
|
||||
headers: [],
|
||||
params: [],
|
||||
paths: [],
|
||||
body: {
|
||||
mode: 'none'
|
||||
}
|
||||
@ -24,6 +25,7 @@ describe('Request Schema Validation', () => {
|
||||
method: 'GET-junk',
|
||||
headers: [],
|
||||
params: [],
|
||||
paths: [],
|
||||
body: {
|
||||
mode: 'none'
|
||||
}
|
||||
|
@ -43,3 +43,48 @@ describe('bruno toml', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('joinPathUrl', () => {
|
||||
it('should join path and query params correctly', () => {
|
||||
const url = 'https://example.com/api/:id';
|
||||
const params = [
|
||||
{ name: 'id', type: 'path', enabled: true, value: '123' },
|
||||
{ name: 'sort', type: 'query', enabled: true, value: 'asc' },
|
||||
{ name: 'filter', type: 'query', enabled: true, value: 'active' }
|
||||
];
|
||||
const expectedUrl = 'https://example.com/api/123?sort=asc&filter=active';
|
||||
|
||||
const result = joinPathUrl(url, params);
|
||||
|
||||
expect(result).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should handle empty path and query params', () => {
|
||||
const url = 'https://example.com/api';
|
||||
const params = [];
|
||||
const expectedUrl = 'https://example.com/api';
|
||||
|
||||
const result = joinPathUrl(url, params);
|
||||
|
||||
expect(result).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should handle empty query params', () => {
|
||||
const url = 'https://example.com/api/:id';
|
||||
const params = [{ name: 'id', type: 'path', enabled: true, value: '123' }];
|
||||
const expectedUrl = 'https://example.com/api/123';
|
||||
|
||||
const result = joinPathUrl(url, params);
|
||||
|
||||
expect(result).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it('should handle invalid URL', () => {
|
||||
const url = 'example.com/api/:id';
|
||||
const params = [{ name: 'id', type: 'path', enabled: true, value: '123' }];
|
||||
const expectedUrl = 'http://example.com/api/123';
|
||||
|
||||
const result = joinPathUrl(url, params);
|
||||
|
||||
expect(result).toEqual(expectedUrl);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user