chore: repo wide lint fixes

This commit is contained in:
Bijin A B
2025-12-03 09:44:50 +05:30
parent 4a38f2d49f
commit 62cf4139d7
460 changed files with 6921 additions and 7052 deletions

View File

@@ -75,7 +75,7 @@ const isUrl = (str) => {
const readOpenApiFile = async (source, options = {}) => {
try {
let content;
if (isUrl(source)) {
// Handle URL input
console.log(chalk.yellow(`Fetching specification from URL: ${source}`));
@@ -83,22 +83,22 @@ const readOpenApiFile = async (source, options = {}) => {
const axiosOptions = {
timeout: 30000, // 30 second timeout
maxContentLength: 10 * 1024 * 1024,
validateStatus: status => status >= 200 && status < 300
validateStatus: (status) => status >= 200 && status < 300
};
// Skip SSL certificate validation if insecure flag is set
if (options.insecure) {
console.log(chalk.yellow('Warning: SSL certificate verification is disabled. Use with caution.'));
axiosOptions.httpsAgent = new (require('https')).Agent({ rejectUnauthorized: false });
}
const response = await axios.get(source, axiosOptions);
content = response.data;
} catch (error) {
if (error.code === 'ECONNABORTED') {
throw new Error('Request timed out. The server took too long to respond.');
} else if (error.code === 'CERT_HAS_EXPIRED' || error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' ||
error.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
} else if (error.code === 'CERT_HAS_EXPIRED' || error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT'
|| error.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
throw new Error(`SSL Certificate error: ${error.code}. Try using --insecure if you trust this source.`);
} else if (error.response) {
throw new Error(`Failed to fetch from URL: ${error.response.status} ${error.response.statusText}`);
@@ -108,7 +108,7 @@ const readOpenApiFile = async (source, options = {}) => {
throw new Error(`Error fetching URL: ${error.message}`);
}
}
// If response is already an object, return it directly
if (typeof content === 'object' && content !== null) {
return content;
@@ -120,7 +120,7 @@ const readOpenApiFile = async (source, options = {}) => {
}
content = fs.readFileSync(source, 'utf8');
}
// If content is a string, try to parse as JSON or YAML
if (typeof content === 'string') {
try {
@@ -133,7 +133,7 @@ const readOpenApiFile = async (source, options = {}) => {
}
}
}
return content;
} catch (error) {
// Let the specific error handling from above propagate
@@ -247,7 +247,7 @@ const handler = async (argv) => {
// Convert WSDL to Bruno format
brunoCollection = await wsdlToBruno(wsdlContent);
}
// Override collection name if provided
if (collectionName) {
brunoCollection.name = collectionName;
@@ -260,17 +260,17 @@ const handler = async (argv) => {
console.log(chalk.green(`Bruno collection saved as JSON to ${outputPath}`));
} else if (output) {
const resolvedOutput = path.resolve(output);
// Check if output is an existing directory
const isOutputDirectory = await exists(resolvedOutput) && isDirectory(resolvedOutput);
// Determine the final output directory
let outputDir;
if (isOutputDirectory) {
// If output is an existing directory, use collection name to create a subdirectory
const dirName = sanitizeName(brunoCollection.name);
outputDir = path.join(resolvedOutput, dirName);
// Check if this subfolder already exists
if (await exists(outputDir)) {
const dirContents = fs.readdirSync(outputDir);
@@ -285,14 +285,14 @@ const handler = async (argv) => {
} else {
// If output doesn't exist or is not a directory, use it directly
outputDir = resolvedOutput;
// Check if parent directory exists
const parentDir = path.dirname(outputDir);
if (!await exists(parentDir)) {
console.error(chalk.red(`Parent directory does not exist: ${parentDir}`));
process.exit(1);
}
fs.mkdirSync(outputDir, { recursive: true });
}
@@ -313,4 +313,4 @@ module.exports = {
isUrl,
readOpenApiFile,
readWSDLFile
};
};

View File

@@ -7,7 +7,7 @@ const { exists, isFile, isDirectory } = require('../utils/filesystem');
const { runSingleRequest } = require('../runner/run-single-request');
const { getEnvVars } = require('../utils/bru');
const { parseEnvironmentJson } = require('../utils/environment');
const { isRequestTagsIncluded } = require("@usebruno/common")
const { isRequestTagsIncluded } = require('@usebruno/common');
const makeJUnitOutput = require('../reporters/junit');
const makeHtmlOutput = require('../reporters/html');
const { rpad } = require('../utils/common');
@@ -98,7 +98,7 @@ const printRunSummary = (results) => {
totalPostResponseTests,
passedPostResponseTests,
failedPostResponseTests
}
};
};
const getJsSandboxRuntime = (sandbox) => {
@@ -199,8 +199,8 @@ const builder = async (yargs) => {
default: false
})
.option('delay', {
type:"number",
description: "Delay between each requests (in miliseconds)"
type: 'number',
description: 'Delay between each requests (in miliseconds)'
})
.option('tags', {
type: 'string',
@@ -393,8 +393,8 @@ const handler = async function (argv) {
const match = value.match(/^([^=]+)=(.*)$/);
if (!match) {
console.error(
chalk.red(`Overridable environment variable not correct: use name=value - presented: `) +
chalk.dim(`${value}`)
chalk.red(`Overridable environment variable not correct: use name=value - presented: `)
+ chalk.dim(`${value}`)
);
process.exit(constants.EXIT_STATUS.ERROR_INCORRECT_ENV_OVERRIDE);
}
@@ -483,7 +483,7 @@ const handler = async function (argv) {
recursive = true;
}
const resolvedPaths = paths.map(p => path.resolve(process.cwd(), p));
const resolvedPaths = paths.map((p) => path.resolve(process.cwd(), p));
for (const resolvedPath of resolvedPaths) {
const pathExists = await exists(resolvedPath);
@@ -499,13 +499,13 @@ const handler = async function (argv) {
requestItems = requestItems.filter((item) => {
const requestHasTests = hasExecutableTestInScript(item.request?.tests);
const requestHasActiveAsserts = item.request?.assertions.some((x) => x.enabled) || false;
const preRequestScript = item.request?.script?.req;
const requestHasPreRequestTests = hasExecutableTestInScript(preRequestScript);
const postResponseScript = item.request?.script?.res;
const requestHasPostResponseTests = hasExecutableTestInScript(postResponseScript);
return requestHasTests || requestHasActiveAsserts || requestHasPreRequestTests || requestHasPostResponseTests;
});
}
@@ -540,7 +540,7 @@ const handler = async function (argv) {
}
reject(`bru.runRequest: invalid request path - ${itemPathname}`);
});
}
};
let currentRequestIndex = 0;
let nJumps = 0; // count the number of jumps to avoid infinite loops
@@ -564,12 +564,12 @@ const handler = async function (argv) {
const isLastRun = currentRequestIndex === requestItems.length - 1;
const isValidDelay = !Number.isNaN(delay) && delay > 0;
if(isValidDelay && !isLastRun){
console.log(chalk.yellow(`Waiting for ${delay}ms or ${(delay/1000).toFixed(3)}s before next request.`));
if (isValidDelay && !isLastRun) {
console.log(chalk.yellow(`Waiting for ${delay}ms or ${(delay / 1000).toFixed(3)}s before next request.`));
await new Promise((resolve) => setTimeout(resolve, delay));
}
if(Number.isNaN(delay) && !isLastRun){
if (Number.isNaN(delay) && !isLastRun) {
console.log(chalk.red(`Ignoring delay because it's not a valid number.`));
}
@@ -610,7 +610,6 @@ const handler = async function (argv) {
});
}
// bail if option is set and there is a failure
if (bail) {
const requestFailure = result?.error && !result?.skipped;
@@ -643,7 +642,7 @@ const handler = async function (argv) {
if (nextRequestIdx >= 0) {
currentRequestIndex = nextRequestIdx;
} else {
console.error("Could not find request with name '" + nextRequestName + "'");
console.error('Could not find request with name \'' + nextRequestName + '\'');
currentRequestIndex++;
}
} else {
@@ -667,13 +666,12 @@ const handler = async function (argv) {
};
const reporters = {
'json': (path) => fs.writeFileSync(path, JSON.stringify(outputJson, null, 2)),
'junit': (path) => makeJUnitOutput(results, path),
json: (path) => fs.writeFileSync(path, JSON.stringify(outputJson, null, 2)),
junit: (path) => makeJUnitOutput(results, path),
html: (path) => makeHtmlOutput(outputJson, path, runCompletionTime, environmentName)
}
};
for (const formatter of Object.keys(formats))
{
for (const formatter of Object.keys(formats)) {
const reportPath = formats[formatter];
const reporter = reporters[formatter];

View File

@@ -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');
};

View File

@@ -17,7 +17,7 @@ const makeHtmlOutput = async (results, outputPath, runCompletionTime, environmen
runnerResults = results;
}
const htmlString = generateHtmlReport({
const htmlString = generateHtmlReport({
runnerResults: runnerResults,
version: `usebruno v${CLI_VERSION}`,
environment: environment,

View File

@@ -23,49 +23,48 @@ const makeJUnitOutput = async (results, outputPath) => {
'@timestamp': new Date().toISOString().split('Z')[0],
'@hostname': os.hostname(),
'@time': result.runtime.toFixed(3),
testcase: []
'testcase': []
};
result.assertionResults &&
result.assertionResults.forEach((assertion) => {
const testcase = {
'@name': `${assertion.lhsExpr} ${assertion.rhsExpr}`,
'@status': assertion.status,
'@classname': result.request.url,
'@time': (result.runtime / totalTests).toFixed(3)
};
result.assertionResults
&& result.assertionResults.forEach((assertion) => {
const testcase = {
'@name': `${assertion.lhsExpr} ${assertion.rhsExpr}`,
'@status': assertion.status,
'@classname': result.request.url,
'@time': (result.runtime / totalTests).toFixed(3)
};
if (assertion.status === 'fail') {
suite['@failures']++;
if (assertion.status === 'fail') {
suite['@failures']++;
testcase.failure = [{ '@type': 'failure', '@message': assertion.error }];
}
testcase.failure = [{ '@type': 'failure', '@message': assertion.error }];
}
suite.testcase.push(testcase);
});
suite.testcase.push(testcase);
});
result.testResults &&
result.testResults.forEach((test) => {
const testcase = {
'@name': test.description,
'@status': test.status,
'@classname': result.request.url,
'@time': (result.runtime / totalTests).toFixed(3)
};
result.testResults
&& result.testResults.forEach((test) => {
const testcase = {
'@name': test.description,
'@status': test.status,
'@classname': result.request.url,
'@time': (result.runtime / totalTests).toFixed(3)
};
if (test.status === 'fail') {
suite['@failures']++;
if (test.status === 'fail') {
suite['@failures']++;
testcase.failure = [{ '@type': 'failure', '@message': test.error }];
}
testcase.failure = [{ '@type': 'failure', '@message': test.error }];
}
suite.testcase.push(testcase);
});
suite.testcase.push(testcase);
});
if (result?.skipped) {
suite['@skipped'] = 1;
}
else if (result.error) {
} else if (result.error) {
suite['@errors'] = 1;
suite['@tests'] = 1;
suite.testcase = [
@@ -74,7 +73,7 @@ const makeJUnitOutput = async (results, outputPath) => {
'@status': 'fail',
'@classname': result.request.url,
'@time': result.runtime.toFixed(3),
error: [{ '@type': 'error', '@message': result.error }]
'error': [{ '@type': 'error', '@message': result.error }]
}
];
}

View File

@@ -89,10 +89,10 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
} else if (contentType === 'multipart/form-data') {
if (Array.isArray(request?.data) && !(request.data instanceof FormData)) {
try {
request.data = request?.data?.map(d => ({
request.data = request?.data?.map((d) => ({
...d,
value: _interpolate(d?.value)
}));
}));
} catch (err) {}
}
} else {
@@ -263,10 +263,10 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
if (request.ntlmConfig) {
request.ntlmConfig.username = _interpolate(request.ntlmConfig.username) || '';
request.ntlmConfig.password = _interpolate(request.ntlmConfig.password) || '';
request.ntlmConfig.domain = _interpolate(request.ntlmConfig.domain) || '';
request.ntlmConfig.domain = _interpolate(request.ntlmConfig.domain) || '';
}
if(request?.auth) delete request.auth;
if (request?.auth) delete request.auth;
if (request) return request;
};

View File

@@ -66,7 +66,7 @@ const prepareRequest = async (item = {}, collection = {}) => {
if (collectionAuth.apikey?.placement === 'header') {
axiosRequest.headers[collectionAuth.apikey?.key] = collectionAuth.apikey?.value;
}
if (collectionAuth.apikey?.placement === 'queryparams') {
if (axiosRequest.url && collectionAuth.apikey?.key) {
try {
@@ -89,7 +89,7 @@ const prepareRequest = async (item = {}, collection = {}) => {
if (collectionAuth.mode === 'oauth2') {
const grantType = get(collectionAuth, 'oauth2.grantType');
if (grantType === 'client_credentials') {
axiosRequest.oauth2 = {
grantType,
@@ -226,7 +226,7 @@ const prepareRequest = async (item = {}, collection = {}) => {
if (request.auth.mode === 'oauth2') {
const grantType = get(request, 'auth.oauth2.grantType');
if (grantType === 'client_credentials') {
axiosRequest.oauth2 = {
grantType: grantType,
@@ -265,12 +265,12 @@ const prepareRequest = async (item = {}, collection = {}) => {
};
}
}
if (request.auth.mode === 'apikey') {
if (request.auth.apikey?.placement === 'header') {
axiosRequest.headers[request.auth.apikey?.key] = request.auth.apikey?.value;
}
if (request.auth.apikey?.placement === 'queryparams') {
if (axiosRequest.url && request.auth.apikey?.key) {
try {

View File

@@ -167,7 +167,7 @@ const runSingleRequest = async function (
// run pre request script
const requestScriptFile = get(request, 'script.req');
const collectionName = collection?.brunoConfig?.name
const collectionName = collection?.brunoConfig?.name;
if (requestScriptFile?.length) {
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime });
const result = await scriptRuntime.runRequestScript(
@@ -236,7 +236,7 @@ const runSingleRequest = async function (
const insecure = get(options, 'insecure', false);
const noproxy = get(options, 'noproxy', false);
const httpsAgentRequestFields = {};
if (insecure) {
httpsAgentRequestFields['rejectUnauthorized'] = false;
} else {
@@ -291,7 +291,7 @@ const runSingleRequest = async function (
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
const collectionProxyEnabled = get(collectionProxyConfig, 'enabled', false);
if (noproxy) {
// If noproxy flag is set, don't use any proxy
proxyMode = 'off';
@@ -383,40 +383,40 @@ const runSingleRequest = async function (
});
}
//set cookies if enabled
// set cookies if enabled
if (!options.disableCookies) {
const cookieString = getCookieStringForUrl(request.url);
if (cookieString && typeof cookieString === 'string' && cookieString.length) {
const existingCookieHeaderName = Object.keys(request.headers).find(
name => name.toLowerCase() === 'cookie'
(name) => name.toLowerCase() === 'cookie'
);
const existingCookieString = existingCookieHeaderName ? request.headers[existingCookieHeaderName] : '';
// Helper function to parse cookies into an object
const parseCookies = (str) => str.split(';').reduce((cookies, cookie) => {
const [name, ...rest] = cookie.split('=');
if (name && name.trim()) {
cookies[name.trim()] = rest.join('=').trim();
}
return cookies;
const [name, ...rest] = cookie.split('=');
if (name && name.trim()) {
cookies[name.trim()] = rest.join('=').trim();
}
return cookies;
}, {});
const mergedCookies = {
...parseCookies(existingCookieString),
...parseCookies(cookieString),
...parseCookies(existingCookieString),
...parseCookies(cookieString)
};
const combinedCookieString = Object.entries(mergedCookies)
.map(([name, value]) => `${name}=${value}`)
.join('; ');
.map(([name, value]) => `${name}=${value}`)
.join('; ');
request.headers[existingCookieHeaderName || 'Cookie'] = combinedCookieString;
}
}
// stringify the request url encoded params
const contentTypeHeader = Object.keys(request.headers).find(
name => name.toLowerCase() === 'content-type'
(name) => name.toLowerCase() === 'content-type'
);
if (contentTypeHeader && request.headers[contentTypeHeader] === 'application/x-www-form-urlencoded') {
@@ -462,7 +462,7 @@ const runSingleRequest = async function (
const token = await getOAuth2Token(request.oauth2);
if (token) {
const { tokenPlacement = 'header', tokenHeaderPrefix = '', tokenQueryKey = 'access_token' } = request.oauth2;
if (tokenPlacement === 'header' && token) {
request.headers['Authorization'] = `${tokenHeaderPrefix} ${token}`.trim();
} else if (tokenPlacement === 'url') {
@@ -478,14 +478,13 @@ const runSingleRequest = async function (
} catch (error) {
console.error('OAuth2 token fetch error:', error.message);
}
// Remove oauth2 config from request to prevent it from being sent
delete request.oauth2;
}
let response, responseTime;
try {
// Set timeout from request settings, default to 0 (no timeout)
const requestTimeout = request.settings?.timeout || 0;
if (requestTimeout > 0) {
@@ -502,7 +501,6 @@ const runSingleRequest = async function (
delete request.ntlmConfig;
}
if (request.awsv4config) {
// todo: make this happen in prepare-request.js
// interpolate the aws v4 config
@@ -537,7 +535,7 @@ const runSingleRequest = async function (
responseTime = response.headers.get('request-duration');
response.headers.delete('request-duration');
//save cookies if enabled
// save cookies if enabled
if (!options.disableCookies) {
saveCookies(request.url, response.headers);
}
@@ -571,7 +569,7 @@ const runSingleRequest = async function (
url: null,
responseTime: 0
},
error: err?.message || err?.errors?.map(e => e?.message)?.at(0) || err?.code || 'Request Failed!',
error: err?.message || err?.errors?.map((e) => e?.message)?.at(0) || err?.code || 'Request Failed!',
status: 'error',
assertionResults: [],
testResults: [],
@@ -586,8 +584,8 @@ const runSingleRequest = async function (
response.responseTime = responseTime;
console.log(
chalk.green(stripExtension(relativeItemPathname)) +
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
chalk.green(stripExtension(relativeItemPathname))
+ chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
);
// Log pre-request test results
@@ -675,7 +673,6 @@ const runSingleRequest = async function (
}
}
logResults(assertionResults, 'Assertions');
return {

View File

@@ -33,17 +33,16 @@ const createRedirectConfig = (error, redirectUrl) => {
if (METHOD_CHANGING_REDIRECTS.includes(statusCode) && originalMethod !== 'head') {
requestConfig.method = 'get';
requestConfig.data = undefined;
// Clean up headers that are no longer relevant
delete requestConfig.headers['content-length'];
delete requestConfig.headers['Content-Length'];
delete requestConfig.headers['content-type'];
delete requestConfig.headers['content-type'];
delete requestConfig.headers['Content-Type'];
} else {
// For 307, 308 and other status codes: preserve method and body
if (requestConfig.data && typeof requestConfig.data === 'object' &&
requestConfig.data.constructor && requestConfig.data.constructor.name === 'FormData') {
if (requestConfig.data && typeof requestConfig.data === 'object'
&& requestConfig.data.constructor && requestConfig.data.constructor.name === 'FormData') {
const formData = requestConfig.data;
if (formData._released || (formData._streams && formData._streams.length === 0)) {
if (error.config._originalMultipartData && error.config.collectionPath) {
@@ -51,7 +50,7 @@ const createRedirectConfig = (error, redirectUrl) => {
requestConfig.data = recreatedForm;
const formHeaders = recreatedForm.getHeaders();
Object.assign(requestConfig.headers, formHeaders);
// preserve the original data for potential future redirects
requestConfig._originalMultipartData = error.config._originalMultipartData;
requestConfig.collectionPath = error.config.collectionPath;
@@ -80,7 +79,7 @@ function makeAxiosInstance({ requestMaxRedirects = 5, disableCookies } = {}) {
proxy: false,
maxRedirects: 0,
headers: {
"User-Agent": `bruno-runtime/${CLI_VERSION}`
'User-Agent': `bruno-runtime/${CLI_VERSION}`
}
});
@@ -133,7 +132,7 @@ function makeAxiosInstance({ requestMaxRedirects = 5, disableCookies } = {}) {
redirectUrl = URL.resolve(error.config.url, locationHeader);
}
if (!disableCookies){
if (!disableCookies) {
saveCookies(error.config.url, error.response.headers);
}

View File

@@ -1,5 +1,5 @@
const _ = require('lodash');
const {
const {
parseRequest: _parseRequest,
parseCollection: _parseCollection
} = require('@usebruno/filestore');
@@ -24,7 +24,7 @@ const collectionBruToJson = (bru) => {
const sequence = _.get(json, 'meta.seq');
if (json?.meta) {
transformedJson.meta = {
name: json.meta.name,
name: json.meta.name
};
if (sequence) {
@@ -94,11 +94,11 @@ const bruToJson = (bru) => {
if (requestType === 'grpc-request') {
const selectedMethod = _.get(json, 'grpc.method');
if(selectedMethod) transformedJson.request.method = selectedMethod;
if (selectedMethod) transformedJson.request.method = selectedMethod;
const selectedMethodType = _.get(json, 'grpc.methodType');
if(selectedMethodType) transformedJson.request.methodType = selectedMethodType;
if (selectedMethodType) transformedJson.request.methodType = selectedMethodType;
const protoPath = _.get(json, 'grpc.protoPath');
if(protoPath) transformedJson.request.protoPath = protoPath;
if (protoPath) transformedJson.request.protoPath = protoPath;
transformedJson.request.auth.mode = _.get(json, 'grpc.auth', 'none');
transformedJson.request.body = _.get(json, 'body', {
mode: 'grpc',

View File

@@ -9,7 +9,7 @@ const chalk = require('chalk');
const createCollectionJsonFromPathname = (collectionPath) => {
const environmentsPath = path.join(collectionPath, `environments`);
// get the collection bruno json config [<collection-path>/bruno.json]
const brunoConfig = getCollectionBrunoJsonConfig(collectionPath);
@@ -29,17 +29,16 @@ const createCollectionJsonFromPathname = (collectionPath) => {
if (stats.isDirectory()) {
if (filePath === environmentsPath) continue;
if (filePath.startsWith('.git') || filePath.startsWith('node_modules')) continue;
// get the folder root
let folderItem = { name: file, pathname: filePath, type: 'folder', items: traverse(filePath) }
let folderItem = { name: file, pathname: filePath, type: 'folder', items: traverse(filePath) };
const folderBruJson = getFolderRoot(filePath);
if (folderBruJson) {
folderItem.root = folderBruJson;
folderItem.seq = folderBruJson.meta.seq;
}
currentDirItems.push(folderItem);
}
else {
} else {
if (['collection.bru', 'folder.bru'].includes(file)) continue;
if (path.extname(filePath) !== '.bru') continue;
@@ -78,7 +77,7 @@ const createCollectionJsonFromPathname = (collectionPath) => {
root: collectionRoot,
pathname: collectionPath,
items: collectionItems
}
};
return collection;
};
@@ -96,7 +95,7 @@ const getCollectionBrunoJsonConfig = (dir) => {
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
const brunoConfig = JSON.parse(brunoConfigFile);
return brunoConfig;
}
};
const getCollectionRoot = (dir) => {
const collectionRootPath = path.join(dir, 'collection.bru');
@@ -191,7 +190,7 @@ const mergeVars = (collection, request, requestTreePath) => {
request.folderVariables = folderVariables;
request.requestVariables = requestVariables;
if(request?.vars) {
if (request?.vars) {
request.vars.req = Array.from(reqVars, ([name, value]) => ({
name,
value,
@@ -309,7 +308,7 @@ const mergeAuth = (collection, request, requestTreePath) => {
if (request.auth && request.auth.mode === 'inherit') {
request.auth = effectiveAuth;
}
}
};
const getAllRequestsInFolder = (folderItems = [], recursive = true) => {
let requests = [];
@@ -330,12 +329,11 @@ const getAllRequestsInFolder = (folderItems = [], recursive = true) => {
const getAllRequestsAtFolderRoot = (folderItems = []) => {
return getAllRequestsInFolder(folderItems, false);
}
};
const getCallStack = (resolvedPaths = [], collection, {recursive}) => {
const getCallStack = (resolvedPaths = [], collection, { recursive }) => {
let requestItems = [];
if (!resolvedPaths || !resolvedPaths.length) {
return requestItems;
}
@@ -380,7 +378,7 @@ const safeWriteFileSync = (filePath, content) => {
/**
* Creates a Bruno collection directory structure from a Bruno collection object
*
*
* @param {Object} collection - The Bruno collection object
* @param {string} dirPath - The output directory path
*/
@@ -392,9 +390,9 @@ const createCollectionFromBrunoObject = async (collection, dirPath) => {
type: 'collection',
ignore: ['node_modules', '.git']
};
fs.writeFileSync(
path.join(dirPath, 'bruno.json'),
path.join(dirPath, 'bruno.json'),
JSON.stringify(brunoConfig, null, 2)
);
@@ -424,7 +422,7 @@ const createCollectionFromBrunoObject = async (collection, dirPath) => {
/**
* Recursively processes collection items to create files and folders
*
*
* @param {Array} items - Collection items
* @param {string} currentPath - Current directory path
*/
@@ -487,17 +485,17 @@ const processCollectionItems = async (items = [], currentPath) => {
}
};
const sortByNameThenSequence = items => {
const isSeqValid = seq => Number.isFinite(seq) && Number.isInteger(seq) && seq > 0;
const sortByNameThenSequence = (items) => {
const isSeqValid = (seq) => Number.isFinite(seq) && Number.isInteger(seq) && seq > 0;
// Sort folders alphabetically by name
const alphabeticallySorted = [...items].sort((a, b) => a.name && b.name && a.name.localeCompare(b.name));
// Extract folders without 'seq'
const withoutSeq = alphabeticallySorted.filter(f => !isSeqValid(f['seq']));
const withoutSeq = alphabeticallySorted.filter((f) => !isSeqValid(f['seq']));
// Extract folders with 'seq' and sort them by 'seq'
const withSeq = alphabeticallySorted.filter(f => isSeqValid(f['seq'])).sort((a, b) => a.seq - b.seq);
const withSeq = alphabeticallySorted.filter((f) => isSeqValid(f['seq'])).sort((a, b) => a.seq - b.seq);
const sortedItems = withoutSeq;
@@ -516,7 +514,7 @@ const sortByNameThenSequence = items => {
const newGroup = Array.isArray(existingItem)
? [...existingItem, item]
: [existingItem, item];
withoutSeq.splice(position, 1, newGroup);
} else {
// Insert item at the specified position
@@ -528,7 +526,6 @@ const sortByNameThenSequence = items => {
return sortedItems.flat();
};
module.exports = {
createCollectionJsonFromPathname,
mergeHeaders,
@@ -541,4 +538,4 @@ module.exports = {
getAllRequestsInFolder,
getAllRequestsAtFolderRoot,
getCallStack
}
};

View File

@@ -120,41 +120,41 @@ const getSubDirectories = (dir) => {
/**
* Sanitizes a filename to make it safe for filesystem operations
*
*
* @param {string} name - The name to sanitize
* @returns {string} - The sanitized name
*/
const sanitizeName = (name) => {
if (!name) return '';
const invalidCharacters = /[<>:"/\\|?*\x00-\x1F]/g;
return name
.replace(invalidCharacters, '-') // replace invalid characters with hyphens
.replace(/^[.\s-]+/, '') // remove leading dots, hyphens and spaces
.replace(/[.\s]+$/, ''); // remove trailing dots and spaces (keep trailing hyphens)
.replace(invalidCharacters, '-') // replace invalid characters with hyphens
.replace(/^[.\s-]+/, '') // remove leading dots, hyphens and spaces
.replace(/[.\s]+$/, ''); // remove trailing dots and spaces (keep trailing hyphens)
};
/**
* Validates if a name is valid for the filesystem
*
*
* @param {string} name - The name to validate
* @returns {boolean} - True if the name is valid, false otherwise
*/
const validateName = (name) => {
if (!name) return false;
const reservedDeviceNames = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i;
const firstCharacter = /^[^.\s\-\<>:"/\\|?*\x00-\x1F]/; // no dot, space, or hyphen at start
const middleCharacters = /^[^<>:"/\\|?*\x00-\x1F]*$/; // no invalid characters
const lastCharacter = /[^.\s]$/; // no dot or space at end, hyphen allowed
if (name.length > 255) return false; // max name length
const middleCharacters = /^[^<>:"/\\|?*\x00-\x1F]*$/; // no invalid characters
const lastCharacter = /[^.\s]$/; // no dot or space at end, hyphen allowed
if (name.length > 255) return false; // max name length
if (reservedDeviceNames.test(name)) return false; // windows reserved names
return (
firstCharacter.test(name) &&
middleCharacters.test(name) &&
lastCharacter.test(name)
firstCharacter.test(name)
&& middleCharacters.test(name)
&& lastCharacter.test(name)
);
};

View File

@@ -39,4 +39,4 @@ const createFormData = (data, collectionPath) => {
module.exports = {
createFormData
}
};

View File

@@ -79,15 +79,14 @@ class PatchedHttpsProxyAgent extends HttpsProxyAgent {
}
}
const getSystemProxyEnvVariables = () => {
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
return {
http_proxy: http_proxy || HTTP_PROXY,
https_proxy: https_proxy || HTTPS_PROXY,
no_proxy: no_proxy || NO_PROXY
};
}
};
};
module.exports = {
shouldUseProxy,

View File

@@ -1,28 +1,27 @@
// Check for meaningful test() calls (not commented out or in strings)
const hasExecutableTestInScript = (script) => {
if (!script) return false;
// Remove single-line comments (// ...) and multi-line comments (/* ... */)
let cleanScript = script
.replace(/\/\/.*$/gm, '') // Remove line comments
.replace(/\/\/.*$/gm, '') // Remove line comments
.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove block comments
// Remove string literals to avoid matching test() inside strings
cleanScript = cleanScript
.replace(/"(?:[^"\\]|\\.)*"/g, '""') // Remove double-quoted strings
.replace(/'(?:[^'\\]|\\.)*'/g, "''") // Remove single-quoted strings
.replace(/"(?:[^"\\]|\\.)*"/g, '""') // Remove double-quoted strings
.replace(/'(?:[^'\\]|\\.)*'/g, '\'\'') // Remove single-quoted strings
.replace(/`(?:[^`\\]|\\.)*`/g, '``'); // Remove template literals
// Look for standalone test() calls (not object method calls like obj.test())
// Find all test( occurrences and check they're not preceded by dots
let hasValidTest = false;
let searchFrom = 0;
while (true) {
const index = cleanScript.indexOf('test', searchFrom);
if (index === -1) break;
// Check if this looks like test( with optional whitespace
const afterTest = cleanScript.substring(index + 4);
if (/^\s*\(/.test(afterTest)) {
@@ -32,13 +31,13 @@ const hasExecutableTestInScript = (script) => {
break;
}
}
searchFrom = index + 1;
}
return hasValidTest;
};
module.exports = {
hasExecutableTestInScript
};
};

View File

@@ -1,19 +1,19 @@
const path = require("node:path");
const path = require('node:path');
const { describe, it, expect } = require('@jest/globals');
const constants = require('../../src/constants');
const { createCollectionJsonFromPathname } = require('../../src/utils/collection');
describe('create collection json from pathname', () => {
it("should throw an error when the pathname is not a valid bruno collection root", () => {
it('should throw an error when the pathname is not a valid bruno collection root', () => {
const invalidCollectionPathname = path.join(__dirname, './fixtures/collection-invalid');
jest.spyOn(console, 'error').mockImplementation(() => { });
let mockProcessExit = jest.spyOn(process, 'exit').mockImplementation((code) => { throw new Error(code); });
try { createCollectionJsonFromPathname(invalidCollectionPathname); } catch { }
expect(mockProcessExit).toHaveBeenCalledWith(constants.EXIT_STATUS.ERROR_NOT_IN_COLLECTION);
jest.restoreAllMocks();
})
});
it("creates a bruno collection json from the collection bru files", () => {
it('creates a bruno collection json from the collection bru files', () => {
const collectionPathname = path.join(__dirname, './fixtures/collection-json-from-pathname/collection');
const outputCollectionJson = createCollectionJsonFromPathname(collectionPathname);
@@ -21,10 +21,10 @@ describe('create collection json from pathname', () => {
expect(c).toBeDefined();
/* collection bruno.json */
expect(c).toHaveProperty('brunoConfig.version', "1");
expect(c).toHaveProperty('brunoConfig.version', '1');
expect(c).toHaveProperty('brunoConfig.name', 'collection');
expect(c).toHaveProperty('brunoConfig.type', 'collection');
expect(c).toHaveProperty('brunoConfig.ignore', ["node_modules", ".git"]);
expect(c).toHaveProperty('brunoConfig.ignore', ['node_modules', '.git']);
expect(c).toHaveProperty('brunoConfig.proxy.enabled', false);
expect(c).toHaveProperty('brunoConfig.proxy.protocol', 'http');
expect(c).toHaveProperty('brunoConfig.proxy.hostname', '<proxy-hostname>');
@@ -169,4 +169,4 @@ describe('create collection json from pathname', () => {
// tests
expect(c).toHaveProperty('items[4].request.tests', 'test(\"request level script\", function() {\n expect(\"test\").to.equal(\"test\");\n});');
});
});
});

View File

@@ -62,11 +62,11 @@ describe('prepare-request: prepareRequest', () => {
describe('API Key Authentication', () => {
it('If collection auth is apikey in header', async () => {
collection.root.request.auth = {
mode: "apikey",
mode: 'apikey',
apikey: {
key: "x-api-key",
value: "{{apiKey}}",
placement: "header"
key: 'x-api-key',
value: '{{apiKey}}',
placement: 'header'
}
};
@@ -76,11 +76,11 @@ describe('prepare-request: prepareRequest', () => {
it('If collection auth is apikey in header and request has existing headers', async () => {
collection.root.request.auth = {
mode: "apikey",
mode: 'apikey',
apikey: {
key: "x-api-key",
value: "{{apiKey}}",
placement: "header"
key: 'x-api-key',
value: '{{apiKey}}',
placement: 'header'
}
};
@@ -92,11 +92,11 @@ describe('prepare-request: prepareRequest', () => {
it('If collection auth is apikey in query parameters', async () => {
collection.root.request.auth = {
mode: "apikey",
mode: 'apikey',
apikey: {
key: "x-api-key",
value: "{{apiKey}}",
placement: "queryparams"
key: 'x-api-key',
value: '{{apiKey}}',
placement: 'queryparams'
}
};
@@ -172,7 +172,7 @@ describe('prepare-request: prepareRequest', () => {
};
const result = await prepareRequest(item, collection);
expect(result.oauth2).toBeDefined();
expect(result.oauth2.grantType).toBe('client_credentials');
expect(result.oauth2.accessTokenUrl).toBe('https://auth.example.com/token');
@@ -204,7 +204,7 @@ describe('prepare-request: prepareRequest', () => {
};
const result = await prepareRequest(item, collection);
expect(result.oauth2).toBeDefined();
expect(result.oauth2.grantType).toBe('password');
expect(result.oauth2.accessTokenUrl).toBe('https://auth.example.com/token');
@@ -298,7 +298,7 @@ describe('prepare-request: prepareRequest', () => {
};
const result = await prepareRequest(item, collection);
const expected = {
username: 'testUser',
password: 'testPass123'
@@ -345,11 +345,11 @@ describe('prepare-request: prepareRequest', () => {
describe('API Key Authentication', () => {
it('If request auth is apikey in header', async () => {
item.request.auth = {
mode: "apikey",
mode: 'apikey',
apikey: {
key: "x-api-key",
value: "{{apiKey}}",
placement: "header"
key: 'x-api-key',
value: '{{apiKey}}',
placement: 'header'
}
};
@@ -359,11 +359,11 @@ describe('prepare-request: prepareRequest', () => {
it('If request auth is apikey in header and request has existing headers', async () => {
item.request.auth = {
mode: "apikey",
mode: 'apikey',
apikey: {
key: "x-api-key",
value: "{{apiKey}}",
placement: "header"
key: 'x-api-key',
value: '{{apiKey}}',
placement: 'header'
}
};
@@ -375,11 +375,11 @@ describe('prepare-request: prepareRequest', () => {
it('If request auth is apikey in query parameters', async () => {
item.request.auth = {
mode: "apikey",
mode: 'apikey',
apikey: {
key: "x-api-key",
value: "{{apiKey}}",
placement: "queryparams"
key: 'x-api-key',
value: '{{apiKey}}',
placement: 'queryparams'
}
};

View File

@@ -49,4 +49,4 @@ describe('HTML Report Generation', () => {
expect(htmlString).toContain('{{ totalDataReceived }}');
expect(htmlString).toContain('{{ averageResponseTime }}');
});
});
});

View File

@@ -306,4 +306,4 @@ describe('hasExecutableTestInScript', () => {
expect(hasExecutableTestInScript(script)).toBe(true);
});
});
});
});