Merge pull request #212 from tpyle/feature/output-collection

Adds an option to collect output from cli runs
This commit is contained in:
Anoop M D 2023-09-26 22:09:57 +05:30 committed by GitHub
commit c7aecbea79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 319 additions and 47 deletions

View File

@ -0,0 +1,238 @@
{
"summary": {
"totalAssertions": 4,
"passedAssertions": 4,
"failedAssertions": 0,
"totalTests": 0,
"passedTests": 0,
"failedTests": 0
},
"requestResults": [
{
"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

@ -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.
## Installation
To install the Bruno CLI, use the node package manager of your choice, such as NPM:
```bash
npm install -g @usebruno/cli
```
## Getting started
Navigate to the directory where your API collection resides, and then run:
```bash
bru run
```
This command will run all the requests in your collection. You can also run a single request by specifying its filename:
```bash
@ -22,25 +27,37 @@ bru run request.bru
```
Or run all requests in a collection's subfolder:
```bash
bru run folder
```
If you need to use an environment, you can specify it with the --env option:
```bash
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](assets/images/cli-demo.png)
## Support
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!
## Changelog
See [here](packages/bruno-cli/changelog.md)
## 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',
type: 'string'
})
.option('output', {
alias: 'o',
describe: 'Path to write JSON results to',
type: 'string'
})
.option('insecure', {
type: 'boolean',
description: 'Allow insecure server connections'
@ -138,12 +143,16 @@ const builder = async (yargs) => {
.example(
'$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'
)
.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) {
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();
// todo
@ -243,36 +252,24 @@ const handler = async function (argv) {
}
const _isFile = await isFile(filename);
let assertionResults = [];
let testResults = [];
let requestResults = [];
let bruJsons = [];
if (_isFile) {
console.log(chalk.yellow('Running Request \n'));
const bruContent = fs.readFileSync(filename, 'utf8');
const bruJson = bruToJson(bruContent);
const result = await runSingleRequest(
filename,
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);
}
bruJsons.push({
bruFilepath: filename,
bruJson
});
}
const _isDirectory = await isDirectory(filename);
if (_isDirectory) {
let bruJsons = [];
if (!recursive) {
console.log(chalk.yellow('Running Folder \n'));
const files = fs.readdirSync(filename);
@ -287,8 +284,6 @@ const handler = async function (argv) {
bruJson
});
}
// order requests by sequence
bruJsons.sort((a, b) => {
const aSequence = a.bruJson.seq || 0;
const bSequence = b.bruJson.seq || 0;
@ -299,35 +294,50 @@ const handler = async function (argv) {
bruJsons = getBruFilesRecursively(filename);
}
}
let assertionResults = [];
let testResults = [];
for (const iter of bruJsons) {
const { bruFilepath, bruJson } = iter;
const result = await runSingleRequest(
bruFilepath,
bruJson,
collectionPath,
collectionVariables,
envVars,
processEnvVars
);
for (const iter of bruJsons) {
const { bruFilepath, bruJson } = iter;
const result = await runSingleRequest(
bruFilepath,
bruJson,
collectionPath,
collectionVariables,
envVars,
processEnvVars
);
if (result) {
requestResults.push(result);
const { assertionResults: _assertionResults, testResults: _testResults } = result;
if (result) {
const { assertionResults: _assertionResults, testResults: _testResults } = result;
assertionResults = assertionResults.concat(_assertionResults);
testResults = testResults.concat(_testResults);
}
assertionResults = assertionResults.concat(_assertionResults);
testResults = testResults.concat(_testResults);
}
}
const summary = printRunSummary(assertionResults, testResults);
console.log(chalk.dim(chalk.grey('Ran all requests.')));
const summary = printRunSummary(assertionResults, testResults);
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);
}
const outputJson = {
summary,
requestResults
};
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) {
console.log('Something went wrong');

View File

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