diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 29edf63b9c..4f82a86e69 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -11,6 +11,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 command = 'run [filename]'; const desc = 'Run a request'; @@ -18,6 +19,7 @@ const printRunSummary = (results) => { let totalRequests = 0; let passedRequests = 0; let failedRequests = 0; + let skippedRequests = 0; let totalAssertions = 0; let passedAssertions = 0; let failedAssertions = 0; @@ -49,7 +51,10 @@ const printRunSummary = (results) => { failedAssertions += 1; } } - if (!hasAnyTestsOrAssertions && result.error) { + if (!hasAnyTestsOrAssertions && result.skipped) { + skippedRequests += 1; + } + else if (!hasAnyTestsOrAssertions && result.error) { failedRequests += 1; } else { passedRequests += 1; @@ -62,6 +67,9 @@ const printRunSummary = (results) => { if (failedRequests > 0) { requestSummary += `, ${chalk.red(`${failedRequests} failed`)}`; } + if (skippedRequests > 0) { + requestSummary += `, ${chalk.magenta(`${skippedRequests} skipped`)}`; + } requestSummary += `, ${totalRequests} total`; let assertSummary = `${rpad('Tests:', maxLength)} ${chalk.green(`${passedTests} passed`)}`; @@ -84,6 +92,7 @@ const printRunSummary = (results) => { totalRequests, passedRequests, failedRequests, + skippedRequests, totalAssertions, passedAssertions, failedAssertions, @@ -144,7 +153,7 @@ const createCollectionFromPath = (collectionPath) => { }); } } - return currentDirItems + return currentDirItems; }; collection.items = traverse(collectionPath); return collection; @@ -634,6 +643,34 @@ const handler = async function (argv) { } const runtime = getJsSandboxRuntime(sandbox); + + const runSingleRequestByPathname = async (relativeItemPathname) => { + return new Promise(async (resolve, reject) => { + let itemPathname = path.join(collectionPath, relativeItemPathname); + if (itemPathname && !itemPathname?.endsWith('.bru')) { + itemPathname = `${itemPathname}.bru`; + } + const bruJson = cloneDeep(findItemInCollection(collection, itemPathname)); + if (bruJson) { + const res = await runSingleRequest( + itemPathname, + bruJson, + collectionPath, + runtimeVariables, + envVars, + processEnvVars, + brunoConfig, + collectionRoot, + runtime, + collection, + runSingleRequestByPathname + ); + resolve(res?.response); + } + reject(`bru.runRequest: invalid request path - ${itemPathname}`); + }); + } + let currentRequestIndex = 0; let nJumps = 0; // count the number of jumps to avoid infinite loops while (currentRequestIndex < bruJsons.length) { @@ -651,7 +688,8 @@ const handler = async function (argv) { brunoConfig, collectionRoot, runtime, - collection + collection, + runSingleRequestByPathname ); results.push({ @@ -701,6 +739,11 @@ const handler = async function (argv) { // determine next request const nextRequestName = result?.nextRequestName; + + if (result?.shouldStopRunnerExecution) { + break; + } + if (nextRequestName !== undefined) { nJumps++; if (nJumps > 10000) { diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 29ed749b94..0d1787273e 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -38,11 +38,13 @@ const runSingleRequest = async function ( brunoConfig, collectionRoot, runtime, - collection + collection, + runSingleRequestByPathname ) { try { let request; let nextRequestName; + let shouldStopRunnerExecution = false; let item = { pathname: path.join(collectionPath, filename), ...bruJson @@ -66,11 +68,41 @@ const runSingleRequest = async function ( collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runSingleRequestByPathname ); if (result?.nextRequestName !== undefined) { nextRequestName = result.nextRequestName; } + + if (result?.stopExecution) { + shouldStopRunnerExecution = true; + } + + if (result?.skipRequest) { + return { + test: { + filename: filename + }, + request: { + method: request.method, + url: request.url, + headers: request.headers, + data: request.data + }, + response: { + status: 'skipped', + statusText: 'request skipped via pre-request script', + data: null, + responseTime: 0 + }, + error: 'Request has been skipped from pre-request script', + skipped: true, + assertionResults: [], + testResults: [], + shouldStopRunnerExecution + }; + } } // interpolate variables inside request @@ -316,7 +348,8 @@ const runSingleRequest = async function ( error: err?.message || err?.errors?.map(e => e?.message)?.at(0) || err?.code || 'Request Failed!', assertionResults: [], testResults: [], - nextRequestName: nextRequestName + nextRequestName: nextRequestName, + shouldStopRunnerExecution }; } } @@ -356,11 +389,16 @@ const runSingleRequest = async function ( collectionPath, null, processEnvVars, - scriptingConfig + scriptingConfig, + runSingleRequestByPathname ); if (result?.nextRequestName !== undefined) { nextRequestName = result.nextRequestName; } + + if (result?.stopExecution) { + shouldStopRunnerExecution = true; + } } // run assertions @@ -401,13 +439,18 @@ const runSingleRequest = async function ( collectionPath, null, processEnvVars, - scriptingConfig + scriptingConfig, + runSingleRequestByPathname ); testResults = get(result, 'results', []); if (result?.nextRequestName !== undefined) { nextRequestName = result.nextRequestName; } + + if (result?.stopExecution) { + shouldStopRunnerExecution = true; + } } if (testResults?.length) { @@ -440,7 +483,8 @@ const runSingleRequest = async function ( error: null, assertionResults, testResults, - nextRequestName: nextRequestName + nextRequestName: nextRequestName, + shouldStopRunnerExecution }; } catch (err) { console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`)); diff --git a/packages/bruno-cli/src/utils/collection.js b/packages/bruno-cli/src/utils/collection.js index 365732c488..64e17cb39b 100644 --- a/packages/bruno-cli/src/utils/collection.js +++ b/packages/bruno-cli/src/utils/collection.js @@ -204,5 +204,6 @@ module.exports = { mergeHeaders, mergeVars, mergeScripts, + findItemInCollection, getTreePathFromCollectionToItem } \ No newline at end of file diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 782878c59a..6ae5ef2208 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -544,7 +544,7 @@ const registerNetworkIpc = (mainWindow) => { if (itemPathname && !itemPathname?.endsWith('.bru')) { itemPathname = `${itemPathname}.bru`; } - const _item = findItemInCollectionByPathname(collection, itemPathname); + const _item = cloneDeep(findItemInCollectionByPathname(collection, itemPathname)); if(_item) { const res = await runRequest({ item: _item, collection, environment, runtimeVariables, runInBackground: true }); resolve(res); @@ -952,7 +952,7 @@ const registerNetworkIpc = (mainWindow) => { if (itemPathname && !itemPathname?.endsWith('.bru')) { itemPathname = `${itemPathname}.bru`; } - const _item = findItemInCollectionByPathname(collection, itemPathname); + const _item = cloneDeep(findItemInCollectionByPathname(collection, itemPathname)); if(_item) { const res = await runRequest({ item: _item, collection, environment, runtimeVariables, runInBackground: true }); resolve(res); diff --git a/packages/bruno-tests/collection/ping-another-one.bru b/packages/bruno-tests/collection/ping-another-one.bru new file mode 100644 index 0000000000..84c1412a84 --- /dev/null +++ b/packages/bruno-tests/collection/ping-another-one.bru @@ -0,0 +1,15 @@ +meta { + name: ping-another-one + type: http + seq: 2 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:pre-request { + throw new Error('this should not execute in a collection run'); +} diff --git a/packages/bruno-tests/collection/ping.bru b/packages/bruno-tests/collection/ping.bru index 3abc7a2d41..8f4f3c6f72 100644 --- a/packages/bruno-tests/collection/ping.bru +++ b/packages/bruno-tests/collection/ping.bru @@ -9,3 +9,7 @@ get { body: none auth: none } + +script:pre-request { + bru.runner.stopExecution(); +} diff --git a/packages/bruno-tests/collection/scripting/api/bru/runRequest.bru b/packages/bruno-tests/collection/scripting/api/bru/runRequest.bru new file mode 100644 index 0000000000..7eb0e332cd --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/bru/runRequest.bru @@ -0,0 +1,96 @@ +meta { + name: runRequest + type: http + seq: 2 +} + +post { + url: {{host}}/api/echo/json + body: json + auth: none +} + +headers { + foo: bar +} + +auth:basic { + username: asd + password: j +} + +auth:bearer { + token: +} + +body:json { + { + "hello": "bruno" + } +} + +assert { + res.status: eq 200 +} + +script:pre-request { + bru.setVar("runRequest-ping-res-1", null); + bru.setVar("runRequest-ping-res-2", null); + bru.setVar("runRequest-ping-res-3", null); + + let pingRes = await bru.runRequest('ping'); + bru.setVar('runRequest-ping-res-1', { + data: pingRes?.data, + statusText: pingRes?.statusText, + status: pingRes?.status + }); +} + +script:post-response { + let pingRes = await bru.runRequest('ping'); + bru.setVar('runRequest-ping-res-2', { + data: pingRes?.data, + statusText: pingRes?.statusText, + status: pingRes?.status + }); +} + +tests { + const pingRes = await bru.runRequest('ping'); + bru.setVar('runRequest-ping-res-3', { + data: pingRes?.data, + statusText: pingRes?.statusText, + status: pingRes?.status + }); + + test("should run request and return valid response in pre-request script", function() { + const expectedPingRes = { + data: "pong", + statusText: "OK", + status: 200 + }; + const pingRes = bru.getVar('runRequest-ping-res-1'); + expect(pingRes).to.eql(expectedPingRes); + }); + + test("should run request and return valid response in post-response script", function() { + const expectedPingRes = { + data: "pong", + statusText: "OK", + status: 200 + }; + const pingRes = bru.getVar('runRequest-ping-res-2'); + expect(pingRes).to.eql(expectedPingRes); + }); + + test("should run request and return valid response in tests script", function() { + const expectedPingRes = { + data: "pong", + statusText: "OK", + status: 200 + }; + const pingRes = bru.getVar('runRequest-ping-res-3'); + expect(pingRes).to.eql(expectedPingRes); + }); + +} diff --git a/packages/bruno-tests/collection/scripting/api/bru/runner/1.bru b/packages/bruno-tests/collection/scripting/api/bru/runner/1.bru new file mode 100644 index 0000000000..97a7edbb6e --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/bru/runner/1.bru @@ -0,0 +1,19 @@ +meta { + name: 1 + type: http + seq: 1 +} + +post { + url: https://echo.usebruno.com + body: none + auth: none +} + +script:pre-request { + bru.setVar('bru-runner-req', 1); +} + +script:post-response { + bru.setVar('bru.runner.skipRequest', true); +} diff --git a/packages/bruno-tests/collection/scripting/api/bru/runner/2.bru b/packages/bruno-tests/collection/scripting/api/bru/runner/2.bru new file mode 100644 index 0000000000..b1be74b22e --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/bru/runner/2.bru @@ -0,0 +1,19 @@ +meta { + name: 2 + type: http + seq: 2 +} + +post { + url: https://echo.usebruno.com + body: none + auth: none +} + +script:pre-request { + bru.runner.skipRequest(); +} + +script:post-response { + bru.setVar('bru.runner.skipRequest', false); +} diff --git a/packages/bruno-tests/collection/scripting/api/bru/runner/3.bru b/packages/bruno-tests/collection/scripting/api/bru/runner/3.bru new file mode 100644 index 0000000000..4ffe49315e --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/bru/runner/3.bru @@ -0,0 +1,21 @@ +meta { + name: 3 + type: http + seq: 3 +} + +post { + url: https://echo.usebruno.com + body: none + auth: none +} + +script:pre-request { +} + +tests { + test("should skip the previous request ('2')", function() { + const wasRequestSkipped = bru.getVar('bru.runner.skipRequest'); + expect(wasRequestSkipped).to.eql(true); + }); +}