forked from extern/bruno
Merge branch 'main' into main
This commit is contained in:
commit
d5a6522563
2
.github/workflows/unit-tests.yml
vendored
2
.github/workflows/unit-tests.yml
vendored
@ -29,5 +29,7 @@ jobs:
|
|||||||
run: npm run test --workspace=packages/bruno-app
|
run: npm run test --workspace=packages/bruno-app
|
||||||
- name: Test Package bruno-js
|
- name: Test Package bruno-js
|
||||||
run: npm run test --workspace=packages/bruno-js
|
run: npm run test --workspace=packages/bruno-js
|
||||||
|
- 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
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
**English** | [Русский](/contributing_ru.md)
|
||||||
|
|
||||||
## Lets make bruno better, together !!
|
## Lets make bruno better, together !!
|
||||||
|
|
||||||
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
|
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
|
||||||
|
37
contributing_ru.md
Normal file
37
contributing_ru.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[English](/contributing.md) | **Русский**
|
||||||
|
|
||||||
|
## Давайте вместе сделаем Бруно лучше!!!
|
||||||
|
|
||||||
|
Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере.
|
||||||
|
|
||||||
|
### Стек
|
||||||
|
|
||||||
|
Bruno построен с использованием NextJs и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
|
||||||
|
|
||||||
|
Библиотеки, которые мы используем
|
||||||
|
|
||||||
|
- CSS - Tailwind
|
||||||
|
- Редакторы кода - Codemirror
|
||||||
|
- Управление состоянием - Redux
|
||||||
|
- Иконки - Tabler Icons
|
||||||
|
- Формы - formik
|
||||||
|
- Валидация схем - Yup
|
||||||
|
- Запросы клиента - axios
|
||||||
|
- Наблюдатель за файловой системой - chokidar
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
|
||||||
|
|
||||||
|
### Приступим к коду
|
||||||
|
|
||||||
|
Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки.
|
||||||
|
|
||||||
|
### Создание Pull Request
|
||||||
|
|
||||||
|
- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи
|
||||||
|
- Пожалуйста, соблюдайте формат создания веток
|
||||||
|
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
|
||||||
|
- Пример: feature/dark-mode
|
||||||
|
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
|
||||||
|
- Пример bugfix/bug-1
|
@ -1,3 +1,5 @@
|
|||||||
|
**English** | [Русский](/docs/development_ru.md)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
55
docs/development_ru.md
Normal file
55
docs/development_ru.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
[English](/docs/development.md) | **Русский**
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение nextjs в одном терминале, а затем запустить приложение electron в другом терминале.
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Локальная разработка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# используйте nodejs 18 версии
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# установите зависимости
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# билд документации по graphql
|
||||||
|
npm run build:graphql-docs
|
||||||
|
|
||||||
|
# билд bruno query
|
||||||
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# запустить next приложение ( терминал 1 )
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# запустить приложение electron ( терминал 2 )
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Устранение неисправностей
|
||||||
|
|
||||||
|
При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Удаление node_modules в подкаталогах
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Удаление package-lock в подкаталогах
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тестирование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
36
package-lock.json
generated
36
package-lock.json
generated
@ -6678,6 +6678,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decomment": {
|
||||||
|
"version": "0.9.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/decomment/-/decomment-0.9.5.tgz",
|
||||||
|
"integrity": "sha512-h0TZ8t6Dp49duwyDHo3iw67mnh9/UpFiSSiOb5gDK1sqoXzrfX/SQxIUQd2R2QEiSnqib0KF2fnKnGfAhAs6lg==",
|
||||||
|
"dependencies": {
|
||||||
|
"esprima": "4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.4",
|
||||||
|
"npm": ">=2.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/decompress-response": {
|
"node_modules/decompress-response": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
||||||
@ -7505,7 +7517,6 @@
|
|||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"esparse": "bin/esparse.js",
|
"esparse": "bin/esparse.js",
|
||||||
"esvalidate": "bin/esvalidate.js"
|
"esvalidate": "bin/esvalidate.js"
|
||||||
@ -16765,7 +16776,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-cli": {
|
"packages/bruno-cli": {
|
||||||
"name": "@usebruno/cli",
|
"name": "@usebruno/cli",
|
||||||
"version": "0.10.1",
|
"version": "0.11.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.6.0",
|
||||||
@ -16773,6 +16784,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
|
"decomment": "^0.9.5",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
@ -16810,7 +16822,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v0.18.0",
|
"version": "v0.19.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.6.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.5.0",
|
||||||
@ -16819,6 +16831,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
"decomment": "^0.9.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
"electron-notarize": "^1.2.2",
|
"electron-notarize": "^1.2.2",
|
||||||
@ -16848,7 +16861,9 @@
|
|||||||
"dmg-license": "^1.0.11"
|
"dmg-license": "^1.0.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/bruno-electron/0.5cd ../.0": {},
|
"packages/bruno-electron/0.5cd ../.0": {
|
||||||
|
"extraneous": true
|
||||||
|
},
|
||||||
"packages/bruno-electron/node_modules/@types/node": {
|
"packages/bruno-electron/node_modules/@types/node": {
|
||||||
"version": "16.18.11",
|
"version": "16.18.11",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -20092,6 +20107,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
|
"decomment": "*",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
@ -21201,6 +21217,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
"decomment": "^0.9.5",
|
||||||
"dmg-license": "^1.0.11",
|
"dmg-license": "^1.0.11",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron": "21.1.1",
|
"electron": "21.1.1",
|
||||||
@ -22309,6 +22326,14 @@
|
|||||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"decomment": {
|
||||||
|
"version": "0.9.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/decomment/-/decomment-0.9.5.tgz",
|
||||||
|
"integrity": "sha512-h0TZ8t6Dp49duwyDHo3iw67mnh9/UpFiSSiOb5gDK1sqoXzrfX/SQxIUQd2R2QEiSnqib0KF2fnKnGfAhAs6lg==",
|
||||||
|
"requires": {
|
||||||
|
"esprima": "4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"decompress-response": {
|
"decompress-response": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
||||||
@ -22958,8 +22983,7 @@
|
|||||||
"esprima": {
|
"esprima": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"esrecurse": {
|
"esrecurse": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
|
@ -6,6 +6,7 @@ import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons'
|
|||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import QueryEditor from 'components/RequestPane/QueryEditor';
|
import QueryEditor from 'components/RequestPane/QueryEditor';
|
||||||
|
import Auth from 'components/RequestPane/Auth';
|
||||||
import GraphQLVariables from 'components/RequestPane/GraphQLVariables';
|
import GraphQLVariables from 'components/RequestPane/GraphQLVariables';
|
||||||
import RequestHeaders from 'components/RequestPane/RequestHeaders';
|
import RequestHeaders from 'components/RequestPane/RequestHeaders';
|
||||||
import Vars from 'components/RequestPane/Vars';
|
import Vars from 'components/RequestPane/Vars';
|
||||||
@ -32,7 +33,14 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
|
|
||||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||||
|
|
||||||
let { schema, loadSchema, isLoading: isSchemaLoading, error: schemaError } = useGraphqlSchema(url, environment);
|
const request = item.draft ? item.draft.request : item.request;
|
||||||
|
|
||||||
|
let {
|
||||||
|
schema,
|
||||||
|
loadSchema,
|
||||||
|
isLoading: isSchemaLoading,
|
||||||
|
error: schemaError
|
||||||
|
} = useGraphqlSchema(url, environment, request, collection.collectionVariables);
|
||||||
|
|
||||||
const loadGqlSchema = () => {
|
const loadGqlSchema = () => {
|
||||||
if (!isSchemaLoading) {
|
if (!isSchemaLoading) {
|
||||||
@ -90,6 +98,9 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
case 'headers': {
|
case 'headers': {
|
||||||
return <RequestHeaders item={item} collection={collection} />;
|
return <RequestHeaders item={item} collection={collection} />;
|
||||||
}
|
}
|
||||||
|
case 'auth': {
|
||||||
|
return <Auth item={item} collection={collection} />;
|
||||||
|
}
|
||||||
case 'vars': {
|
case 'vars': {
|
||||||
return <Vars item={item} collection={collection} />;
|
return <Vars item={item} collection={collection} />;
|
||||||
}
|
}
|
||||||
@ -135,6 +146,9 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||||
Headers
|
Headers
|
||||||
</div>
|
</div>
|
||||||
|
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
|
||||||
|
Auth
|
||||||
|
</div>
|
||||||
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
|
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
|
||||||
Vars
|
Vars
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@ import { simpleHash } from 'utils/common';
|
|||||||
|
|
||||||
const schemaHashPrefix = 'bruno.graphqlSchema';
|
const schemaHashPrefix = 'bruno.graphqlSchema';
|
||||||
|
|
||||||
const useGraphqlSchema = (endpoint, environment) => {
|
const useGraphqlSchema = (endpoint, environment, request, collectionVariables) => {
|
||||||
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
|
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -25,7 +25,7 @@ const useGraphqlSchema = (endpoint, environment) => {
|
|||||||
|
|
||||||
const loadSchema = () => {
|
const loadSchema = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetchGqlSchema(endpoint, environment)
|
fetchGqlSchema(endpoint, environment, request, collectionVariables)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.then((s) => {
|
.then((s) => {
|
||||||
if (s && s.data) {
|
if (s && s.data) {
|
||||||
|
@ -105,7 +105,7 @@ const Sidebar = () => {
|
|||||||
Star
|
Star
|
||||||
</GitHubButton>
|
</GitHubButton>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.19.0</div>
|
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.20.0</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import useTelemetry from './useTelemetry';
|
import useTelemetry from './useTelemetry';
|
||||||
import useCollectionTreeSync from './useCollectionTreeSync';
|
import useCollectionTreeSync from './useCollectionTreeSync';
|
||||||
|
import useCollectionNextAction from './useCollectionNextAction';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
|
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
@ -10,6 +11,7 @@ export const AppContext = React.createContext();
|
|||||||
export const AppProvider = (props) => {
|
export const AppProvider = (props) => {
|
||||||
useTelemetry();
|
useTelemetry();
|
||||||
useCollectionTreeSync();
|
useCollectionTreeSync();
|
||||||
|
useCollectionNextAction();
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import each from 'lodash/each';
|
||||||
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
import { getDefaultRequestPaneTab, findItemInCollectionByPathname } from 'utils/collections/index';
|
||||||
|
import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
||||||
|
import { updateNextAction } from 'providers/ReduxStore/slices/collections/index';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
const useCollectionNextAction = () => {
|
||||||
|
const collections = useSelector((state) => state.collections.collections);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
each(collections, (collection) => {
|
||||||
|
if (collection.nextAction && collection.nextAction.type === 'OPEN_REQUEST') {
|
||||||
|
const item = findItemInCollectionByPathname(collection, get(collection, 'nextAction.payload.pathname'));
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
dispatch(updateNextAction(collection.uid, null));
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: item.uid,
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
requestPaneTab: getDefaultRequestPaneTab(item.type)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(hideHomePage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [collections, each, dispatch, updateNextAction, hideHomePage, addTab]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCollectionNextAction;
|
@ -26,6 +26,7 @@ import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
updateLastAction,
|
updateLastAction,
|
||||||
|
updateNextAction,
|
||||||
resetRunResults,
|
resetRunResults,
|
||||||
requestCancelled,
|
requestCancelled,
|
||||||
responseReceived,
|
responseReceived,
|
||||||
@ -595,6 +596,19 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
||||||
|
// the useCollectionNextAction() will track this and open the new request in a new tab
|
||||||
|
// once the request is created
|
||||||
|
dispatch(
|
||||||
|
updateNextAction({
|
||||||
|
nextAction: {
|
||||||
|
type: 'OPEN_REQUEST',
|
||||||
|
payload: {
|
||||||
|
pathname: fullName
|
||||||
|
}
|
||||||
|
},
|
||||||
|
collectionUid
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
||||||
}
|
}
|
||||||
@ -612,6 +626,20 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject);
|
||||||
|
|
||||||
|
// the useCollectionNextAction() will track this and open the new request in a new tab
|
||||||
|
// once the request is created
|
||||||
|
dispatch(
|
||||||
|
updateNextAction({
|
||||||
|
nextAction: {
|
||||||
|
type: 'OPEN_REQUEST',
|
||||||
|
payload: {
|
||||||
|
pathname: fullName
|
||||||
|
}
|
||||||
|
},
|
||||||
|
collectionUid
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
return reject(new Error('Duplicate request names are not allowed under the same folder'));
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,8 @@ export const collectionsSlice = createSlice({
|
|||||||
createCollection: (state, action) => {
|
createCollection: (state, action) => {
|
||||||
const collectionUids = map(state.collections, (c) => c.uid);
|
const collectionUids = map(state.collections, (c) => c.uid);
|
||||||
const collection = action.payload;
|
const collection = action.payload;
|
||||||
|
|
||||||
|
// TODO: move this to use the nextAction approach
|
||||||
// last action is used to track the last action performed on the collection
|
// last action is used to track the last action performed on the collection
|
||||||
// this is optional
|
// this is optional
|
||||||
// this is used in scenarios where we want to know the last action performed on the collection
|
// this is used in scenarios where we want to know the last action performed on the collection
|
||||||
@ -47,6 +49,10 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.importedAt = new Date().getTime();
|
collection.importedAt = new Date().getTime();
|
||||||
collection.lastAction = null;
|
collection.lastAction = null;
|
||||||
|
|
||||||
|
// an improvement over the above approach.
|
||||||
|
// this defines an action that need to be performed next and is executed vy the useCollectionNextAction()
|
||||||
|
collection.nextAction = null;
|
||||||
|
|
||||||
collapseCollection(collection);
|
collapseCollection(collection);
|
||||||
addDepth(collection.items);
|
addDepth(collection.items);
|
||||||
if (!collectionUids.includes(collection.uid)) {
|
if (!collectionUids.includes(collection.uid)) {
|
||||||
@ -93,6 +99,14 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.lastAction = lastAction;
|
collection.lastAction = lastAction;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateNextAction: (state, action) => {
|
||||||
|
const { collectionUid, nextAction } = action.payload;
|
||||||
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
collection.nextAction = nextAction;
|
||||||
|
}
|
||||||
|
},
|
||||||
collectionUnlinkEnvFileEvent: (state, action) => {
|
collectionUnlinkEnvFileEvent: (state, action) => {
|
||||||
const { data: environment, meta } = action.payload;
|
const { data: environment, meta } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
||||||
@ -1197,6 +1211,7 @@ export const {
|
|||||||
removeCollection,
|
removeCollection,
|
||||||
sortCollections,
|
sortCollections,
|
||||||
updateLastAction,
|
updateLastAction,
|
||||||
|
updateNextAction,
|
||||||
collectionUnlinkEnvFileEvent,
|
collectionUnlinkEnvFileEvent,
|
||||||
saveEnvironment,
|
saveEnvironment,
|
||||||
selectEnvironment,
|
selectEnvironment,
|
||||||
|
@ -41,6 +41,16 @@ const addSuffixToDuplicateName = (item, index, allItems) => {
|
|||||||
return nameSuffix !== 0 ? `${item.name}_${nameSuffix}` : item.name;
|
return nameSuffix !== 0 ? `${item.name}_${nameSuffix}` : item.name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const regexVariable = new RegExp('{{.*?}}', 'g');
|
||||||
|
|
||||||
|
const normalizeVariables = (value) => {
|
||||||
|
const variables = value.match(regexVariable) || [];
|
||||||
|
each(variables, (variable) => {
|
||||||
|
value = value.replace(variable, variable.replace('_.', '').replaceAll(' ', ''));
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
const transformInsomniaRequestItem = (request, index, allRequests) => {
|
const transformInsomniaRequestItem = (request, index, allRequests) => {
|
||||||
const name = addSuffixToDuplicateName(request, index, allRequests);
|
const name = addSuffixToDuplicateName(request, index, allRequests);
|
||||||
|
|
||||||
@ -51,6 +61,11 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
|||||||
request: {
|
request: {
|
||||||
url: request.url,
|
url: request.url,
|
||||||
method: request.method,
|
method: request.method,
|
||||||
|
auth: {
|
||||||
|
mode: 'none',
|
||||||
|
basic: null,
|
||||||
|
bearer: null
|
||||||
|
},
|
||||||
headers: [],
|
headers: [],
|
||||||
params: [],
|
params: [],
|
||||||
body: {
|
body: {
|
||||||
@ -84,7 +99,22 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const mimeType = get(request, 'body.mimeType', '');
|
const authType = get(request, 'authentication.type', '');
|
||||||
|
|
||||||
|
if (authType === 'basic') {
|
||||||
|
brunoRequestItem.request.auth.mode = 'basic';
|
||||||
|
brunoRequestItem.request.auth.basic = {
|
||||||
|
username: normalizeVariables(get(request, 'authentication.username', '')),
|
||||||
|
password: normalizeVariables(get(request, 'authentication.password', ''))
|
||||||
|
};
|
||||||
|
} else if (authType === 'bearer') {
|
||||||
|
brunoRequestItem.request.auth.mode = 'bearer';
|
||||||
|
brunoRequestItem.request.auth.bearer = {
|
||||||
|
token: normalizeVariables(get(request, 'authentication.token', ''))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeType = get(request, 'body.mimeType', '').split(';')[0];
|
||||||
|
|
||||||
if (mimeType === 'application/json') {
|
if (mimeType === 'application/json') {
|
||||||
brunoRequestItem.request.body.mode = 'json';
|
brunoRequestItem.request.body.mode = 'json';
|
||||||
|
@ -29,11 +29,14 @@ const sendHttpRequest = async (item, collection, environment, collectionVariable
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchGqlSchema = async (endpoint, environment) => {
|
export const fetchGqlSchema = async (endpoint, environment, request, collectionVariables) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer.invoke('fetch-gql-schema', endpoint, environment).then(resolve).catch(reject);
|
ipcRenderer
|
||||||
|
.invoke('fetch-gql-schema', endpoint, environment, request, collectionVariables)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
- show response time in milliseconds per request and total
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
- fix(#119) Support for Basic and Bearer Auth
|
- fix(#119) Support for Basic and Bearer Auth
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"summary": {
|
"summary": {
|
||||||
|
"totalRequests": 10,
|
||||||
|
"passedRequests": 10,
|
||||||
|
"failedRequests": 0,
|
||||||
"totalAssertions": 4,
|
"totalAssertions": 4,
|
||||||
"passedAssertions": 4,
|
"passedAssertions": 0,
|
||||||
"failedAssertions": 0,
|
"failedAssertions": 4,
|
||||||
"totalTests": 0,
|
"totalTests": 0,
|
||||||
"passedTests": 0,
|
"passedTests": 0,
|
||||||
"failedTests": 0
|
"failedTests": 0
|
||||||
@ -11,53 +14,33 @@
|
|||||||
{
|
{
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8080/test/v4",
|
"url": "http://localhost:3000/test/v4",
|
||||||
"headers": {}
|
"headers": {}
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"status": 200,
|
"status": 404,
|
||||||
"statusText": "OK",
|
"statusText": "Not Found",
|
||||||
"headers": {
|
"headers": {
|
||||||
"x-powered-by": "Express",
|
"x-powered-by": "Express",
|
||||||
"content-type": "application/json; charset=utf-8",
|
"content-security-policy": "default-src 'none'",
|
||||||
"content-length": "497",
|
"x-content-type-options": "nosniff",
|
||||||
"etag": "W/\"1f1-08gGpUcq2NTnMCVT5AuXxQ0DzGE\"",
|
"content-type": "text/html; charset=utf-8",
|
||||||
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
|
"content-length": "146",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
"connection": "close"
|
"connection": "close"
|
||||||
},
|
},
|
||||||
"data": {
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot GET /test/v4</pre>\n</body>\n</html>\n"
|
||||||
"path": "/test/v4",
|
|
||||||
"headers": {
|
|
||||||
"accept": "application/json, text/plain, */*",
|
|
||||||
"user-agent": "axios/1.5.0",
|
|
||||||
"accept-encoding": "gzip, compress, deflate, br",
|
|
||||||
"host": "localhost:8080",
|
|
||||||
"connection": "close"
|
|
||||||
},
|
|
||||||
"method": "GET",
|
|
||||||
"body": "",
|
|
||||||
"fresh": false,
|
|
||||||
"hostname": "localhost",
|
|
||||||
"ip": "",
|
|
||||||
"ips": [],
|
|
||||||
"protocol": "http",
|
|
||||||
"query": {},
|
|
||||||
"subdomains": [],
|
|
||||||
"xhr": false,
|
|
||||||
"os": {
|
|
||||||
"hostname": "05512cb2102c"
|
|
||||||
},
|
|
||||||
"connection": {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"error": null,
|
||||||
"assertionResults": [
|
"assertionResults": [
|
||||||
{
|
{
|
||||||
"uid": "mTrKBl5YU6jiAVG-phKT4",
|
"uid": "oidgfXLiyD8Jv0NBAHUHF",
|
||||||
"lhsExpr": "res.status",
|
"lhsExpr": "res.status",
|
||||||
"rhsExpr": "200",
|
"rhsExpr": "200",
|
||||||
"rhsOperand": "200",
|
"rhsOperand": "200",
|
||||||
"operator": "eq",
|
"operator": "eq",
|
||||||
"status": "pass"
|
"status": "fail",
|
||||||
|
"error": "expected 404 to equal 200"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"testResults": []
|
"testResults": []
|
||||||
@ -65,53 +48,33 @@
|
|||||||
{
|
{
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8080/test/v2",
|
"url": "http://localhost:3000/test/v2",
|
||||||
"headers": {}
|
"headers": {}
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"status": 200,
|
"status": 404,
|
||||||
"statusText": "OK",
|
"statusText": "Not Found",
|
||||||
"headers": {
|
"headers": {
|
||||||
"x-powered-by": "Express",
|
"x-powered-by": "Express",
|
||||||
"content-type": "application/json; charset=utf-8",
|
"content-security-policy": "default-src 'none'",
|
||||||
"content-length": "497",
|
"x-content-type-options": "nosniff",
|
||||||
"etag": "W/\"1f1-lMqxZgVOJiQXjF5yk3AFEU8O9Ro\"",
|
"content-type": "text/html; charset=utf-8",
|
||||||
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
|
"content-length": "146",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
"connection": "close"
|
"connection": "close"
|
||||||
},
|
},
|
||||||
"data": {
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot GET /test/v2</pre>\n</body>\n</html>\n"
|
||||||
"path": "/test/v2",
|
|
||||||
"headers": {
|
|
||||||
"accept": "application/json, text/plain, */*",
|
|
||||||
"user-agent": "axios/1.5.0",
|
|
||||||
"accept-encoding": "gzip, compress, deflate, br",
|
|
||||||
"host": "localhost:8080",
|
|
||||||
"connection": "close"
|
|
||||||
},
|
|
||||||
"method": "GET",
|
|
||||||
"body": "",
|
|
||||||
"fresh": false,
|
|
||||||
"hostname": "localhost",
|
|
||||||
"ip": "",
|
|
||||||
"ips": [],
|
|
||||||
"protocol": "http",
|
|
||||||
"query": {},
|
|
||||||
"subdomains": [],
|
|
||||||
"xhr": false,
|
|
||||||
"os": {
|
|
||||||
"hostname": "05512cb2102c"
|
|
||||||
},
|
|
||||||
"connection": {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"error": null,
|
||||||
"assertionResults": [
|
"assertionResults": [
|
||||||
{
|
{
|
||||||
"uid": "XsjjGx9cjt5t8tE_t69ZB",
|
"uid": "IgliYuHd9wKp6JNyqyHFK",
|
||||||
"lhsExpr": "res.status",
|
"lhsExpr": "res.status",
|
||||||
"rhsExpr": "200",
|
"rhsExpr": "200",
|
||||||
"rhsOperand": "200",
|
"rhsOperand": "200",
|
||||||
"operator": "eq",
|
"operator": "eq",
|
||||||
"status": "pass"
|
"status": "fail",
|
||||||
|
"error": "expected 404 to equal 200"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"testResults": []
|
"testResults": []
|
||||||
@ -119,53 +82,33 @@
|
|||||||
{
|
{
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8080/test/v3",
|
"url": "http://localhost:3000/test/v3",
|
||||||
"headers": {}
|
"headers": {}
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"status": 200,
|
"status": 404,
|
||||||
"statusText": "OK",
|
"statusText": "Not Found",
|
||||||
"headers": {
|
"headers": {
|
||||||
"x-powered-by": "Express",
|
"x-powered-by": "Express",
|
||||||
"content-type": "application/json; charset=utf-8",
|
"content-security-policy": "default-src 'none'",
|
||||||
"content-length": "497",
|
"x-content-type-options": "nosniff",
|
||||||
"etag": "W/\"1f1-tSiYu0/vWz3r+NYRCaed0aW1waw\"",
|
"content-type": "text/html; charset=utf-8",
|
||||||
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
|
"content-length": "146",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
"connection": "close"
|
"connection": "close"
|
||||||
},
|
},
|
||||||
"data": {
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot GET /test/v3</pre>\n</body>\n</html>\n"
|
||||||
"path": "/test/v3",
|
|
||||||
"headers": {
|
|
||||||
"accept": "application/json, text/plain, */*",
|
|
||||||
"user-agent": "axios/1.5.0",
|
|
||||||
"accept-encoding": "gzip, compress, deflate, br",
|
|
||||||
"host": "localhost:8080",
|
|
||||||
"connection": "close"
|
|
||||||
},
|
|
||||||
"method": "GET",
|
|
||||||
"body": "",
|
|
||||||
"fresh": false,
|
|
||||||
"hostname": "localhost",
|
|
||||||
"ip": "",
|
|
||||||
"ips": [],
|
|
||||||
"protocol": "http",
|
|
||||||
"query": {},
|
|
||||||
"subdomains": [],
|
|
||||||
"xhr": false,
|
|
||||||
"os": {
|
|
||||||
"hostname": "05512cb2102c"
|
|
||||||
},
|
|
||||||
"connection": {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"error": null,
|
||||||
"assertionResults": [
|
"assertionResults": [
|
||||||
{
|
{
|
||||||
"uid": "i_8MmDMtJA9YfvB_FrW15",
|
"uid": "u-3sRebrCyuUbZOkwS0z8",
|
||||||
"lhsExpr": "res.status",
|
"lhsExpr": "res.status",
|
||||||
"rhsExpr": "200",
|
"rhsExpr": "200",
|
||||||
"rhsOperand": "200",
|
"rhsOperand": "200",
|
||||||
"operator": "eq",
|
"operator": "eq",
|
||||||
"status": "pass"
|
"status": "fail",
|
||||||
|
"error": "expected 404 to equal 200"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"testResults": []
|
"testResults": []
|
||||||
@ -173,7 +116,7 @@
|
|||||||
{
|
{
|
||||||
"request": {
|
"request": {
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"url": "http://localhost:8080/test/v1",
|
"url": "http://localhost:3000/test/v1",
|
||||||
"headers": {
|
"headers": {
|
||||||
"content-type": "application/json"
|
"content-type": "application/json"
|
||||||
},
|
},
|
||||||
@ -181,57 +124,201 @@
|
|||||||
"test": "hello"
|
"test": "hello"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 404,
|
||||||
|
"statusText": "Not Found",
|
||||||
|
"headers": {
|
||||||
|
"x-powered-by": "Express",
|
||||||
|
"content-security-policy": "default-src 'none'",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
"content-type": "text/html; charset=utf-8",
|
||||||
|
"content-length": "147",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
|
"connection": "close"
|
||||||
|
},
|
||||||
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /test/v1</pre>\n</body>\n</html>\n"
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [
|
||||||
|
{
|
||||||
|
"uid": "PpKLK6I38I5_ibw4lZqLb",
|
||||||
|
"lhsExpr": "res.status",
|
||||||
|
"rhsExpr": "eq 200",
|
||||||
|
"rhsOperand": "200",
|
||||||
|
"operator": "eq",
|
||||||
|
"status": "fail",
|
||||||
|
"error": "expected 404 to equal 200"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testResults": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "http://localhost:3000/test",
|
||||||
|
"headers": {}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 404,
|
||||||
|
"statusText": "Not Found",
|
||||||
|
"headers": {
|
||||||
|
"x-powered-by": "Express",
|
||||||
|
"content-security-policy": "default-src 'none'",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
"content-type": "text/html; charset=utf-8",
|
||||||
|
"content-length": "144",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
|
"connection": "close"
|
||||||
|
},
|
||||||
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /test</pre>\n</body>\n</html>\n"
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [],
|
||||||
|
"testResults": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "HEAD",
|
||||||
|
"url": "http://localhost:3000/",
|
||||||
|
"headers": {}
|
||||||
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"statusText": "OK",
|
"statusText": "OK",
|
||||||
"headers": {
|
"headers": {
|
||||||
"x-powered-by": "Express",
|
"x-powered-by": "Express",
|
||||||
"content-type": "application/json; charset=utf-8",
|
"content-type": "text/html; charset=utf-8",
|
||||||
"content-length": "623",
|
"content-length": "12",
|
||||||
"etag": "W/\"26f-ku5QGz4p9f02u79vJIve7JH3QYM\"",
|
"etag": "W/\"c-Lve95gjOVATpfV8EL5X4nxwjKHE\"",
|
||||||
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
"connection": "close"
|
"connection": "close"
|
||||||
},
|
},
|
||||||
|
"data": ""
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [],
|
||||||
|
"testResults": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"headers": {}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 404,
|
||||||
|
"statusText": "Not Found",
|
||||||
|
"headers": {
|
||||||
|
"x-powered-by": "Express",
|
||||||
|
"content-security-policy": "default-src 'none'",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
"content-type": "text/html; charset=utf-8",
|
||||||
|
"content-length": "140",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
|
"connection": "close"
|
||||||
|
},
|
||||||
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /</pre>\n</body>\n</html>\n"
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [],
|
||||||
|
"testResults": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "http://localhost:3000/",
|
||||||
|
"headers": {
|
||||||
|
"content-type": "multipart/form-data; boundary=--------------------------897965859410704836065858"
|
||||||
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"path": "/test/v1",
|
"_overheadLength": 103,
|
||||||
"headers": {
|
"_valueLength": 3,
|
||||||
"accept": "application/json, text/plain, */*",
|
"_valuesToMeasure": [],
|
||||||
"content-type": "application/json",
|
"writable": false,
|
||||||
"user-agent": "axios/1.5.0",
|
"readable": true,
|
||||||
"content-length": "16",
|
"dataSize": 0,
|
||||||
"accept-encoding": "gzip, compress, deflate, br",
|
"maxDataSize": 2097152,
|
||||||
"host": "localhost:8080",
|
"pauseStreams": true,
|
||||||
"connection": "close"
|
"_released": true,
|
||||||
},
|
"_streams": [],
|
||||||
"method": "POST",
|
"_currentStream": null,
|
||||||
"body": "{\"test\":\"hello\"}",
|
"_insideLoop": false,
|
||||||
"fresh": false,
|
"_pendingNext": false,
|
||||||
"hostname": "localhost",
|
"_boundary": "--------------------------897965859410704836065858",
|
||||||
"ip": "",
|
"_events": {},
|
||||||
"ips": [],
|
"_eventsCount": 3
|
||||||
"protocol": "http",
|
|
||||||
"query": {},
|
|
||||||
"subdomains": [],
|
|
||||||
"xhr": false,
|
|
||||||
"os": {
|
|
||||||
"hostname": "05512cb2102c"
|
|
||||||
},
|
|
||||||
"connection": {},
|
|
||||||
"json": {
|
|
||||||
"test": "hello"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"assertionResults": [
|
"response": {
|
||||||
{
|
"status": 404,
|
||||||
"uid": "hNBSF_GBdSTFHNiyCcOn9",
|
"statusText": "Not Found",
|
||||||
"lhsExpr": "res.status",
|
"headers": {
|
||||||
"rhsExpr": "200",
|
"x-powered-by": "Express",
|
||||||
"rhsOperand": "200",
|
"content-security-policy": "default-src 'none'",
|
||||||
"operator": "eq",
|
"x-content-type-options": "nosniff",
|
||||||
"status": "pass"
|
"content-type": "text/html; charset=utf-8",
|
||||||
}
|
"content-length": "140",
|
||||||
],
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
|
"connection": "close"
|
||||||
|
},
|
||||||
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /</pre>\n</body>\n</html>\n"
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [],
|
||||||
|
"testResults": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "http://localhost:3000/",
|
||||||
|
"headers": {
|
||||||
|
"content-type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
"data": "a=b&c=d"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 404,
|
||||||
|
"statusText": "Not Found",
|
||||||
|
"headers": {
|
||||||
|
"x-powered-by": "Express",
|
||||||
|
"content-security-policy": "default-src 'none'",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
"content-type": "text/html; charset=utf-8",
|
||||||
|
"content-length": "140",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
|
"connection": "close"
|
||||||
|
},
|
||||||
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /</pre>\n</body>\n</html>\n"
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [],
|
||||||
|
"testResults": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "http://localhost:3000/test",
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/xml"
|
||||||
|
},
|
||||||
|
"data": "<xml>\n <test>1</test>\n</xml>"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 404,
|
||||||
|
"statusText": "Not Found",
|
||||||
|
"headers": {
|
||||||
|
"x-powered-by": "Express",
|
||||||
|
"content-security-policy": "default-src 'none'",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
"content-type": "text/html; charset=utf-8",
|
||||||
|
"content-length": "144",
|
||||||
|
"date": "Fri, 29 Sep 2023 00:37:50 GMT",
|
||||||
|
"connection": "close"
|
||||||
|
},
|
||||||
|
"data": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot POST /test</pre>\n</body>\n</html>\n"
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"assertionResults": [],
|
||||||
"testResults": []
|
"testResults": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@usebruno/cli",
|
"name": "@usebruno/cli",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -13,6 +13,9 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/usebruno/bruno.git"
|
"url": "git+https://github.com/usebruno/bruno.git"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"bin",
|
"bin",
|
||||||
@ -26,6 +29,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
|
"decomment": "^0.9.5",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
@ -12,17 +12,56 @@ const { dotenvToJson } = require('@usebruno/lang');
|
|||||||
const command = 'run [filename]';
|
const command = 'run [filename]';
|
||||||
const desc = 'Run a request';
|
const desc = 'Run a request';
|
||||||
|
|
||||||
const printRunSummary = (assertionResults, testResults) => {
|
const printRunSummary = (results) => {
|
||||||
// display assertion results and test results summary
|
let totalRequests = 0;
|
||||||
const totalAssertions = assertionResults.length;
|
let passedRequests = 0;
|
||||||
const passedAssertions = assertionResults.filter((result) => result.status === 'pass').length;
|
let failedRequests = 0;
|
||||||
const failedAssertions = totalAssertions - passedAssertions;
|
let totalAssertions = 0;
|
||||||
|
let passedAssertions = 0;
|
||||||
|
let failedAssertions = 0;
|
||||||
|
let totalTests = 0;
|
||||||
|
let passedTests = 0;
|
||||||
|
let failedTests = 0;
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
totalRequests += 1;
|
||||||
|
totalTests += result.testResults.length;
|
||||||
|
totalAssertions += result.assertionResults.length;
|
||||||
|
let anyFailed = false;
|
||||||
|
let hasAnyTestsOrAssertions = false;
|
||||||
|
for (const testResult of result.testResults) {
|
||||||
|
hasAnyTestsOrAssertions = true;
|
||||||
|
if (testResult.status === 'pass') {
|
||||||
|
passedTests += 1;
|
||||||
|
} else {
|
||||||
|
anyFailed = true;
|
||||||
|
failedTests += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const assertionResult of result.assertionResults) {
|
||||||
|
hasAnyTestsOrAssertions = true;
|
||||||
|
if (assertionResult.status === 'pass') {
|
||||||
|
passedAssertions += 1;
|
||||||
|
} else {
|
||||||
|
anyFailed = true;
|
||||||
|
failedAssertions += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasAnyTestsOrAssertions && result.error) {
|
||||||
|
failedRequests += 1;
|
||||||
|
} else {
|
||||||
|
passedRequests += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const totalTests = testResults.length;
|
|
||||||
const passedTests = testResults.filter((result) => result.status === 'pass').length;
|
|
||||||
const failedTests = totalTests - passedTests;
|
|
||||||
const maxLength = 12;
|
const maxLength = 12;
|
||||||
|
|
||||||
|
let requestSummary = `${rpad('Requests:', maxLength)} ${chalk.green(`${passedRequests} passed`)}`;
|
||||||
|
if (failedRequests > 0) {
|
||||||
|
requestSummary += `, ${chalk.red(`${failedRequests} failed`)}`;
|
||||||
|
}
|
||||||
|
requestSummary += `, ${totalRequests} total`;
|
||||||
|
|
||||||
let assertSummary = `${rpad('Tests:', maxLength)} ${chalk.green(`${passedTests} passed`)}`;
|
let assertSummary = `${rpad('Tests:', maxLength)} ${chalk.green(`${passedTests} passed`)}`;
|
||||||
if (failedTests > 0) {
|
if (failedTests > 0) {
|
||||||
assertSummary += `, ${chalk.red(`${failedTests} failed`)}`;
|
assertSummary += `, ${chalk.red(`${failedTests} failed`)}`;
|
||||||
@ -35,10 +74,14 @@ const printRunSummary = (assertionResults, testResults) => {
|
|||||||
}
|
}
|
||||||
testSummary += `, ${totalAssertions} total`;
|
testSummary += `, ${totalAssertions} total`;
|
||||||
|
|
||||||
console.log('\n' + chalk.bold(assertSummary));
|
console.log('\n' + chalk.bold(requestSummary));
|
||||||
|
console.log(chalk.bold(assertSummary));
|
||||||
console.log(chalk.bold(testSummary));
|
console.log(chalk.bold(testSummary));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
totalRequests,
|
||||||
|
passedRequests,
|
||||||
|
failedRequests,
|
||||||
totalAssertions,
|
totalAssertions,
|
||||||
passedAssertions,
|
passedAssertions,
|
||||||
failedAssertions,
|
failedAssertions,
|
||||||
@ -255,9 +298,7 @@ const handler = async function (argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const _isFile = await isFile(filename);
|
const _isFile = await isFile(filename);
|
||||||
let assertionResults = [];
|
let results = [];
|
||||||
let testResults = [];
|
|
||||||
let testrunResults = [];
|
|
||||||
|
|
||||||
let bruJsons = [];
|
let bruJsons = [];
|
||||||
|
|
||||||
@ -311,17 +352,12 @@ const handler = async function (argv) {
|
|||||||
brunoConfig
|
brunoConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result) {
|
results.push(result);
|
||||||
testrunResults.push(result);
|
|
||||||
const { assertionResults: _assertionResults, testResults: _testResults } = result;
|
|
||||||
|
|
||||||
assertionResults = assertionResults.concat(_assertionResults);
|
|
||||||
testResults = testResults.concat(_testResults);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary = printRunSummary(assertionResults, testResults);
|
const summary = printRunSummary(results);
|
||||||
console.log(chalk.dim(chalk.grey('Ran all requests.')));
|
const totalTime = results.reduce((acc, res) => acc + res.response.responseTime, 0);
|
||||||
|
console.log(chalk.dim(chalk.grey(`Ran all requests - ${totalTime} ms`)));
|
||||||
|
|
||||||
if (outputPath && outputPath.length) {
|
if (outputPath && outputPath.length) {
|
||||||
const outputDir = path.dirname(outputPath);
|
const outputDir = path.dirname(outputPath);
|
||||||
@ -333,14 +369,14 @@ const handler = async function (argv) {
|
|||||||
|
|
||||||
const outputJson = {
|
const outputJson = {
|
||||||
summary,
|
summary,
|
||||||
results: testrunResults
|
results
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(outputPath, JSON.stringify(outputJson, null, 2));
|
fs.writeFileSync(outputPath, JSON.stringify(outputJson, null, 2));
|
||||||
console.log(chalk.dim(chalk.grey(`Wrote results to ${outputPath}`)));
|
console.log(chalk.dim(chalk.grey(`Wrote results to ${outputPath}`)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (summary.failedAssertions > 0 || summary.failedTests > 0) {
|
if (summary.failedAssertions + summary.failedTests + summary.failedRequests > 0) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -354,5 +390,6 @@ module.exports = {
|
|||||||
command,
|
command,
|
||||||
desc,
|
desc,
|
||||||
builder,
|
builder,
|
||||||
handler
|
handler,
|
||||||
|
printRunSummary
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { get, each, filter } = require('lodash');
|
const { get, each, filter } = require('lodash');
|
||||||
|
const decomment = require('decomment');
|
||||||
|
|
||||||
const prepareRequest = (request) => {
|
const prepareRequest = (request) => {
|
||||||
const headers = {};
|
const headers = {};
|
||||||
@ -39,7 +40,7 @@ const prepareRequest = (request) => {
|
|||||||
axiosRequest.headers['content-type'] = 'application/json';
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
axiosRequest.data = JSON.parse(request.body.json);
|
axiosRequest.data = JSON.parse(decomment(request.body.json));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
axiosRequest.data = request.body.json;
|
axiosRequest.data = request.body.json;
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ const prepareRequest = (request) => {
|
|||||||
if (request.body.mode === 'graphql') {
|
if (request.body.mode === 'graphql') {
|
||||||
const graphqlQuery = {
|
const graphqlQuery = {
|
||||||
query: get(request, 'body.graphql.query'),
|
query: get(request, 'body.graphql.query'),
|
||||||
variables: JSON.parse(get(request, 'body.graphql.variables') || '{}')
|
variables: JSON.parse(decomment(get(request, 'body.graphql.variables') || '{}'))
|
||||||
};
|
};
|
||||||
if (!contentTypeDefined) {
|
if (!contentTypeDefined) {
|
||||||
axiosRequest.headers['content-type'] = 'application/json';
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const decomment = require('decomment');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { forOwn, each, extend, get } = require('lodash');
|
const { forOwn, each, extend, get } = require('lodash');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const axios = require('axios');
|
const axios = requir
|
||||||
|
e('axios');
|
||||||
const prepareRequest = require('./prepare-request');
|
const prepareRequest = require('./prepare-request');
|
||||||
const interpolateVars = require('./interpolate-vars');
|
const interpolateVars = require('./interpolate-vars');
|
||||||
const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@usebruno/js');
|
const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@usebruno/js');
|
||||||
@ -12,6 +14,7 @@ const { getOptions } = require('../utils/bru');
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const { HttpProxyAgent } = require('http-proxy-agent');
|
const { HttpProxyAgent } = require('http-proxy-agent');
|
||||||
|
const { makeAxiosInstance } = require('../utils/axios-instance');
|
||||||
|
|
||||||
const runSingleRequest = async function (
|
const runSingleRequest = async function (
|
||||||
filename,
|
filename,
|
||||||
@ -22,9 +25,9 @@ const runSingleRequest = async function (
|
|||||||
processEnvVars,
|
processEnvVars,
|
||||||
brunoConfig
|
brunoConfig
|
||||||
) {
|
) {
|
||||||
let request;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let request;
|
||||||
|
|
||||||
request = prepareRequest(bruJson.request);
|
request = prepareRequest(bruJson.request);
|
||||||
|
|
||||||
// make axios work in node using form data
|
// make axios work in node using form data
|
||||||
@ -57,7 +60,7 @@ const runSingleRequest = async function (
|
|||||||
if (requestScriptFile && requestScriptFile.length) {
|
if (requestScriptFile && requestScriptFile.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
await scriptRuntime.runRequestScript(
|
await scriptRuntime.runRequestScript(
|
||||||
requestScriptFile,
|
decomment(requestScriptFile),
|
||||||
request,
|
request,
|
||||||
envVariables,
|
envVariables,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
@ -124,10 +127,48 @@ const runSingleRequest = async function (
|
|||||||
request.data = qs.stringify(request.data);
|
request.data = qs.stringify(request.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run request
|
let response, responseTime;
|
||||||
const response = await axios(request);
|
try {
|
||||||
|
// run request
|
||||||
|
const axiosInstance = makeAxiosInstance();
|
||||||
|
|
||||||
console.log(chalk.green(stripExtension(filename)) + chalk.dim(` (${response.status} ${response.statusText})`));
|
/** @type {import('axios').AxiosResponse} */
|
||||||
|
response = await axiosInstance(request);
|
||||||
|
|
||||||
|
// Prevents the duration on leaking to the actual result
|
||||||
|
responseTime = response.headers.get('request-duration');
|
||||||
|
response.headers.delete('request-duration');
|
||||||
|
} catch (err) {
|
||||||
|
if (err && err.response) {
|
||||||
|
response = err.response;
|
||||||
|
|
||||||
|
// Prevents the duration on leaking to the actual result
|
||||||
|
responseTime = response.headers.get('request-duration');
|
||||||
|
response.headers.delete('request-duration');
|
||||||
|
} else {
|
||||||
|
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
method: request.method,
|
||||||
|
url: request.url,
|
||||||
|
headers: request.headers,
|
||||||
|
data: request.data
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
status: null,
|
||||||
|
statusText: null,
|
||||||
|
headers: null,
|
||||||
|
data: null,
|
||||||
|
responseTime: 0
|
||||||
|
},
|
||||||
|
error: err.message,
|
||||||
|
assertionResults: [],
|
||||||
|
testResults: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.green(stripExtension(filename)) + chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`));
|
||||||
|
|
||||||
// run post-response vars
|
// run post-response vars
|
||||||
const postResponseVars = get(bruJson, 'request.vars.res');
|
const postResponseVars = get(bruJson, 'request.vars.res');
|
||||||
@ -149,7 +190,7 @@ const runSingleRequest = async function (
|
|||||||
if (responseScriptFile && responseScriptFile.length) {
|
if (responseScriptFile && responseScriptFile.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
await scriptRuntime.runResponseScript(
|
await scriptRuntime.runResponseScript(
|
||||||
responseScriptFile,
|
decomment(responseScriptFile),
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVariables,
|
envVariables,
|
||||||
@ -190,7 +231,7 @@ const runSingleRequest = async function (
|
|||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const result = await testRuntime.runTests(
|
const result = await testRuntime.runTests(
|
||||||
testFile,
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVariables,
|
envVariables,
|
||||||
@ -223,102 +264,32 @@ const runSingleRequest = async function (
|
|||||||
status: response.status,
|
status: response.status,
|
||||||
statusText: response.statusText,
|
statusText: response.statusText,
|
||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
data: response.data
|
data: response.data,
|
||||||
|
responseTime
|
||||||
},
|
},
|
||||||
|
error: null,
|
||||||
assertionResults,
|
assertionResults,
|
||||||
testResults
|
testResults
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err && err.response) {
|
return {
|
||||||
console.log(
|
request: {
|
||||||
chalk.green(stripExtension(filename)) + chalk.dim(` (${err.response.status} ${err.response.statusText})`)
|
method: null,
|
||||||
);
|
url: null,
|
||||||
|
headers: null,
|
||||||
// run post-response vars
|
data: null
|
||||||
const postResponseVars = get(bruJson, 'request.vars.res');
|
},
|
||||||
if (postResponseVars && postResponseVars.length) {
|
response: {
|
||||||
const varsRuntime = new VarsRuntime();
|
status: null,
|
||||||
varsRuntime.runPostResponseVars(
|
statusText: null,
|
||||||
postResponseVars,
|
headers: null,
|
||||||
request,
|
data: null,
|
||||||
err.response,
|
responseTime: 0
|
||||||
envVariables,
|
},
|
||||||
collectionVariables,
|
error: err.message,
|
||||||
collectionPath,
|
assertionResults: [],
|
||||||
processEnvVars
|
testResults: []
|
||||||
);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// run post response script
|
|
||||||
const responseScriptFile = get(bruJson, 'request.script.res');
|
|
||||||
if (responseScriptFile && responseScriptFile.length) {
|
|
||||||
const scriptRuntime = new ScriptRuntime();
|
|
||||||
await scriptRuntime.runResponseScript(
|
|
||||||
responseScriptFile,
|
|
||||||
request,
|
|
||||||
err.response,
|
|
||||||
envVariables,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
null,
|
|
||||||
processEnvVars
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// run assertions
|
|
||||||
let assertionResults = [];
|
|
||||||
const assertions = get(bruJson, 'request.assertions');
|
|
||||||
if (assertions) {
|
|
||||||
const assertRuntime = new AssertRuntime();
|
|
||||||
assertionResults = assertRuntime.runAssertions(
|
|
||||||
assertions,
|
|
||||||
request,
|
|
||||||
err.response,
|
|
||||||
envVariables,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath
|
|
||||||
);
|
|
||||||
|
|
||||||
each(assertionResults, (r) => {
|
|
||||||
if (r.status === 'pass') {
|
|
||||||
console.log(chalk.green(` ✓ `) + chalk.dim(`assert: ${r.lhsExpr}: ${r.rhsExpr}`));
|
|
||||||
} else {
|
|
||||||
console.log(chalk.red(` ✕ `) + chalk.red(`assert: ${r.lhsExpr}: ${r.rhsExpr}`));
|
|
||||||
console.log(chalk.red(` ${r.error}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests
|
|
||||||
let testResults = [];
|
|
||||||
const testFile = get(bruJson, 'request.tests');
|
|
||||||
if (typeof testFile === 'string') {
|
|
||||||
const testRuntime = new TestRuntime();
|
|
||||||
const result = await testRuntime.runTests(
|
|
||||||
testFile,
|
|
||||||
request,
|
|
||||||
err.response,
|
|
||||||
envVariables,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
null,
|
|
||||||
processEnvVars
|
|
||||||
);
|
|
||||||
testResults = get(result, 'results', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testResults && testResults.length) {
|
|
||||||
each(testResults, (testResult) => {
|
|
||||||
if (testResult.status === 'pass') {
|
|
||||||
console.log(chalk.green(` ✓ `) + chalk.dim(testResult.description));
|
|
||||||
} else {
|
|
||||||
console.log(chalk.red(` ✕ `) + chalk.red(testResult.description));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
40
packages/bruno-cli/src/utils/axios-instance.js
Normal file
40
packages/bruno-cli/src/utils/axios-instance.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that configures axios with timing interceptors
|
||||||
|
* Important to note here that the timings are not completely accurate.
|
||||||
|
* @see https://github.com/axios/axios/issues/695
|
||||||
|
* @returns {import('axios').AxiosStatic}
|
||||||
|
*/
|
||||||
|
function makeAxiosInstance() {
|
||||||
|
/** @type {import('axios').AxiosStatic} */
|
||||||
|
const instance = axios.create();
|
||||||
|
|
||||||
|
instance.interceptors.request.use((config) => {
|
||||||
|
config.headers['request-start-time'] = Date.now();
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
const end = Date.now();
|
||||||
|
const start = response.config.headers['request-start-time'];
|
||||||
|
response.headers['request-duration'] = end - start;
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (error.response) {
|
||||||
|
const end = Date.now();
|
||||||
|
const start = error.config.headers['request-start-time'];
|
||||||
|
error.response.headers['request-duration'] = end - start;
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
makeAxiosInstance
|
||||||
|
};
|
67
packages/bruno-cli/tests/commands/run.spec.js
Normal file
67
packages/bruno-cli/tests/commands/run.spec.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const { describe, it, expect } = require('@jest/globals');
|
||||||
|
|
||||||
|
const { printRunSummary } = require('../../src/commands/run');
|
||||||
|
|
||||||
|
describe('printRunSummary', () => {
|
||||||
|
// Suppress console.log output
|
||||||
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
|
||||||
|
it('should produce the correct summary for a successful run', () => {
|
||||||
|
const results = [
|
||||||
|
{
|
||||||
|
testResults: [{ status: 'pass' }, { status: 'pass' }, { status: 'pass' }],
|
||||||
|
assertionResults: [{ status: 'pass' }, { status: 'pass' }],
|
||||||
|
error: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testResults: [{ status: 'pass' }, { status: 'pass' }],
|
||||||
|
assertionResults: [{ status: 'pass' }, { status: 'pass' }, { status: 'pass' }],
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const summary = printRunSummary(results);
|
||||||
|
|
||||||
|
expect(summary.totalRequests).toBe(2);
|
||||||
|
expect(summary.passedRequests).toBe(2);
|
||||||
|
expect(summary.failedRequests).toBe(0);
|
||||||
|
expect(summary.totalAssertions).toBe(5);
|
||||||
|
expect(summary.passedAssertions).toBe(5);
|
||||||
|
expect(summary.failedAssertions).toBe(0);
|
||||||
|
expect(summary.totalTests).toBe(5);
|
||||||
|
expect(summary.passedTests).toBe(5);
|
||||||
|
expect(summary.failedTests).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should produce the correct summary for a failed run', () => {
|
||||||
|
const results = [
|
||||||
|
{
|
||||||
|
testResults: [{ status: 'fail' }, { status: 'pass' }, { status: 'pass' }],
|
||||||
|
assertionResults: [{ status: 'pass' }, { status: 'fail' }],
|
||||||
|
error: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testResults: [{ status: 'pass' }, { status: 'fail' }],
|
||||||
|
assertionResults: [{ status: 'pass' }, { status: 'fail' }, { status: 'fail' }],
|
||||||
|
error: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testResults: [],
|
||||||
|
assertionResults: [],
|
||||||
|
error: new Error('Request failed')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const summary = printRunSummary(results);
|
||||||
|
|
||||||
|
expect(summary.totalRequests).toBe(3);
|
||||||
|
expect(summary.passedRequests).toBe(2);
|
||||||
|
expect(summary.failedRequests).toBe(1);
|
||||||
|
expect(summary.totalAssertions).toBe(5);
|
||||||
|
expect(summary.passedAssertions).toBe(2);
|
||||||
|
expect(summary.failedAssertions).toBe(3);
|
||||||
|
expect(summary.totalTests).toBe(5);
|
||||||
|
expect(summary.passedTests).toBe(3);
|
||||||
|
expect(summary.failedTests).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "v0.19.0",
|
"version": "v0.20.0",
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||||
"homepage": "https://www.usebruno.com",
|
"homepage": "https://www.usebruno.com",
|
||||||
@ -21,6 +21,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
"decomment": "^0.9.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
"electron-notarize": "^1.2.2",
|
"electron-notarize": "^1.2.2",
|
||||||
|
@ -103,7 +103,7 @@ const addEnvironmentFile = async (win, pathname, collectionUid, collectionPath)
|
|||||||
const envSecrets = environmentSecretsStore.getEnvSecrets(collectionPath, file.data);
|
const envSecrets = environmentSecretsStore.getEnvSecrets(collectionPath, file.data);
|
||||||
_.each(envSecrets, (secret) => {
|
_.each(envSecrets, (secret) => {
|
||||||
const variable = _.find(file.data.variables, (v) => v.name === secret.name);
|
const variable = _.find(file.data.variables, (v) => v.name === secret.name);
|
||||||
if (variable) {
|
if (variable && secret.value) {
|
||||||
variable.value = decryptString(secret.value);
|
variable.value = decryptString(secret.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -137,7 +137,7 @@ const changeEnvironmentFile = async (win, pathname, collectionUid, collectionPat
|
|||||||
const envSecrets = environmentSecretsStore.getEnvSecrets(collectionPath, file.data);
|
const envSecrets = environmentSecretsStore.getEnvSecrets(collectionPath, file.data);
|
||||||
_.each(envSecrets, (secret) => {
|
_.each(envSecrets, (secret) => {
|
||||||
const variable = _.find(file.data.variables, (v) => v.name === secret.name);
|
const variable = _.find(file.data.variables, (v) => v.name === secret.name);
|
||||||
if (variable) {
|
if (variable && secret.value) {
|
||||||
variable.value = decryptString(secret.value);
|
variable.value = decryptString(secret.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -35,7 +35,10 @@ app.on('ready', async () => {
|
|||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
webviewTag: true
|
webviewTag: true
|
||||||
}
|
},
|
||||||
|
title: 'Bruno',
|
||||||
|
icon: path.join(__dirname, 'about/256x256.png'),
|
||||||
|
autoHideMenuBar: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = isDev
|
const url = isDev
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const decomment = require('decomment');
|
||||||
const Mustache = require('mustache');
|
const Mustache = require('mustache');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const { ipcMain } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
@ -153,7 +154,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (requestScript && requestScript.length) {
|
if (requestScript && requestScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runRequestScript(
|
const result = await scriptRuntime.runRequestScript(
|
||||||
requestScript,
|
decomment(requestScript),
|
||||||
request,
|
request,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
@ -280,7 +281,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (responseScript && responseScript.length) {
|
if (responseScript && responseScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runResponseScript(
|
const result = await scriptRuntime.runResponseScript(
|
||||||
responseScript,
|
decomment(responseScript),
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVars,
|
envVars,
|
||||||
@ -326,7 +327,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
testFile,
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVars,
|
envVars,
|
||||||
@ -405,7 +406,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
testFile,
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
error.response,
|
error.response,
|
||||||
envVars,
|
envVars,
|
||||||
@ -461,10 +462,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('fetch-gql-schema', async (event, endpoint, environment) => {
|
ipcMain.handle('fetch-gql-schema', async (event, endpoint, environment, request, collectionVariables) => {
|
||||||
try {
|
try {
|
||||||
const envVars = getEnvVars(environment);
|
const envVars = getEnvVars(environment);
|
||||||
const request = prepareGqlIntrospectionRequest(endpoint, envVars);
|
const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request);
|
||||||
|
|
||||||
const preferences = getPreferences();
|
const preferences = getPreferences();
|
||||||
const sslVerification = get(preferences, 'request.sslVerification', true);
|
const sslVerification = get(preferences, 'request.sslVerification', true);
|
||||||
@ -475,7 +476,9 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios(request);
|
interpolateVars(preparedRequest, envVars, collectionVariables);
|
||||||
|
|
||||||
|
const response = await axios(preparedRequest);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
@ -604,7 +607,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (requestScript && requestScript.length) {
|
if (requestScript && requestScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runRequestScript(
|
const result = await scriptRuntime.runRequestScript(
|
||||||
requestScript,
|
decomment(requestScript),
|
||||||
request,
|
request,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
@ -705,7 +708,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (responseScript && responseScript.length) {
|
if (responseScript && responseScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runResponseScript(
|
const result = await scriptRuntime.runResponseScript(
|
||||||
responseScript,
|
decomment(responseScript),
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVars,
|
envVars,
|
||||||
@ -749,7 +752,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
testFile,
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVars,
|
envVars,
|
||||||
@ -829,7 +832,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
testFile,
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
error.response,
|
error.response,
|
||||||
envVars,
|
envVars,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
const Mustache = require('mustache');
|
const Mustache = require('mustache');
|
||||||
const { getIntrospectionQuery } = require('graphql');
|
const { getIntrospectionQuery } = require('graphql');
|
||||||
|
const { get } = require('lodash');
|
||||||
|
|
||||||
// override the default escape function to prevent escaping
|
// override the default escape function to prevent escaping
|
||||||
Mustache.escape = function (value) {
|
Mustache.escape = function (value) {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const prepareGqlIntrospectionRequest = (endpoint, envVars) => {
|
const prepareGqlIntrospectionRequest = (endpoint, envVars, request) => {
|
||||||
if (endpoint && endpoint.length) {
|
if (endpoint && endpoint.length) {
|
||||||
endpoint = Mustache.render(endpoint, envVars);
|
endpoint = Mustache.render(endpoint, envVars);
|
||||||
}
|
}
|
||||||
@ -15,7 +16,7 @@ const prepareGqlIntrospectionRequest = (endpoint, envVars) => {
|
|||||||
query: introspectionQuery
|
query: introspectionQuery
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = {
|
let axiosRequest = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
headers: {
|
headers: {
|
||||||
@ -25,7 +26,20 @@ const prepareGqlIntrospectionRequest = (endpoint, envVars) => {
|
|||||||
data: JSON.stringify(queryParams)
|
data: JSON.stringify(queryParams)
|
||||||
};
|
};
|
||||||
|
|
||||||
return request;
|
if (request.auth) {
|
||||||
|
if (request.auth.mode === 'basic') {
|
||||||
|
axiosRequest.auth = {
|
||||||
|
username: get(request, 'auth.basic.username'),
|
||||||
|
password: get(request, 'auth.basic.password')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.auth.mode === 'bearer') {
|
||||||
|
axiosRequest.headers.authorization = `Bearer ${get(request, 'auth.bearer.token')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = prepareGqlIntrospectionRequest;
|
module.exports = prepareGqlIntrospectionRequest;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { get, each, filter } = require('lodash');
|
const { get, each, filter } = require('lodash');
|
||||||
|
const decomment = require('decomment');
|
||||||
|
|
||||||
const prepareRequest = (request) => {
|
const prepareRequest = (request) => {
|
||||||
const headers = {};
|
const headers = {};
|
||||||
@ -37,7 +38,8 @@ const prepareRequest = (request) => {
|
|||||||
axiosRequest.headers['content-type'] = 'application/json';
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
axiosRequest.data = JSON.parse(request.body.json);
|
// axiosRequest.data = JSON.parse(request.body.json);
|
||||||
|
axiosRequest.data = JSON.parse(decomment(request.body.json));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
axiosRequest.data = request.body.json;
|
axiosRequest.data = request.body.json;
|
||||||
}
|
}
|
||||||
@ -76,7 +78,7 @@ const prepareRequest = (request) => {
|
|||||||
if (request.body.mode === 'graphql') {
|
if (request.body.mode === 'graphql') {
|
||||||
const graphqlQuery = {
|
const graphqlQuery = {
|
||||||
query: get(request, 'body.graphql.query'),
|
query: get(request, 'body.graphql.query'),
|
||||||
variables: JSON.parse(get(request, 'body.graphql.variables') || '{}')
|
variables: JSON.parse(decomment(get(request, 'body.graphql.variables') || '{}'))
|
||||||
};
|
};
|
||||||
if (!contentTypeDefined) {
|
if (!contentTypeDefined) {
|
||||||
axiosRequest.headers['content-type'] = 'application/json';
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
|
18
readme.md
18
readme.md
@ -6,10 +6,12 @@
|
|||||||
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
|
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
|
||||||
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
|
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
|
||||||
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
|
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
|
||||||
[![Twitter](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=twitter)](https://twitter.com/use_bruno)
|
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
|
||||||
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
||||||
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
|
**English** | [Русский](/readme_ru.md)
|
||||||
|
|
||||||
Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there.
|
Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there.
|
||||||
|
|
||||||
Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests.
|
Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests.
|
||||||
@ -30,13 +32,19 @@ Or any version control system of your choice
|
|||||||
|
|
||||||
![bruno](assets/images/version-control.png) <br /><br />
|
![bruno](assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
### Website 📄
|
### Important Links 📌
|
||||||
|
|
||||||
Please visit [here](https://www.usebruno.com) to checkout our website and download the app
|
- [Our Long Term Vision](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
|
||||||
|
- [Documentation](https://docs.usebruno.com)
|
||||||
|
- [Website](https://www.usebruno.com)
|
||||||
|
- [Download](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
### Documentation 📄
|
### Showcase 🎥
|
||||||
|
|
||||||
Please visit [here](https://docs.usebruno.com) for documentation
|
- [Testimonials](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
- [Knowledge Hub](https://github.com/usebruno/bruno/discussions/386)
|
||||||
|
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
|
||||||
|
|
||||||
### Support ❤️
|
### Support ❤️
|
||||||
|
|
||||||
|
79
readme_ru.md
Normal file
79
readme_ru.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<br />
|
||||||
|
<img src="assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
|
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
|
||||||
|
|
||||||
|
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
|
||||||
|
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
|
||||||
|
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
|
||||||
|
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
|
||||||
|
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
||||||
|
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
|
[English](/readme.md) | **Русский**
|
||||||
|
|
||||||
|
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
||||||
|
|
||||||
|
Bruno хранит ваши коллекции непосредственно в папке в вашей файловой системе. Для сохранения информации об API-запросах мы используем язык Bru.
|
||||||
|
|
||||||
|
Для совместной работы над коллекциями API можно использовать git или любой другой контроль версий по вашему выбору.
|
||||||
|
|
||||||
|
Bruno работает только в автономном режиме. Добавление облачной синхронизации в Bruno не планируется. Мы ценим конфиденциальность ваших данных и считаем, что они должны оставаться на вашем устройстве. Ознакомьтесь с нашим долгосрочным видением [здесь](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
|
![bruno](assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
|
### Работа на нескольких платформах 🖥️
|
||||||
|
|
||||||
|
![bruno](assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
|
### Совместная работа через Git 👩💻🧑💻
|
||||||
|
|
||||||
|
Или другая система контроля версий по вашему выбору
|
||||||
|
|
||||||
|
![bruno](assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
|
### Важные ссылки 📌
|
||||||
|
|
||||||
|
- [Наше долгосрочное видение](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
|
||||||
|
- [Документация](https://docs.usebruno.com)
|
||||||
|
- [Сайт](https://www.usebruno.com)
|
||||||
|
- [Скачать Bruno](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
|
### Витрина 🎥
|
||||||
|
|
||||||
|
- [Отзывы](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
- [Центр знаний](https://github.com/usebruno/bruno/discussions/386)
|
||||||
|
- [Скриптомания](https://github.com/usebruno/bruno/discussions/385)
|
||||||
|
|
||||||
|
### Поддержка ❤️
|
||||||
|
|
||||||
|
Гав! Если вам нравится проект, нажмите на звездочку ⭐ !!!
|
||||||
|
|
||||||
|
### Поделись отзывами 📣
|
||||||
|
|
||||||
|
Если Бруно помог вам в работе и в ваших командах, пожалуйста, не забудьте поделиться своим [отзывом на нашем обсуждении в github](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
|
||||||
|
### Внести вклад 👩💻🧑💻
|
||||||
|
|
||||||
|
Я рад, что Вы хотите улучшить Бруно. Пожалуйста, ознакомьтесь с [этим гайдом](contributing_ru.md)
|
||||||
|
|
||||||
|
Даже если вы не можете внести свой вклад с помощью кода, пожалуйста, не стесняйтесь сообщать об ошибках и пожеланиях к функциям, которые необходимо реализовать для решения вашей задачи.
|
||||||
|
|
||||||
|
### Авторы
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://github.com/usebruno/bruno/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Оставайтесь на связи 🌐
|
||||||
|
|
||||||
|
[X ( Twitter )](https://twitter.com/use_bruno) <br />
|
||||||
|
[Наш сайт](https://www.usebruno.com) <br />
|
||||||
|
[Discord](https://discord.com/invite/KgcZUncpjq)
|
||||||
|
|
||||||
|
### Лицензия 📄
|
||||||
|
|
||||||
|
[MIT](license.md)
|
Loading…
Reference in New Issue
Block a user