mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-15 20:01:28 +00:00
Merge pull request #4641 from lohxt1/folder_sequencing_cli
refactor: `bruno-cli` - follow folder/request sequences during collection runs
This commit is contained in:
@@ -12,7 +12,7 @@ const { rpad } = require('../utils/common');
|
||||
const { bruToJson, getOptions, collectionBruToJson } = require('../utils/bru');
|
||||
const { dotenvToJson } = require('@usebruno/lang');
|
||||
const constants = require('../constants');
|
||||
const { findItemInCollection } = require('../utils/collection');
|
||||
const { findItemInCollection, getAllRequestsInFolder, createCollectionJsonFromPathname } = require('../utils/collection');
|
||||
const command = 'run [filename]';
|
||||
const desc = 'Run a request';
|
||||
|
||||
@@ -22,6 +22,7 @@ const printRunSummary = (results) => {
|
||||
passedRequests,
|
||||
failedRequests,
|
||||
skippedRequests,
|
||||
errorRequests,
|
||||
totalAssertions,
|
||||
passedAssertions,
|
||||
failedAssertions,
|
||||
@@ -36,6 +37,9 @@ const printRunSummary = (results) => {
|
||||
if (failedRequests > 0) {
|
||||
requestSummary += `, ${chalk.red(`${failedRequests} failed`)}`;
|
||||
}
|
||||
if (errorRequests > 0) {
|
||||
requestSummary += `, ${chalk.red(`${errorRequests} error`)}`;
|
||||
}
|
||||
if (skippedRequests > 0) {
|
||||
requestSummary += `, ${chalk.magenta(`${skippedRequests} skipped`)}`;
|
||||
}
|
||||
@@ -62,6 +66,7 @@ const printRunSummary = (results) => {
|
||||
passedRequests,
|
||||
failedRequests,
|
||||
skippedRequests,
|
||||
errorRequests,
|
||||
totalAssertions,
|
||||
passedAssertions,
|
||||
failedAssertions,
|
||||
@@ -71,163 +76,6 @@ const printRunSummary = (results) => {
|
||||
}
|
||||
};
|
||||
|
||||
const createCollectionFromPath = (collectionPath) => {
|
||||
const environmentsPath = path.join(collectionPath, `environments`);
|
||||
const getFilesInOrder = (collectionPath) => {
|
||||
let collection = {
|
||||
pathname: collectionPath
|
||||
};
|
||||
const traverse = (currentPath) => {
|
||||
const filesInCurrentDir = fs.readdirSync(currentPath);
|
||||
|
||||
if (currentPath.includes('node_modules')) {
|
||||
return;
|
||||
}
|
||||
const currentDirItems = [];
|
||||
for (const file of filesInCurrentDir) {
|
||||
const filePath = path.join(currentPath, file);
|
||||
const stats = fs.lstatSync(filePath);
|
||||
if (
|
||||
stats.isDirectory() &&
|
||||
filePath !== environmentsPath &&
|
||||
!filePath.startsWith('.git') &&
|
||||
!filePath.startsWith('node_modules')
|
||||
) {
|
||||
let folderItem = { name: file, pathname: filePath, type: 'folder', items: traverse(filePath) }
|
||||
const folderBruFilePath = path.join(filePath, 'folder.bru');
|
||||
const folderBruFileExists = fs.existsSync(folderBruFilePath);
|
||||
if(folderBruFileExists) {
|
||||
const folderBruContent = fs.readFileSync(folderBruFilePath, 'utf8');
|
||||
let folderBruJson = collectionBruToJson(folderBruContent);
|
||||
folderItem.root = folderBruJson;
|
||||
}
|
||||
currentDirItems.push(folderItem);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of filesInCurrentDir) {
|
||||
if (['collection.bru', 'folder.bru'].includes(file)) {
|
||||
continue;
|
||||
}
|
||||
const filePath = path.join(currentPath, file);
|
||||
const stats = fs.lstatSync(filePath);
|
||||
|
||||
if (!stats.isDirectory() && path.extname(filePath) === '.bru') {
|
||||
const bruContent = fs.readFileSync(filePath, 'utf8');
|
||||
const bruJson = bruToJson(bruContent);
|
||||
currentDirItems.push({
|
||||
name: file,
|
||||
pathname: filePath,
|
||||
...bruJson
|
||||
});
|
||||
}
|
||||
}
|
||||
return currentDirItems;
|
||||
};
|
||||
collection.items = traverse(collectionPath);
|
||||
return collection;
|
||||
};
|
||||
return getFilesInOrder(collectionPath);
|
||||
};
|
||||
|
||||
const getBruFilesRecursively = (dir, testsOnly) => {
|
||||
const environmentsPath = 'environments';
|
||||
const collection = {};
|
||||
|
||||
const getFilesInOrder = (dir) => {
|
||||
let bruJsons = [];
|
||||
|
||||
const traverse = (currentPath) => {
|
||||
const filesInCurrentDir = fs.readdirSync(currentPath);
|
||||
|
||||
if (currentPath.includes('node_modules')) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of filesInCurrentDir) {
|
||||
const filePath = path.join(currentPath, file);
|
||||
const stats = fs.statSync(filePath);
|
||||
|
||||
// todo: we might need a ignore config inside bruno.json
|
||||
if (
|
||||
stats.isDirectory() &&
|
||||
filePath !== environmentsPath &&
|
||||
!filePath.startsWith('.git') &&
|
||||
!filePath.startsWith('node_modules')
|
||||
) {
|
||||
traverse(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
const currentDirBruJsons = [];
|
||||
for (const file of filesInCurrentDir) {
|
||||
if (['collection.bru', 'folder.bru'].includes(file)) {
|
||||
continue;
|
||||
}
|
||||
const filePath = path.join(currentPath, file);
|
||||
const stats = fs.lstatSync(filePath);
|
||||
|
||||
if (!stats.isDirectory() && path.extname(filePath) === '.bru') {
|
||||
const bruContent = fs.readFileSync(filePath, 'utf8');
|
||||
const bruJson = bruToJson(bruContent);
|
||||
const requestHasTests = bruJson.request?.tests;
|
||||
const requestHasActiveAsserts = bruJson.request?.assertions.some((x) => x.enabled) || false;
|
||||
|
||||
if (testsOnly) {
|
||||
if (requestHasTests || requestHasActiveAsserts) {
|
||||
currentDirBruJsons.push({
|
||||
bruFilepath: filePath,
|
||||
bruJson
|
||||
});
|
||||
}
|
||||
} else {
|
||||
currentDirBruJsons.push({
|
||||
bruFilepath: filePath,
|
||||
bruJson
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// order requests by sequence
|
||||
currentDirBruJsons.sort((a, b) => {
|
||||
const aSequence = a.bruJson.seq || 0;
|
||||
const bSequence = b.bruJson.seq || 0;
|
||||
return aSequence - bSequence;
|
||||
});
|
||||
|
||||
bruJsons = bruJsons.concat(currentDirBruJsons);
|
||||
};
|
||||
|
||||
traverse(dir);
|
||||
return bruJsons;
|
||||
};
|
||||
|
||||
return getFilesInOrder(dir);
|
||||
};
|
||||
|
||||
const getCollectionRoot = (dir) => {
|
||||
const collectionRootPath = path.join(dir, 'collection.bru');
|
||||
const exists = fs.existsSync(collectionRootPath);
|
||||
if (!exists) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(collectionRootPath, 'utf8');
|
||||
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 getJsSandboxRuntime = (sandbox) => {
|
||||
return sandbox === 'safe' ? 'quickjs' : 'vm2';
|
||||
};
|
||||
@@ -320,7 +168,6 @@ const builder = async (yargs) => {
|
||||
type:"number",
|
||||
description: "Delay between each requests (in miliseconds)"
|
||||
})
|
||||
|
||||
.example('$0 run request.bru', 'Run a request')
|
||||
.example('$0 run request.bru --env local', 'Run a request with the environment set to local')
|
||||
.example('$0 run folder', 'Run all requests in a folder')
|
||||
@@ -390,25 +237,8 @@ const handler = async function (argv) {
|
||||
} = argv;
|
||||
const collectionPath = process.cwd();
|
||||
|
||||
// todo
|
||||
// right now, bru must be run from the root of the collection
|
||||
// will add support in the future to run it from anywhere inside the collection
|
||||
const brunoJsonPath = path.join(collectionPath, 'bruno.json');
|
||||
const brunoJsonExists = await exists(brunoJsonPath);
|
||||
if (!brunoJsonExists) {
|
||||
console.error(chalk.red(`You can run only at the root of a collection`));
|
||||
process.exit(constants.EXIT_STATUS.ERROR_NOT_IN_COLLECTION);
|
||||
}
|
||||
|
||||
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
|
||||
const brunoConfig = JSON.parse(brunoConfigFile);
|
||||
const collectionRoot = getCollectionRoot(collectionPath);
|
||||
let collection = createCollectionFromPath(collectionPath);
|
||||
collection = {
|
||||
brunoConfig,
|
||||
root: collectionRoot,
|
||||
...collection
|
||||
}
|
||||
let collection = createCollectionJsonFromPathname(collectionPath);
|
||||
const { root: collectionRoot, brunoConfig } = collection;
|
||||
|
||||
if (clientCertConfig) {
|
||||
try {
|
||||
@@ -444,7 +274,6 @@ const handler = async function (argv) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (filename && filename.length) {
|
||||
const pathExists = await exists(filename);
|
||||
if (!pathExists) {
|
||||
@@ -566,54 +395,39 @@ const handler = async function (argv) {
|
||||
const _isFile = isFile(filename);
|
||||
let results = [];
|
||||
|
||||
let bruJsons = [];
|
||||
let requestItems = [];
|
||||
|
||||
if (_isFile) {
|
||||
console.log(chalk.yellow('Running Request \n'));
|
||||
const bruContent = fs.readFileSync(filename, 'utf8');
|
||||
const bruJson = bruToJson(bruContent);
|
||||
bruJsons.push({
|
||||
bruFilepath: filename,
|
||||
bruJson
|
||||
});
|
||||
const requestItem = bruToJson(bruContent);
|
||||
requestItem.pathname = path.resolve(collectionPath, filename);
|
||||
requestItems.push(requestItem);
|
||||
}
|
||||
|
||||
const _isDirectory = isDirectory(filename);
|
||||
if (_isDirectory) {
|
||||
if (!recursive) {
|
||||
console.log(chalk.yellow('Running Folder \n'));
|
||||
const files = fs.readdirSync(filename);
|
||||
const bruFiles = files.filter((file) => !['folder.bru'].includes(file) && file.endsWith('.bru'));
|
||||
|
||||
for (const bruFile of bruFiles) {
|
||||
const bruFilepath = path.join(filename, bruFile);
|
||||
const bruContent = fs.readFileSync(bruFilepath, 'utf8');
|
||||
const bruJson = bruToJson(bruContent);
|
||||
const requestHasTests = bruJson.request?.tests;
|
||||
const requestHasActiveAsserts = bruJson.request?.assertions.some((x) => x.enabled) || false;
|
||||
if (testsOnly) {
|
||||
if (requestHasTests || requestHasActiveAsserts) {
|
||||
bruJsons.push({
|
||||
bruFilepath,
|
||||
bruJson
|
||||
});
|
||||
}
|
||||
} else {
|
||||
bruJsons.push({
|
||||
bruFilepath,
|
||||
bruJson
|
||||
});
|
||||
}
|
||||
}
|
||||
bruJsons.sort((a, b) => {
|
||||
const aSequence = a.bruJson.seq || 0;
|
||||
const bSequence = b.bruJson.seq || 0;
|
||||
return aSequence - bSequence;
|
||||
});
|
||||
} else {
|
||||
console.log(chalk.yellow('Running Folder Recursively \n'));
|
||||
}
|
||||
const resolvedFilepath = path.resolve(filename);
|
||||
if (resolvedFilepath === collectionPath) {
|
||||
requestItems = getAllRequestsInFolder(collection?.items, recursive);
|
||||
} else {
|
||||
const folderItem = findItemInCollection(collection, resolvedFilepath);
|
||||
if (folderItem) {
|
||||
requestItems = getAllRequestsInFolder(folderItem.items, recursive);
|
||||
}
|
||||
}
|
||||
|
||||
bruJsons = getBruFilesRecursively(filename, testsOnly);
|
||||
if (testsOnly) {
|
||||
requestItems = requestItems.filter((iter) => {
|
||||
const requestHasTests = iter.request?.tests;
|
||||
const requestHasActiveAsserts = iter.request?.assertions.some((x) => x.enabled) || false;
|
||||
return requestHasTests || requestHasActiveAsserts;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,11 +439,10 @@ const handler = async function (argv) {
|
||||
if (itemPathname && !itemPathname?.endsWith('.bru')) {
|
||||
itemPathname = `${itemPathname}.bru`;
|
||||
}
|
||||
const bruJson = cloneDeep(findItemInCollection(collection, itemPathname));
|
||||
if (bruJson) {
|
||||
const requestItem = cloneDeep(findItemInCollection(collection, itemPathname));
|
||||
if (requestItem) {
|
||||
const res = await runSingleRequest(
|
||||
itemPathname,
|
||||
bruJson,
|
||||
requestItem,
|
||||
collectionPath,
|
||||
runtimeVariables,
|
||||
envVars,
|
||||
@@ -648,14 +461,13 @@ const handler = async function (argv) {
|
||||
|
||||
let currentRequestIndex = 0;
|
||||
let nJumps = 0; // count the number of jumps to avoid infinite loops
|
||||
while (currentRequestIndex < bruJsons.length) {
|
||||
const iter = cloneDeep(bruJsons[currentRequestIndex]);
|
||||
const { bruFilepath, bruJson } = iter;
|
||||
while (currentRequestIndex < requestItems.length) {
|
||||
const requestItem = cloneDeep(requestItems[currentRequestIndex]);
|
||||
const { pathname } = requestItem;
|
||||
|
||||
const start = process.hrtime();
|
||||
const result = await runSingleRequest(
|
||||
bruFilepath,
|
||||
bruJson,
|
||||
requestItem,
|
||||
collectionPath,
|
||||
runtimeVariables,
|
||||
envVars,
|
||||
@@ -667,7 +479,7 @@ const handler = async function (argv) {
|
||||
runSingleRequestByPathname
|
||||
);
|
||||
|
||||
const isLastRun = currentRequestIndex === bruJsons.length - 1;
|
||||
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.`));
|
||||
@@ -681,7 +493,7 @@ const handler = async function (argv) {
|
||||
results.push({
|
||||
...result,
|
||||
runtime: process.hrtime(start)[0] + process.hrtime(start)[1] / 1e9,
|
||||
suitename: bruFilepath.replace('.bru', '')
|
||||
suitename: pathname.replace('.bru', '')
|
||||
});
|
||||
|
||||
if (reporterSkipAllHeaders) {
|
||||
@@ -739,7 +551,7 @@ const handler = async function (argv) {
|
||||
if (nextRequestName === null) {
|
||||
break;
|
||||
}
|
||||
const nextRequestIdx = bruJsons.findIndex((iter) => iter.bruJson.name === nextRequestName);
|
||||
const nextRequestIdx = requestItems.findIndex((iter) => iter.name === nextRequestName);
|
||||
if (nextRequestIdx >= 0) {
|
||||
currentRequestIndex = nextRequestIdx;
|
||||
} else {
|
||||
|
||||
@@ -31,8 +31,7 @@ const onConsoleLog = (type, args) => {
|
||||
};
|
||||
|
||||
const runSingleRequest = async function (
|
||||
filename,
|
||||
bruJson,
|
||||
item,
|
||||
collectionPath,
|
||||
runtimeVariables,
|
||||
envVariables,
|
||||
@@ -43,14 +42,12 @@ const runSingleRequest = async function (
|
||||
collection,
|
||||
runSingleRequestByPathname
|
||||
) {
|
||||
const { pathname: itemPathname } = item;
|
||||
const relativeItemPathname = path.relative(collectionPath, itemPathname);
|
||||
try {
|
||||
let request;
|
||||
let nextRequestName;
|
||||
let shouldStopRunnerExecution = false;
|
||||
let item = {
|
||||
pathname: path.join(collectionPath, filename),
|
||||
...bruJson
|
||||
}
|
||||
request = prepareRequest(item, collection);
|
||||
|
||||
request.__bruno__executionMode = 'cli';
|
||||
@@ -84,7 +81,7 @@ const runSingleRequest = async function (
|
||||
if (result?.skipRequest) {
|
||||
return {
|
||||
test: {
|
||||
filename: filename
|
||||
filename: relativeItemPathname
|
||||
},
|
||||
request: {
|
||||
method: request.method,
|
||||
@@ -98,7 +95,8 @@ const runSingleRequest = async function (
|
||||
data: null,
|
||||
responseTime: 0
|
||||
},
|
||||
error: 'Request has been skipped from pre-request script',
|
||||
error: null,
|
||||
status: 'skipped',
|
||||
skipped: true,
|
||||
assertionResults: [],
|
||||
testResults: [],
|
||||
@@ -362,10 +360,10 @@ const runSingleRequest = async function (
|
||||
responseTime = response.headers.get('request-duration');
|
||||
response.headers.delete('request-duration');
|
||||
} else {
|
||||
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
|
||||
console.log(chalk.red(stripExtension(relativeItemPathname)) + chalk.dim(` (${err.message})`));
|
||||
return {
|
||||
test: {
|
||||
filename: filename
|
||||
filename: relativeItemPathname
|
||||
},
|
||||
request: {
|
||||
method: request.method,
|
||||
@@ -374,13 +372,14 @@ const runSingleRequest = async function (
|
||||
data: request.data
|
||||
},
|
||||
response: {
|
||||
status: null,
|
||||
status: 'error',
|
||||
statusText: null,
|
||||
headers: null,
|
||||
data: null,
|
||||
responseTime: 0
|
||||
},
|
||||
error: err?.message || err?.errors?.map(e => e?.message)?.at(0) || err?.code || 'Request Failed!',
|
||||
status: 'error',
|
||||
assertionResults: [],
|
||||
testResults: [],
|
||||
nextRequestName: nextRequestName,
|
||||
@@ -392,12 +391,12 @@ const runSingleRequest = async function (
|
||||
response.responseTime = responseTime;
|
||||
|
||||
console.log(
|
||||
chalk.green(stripExtension(filename)) +
|
||||
chalk.green(stripExtension(relativeItemPathname)) +
|
||||
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
|
||||
);
|
||||
|
||||
// run post-response vars
|
||||
const postResponseVars = get(bruJson, 'request.vars.res');
|
||||
const postResponseVars = get(item, 'request.vars.res');
|
||||
if (postResponseVars?.length) {
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||
varsRuntime.runPostResponseVars(
|
||||
@@ -438,7 +437,7 @@ const runSingleRequest = async function (
|
||||
|
||||
// run assertions
|
||||
let assertionResults = [];
|
||||
const assertions = get(bruJson, 'request.assertions');
|
||||
const assertions = get(item, 'request.assertions');
|
||||
if (assertions) {
|
||||
const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime });
|
||||
assertionResults = assertRuntime.runAssertions(
|
||||
@@ -500,7 +499,7 @@ const runSingleRequest = async function (
|
||||
|
||||
return {
|
||||
test: {
|
||||
filename: filename
|
||||
filename: relativeItemPathname
|
||||
},
|
||||
request: {
|
||||
method: request.method,
|
||||
@@ -516,16 +515,17 @@ const runSingleRequest = async function (
|
||||
responseTime
|
||||
},
|
||||
error: null,
|
||||
status: 'pass',
|
||||
assertionResults,
|
||||
testResults,
|
||||
nextRequestName: nextRequestName,
|
||||
shouldStopRunnerExecution
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
|
||||
console.log(chalk.red(stripExtension(relativeItemPathname)) + chalk.dim(` (${err.message})`));
|
||||
return {
|
||||
test: {
|
||||
filename: filename
|
||||
filename: relativeItemPathname
|
||||
},
|
||||
request: {
|
||||
method: null,
|
||||
@@ -534,12 +534,13 @@ const runSingleRequest = async function (
|
||||
data: null
|
||||
},
|
||||
response: {
|
||||
status: null,
|
||||
status: 'error',
|
||||
statusText: null,
|
||||
headers: null,
|
||||
data: null,
|
||||
responseTime: 0
|
||||
},
|
||||
status: 'error',
|
||||
error: err.message,
|
||||
assertionResults: [],
|
||||
testResults: []
|
||||
|
||||
@@ -15,6 +15,17 @@ const collectionBruToJson = (bru) => {
|
||||
}
|
||||
};
|
||||
|
||||
// add meta if it exists
|
||||
// this is only for folder bru file
|
||||
// in the future, all of this will be replaced by standard bru lang
|
||||
const sequence = _.get(json, 'meta.seq');
|
||||
if (json?.meta) {
|
||||
transformedJson.meta = {
|
||||
name: json.meta.name,
|
||||
seq: !isNaN(sequence) ? Number(sequence) : 1
|
||||
};
|
||||
}
|
||||
|
||||
return transformedJson;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
|
||||
@@ -4,6 +4,112 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { jsonToBruV2, envJsonToBruV2, jsonToCollectionBru } = require('@usebruno/lang');
|
||||
const { sanitizeName } = require('./filesystem');
|
||||
const { bruToJson, collectionBruToJson } = require('./bru');
|
||||
const constants = require('../constants');
|
||||
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);
|
||||
|
||||
// get the collection root [<collection-path>/collection.bru]
|
||||
const collectionRoot = getCollectionRoot(collectionPath);
|
||||
|
||||
// get the collection items recursively
|
||||
const traverse = (currentPath) => {
|
||||
const filesInCurrentDir = fs.readdirSync(currentPath);
|
||||
if (currentPath.includes('node_modules')) {
|
||||
return;
|
||||
}
|
||||
const currentDirItems = [];
|
||||
for (const file of filesInCurrentDir) {
|
||||
const filePath = path.join(currentPath, file);
|
||||
const stats = fs.lstatSync(filePath);
|
||||
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) }
|
||||
const folderBruJson = getFolderRoot(filePath);
|
||||
if (folderBruJson) {
|
||||
folderItem.root = folderBruJson;
|
||||
folderItem.seq = folderBruJson.meta.seq;
|
||||
}
|
||||
currentDirItems.push(folderItem);
|
||||
}
|
||||
else {
|
||||
if (['collection.bru', 'folder.bru'].includes(file)) continue;
|
||||
if (path.extname(filePath) !== '.bru') continue;
|
||||
|
||||
// get the request item
|
||||
const bruContent = fs.readFileSync(filePath, 'utf8');
|
||||
const requestItem = bruToJson(bruContent);
|
||||
currentDirItems.push({
|
||||
name: file,
|
||||
pathname: filePath,
|
||||
...requestItem
|
||||
});
|
||||
}
|
||||
}
|
||||
let currentDirFolderItems = currentDirItems?.filter((iter) => iter.type === 'folder');
|
||||
let sortedFolderItems = currentDirFolderItems?.sort((a, b) => a.seq - b.seq);
|
||||
|
||||
let currentDirRequestItems = currentDirItems?.filter((iter) => iter.type !== 'folder');
|
||||
let sortedRequestItems = currentDirRequestItems?.sort((a, b) => a.seq - b.seq);
|
||||
|
||||
return sortedFolderItems?.concat(sortedRequestItems);
|
||||
};
|
||||
let collectionItems = traverse(collectionPath);
|
||||
|
||||
let collection = {
|
||||
brunoConfig,
|
||||
root: collectionRoot,
|
||||
pathname: collectionPath,
|
||||
items: collectionItems
|
||||
}
|
||||
|
||||
return collection;
|
||||
};
|
||||
|
||||
const getCollectionBrunoJsonConfig = (dir) => {
|
||||
// right now, bru must be run from the root of the collection
|
||||
// will add support in the future to run it from anywhere inside the collection
|
||||
const brunoJsonPath = path.join(dir, 'bruno.json');
|
||||
const brunoJsonExists = fs.existsSync(brunoJsonPath);
|
||||
if (!brunoJsonExists) {
|
||||
console.error(chalk.red(`You can run only at the root of a collection`));
|
||||
process.exit(constants.EXIT_STATUS.ERROR_NOT_IN_COLLECTION);
|
||||
}
|
||||
|
||||
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
|
||||
const brunoConfig = JSON.parse(brunoConfigFile);
|
||||
return brunoConfig;
|
||||
}
|
||||
|
||||
const getCollectionRoot = (dir) => {
|
||||
const collectionRootPath = path.join(dir, 'collection.bru');
|
||||
const exists = fs.existsSync(collectionRootPath);
|
||||
if (!exists) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(collectionRootPath, 'utf8');
|
||||
return collectionBruToJson(content);
|
||||
};
|
||||
|
||||
const getFolderRoot = (dir) => {
|
||||
const folderRootPath = path.join(dir, 'folder.bru');
|
||||
const exists = fs.existsSync(folderRootPath);
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(folderRootPath, 'utf8');
|
||||
return collectionBruToJson(content);
|
||||
};
|
||||
|
||||
const mergeHeaders = (collection, request, requestTreePath) => {
|
||||
let headers = new Map();
|
||||
@@ -204,6 +310,27 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
|
||||
return path;
|
||||
};
|
||||
|
||||
const getAllRequestsInFolder = (folderItems = [], recursive = true) => {
|
||||
let requests = [];
|
||||
|
||||
if (folderItems && folderItems.length) {
|
||||
folderItems.forEach((item) => {
|
||||
if (item.type !== 'folder') {
|
||||
requests.push(item);
|
||||
} else {
|
||||
if (recursive) {
|
||||
requests = requests.concat(getAllRequestsInFolder(item.items, recursive));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return requests;
|
||||
};
|
||||
|
||||
const getAllRequestsAtFolderRoot = (folderItems = []) => {
|
||||
return getAllRequestsInFolder(folderItems, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe write file implementation to handle errors
|
||||
* @param {string} filePath - Path to write file
|
||||
@@ -334,11 +461,16 @@ const processCollectionItems = async (items = [], currentPath) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
createCollectionJsonFromPathname,
|
||||
mergeHeaders,
|
||||
mergeVars,
|
||||
mergeScripts,
|
||||
findItemInCollection,
|
||||
getTreePathFromCollectionToItem,
|
||||
getAllRequestsInFolder,
|
||||
getAllRequestsAtFolderRoot,
|
||||
createCollectionFromBrunoObject
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
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", () => {
|
||||
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", () => {
|
||||
const collectionPathname = path.join(__dirname, './fixtures/collection-json-from-pathname/collection');
|
||||
const outputCollectionJson = createCollectionJsonFromPathname(collectionPathname);
|
||||
|
||||
let c = outputCollectionJson;
|
||||
expect(c).toBeDefined();
|
||||
|
||||
/* collection bruno.json */
|
||||
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.proxy.enabled', false);
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.protocol', 'http');
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.hostname', '<proxy-hostname>');
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.port', 3000);
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.auth.enabled', false);
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.auth.username', '<user-name>');
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.auth.password', '<password>');
|
||||
expect(c).toHaveProperty('brunoConfig.proxy.bypassProxy', '');
|
||||
expect(c).toHaveProperty('brunoConfig.scripts.moduleWhitelist', ['crypto', 'buffer']);
|
||||
expect(c).toHaveProperty('brunoConfig.scripts.filesystemAccess.allow', true);
|
||||
expect(c).toHaveProperty('brunoConfig.clientCertificates.enabled', true);
|
||||
expect(c).toHaveProperty('brunoConfig.clientCertificates.certs', []);
|
||||
|
||||
/* collection pathname */
|
||||
expect(c).toHaveProperty('pathname', collectionPathname);
|
||||
|
||||
/* collection root */
|
||||
// headers
|
||||
expect(c).toHaveProperty('root.request.headers[0].name', 'collection_header');
|
||||
expect(c).toHaveProperty('root.request.headers[0].value', 'collection_header_value');
|
||||
expect(c).toHaveProperty('root.request.headers[0].enabled', true);
|
||||
// auth
|
||||
expect(c).toHaveProperty('root.request.auth.mode', 'basic');
|
||||
expect(c).toHaveProperty('root.request.auth.basic.username', 'username');
|
||||
expect(c).toHaveProperty('root.request.auth.basic.password', 'password');
|
||||
// pre-request scripts
|
||||
expect(c).toHaveProperty('root.request.script.req', 'const collectionPreRequestScript = true;');
|
||||
// collection root - post-response scripts
|
||||
expect(c).toHaveProperty('root.request.script.res', 'const collectionPostResponseScript = true;');
|
||||
// pre-request vars
|
||||
expect(c).toHaveProperty('root.request.vars.req[0].name', 'collection_pre_var');
|
||||
expect(c).toHaveProperty('root.request.vars.req[0].value', 'collection_pre_var_value');
|
||||
expect(c).toHaveProperty('root.request.vars.req[0].enabled', true);
|
||||
// post-response vars
|
||||
expect(c).toHaveProperty('root.request.vars.res[0].name', 'collection_post_var');
|
||||
expect(c).toHaveProperty('root.request.vars.res[0].value', 'collection_post_var_value');
|
||||
expect(c).toHaveProperty('root.request.vars.res[0].enabled', true);
|
||||
// tests
|
||||
expect(c).toHaveProperty('root.request.tests', 'test(\"collection level script\", function() {\n expect(\"test\").to.equal(\"test\");\n});');
|
||||
|
||||
/* collection items names and sequences */
|
||||
// <collection-root>/folder_2
|
||||
expect(c).toHaveProperty('items[0].type', 'folder');
|
||||
expect(c).toHaveProperty('items[0].name', 'folder_2');
|
||||
expect(c).toHaveProperty('items[0].seq', 1);
|
||||
|
||||
// <collection-root>/folder_2/request_1
|
||||
expect(c).toHaveProperty('items[0].items[0].name', 'request_1');
|
||||
expect(c).toHaveProperty('items[0].items[0].seq', 1);
|
||||
|
||||
// <collection-root>/folder_2/request_3
|
||||
expect(c).toHaveProperty('items[0].items[1].name', 'request_3');
|
||||
expect(c).toHaveProperty('items[0].items[1].seq', 2);
|
||||
|
||||
// <collection-root>/folder_2/request_2
|
||||
expect(c).toHaveProperty('items[0].items[2].name', 'request_2');
|
||||
expect(c).toHaveProperty('items[0].items[2].seq', 3);
|
||||
|
||||
// <collection-root>/folder_1
|
||||
expect(c).toHaveProperty('items[1].type', 'folder');
|
||||
expect(c).toHaveProperty('items[1].name', 'folder_1');
|
||||
expect(c).toHaveProperty('items[1].seq', 5);
|
||||
|
||||
// <collection-root>/folder_1/folder_2
|
||||
expect(c).toHaveProperty('items[1].items[0].name', 'folder_2');
|
||||
expect(c).toHaveProperty('items[1].items[0].seq', 1);
|
||||
|
||||
// <collection-root>/folder_1/folder_2/request_3
|
||||
expect(c).toHaveProperty('items[1].items[0].items[0].name', 'request_3');
|
||||
expect(c).toHaveProperty('items[1].items[0].items[0].seq', 1);
|
||||
|
||||
// <collection-root>/folder_1/folder_2/request_1
|
||||
expect(c).toHaveProperty('items[1].items[0].items[1].name', 'request_1');
|
||||
expect(c).toHaveProperty('items[1].items[0].items[1].seq', 2);
|
||||
|
||||
// <collection-root>/folder_1/folder_2/request_2
|
||||
expect(c).toHaveProperty('items[1].items[0].items[2].name', 'request_2');
|
||||
expect(c).toHaveProperty('items[1].items[0].items[2].seq', 3);
|
||||
|
||||
// <collection-root>/folder_1/folder_1
|
||||
expect(c).toHaveProperty('items[1].items[1].name', 'folder_1');
|
||||
expect(c).toHaveProperty('items[1].items[1].seq', 2);
|
||||
|
||||
// <collection-root>/folder_1/folder_1/request_3
|
||||
expect(c).toHaveProperty('items[1].items[1].items[0].name', 'request_3');
|
||||
expect(c).toHaveProperty('items[1].items[1].items[0].seq', 1);
|
||||
|
||||
// <collection-root>/folder_1/folder_1/request_2
|
||||
expect(c).toHaveProperty('items[1].items[1].items[1].name', 'request_2');
|
||||
expect(c).toHaveProperty('items[1].items[1].items[1].seq', 2);
|
||||
|
||||
// <collection-root>/folder_1/folder_1/request_1
|
||||
expect(c).toHaveProperty('items[1].items[1].items[2].name', 'request_1');
|
||||
expect(c).toHaveProperty('items[1].items[1].items[2].seq', 3);
|
||||
|
||||
// <collection-root>/folder_1/request_1
|
||||
expect(c).toHaveProperty('items[1].items[2].name', 'request_1');
|
||||
expect(c).toHaveProperty('items[1].items[2].seq', 3);
|
||||
|
||||
// <collection-root>/folder_1/request_3
|
||||
expect(c).toHaveProperty('items[1].items[3].name', 'request_3');
|
||||
expect(c).toHaveProperty('items[1].items[3].seq', 4);
|
||||
|
||||
// <collection-root>/folder_1/request_2
|
||||
expect(c).toHaveProperty('items[1].items[4].name', 'request_2');
|
||||
expect(c).toHaveProperty('items[1].items[4].seq', 5);
|
||||
|
||||
// <collection-root>/request_2
|
||||
expect(c).toHaveProperty('items[2].name', 'request_3');
|
||||
expect(c).toHaveProperty('items[2].seq', 2);
|
||||
|
||||
// <collection-root>/request_3
|
||||
expect(c).toHaveProperty('items[3].name', 'request_1');
|
||||
expect(c).toHaveProperty('items[3].seq', 3);
|
||||
|
||||
// <collection-root>/request_4
|
||||
expect(c).toHaveProperty('items[4].name', 'request_2');
|
||||
expect(c).toHaveProperty('items[4].seq', 4);
|
||||
|
||||
/* collection request item - <collection-root>/request_4 */
|
||||
// <collection-root>/request_4
|
||||
// headers
|
||||
expect(c).toHaveProperty('items[4].request.headers[0].name', 'request_header');
|
||||
expect(c).toHaveProperty('items[4].request.headers[0].value', 'request_header_value');
|
||||
expect(c).toHaveProperty('items[4].request.headers[0].enabled', true);
|
||||
// auth
|
||||
expect(c).toHaveProperty('items[4].request.auth.mode', 'basic');
|
||||
expect(c).toHaveProperty('items[4].request.auth.basic.username', 'username');
|
||||
expect(c).toHaveProperty('items[4].request.auth.basic.password', 'password');
|
||||
// pre-request scripts
|
||||
expect(c).toHaveProperty('items[4].request.script.req', 'const requestPreRequestScript = true;');
|
||||
// request items[4] - post-response scripts
|
||||
expect(c).toHaveProperty('items[4].request.script.res', 'const requestPostResponseScript = true;');
|
||||
// pre-request vars
|
||||
expect(c).toHaveProperty('items[4].request.vars.req[0].name', 'request_pre_var');
|
||||
expect(c).toHaveProperty('items[4].request.vars.req[0].value', 'request_pre_var_value');
|
||||
expect(c).toHaveProperty('items[4].request.vars.req[0].enabled', true);
|
||||
// post-response vars
|
||||
expect(c).toHaveProperty('items[4].request.vars.res[0].name', 'request_post_var');
|
||||
expect(c).toHaveProperty('items[4].request.vars.res[0].value', 'request_post_var_value');
|
||||
expect(c).toHaveProperty('items[4].request.vars.res[0].enabled', true);
|
||||
// tests
|
||||
expect(c).toHaveProperty('items[4].request.tests', 'test(\"request level script\", function() {\n expect(\"test\").to.equal(\"test\");\n});');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "collection",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
],
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"protocol": "http",
|
||||
"hostname": "<proxy-hostname>",
|
||||
"port": 3000,
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"username": "<user-name>",
|
||||
"password": "<password>"
|
||||
},
|
||||
"bypassProxy": ""
|
||||
},
|
||||
"scripts": {
|
||||
"moduleWhitelist": ["crypto", "buffer"],
|
||||
"filesystemAccess": {
|
||||
"allow": true
|
||||
}
|
||||
},
|
||||
"clientCertificates": {
|
||||
"enabled": true,
|
||||
"certs": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
headers {
|
||||
collection_header: collection_header_value
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: username
|
||||
password: password
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
collection_pre_var: collection_pre_var_value
|
||||
}
|
||||
|
||||
vars:post-response {
|
||||
collection_post_var: collection_post_var_value
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const collectionPreRequestScript = true;
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
const collectionPostResponseScript = true;
|
||||
}
|
||||
|
||||
tests {
|
||||
test("collection level script", function() {
|
||||
expect("test").to.equal("test");
|
||||
});
|
||||
}
|
||||
|
||||
docs {
|
||||
# docs
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
meta {
|
||||
name: folder_1
|
||||
seq: 5
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
meta {
|
||||
name: folder_1
|
||||
seq: 2
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_1
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_2
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_3
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
meta {
|
||||
name: folder_2
|
||||
seq: 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_1
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_2
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_3
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_1
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_2
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_3
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
meta {
|
||||
name: folder_2
|
||||
seq: 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_1
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_2
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_3
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_1
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
meta {
|
||||
name: request_2
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://echo.usebruno.com/:request_path_param?request_query_param=request_query_param_value
|
||||
body: text
|
||||
auth: basic
|
||||
}
|
||||
|
||||
params:query {
|
||||
request_query_param: request_query_param_value
|
||||
}
|
||||
|
||||
params:path {
|
||||
request_path_param: request_path_param_value
|
||||
}
|
||||
|
||||
headers {
|
||||
request_header: request_header_value
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: username
|
||||
password: password
|
||||
}
|
||||
|
||||
body:text {
|
||||
ping
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
request_pre_var: request_pre_var_value
|
||||
}
|
||||
|
||||
vars:post-response {
|
||||
request_post_var: request_post_var_value
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: eq 200
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const requestPreRequestScript = true;
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
const requestPostResponseScript = true;
|
||||
}
|
||||
|
||||
tests {
|
||||
test("request level script", function() {
|
||||
expect("test").to.equal("test");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: request_3
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://echo.usebruno.com
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
Reference in New Issue
Block a user