diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 29edf63b9..4f82a86e6 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 b2bbc3795..021980dfd 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -40,11 +40,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 @@ -68,11 +70,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 @@ -323,7 +355,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 }; } } @@ -363,11 +396,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 @@ -408,13 +446,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) { @@ -447,7 +490,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 365732c48..64e17cb39 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-tests/collection/ping-another-one.bru b/packages/bruno-tests/collection/ping-another-one.bru new file mode 100644 index 000000000..84c1412a8 --- /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 3abc7a2d4..8f4f3c6f7 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-1.bru b/packages/bruno-tests/collection/scripting/api/bru/runRequest-1.bru index 599f80c4c..95b87239f 100644 --- a/packages/bruno-tests/collection/scripting/api/bru/runRequest-1.bru +++ b/packages/bruno-tests/collection/scripting/api/bru/runRequest-1.bru @@ -43,7 +43,10 @@ tests { test("should get global env var set in runRequest-2", function() { const val = bru.getGlobalEnvVar("run-request-global-env-var"); - expect(val).to.equal("run-request-global-env-var-value"); + const executionMode = req.getExecutionMode(); + if (executionMode == 'runner') { + expect(val).to.equal("run-request-global-env-var-value"); + } }); test("should get response of runRequest-2", function() { 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 000000000..7eb0e332c --- /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 000000000..97a7edbb6 --- /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 000000000..b1be74b22 --- /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 000000000..4abe00b4c --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/bru/runner/3.bru @@ -0,0 +1,11 @@ +meta { + name: 3 + type: http + seq: 3 +} + +post { + url: https://echo.usebruno.com + body: none + auth: none +}