mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 14:08:38 +00:00
Merge branch 'main' into feature/1602-multipart-content-type
# Conflicts: # packages/bruno-lang/v2/src/bruToJson.js # packages/bruno-lang/v2/src/jsonToBru.js
This commit is contained in:
@@ -10,6 +10,7 @@ const makeHtmlOutput = require('../reporters/html');
|
||||
const { rpad } = require('../utils/common');
|
||||
const { bruToJson, getOptions, collectionBruToJson } = require('../utils/bru');
|
||||
const { dotenvToJson } = require('@usebruno/lang');
|
||||
const constants = require('../constants');
|
||||
const command = 'run [filename]';
|
||||
const desc = 'Run a request';
|
||||
|
||||
@@ -178,6 +179,17 @@ const getCollectionRoot = (dir) => {
|
||||
return collectionBruToJson(content);
|
||||
};
|
||||
|
||||
const getFolderRoot = (dir) => {
|
||||
const folderRootPath = path.join(dir, 'folder.bru');
|
||||
const exists = fs.existsSync(folderRootPath);
|
||||
if (!exists) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(folderRootPath, 'utf8');
|
||||
return collectionBruToJson(content);
|
||||
};
|
||||
|
||||
const builder = async (yargs) => {
|
||||
yargs
|
||||
.option('r', {
|
||||
@@ -189,6 +201,12 @@ const builder = async (yargs) => {
|
||||
type: 'string',
|
||||
description: 'CA certificate to verify peer against'
|
||||
})
|
||||
.option('ignore-truststore', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'The specified custom CA certificate (--cacert) will be used exclusively and the default truststore is ignored, if this option is specified. Evaluated in combination with "--cacert" only.'
|
||||
})
|
||||
.option('env', {
|
||||
describe: 'Environment variables',
|
||||
type: 'string'
|
||||
@@ -240,12 +258,33 @@ 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 request.bru --tests-only', 'Run all requests that have a test');
|
||||
|
||||
.example('$0 run request.bru --tests-only', 'Run all requests that have a test')
|
||||
.example(
|
||||
'$0 run request.bru --cacert myCustomCA.pem',
|
||||
'Use a custom CA certificate in combination with the default truststore when validating the peer of this request.'
|
||||
)
|
||||
.example(
|
||||
'$0 run folder --cacert myCustomCA.pem --ignore-truststore',
|
||||
'Use a custom CA certificate exclusively when validating the peers of the requests in the specified folder.'
|
||||
);
|
||||
};
|
||||
|
||||
const handler = async function (argv) {
|
||||
try {
|
||||
let { filename, cacert, env, envVar, insecure, r: recursive, output: outputPath, format, testsOnly, bail } = argv;
|
||||
let {
|
||||
filename,
|
||||
cacert,
|
||||
ignoreTruststore,
|
||||
env,
|
||||
envVar,
|
||||
insecure,
|
||||
r: recursive,
|
||||
output: outputPath,
|
||||
format,
|
||||
testsOnly,
|
||||
bail
|
||||
} = argv;
|
||||
const collectionPath = process.cwd();
|
||||
|
||||
// todo
|
||||
@@ -255,7 +294,7 @@ const handler = async function (argv) {
|
||||
const brunoJsonExists = await exists(brunoJsonPath);
|
||||
if (!brunoJsonExists) {
|
||||
console.error(chalk.red(`You can run only at the root of a collection`));
|
||||
return;
|
||||
process.exit(constants.EXIT_STATUS.ERROR_NOT_IN_COLLECTION);
|
||||
}
|
||||
|
||||
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
|
||||
@@ -266,7 +305,7 @@ const handler = async function (argv) {
|
||||
const pathExists = await exists(filename);
|
||||
if (!pathExists) {
|
||||
console.error(chalk.red(`File or directory ${filename} does not exist`));
|
||||
return;
|
||||
process.exit(constants.EXIT_STATUS.ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
filename = './';
|
||||
@@ -282,7 +321,7 @@ const handler = async function (argv) {
|
||||
|
||||
if (!envPathExists) {
|
||||
console.error(chalk.red(`Environment file not found: `) + chalk.dim(`environments/${env}.bru`));
|
||||
return;
|
||||
process.exit(constants.EXIT_STATUS.ERROR_ENV_NOT_FOUND);
|
||||
}
|
||||
|
||||
const envBruContent = fs.readFileSync(envFile, 'utf8');
|
||||
@@ -299,7 +338,7 @@ const handler = async function (argv) {
|
||||
processVars = envVar;
|
||||
} else {
|
||||
console.error(chalk.red(`overridable environment variables not parsable: use name=value`));
|
||||
return;
|
||||
process.exit(constants.EXIT_STATUS.ERROR_MALFORMED_ENV_OVERRIDE);
|
||||
}
|
||||
if (processVars && Array.isArray(processVars)) {
|
||||
for (const value of processVars.values()) {
|
||||
@@ -310,7 +349,7 @@ const handler = async function (argv) {
|
||||
chalk.red(`Overridable environment variable not correct: use name=value - presented: `) +
|
||||
chalk.dim(`${value}`)
|
||||
);
|
||||
return;
|
||||
process.exit(constants.EXIT_STATUS.ERROR_INCORRECT_ENV_OVERRIDE);
|
||||
}
|
||||
envVars[match[1]] = match[2];
|
||||
}
|
||||
@@ -336,10 +375,11 @@ const handler = async function (argv) {
|
||||
}
|
||||
}
|
||||
}
|
||||
options['ignoreTruststore'] = ignoreTruststore;
|
||||
|
||||
if (['json', 'junit', 'html'].indexOf(format) === -1) {
|
||||
console.error(chalk.red(`Format must be one of "json", "junit or "html"`));
|
||||
return;
|
||||
process.exit(constants.EXIT_STATUS.ERROR_INCORRECT_OUTPUT_FORMAT);
|
||||
}
|
||||
|
||||
// load .env file at root of collection if it exists
|
||||
@@ -451,7 +491,7 @@ const handler = async function (argv) {
|
||||
nJumps++;
|
||||
if (nJumps > 10000) {
|
||||
console.error(chalk.red(`Too many jumps, possible infinite loop`));
|
||||
process.exit(1);
|
||||
process.exit(constants.EXIT_STATUS.ERROR_INFINTE_LOOP);
|
||||
}
|
||||
if (nextRequestName === null) {
|
||||
break;
|
||||
@@ -477,7 +517,7 @@ const handler = async function (argv) {
|
||||
const outputDirExists = await exists(outputDir);
|
||||
if (!outputDirExists) {
|
||||
console.error(chalk.red(`Output directory ${outputDir} does not exist`));
|
||||
process.exit(1);
|
||||
process.exit(constants.EXIT_STATUS.ERROR_MISSING_OUTPUT_DIR);
|
||||
}
|
||||
|
||||
const outputJson = {
|
||||
@@ -497,12 +537,12 @@ const handler = async function (argv) {
|
||||
}
|
||||
|
||||
if (summary.failedAssertions + summary.failedTests + summary.failedRequests > 0) {
|
||||
process.exit(1);
|
||||
process.exit(constants.EXIT_STATUS.ERROR_FAILED_COLLECTION);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Something went wrong');
|
||||
console.error(chalk.red(err.message));
|
||||
process.exit(1);
|
||||
process.exit(constants.EXIT_STATUS.ERROR_GENERIC);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,32 @@ const { version } = require('../package.json');
|
||||
const CLI_EPILOGUE = `Documentation: https://docs.usebruno.com (v${version})`;
|
||||
const CLI_VERSION = version;
|
||||
|
||||
// Exit codes
|
||||
const EXIT_STATUS = {
|
||||
// One or more assertions, tests, or requests failed
|
||||
ERROR_FAILED_COLLECTION: 1,
|
||||
// The specified output dir does not exist
|
||||
ERROR_MISSING_OUTPUT_DIR: 2,
|
||||
// request chain caused an endless loop
|
||||
ERROR_INFINTE_LOOP: 3,
|
||||
// bru was called outside of a collection root
|
||||
ERROR_NOT_IN_COLLECTION: 4,
|
||||
// The specified file was not found
|
||||
ERROR_FILE_NOT_FOUND: 5,
|
||||
// The specified environment was not found
|
||||
ERROR_ENV_NOT_FOUND: 6,
|
||||
// Environment override not presented as string or object
|
||||
ERROR_MALFORMED_ENV_OVERRIDE: 7,
|
||||
// Environment overrides format incorrect
|
||||
ERROR_INCORRECT_ENV_OVERRIDE: 8,
|
||||
// Invalid output format requested
|
||||
ERROR_INCORRECT_OUTPUT_FORMAT: 9,
|
||||
// Everything else
|
||||
ERROR_GENERIC: 255
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
CLI_EPILOGUE,
|
||||
CLI_VERSION
|
||||
CLI_VERSION,
|
||||
EXIT_STATUS
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ const run = async () => {
|
||||
.commandDir('commands')
|
||||
.epilogue(CLI_EPILOGUE)
|
||||
.usage('Usage: $0 <command> [options]')
|
||||
.demandCommand(1, "Woof !! Let's play with some APIs !!")
|
||||
.demandCommand(1, "Woof!! Let's play with some APIs!!")
|
||||
.help('h')
|
||||
.alias('h', 'help');
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { interpolate } = require('@usebruno/common');
|
||||
const { each, forOwn, cloneDeep } = require('lodash');
|
||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
let contentType = '';
|
||||
@@ -66,7 +66,7 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
}
|
||||
|
||||
if (typeof request.data === 'string') {
|
||||
if (request.data.length) {
|
||||
if (request?.data?.length) {
|
||||
request.data = _interpolate(request.data);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,36 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
param.value = _interpolate(param.value);
|
||||
});
|
||||
|
||||
if (request?.params?.length) {
|
||||
let url = request.url;
|
||||
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
|
||||
try {
|
||||
url = new URL(url);
|
||||
} catch (e) {
|
||||
throw { message: 'Invalid URL format', originalError: e.message };
|
||||
}
|
||||
|
||||
const interpolatedUrlPath = url.pathname
|
||||
.split('/')
|
||||
.filter((path) => path !== '')
|
||||
.map((path) => {
|
||||
if (path[0] !== ':') {
|
||||
return '/' + path;
|
||||
} else {
|
||||
const name = path.slice(1);
|
||||
const existingPathParam = request.params.find((param) => param.type === 'path' && param.name === name);
|
||||
return existingPathParam ? '/' + existingPathParam.value : '';
|
||||
}
|
||||
})
|
||||
.join('');
|
||||
|
||||
request.url = url.origin + interpolatedUrlPath + url.search;
|
||||
}
|
||||
|
||||
if (request.proxy) {
|
||||
request.proxy.protocol = _interpolate(request.proxy.protocol);
|
||||
request.proxy.hostname = _interpolate(request.proxy.hostname);
|
||||
|
||||
@@ -64,7 +64,7 @@ const prepareRequest = (request, collectionRoot) => {
|
||||
};
|
||||
|
||||
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||
if (collectionAuth && request.auth.mode === 'none') {
|
||||
if (collectionAuth && request.auth.mode === 'inherit') {
|
||||
if (collectionAuth.mode === 'basic') {
|
||||
axiosRequest.auth = {
|
||||
username: get(collectionAuth, 'basic.username'),
|
||||
@@ -107,10 +107,16 @@ const prepareRequest = (request, collectionRoot) => {
|
||||
if (!contentTypeDefined) {
|
||||
axiosRequest.headers['content-type'] = 'application/json';
|
||||
}
|
||||
let jsonBody;
|
||||
try {
|
||||
axiosRequest.data = JSONbig.parse(decomment(request.body.json));
|
||||
} catch (ex) {
|
||||
axiosRequest.data = request.body.json;
|
||||
jsonBody = decomment(request?.body?.json);
|
||||
} catch (error) {
|
||||
jsonBody = request?.body?.json;
|
||||
}
|
||||
try {
|
||||
axiosRequest.data = JSONbig.parse(jsonBody);
|
||||
} catch (error) {
|
||||
axiosRequest.data = jsonBody;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ const qs = require('qs');
|
||||
const chalk = require('chalk');
|
||||
const decomment = require('decomment');
|
||||
const fs = require('fs');
|
||||
const tls = require('tls');
|
||||
const { forOwn, isUndefined, isNull, each, extend, get, compact } = require('lodash');
|
||||
const FormData = require('form-data');
|
||||
const prepareRequest = require('./prepare-request');
|
||||
@@ -17,7 +18,7 @@ const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
const { makeAxiosInstance } = require('../utils/axios-instance');
|
||||
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
|
||||
|
||||
const path = require('path');
|
||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||
|
||||
const runSingleRequest = async function (
|
||||
@@ -106,7 +107,11 @@ const runSingleRequest = async function (
|
||||
const caCert = caCertArray.find((el) => el);
|
||||
if (caCert && caCert.length > 1) {
|
||||
try {
|
||||
httpsAgentRequestFields['ca'] = fs.readFileSync(caCert);
|
||||
let caCertBuffer = fs.readFileSync(caCert);
|
||||
if (!options['ignoreTruststore']) {
|
||||
caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates
|
||||
}
|
||||
httpsAgentRequestFields['ca'] = caCertBuffer;
|
||||
} catch (err) {
|
||||
console.log('Error reading CA cert file:' + caCert, err);
|
||||
}
|
||||
@@ -122,18 +127,30 @@ const runSingleRequest = async function (
|
||||
// client certificate config
|
||||
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
|
||||
for (let clientCert of clientCertConfig) {
|
||||
const domain = interpolateString(clientCert.domain, interpolationOptions);
|
||||
const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
|
||||
const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
|
||||
if (domain && certFilePath && keyFilePath) {
|
||||
const domain = interpolateString(clientCert?.domain, interpolationOptions);
|
||||
const type = clientCert?.type || 'cert';
|
||||
if (domain) {
|
||||
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
|
||||
|
||||
if (request.url.match(hostRegex)) {
|
||||
try {
|
||||
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
|
||||
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
|
||||
} catch (err) {
|
||||
console.log('Error reading cert/key file', err);
|
||||
if (type === 'cert') {
|
||||
try {
|
||||
let certFilePath = interpolateString(clientCert?.certFilePath, interpolationOptions);
|
||||
certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath);
|
||||
let keyFilePath = interpolateString(clientCert?.keyFilePath, interpolationOptions);
|
||||
keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath);
|
||||
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
|
||||
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
|
||||
} catch (err) {
|
||||
console.log(chalk.red('Error reading cert/key file'), chalk.red(err?.message));
|
||||
}
|
||||
} else if (type === 'pfx') {
|
||||
try {
|
||||
let pfxFilePath = interpolateString(clientCert?.pfxFilePath, interpolationOptions);
|
||||
pfxFilePath = path.isAbsolute(pfxFilePath) ? pfxFilePath : path.join(collectionPath, pfxFilePath);
|
||||
httpsAgentRequestFields['pfx'] = fs.readFileSync(pfxFilePath);
|
||||
} catch (err) {
|
||||
console.log(chalk.red('Error reading pfx file'), chalk.red(err?.message));
|
||||
}
|
||||
}
|
||||
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
|
||||
break;
|
||||
@@ -305,7 +322,7 @@ const runSingleRequest = async function (
|
||||
response,
|
||||
envVariables,
|
||||
collectionVariables,
|
||||
collectionPath
|
||||
processEnvVars
|
||||
);
|
||||
|
||||
each(assertionResults, (r) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ const collectionBruToJson = (bru) => {
|
||||
|
||||
const transformedJson = {
|
||||
request: {
|
||||
params: _.get(json, 'query', []),
|
||||
params: _.get(json, 'params', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
script: _.get(json, 'script', {}),
|
||||
@@ -60,7 +60,7 @@ const bruToJson = (bru) => {
|
||||
method: _.upperCase(_.get(json, 'http.method')),
|
||||
url: _.get(json, 'http.url'),
|
||||
auth: _.get(json, 'auth', {}),
|
||||
params: _.get(json, 'query', []),
|
||||
params: _.get(json, 'params', []),
|
||||
headers: _.get(json, 'headers', []),
|
||||
body: _.get(json, 'body', {}),
|
||||
vars: _.get(json, 'vars', []),
|
||||
|
||||
Reference in New Issue
Block a user