Merge branch 'main' into feature/license_all_modules

This commit is contained in:
Anoop M D 2023-09-26 22:49:19 +05:30 committed by GitHub
commit cbb975d81d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 329 additions and 48 deletions

View File

@ -1,5 +1,9 @@
# Changelog # Changelog
## 0.9.0
- `--output` flag to collect the results of your API tests
## 0.8.0 ## 0.8.0
- `--env-var` flag to set environment variables - `--env-var` flag to set environment variables

View File

@ -0,0 +1,238 @@
{
"summary": {
"totalAssertions": 4,
"passedAssertions": 4,
"failedAssertions": 0,
"totalTests": 0,
"passedTests": 0,
"failedTests": 0
},
"results": [
{
"request": {
"method": "GET",
"url": "http://localhost:8080/test/v4",
"headers": {}
},
"response": {
"status": 200,
"statusText": "OK",
"headers": {
"x-powered-by": "Express",
"content-type": "application/json; charset=utf-8",
"content-length": "497",
"etag": "W/\"1f1-08gGpUcq2NTnMCVT5AuXxQ0DzGE\"",
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
"connection": "close"
},
"data": {
"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": {}
}
},
"assertionResults": [
{
"uid": "mTrKBl5YU6jiAVG-phKT4",
"lhsExpr": "res.status",
"rhsExpr": "200",
"rhsOperand": "200",
"operator": "eq",
"status": "pass"
}
],
"testResults": []
},
{
"request": {
"method": "GET",
"url": "http://localhost:8080/test/v2",
"headers": {}
},
"response": {
"status": 200,
"statusText": "OK",
"headers": {
"x-powered-by": "Express",
"content-type": "application/json; charset=utf-8",
"content-length": "497",
"etag": "W/\"1f1-lMqxZgVOJiQXjF5yk3AFEU8O9Ro\"",
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
"connection": "close"
},
"data": {
"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": {}
}
},
"assertionResults": [
{
"uid": "XsjjGx9cjt5t8tE_t69ZB",
"lhsExpr": "res.status",
"rhsExpr": "200",
"rhsOperand": "200",
"operator": "eq",
"status": "pass"
}
],
"testResults": []
},
{
"request": {
"method": "GET",
"url": "http://localhost:8080/test/v3",
"headers": {}
},
"response": {
"status": 200,
"statusText": "OK",
"headers": {
"x-powered-by": "Express",
"content-type": "application/json; charset=utf-8",
"content-length": "497",
"etag": "W/\"1f1-tSiYu0/vWz3r+NYRCaed0aW1waw\"",
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
"connection": "close"
},
"data": {
"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": {}
}
},
"assertionResults": [
{
"uid": "i_8MmDMtJA9YfvB_FrW15",
"lhsExpr": "res.status",
"rhsExpr": "200",
"rhsOperand": "200",
"operator": "eq",
"status": "pass"
}
],
"testResults": []
},
{
"request": {
"method": "POST",
"url": "http://localhost:8080/test/v1",
"headers": {
"content-type": "application/json"
},
"data": {
"test": "hello"
}
},
"response": {
"status": 200,
"statusText": "OK",
"headers": {
"x-powered-by": "Express",
"content-type": "application/json; charset=utf-8",
"content-length": "623",
"etag": "W/\"26f-ku5QGz4p9f02u79vJIve7JH3QYM\"",
"date": "Mon, 25 Sep 2023 21:43:02 GMT",
"connection": "close"
},
"data": {
"path": "/test/v1",
"headers": {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
"user-agent": "axios/1.5.0",
"content-length": "16",
"accept-encoding": "gzip, compress, deflate, br",
"host": "localhost:8080",
"connection": "close"
},
"method": "POST",
"body": "{\"test\":\"hello\"}",
"fresh": false,
"hostname": "localhost",
"ip": "",
"ips": [],
"protocol": "http",
"query": {},
"subdomains": [],
"xhr": false,
"os": {
"hostname": "05512cb2102c"
},
"connection": {},
"json": {
"test": "hello"
}
}
},
"assertionResults": [
{
"uid": "hNBSF_GBdSTFHNiyCcOn9",
"lhsExpr": "res.status",
"rhsExpr": "200",
"rhsOperand": "200",
"operator": "eq",
"status": "pass"
}
],
"testResults": []
}
]
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@usebruno/cli", "name": "@usebruno/cli",
"version": "0.8.0", "version": "0.9.0",
"license" : "MIT", "license" : "MIT",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -5,16 +5,21 @@ With Bruno CLI, you can now run your API collections with ease using simple comm
This makes it easier to test your APIs in different environments, automate your testing process, and integrate your API tests with your continuous integration and deployment workflows. This makes it easier to test your APIs in different environments, automate your testing process, and integrate your API tests with your continuous integration and deployment workflows.
## Installation ## Installation
To install the Bruno CLI, use the node package manager of your choice, such as NPM: To install the Bruno CLI, use the node package manager of your choice, such as NPM:
```bash ```bash
npm install -g @usebruno/cli npm install -g @usebruno/cli
``` ```
## Getting started ## Getting started
Navigate to the directory where your API collection resides, and then run: Navigate to the directory where your API collection resides, and then run:
```bash ```bash
bru run bru run
``` ```
This command will run all the requests in your collection. You can also run a single request by specifying its filename: This command will run all the requests in your collection. You can also run a single request by specifying its filename:
```bash ```bash
@ -22,25 +27,37 @@ bru run request.bru
``` ```
Or run all requests in a collection's subfolder: Or run all requests in a collection's subfolder:
```bash ```bash
bru run folder bru run folder
``` ```
If you need to use an environment, you can specify it with the --env option: If you need to use an environment, you can specify it with the --env option:
```bash ```bash
bru run folder --env Local bru run folder --env Local
``` ```
If you need to collect the results of your API tests, you can specify the --output option:
```bash
bru run folder --output results.json
```
## Demo ## Demo
![demo](assets/images/cli-demo.png) ![demo](assets/images/cli-demo.png)
## Support ## Support
If you encounter any issues or have any feedback or suggestions, please raise them on our [GitHub repository](https://github.com/usebruno/bruno) If you encounter any issues or have any feedback or suggestions, please raise them on our [GitHub repository](https://github.com/usebruno/bruno)
Thank you for using Bruno CLI! Thank you for using Bruno CLI!
## Changelog ## Changelog
See [here](packages/bruno-cli/changelog.md) See [here](packages/bruno-cli/changelog.md)
## License ## License
[MIT](license.md)
[MIT](license.md)

View File

@ -127,6 +127,11 @@ const builder = async (yargs) => {
describe: 'Overwrite a single environment variable, multiple usages possible', describe: 'Overwrite a single environment variable, multiple usages possible',
type: 'string' type: 'string'
}) })
.option('output', {
alias: 'o',
describe: 'Path to write JSON results to',
type: 'string'
})
.option('insecure', { .option('insecure', {
type: 'boolean', type: 'boolean',
description: 'Allow insecure server connections' description: 'Allow insecure server connections'
@ -138,12 +143,16 @@ const builder = async (yargs) => {
.example( .example(
'$0 run request.bru --env local --env-var secret=xxx', '$0 run request.bru --env local --env-var secret=xxx',
'Run a request with the environment set to local and overwrite the variable secret with value xxx' 'Run a request with the environment set to local and overwrite the variable secret with value xxx'
)
.example(
'$0 run request.bru --output results.json',
'Run a request and write the results to results.json in the current directory'
); );
}; };
const handler = async function (argv) { const handler = async function (argv) {
try { try {
let { filename, cacert, env, envVar, insecure, r: recursive } = argv; let { filename, cacert, env, envVar, insecure, r: recursive, output: outputPath } = argv;
const collectionPath = process.cwd(); const collectionPath = process.cwd();
// todo // todo
@ -243,36 +252,24 @@ const handler = async function (argv) {
} }
const _isFile = await isFile(filename); const _isFile = await isFile(filename);
let assertionResults = [];
let testResults = [];
let testrunResults = [];
let bruJsons = [];
if (_isFile) { if (_isFile) {
console.log(chalk.yellow('Running Request \n')); console.log(chalk.yellow('Running Request \n'));
const bruContent = fs.readFileSync(filename, 'utf8'); const bruContent = fs.readFileSync(filename, 'utf8');
const bruJson = bruToJson(bruContent); const bruJson = bruToJson(bruContent);
const result = await runSingleRequest( bruJsons.push({
filename, bruFilepath: filename,
bruJson, bruJson
collectionPath, });
collectionVariables,
envVars,
processEnvVars
);
if (result) {
const { assertionResults, testResults } = result;
const summary = printRunSummary(assertionResults, testResults);
console.log(chalk.dim(chalk.grey('Done.')));
if (summary.failedAssertions > 0 || summary.failedTests > 0) {
process.exit(1);
}
} else {
process.exit(1);
}
} }
const _isDirectory = await isDirectory(filename); const _isDirectory = await isDirectory(filename);
if (_isDirectory) { if (_isDirectory) {
let bruJsons = [];
if (!recursive) { if (!recursive) {
console.log(chalk.yellow('Running Folder \n')); console.log(chalk.yellow('Running Folder \n'));
const files = fs.readdirSync(filename); const files = fs.readdirSync(filename);
@ -287,8 +284,6 @@ const handler = async function (argv) {
bruJson bruJson
}); });
} }
// order requests by sequence
bruJsons.sort((a, b) => { bruJsons.sort((a, b) => {
const aSequence = a.bruJson.seq || 0; const aSequence = a.bruJson.seq || 0;
const bSequence = b.bruJson.seq || 0; const bSequence = b.bruJson.seq || 0;
@ -299,35 +294,50 @@ const handler = async function (argv) {
bruJsons = getBruFilesRecursively(filename); bruJsons = getBruFilesRecursively(filename);
} }
}
let assertionResults = []; for (const iter of bruJsons) {
let testResults = []; const { bruFilepath, bruJson } = iter;
const result = await runSingleRequest(
bruFilepath,
bruJson,
collectionPath,
collectionVariables,
envVars,
processEnvVars
);
for (const iter of bruJsons) { if (result) {
const { bruFilepath, bruJson } = iter; testrunResults.push(result);
const result = await runSingleRequest( const { assertionResults: _assertionResults, testResults: _testResults } = result;
bruFilepath,
bruJson,
collectionPath,
collectionVariables,
envVars,
processEnvVars
);
if (result) { assertionResults = assertionResults.concat(_assertionResults);
const { assertionResults: _assertionResults, testResults: _testResults } = result; testResults = testResults.concat(_testResults);
assertionResults = assertionResults.concat(_assertionResults);
testResults = testResults.concat(_testResults);
}
} }
}
const summary = printRunSummary(assertionResults, testResults); const summary = printRunSummary(assertionResults, testResults);
console.log(chalk.dim(chalk.grey('Ran all requests.'))); console.log(chalk.dim(chalk.grey('Ran all requests.')));
if (summary.failedAssertions > 0 || summary.failedTests > 0) { if (outputPath && outputPath.length) {
const outputDir = path.dirname(outputPath);
const outputDirExists = await exists(outputDir);
if (!outputDirExists) {
console.error(chalk.red(`Output directory ${outputDir} does not exist`));
process.exit(1); process.exit(1);
} }
const outputJson = {
summary,
results: testrunResults
};
fs.writeFileSync(outputPath, JSON.stringify(outputJson, null, 2));
console.log(chalk.dim(chalk.grey(`Wrote results to ${outputPath}`)));
}
if (summary.failedAssertions > 0 || summary.failedTests > 0) {
process.exit(1);
} }
} catch (err) { } catch (err) {
console.log('Something went wrong'); console.log('Something went wrong');

View File

@ -171,6 +171,18 @@ const runSingleRequest = async function (
} }
return { return {
request: {
method: request.method,
url: request.url,
headers: request.headers,
data: request.data
},
response: {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data
},
assertionResults, assertionResults,
testResults testResults
}; };