Skip to content

Commit

Permalink
feat(usebruno#1659): Improve report options to enable anonymization
Browse files Browse the repository at this point in the history
  • Loading branch information
trusta committed Mar 16, 2024
1 parent 410eecc commit 2c2407b
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 12 deletions.
104 changes: 103 additions & 1 deletion packages/bruno-cli/src/commands/run.js
@@ -1,6 +1,7 @@
const fs = require('fs');
const chalk = require('chalk');
const path = require('path');
const minimatch = require('minimatch');
const { forOwn } = require('lodash');
const { exists, isFile, isDirectory } = require('../utils/filesystem');
const { runSingleRequest } = require('../runner/run-single-request');
Expand Down Expand Up @@ -92,6 +93,46 @@ const printRunSummary = (results) => {
};
};

const cleanResults = (results, opts) => {
if (opts.skipSensitiveData || opts.omitRequestBodies) {
results.filter((res) => !!res.request.data).forEach((res) => (res.request.data = '[REDACTED]'));
}
if (opts.skipSensitiveData || opts.omitResponseBodies) {
results.filter((res) => !!res.response.data).forEach((res) => (res.response.data = '[REDACTED]'));
}
if (opts.hideRequestBody) {
results
.filter((res) => !!res.request.data)
.filter((res) => opts.hideRequestBody.find((path) => minimatch(res.test.filename, path)))
.forEach((res) => (res.request.data = '[REDACTED]'));
}
if (opts.hideResponseBody) {
results
.filter((res) => !!res.response.data)
.filter((res) => opts.hideResponseBody.find((path) => minimatch(res.test.filename, path)))
.forEach((res) => (res.response.data = '[REDACTED]'));
}
if (opts.skipSensitiveData || opts.omitHeaders) {
results.forEach((res) => {
res.request.headers = null;
res.response.headers = null;
});
}
if (opts.skipHeaders) {
results.forEach((res) => {
opts.skipHeaders.forEach((header) => {
if (res.request.headers && res.request.headers[header]) {
res.request.headers[header] = '[REDACTED]';
}
if (res.response.headers && res.response.headers[header]) {
res.response.headers[header] = '[REDACTED]';
}
});
});
}
return results;
};

const getBruFilesRecursively = (dir, testsOnly) => {
const environmentsPath = 'environments';

Expand Down Expand Up @@ -208,6 +249,54 @@ const builder = async (yargs) => {
default: 'json',
type: 'string'
})
.option('reporter-html-template', {
describe: 'Specify a path to the custom template which will be used to render the HTML report.',
type: 'string'
})
.option('reporter-html-title', {
describe:
'Give your report a different main Title in the centre of the report. If this is not set, the report will show "Bruno run dashboard".',
default: 'Bruno run dashboard',
type: 'string'
})
.option('reporter-omitRequestBodies', {
type: 'boolean',
default: false,
description: 'Exclude all Request Bodies from the final report'
})
.option('reporter-omitResponseBodies', {
type: 'boolean',
default: false,
description: 'Exclude all Response Bodies from the final report'
})
.option('reporter-hideRequestBody', {
type: 'array',
default: [],
description:
'Exclude certain Request Bodies from the final report. Enter the minimatch pattern for the request file name you wish to hide'
})
.option('reporter-hideResponseBody', {
type: 'array',
default: [],
description:
'Exclude certain Response Bodies from the final report. Enter the minimatch pattern for the request file name you wish to hide'
})
.option('reporter-omitHeaders', {
type: 'boolean',
default: false,
description: 'Exclude all Headers from the final report'
})
.option('reporter-skipHeaders', {
type: 'array',
default: [],
description: 'Exclude the given Headers from the final report. Enter the header names you wish to redact.'
})
.option('reporter-skipSensitiveData', {
type: 'boolean',
default: false,
description:
'Exclude all the Request/Response Headers and the Request/Response bodies, from each request in the final report. This will only show the main request info and the Test Results.'
})
.option('insecure', {
type: 'boolean',
description: 'Allow insecure server connections'
Expand Down Expand Up @@ -240,6 +329,10 @@ const builder = async (yargs) => {
'$0 run request.bru --output results.html --format html',
'Run a request and write the results to results.html in html format in the current directory'
)
.example(
'$0 run folder --output results.html --format html --reporter-html-title "Any running test" --reporter-skipHeaders Authorization Cookies Set-Cookies --reporter-hideRequestBody **/Login.bru folder1/Secret.bru --reporter-hideResponseBody Login.bru',
'Run a request and write the results to results.html in html format in the current directory, but customize the output by deleting certain sensitive data.'
)
.example('$0 run request.bru --test-only', 'Run all requests that have a test');
};

Expand Down Expand Up @@ -480,6 +573,15 @@ const handler = async function (argv) {
process.exit(1);
}

results = cleanResults(results, {
omitRequestBodies: argv.reporterOmitRequestBodies,
omitResponseBodies: argv.reporterOmitResponseBodies,
hideRequestBody: argv.reporterHideRequestBody,
hideResponseBody: argv.reporterHideResponseBody,
omitHeaders: argv.reporterOmitHeaders,
skipHeaders: argv.reporterSkipHeaders,
skipSensitiveData: argv.reporterSkipSensitiveData
});
const outputJson = {
summary,
results
Expand All @@ -490,7 +592,7 @@ const handler = async function (argv) {
} else if (format === 'junit') {
makeJUnitOutput(results, outputPath);
} else if (format === 'html') {
makeHtmlOutput(outputJson, outputPath);
makeHtmlOutput(outputJson, outputPath, argv.reporterHtmlTemplate, argv.reporterHtmlTitle);
}

console.log(chalk.dim(chalk.grey(`Wrote results to ${outputPath}`)));
Expand Down
84 changes: 77 additions & 7 deletions packages/bruno-cli/src/reporters/html-template.html
Expand Up @@ -6,7 +6,11 @@
<!-- Would use latest version, you'd better specify a version -->
<script src="https://unpkg.com/naive-ui"></script>

<title>Bruno</title>
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/default.min.css" />
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
<script src="https://unpkg.com/@highlightjs/vue-plugin@2.1.0/dist/highlightjs-vue.min.js"></script>

<title>__TITLE__</title>
<style>
.error > .status {
color: red;
Expand All @@ -29,11 +33,11 @@
</head>
<body>
<div id="app">
<n-config-provider :theme="theme">
<n-config-provider :theme="theme" :hljs="hljs">
<n-layout embedded position="absolute" content-style="padding: 24px;">
<n-card>
<n-flex>
<n-page-header title="Bruno run dashboard">
<n-page-header title="__TITLE__">
<template #avatar>
<n-avatar size="large" style="background-color: transparent">
<svg id="emoji" width="34" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
Expand Down Expand Up @@ -313,7 +317,7 @@
<n-alert v-if="hasError" title="Error" type="error">
{{result.error}}
</n-alert>
<n-card title="REQUEST HEADERS">
<n-card v-if="result.request.headers" title="REQUEST HEADERS">
<n-data-table
:columns="headerColumns"
:data="headerDataRequest"
Expand All @@ -323,9 +327,9 @@
v-if="result.request.data"
title="REQUEST BODY"
>
<pre>{{result.request.data}}</pre>
<highlightjs autodetect :code="formattedRequestData" />
</n-card>
<n-card title="RESPONSE HEADERS">
<n-card v-if="result.response.headers" title="RESPONSE HEADERS">
<n-data-table
:columns="headerColumns"
:data="headerDataResponse"
Expand All @@ -335,7 +339,7 @@
v-if="result.response.data"
title="RESPONSE BODY"
>
<pre>{{result.response.data}}</pre>
<highlightjs autodetect :code="formattedResponseData" />
</n-card>
<n-card title="ASSERTIONS INFORMATION">
<n-data-table
Expand Down Expand Up @@ -373,6 +377,7 @@
darkMode.value = event.matches;
});
return {
hljs,
res,
theme,
darkMode,
Expand Down Expand Up @@ -606,6 +611,68 @@
return props.result.testResults.length + props.result.assertionResults.length;
});

function isJSON(data) {
var ret = true;
try {
JSON.parse(data);
} catch (e) {
ret = false;
}
return ret;
}
// see https://gist.github.com/sente/1083506/d2834134cd070dbcc08bf42ee27dabb746a1c54d#gistcomment-2254622
function formatXML(data) {
const PADDING = ' '.repeat(2); // set desired indent size here
const reg = /(>)(<)(\/*)/g;
let pad = 0;
xml = data.replace(reg, '$1\r\n$2$3');

return xml
.split('\r\n')
.map((node, index) => {
let indent = 0;
if (node.match(/.+<\/\w[^>]*>$/)) {
indent = 0;
} else if (node.match(/^<\/\w/) && pad > 0) {
pad -= 1;
} else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
indent = 1;
} else {
indent = 0;
}

pad += indent;
return PADDING.repeat(pad - indent) + node;
})
.join('\r\n');
}

function isXML(data) {
return data.length > 0 && data[0] === '<';
}

function formatData(data) {
if (!data) {
return data;
}
if (typeof data === 'object') {
return JSON.stringify(data, null, 2);
}
if (isJSON(data)) {
const obj = JSON.parse(data);
return JSON.stringify(obj, null, 2);
} else if (isXML(data)) {
return formatXML(data);
}
return data;
}
const formattedRequestData = computed(() => {
return formatData(props.result.request.data);
});
const formattedResponseData = computed(() => {
return formatData(props.result.response.data);
});

const hasError = computed(() => !!props.result.error);
const hasFailure = computed(() => total.value !== totalPassed.value);
const suitename = computed(() => props.result.suitename.replace(props.group + '/', ''));
Expand All @@ -626,11 +693,14 @@
result: props.result,
suitename,
testDuration,
formattedRequestData,
formattedResponseData,
name
};
}
});
app.use(naive);
app.use(hljsVuePlugin);
app.mount('#app');
</script>
</body>
Expand Down
6 changes: 3 additions & 3 deletions packages/bruno-cli/src/reporters/html.js
@@ -1,13 +1,13 @@
const fs = require('fs');
const path = require('path');

const makeHtmlOutput = async (results, outputPath) => {
const makeHtmlOutput = async (results, outputPath, templatePath, title) => {
const resultsJson = JSON.stringify(results, null, 2);

const reportPath = path.join(__dirname, 'html-template.html');
const reportPath = templatePath || path.join(__dirname, 'html-template.html');
const template = fs.readFileSync(reportPath, 'utf8');

fs.writeFileSync(outputPath, template.replace('__RESULTS_JSON__', resultsJson));
fs.writeFileSync(outputPath, template.replace('__RESULTS_JSON__', resultsJson).replaceAll('__TITLE__', title));
};

module.exports = makeHtmlOutput;
3 changes: 2 additions & 1 deletion packages/bruno-cli/tests/reporters/html.spec.js
Expand Up @@ -73,9 +73,10 @@ describe('makeHtmlOutput', () => {
]
};

makeHtmlOutput(outputJson, '/tmp/testfile.html');
makeHtmlOutput(outputJson, '/tmp/testfile.html', null, 'any title');

const htmlReport = fs.writeFileSync.mock.calls[0][1];
expect(htmlReport).toContain(JSON.stringify(outputJson, null, 2));
expect(htmlReport).toContain('any title');
});
});

0 comments on commit 2c2407b

Please sign in to comment.