From 10408a344aa20d4205e33c2c5e37e52f631e80b2 Mon Sep 17 00:00:00 2001 From: sanish-bruno Date: Fri, 23 Jan 2026 00:13:29 +0530 Subject: [PATCH] feat: enhance hook control flow and response handling --- .../src/runner/run-single-request.js | 177 +++++++++++------- .../bruno-electron/src/ipc/network/index.js | 36 +++- .../bruno-js/src/runtime/hooks-executor.js | 9 + .../bruno-js/src/runtime/hooks-runtime.js | 37 +++- .../legacy-set-next-request-first.bru | 45 +++++ .../legacy-set-next-request-skipped.bru | 30 +++ .../legacy-set-next-request-target.bru | 43 +++++ .../set-next-null-should-not-run.bru | 30 +++ .../set-next-null-trigger.bru | 45 +++++ .../set-next-request-first.bru | 45 +++++ .../set-next-request-skipped.bru | 30 +++ .../set-next-request-target.bru | 43 +++++ .../skip-request-in-before-hook.bru | 32 ++++ tests/scripting/hooks/hooks.spec.ts | 12 +- 14 files changed, 530 insertions(+), 84 deletions(-) create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-first.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-skipped.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-target.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/set-next-null-should-not-run.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/set-next-null-trigger.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/set-next-request-first.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/set-next-request-skipped.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/set-next-request-target.bru create mode 100644 packages/bruno-tests/hooks-comprehensive-tests/skip-request-in-before-hook.bru diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index a6d0aca45..2571c983e 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -38,6 +38,71 @@ const getCACertHostRegex = (domain) => { return '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); }; +/** + * Apply runner control signals from a script/hook result + * @param {object} result - Result from script/hook execution + * @param {object} state - Current runner state (modified in place) + * @param {string|undefined} state.nextRequestName - Current next request name + * @param {boolean} state.shouldStopRunnerExecution - Current stop flag + * @returns {object} Updated state with nextRequestName and shouldStopRunnerExecution + */ +const applyRunnerControlFromResult = (result, state) => { + if (result?.nextRequestName !== undefined) { + state.nextRequestName = result.nextRequestName; + } + if (result?.stopExecution) { + state.shouldStopRunnerExecution = true; + } + return state; +}; + +/** + * Create a standardized skipped response object + * @param {object} options - Options for creating the response + * @param {string} options.filename - The relative file pathname + * @param {object} options.request - The request object + * @param {string} options.statusText - The reason for skipping + * @param {array} [options.preRequestTestResults] - Pre-request test results + * @param {array} [options.postResponseTestResults] - Post-response test results + * @param {string} [options.nextRequestName] - Next request name if set + * @param {boolean} [options.shouldStopRunnerExecution] - Stop execution flag + * @returns {object} Standardized skipped response object + */ +const createSkippedResponse = ({ + filename, + request, + statusText, + preRequestTestResults = [], + postResponseTestResults = [], + nextRequestName, + shouldStopRunnerExecution = false +}) => { + return { + test: { filename }, + request: { + method: request.method, + url: request.url, + headers: request.headers, + data: request.data + }, + response: { + status: 'skipped', + statusText, + data: null, + responseTime: 0 + }, + error: null, + status: 'skipped', + skipped: true, + assertionResults: [], + testResults: [], + preRequestTestResults, + postResponseTestResults, + nextRequestName, + shouldStopRunnerExecution + }; +}; + /** * Extract prompt variables from a request * Tries to respect the hierarchy of the variables and avoid unnecessary prompts as much as possible @@ -136,31 +201,12 @@ const runSingleRequest = async function ( if (promptVars.length > 0) { const errorMsg = `Prompt variables detected in request. CLI execution is not supported for requests with prompt variables. \nPrompts: ${promptVars.join(', ')}`; console.log(chalk.yellow(stripExtension(relativeItemPathname) + ' Skipped:') + chalk.dim(` (${errorMsg})`)); - return { - test: { - filename: relativeItemPathname - }, - request: { - method: request.method, - url: request.url, - headers: request.headers, - data: request.data - }, - response: { - status: 'skipped', - statusText: errorMsg, - data: null, - responseTime: 0 - }, - error: null, - status: 'skipped', - skipped: true, - assertionResults: [], - testResults: [], - preRequestTestResults: [], - postResponseTestResults: [], + return createSkippedResponse({ + filename: relativeItemPathname, + request, + statusText: errorMsg, shouldStopRunnerExecution - }; + }); } request.__bruno__executionMode = 'cli'; @@ -194,12 +240,28 @@ const runSingleRequest = async function ( // Hooks are called in registration order: collection -> folder(s) -> request const beforeRequestEventData = { request, req: new BrunoRequest(request), collection }; - await executeAllHooksConsolidated( + const beforeRequestHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, HOOK_EVENTS.HTTP_BEFORE_REQUEST, beforeRequestEventData ); + // Check runner control from hooks + const runnerState = { nextRequestName, shouldStopRunnerExecution }; + applyRunnerControlFromResult(beforeRequestHooksResult, runnerState); + nextRequestName = runnerState.nextRequestName; + shouldStopRunnerExecution = runnerState.shouldStopRunnerExecution; + + if (beforeRequestHooksResult?.skipRequest) { + return createSkippedResponse({ + filename: relativeItemPathname, + request, + statusText: 'request skipped via beforeRequest hook', + nextRequestName, + shouldStopRunnerExecution + }); + } + // run pre request script const requestScriptFile = get(request, 'script.req'); if (requestScriptFile?.length) { @@ -216,40 +278,18 @@ const runSingleRequest = async function ( runSingleRequestByPathname, collectionName ); - if (result?.nextRequestName !== undefined) { - nextRequestName = result.nextRequestName; - } - - if (result?.stopExecution) { - shouldStopRunnerExecution = true; - } + applyRunnerControlFromResult(result, runnerState); + nextRequestName = runnerState.nextRequestName; + shouldStopRunnerExecution = runnerState.shouldStopRunnerExecution; if (result?.skipRequest) { - return { - test: { - filename: relativeItemPathname - }, - 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: null, - status: 'skipped', - skipped: true, - assertionResults: [], - testResults: [], + return createSkippedResponse({ + filename: relativeItemPathname, + request, + statusText: 'request skipped via pre-request script', preRequestTestResults: result?.results || [], - postResponseTestResults: [], shouldStopRunnerExecution - }; + }); } preRequestTestResults = result?.results || []; @@ -685,12 +725,17 @@ const runSingleRequest = async function ( collection }; - await executeAllHooksConsolidated( + const afterResponseHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, HOOK_EVENTS.HTTP_AFTER_RESPONSE, afterResponseEventData ); + // Check runner control from hooks + applyRunnerControlFromResult(afterResponseHooksResult, runnerState); + nextRequestName = runnerState.nextRequestName; + shouldStopRunnerExecution = runnerState.shouldStopRunnerExecution; + // run post-response vars const postResponseVars = get(item, 'request.vars.res'); if (postResponseVars?.length) { @@ -724,13 +769,9 @@ const runSingleRequest = async function ( runSingleRequestByPathname, collectionName ); - if (result?.nextRequestName !== undefined) { - nextRequestName = result.nextRequestName; - } - - if (result?.stopExecution) { - shouldStopRunnerExecution = true; - } + applyRunnerControlFromResult(result, runnerState); + nextRequestName = runnerState.nextRequestName; + shouldStopRunnerExecution = runnerState.shouldStopRunnerExecution; postResponseTestResults = result?.results || []; logResults(postResponseTestResults, 'Post-Response Tests'); @@ -774,13 +815,9 @@ const runSingleRequest = async function ( ); testResults = get(result, 'results', []); - if (result?.nextRequestName !== undefined) { - nextRequestName = result.nextRequestName; - } - - if (result?.stopExecution) { - shouldStopRunnerExecution = true; - } + applyRunnerControlFromResult(result, runnerState); + nextRequestName = runnerState.nextRequestName; + shouldStopRunnerExecution = runnerState.shouldStopRunnerExecution; logResults(testResults, 'Tests'); } catch (error) { diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 7c82fa1d3..aab748130 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -1478,13 +1478,37 @@ const registerNetworkIpc = (mainWindow) => { // Call beforeRequest hooks using consolidated approach when multiple levels have hooks const beforeRequestEventData = { request, req: new BrunoRequest(request), collection, collectionUid }; - await executeAllHooksConsolidated( + const beforeRequestHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, HOOK_EVENTS.HTTP_BEFORE_REQUEST, beforeRequestEventData, hookOptions ); + // Check runner control from hooks + if (beforeRequestHooksResult?.nextRequestName !== undefined) { + nextRequestName = beforeRequestHooksResult.nextRequestName; + } + if (beforeRequestHooksResult?.stopExecution) { + stopRunnerExecution = true; + } + if (beforeRequestHooksResult?.skipRequest) { + mainWindow.webContents.send('main:run-folder-event', { + type: 'runner-request-skipped', + error: 'Request has been skipped from beforeRequest hook', + responseReceived: { + status: 'skipped', + statusText: 'request skipped via beforeRequest hook', + data: null, + responseTime: 0, + headers: null + }, + ...eventData + }); + currentRequestIndex++; + continue; + } + let preRequestScriptResult; let preRequestError = null; try { @@ -1716,13 +1740,21 @@ const registerNetworkIpc = (mainWindow) => { collectionUid }; - await executeAllHooksConsolidated( + const afterResponseHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, HOOK_EVENTS.HTTP_AFTER_RESPONSE, afterResponseEventData, hookOptions ); + // Check runner control from hooks + if (afterResponseHooksResult?.nextRequestName !== undefined) { + nextRequestName = afterResponseHooksResult.nextRequestName; + } + if (afterResponseHooksResult?.stopExecution) { + stopRunnerExecution = true; + } + let postResponseScriptResult; let postResponseError = null; try { diff --git a/packages/bruno-js/src/runtime/hooks-executor.js b/packages/bruno-js/src/runtime/hooks-executor.js index 6a33ad7e9..6eeb5008d 100644 --- a/packages/bruno-js/src/runtime/hooks-executor.js +++ b/packages/bruno-js/src/runtime/hooks-executor.js @@ -157,6 +157,15 @@ const executeConsolidatedHooks = async (extractedHooks, hookEvent, eventData, op if (result?.hookManager) { await result.hookManager.call(hookEvent, eventData); + + // IMPORTANT: Re-capture runner control values AFTER hooks have been called + // The hooks may have called bru.runner.setNextRequest(), bru.runner.skipRequest(), etc. + // These values are stored on the bru instance which is returned in result.__bru + if (result.__bru) { + result.nextRequestName = result.__bru.nextRequest; + result.skipRequest = result.__bru.skipRequest; + result.stopExecution = result.__bru.stopExecution; + } } return result; diff --git a/packages/bruno-js/src/runtime/hooks-runtime.js b/packages/bruno-js/src/runtime/hooks-runtime.js index 3a7ac0f1f..9f95185be 100644 --- a/packages/bruno-js/src/runtime/hooks-runtime.js +++ b/packages/bruno-js/src/runtime/hooks-runtime.js @@ -153,7 +153,11 @@ class HooksRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables) + globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution, + __bru: bru }; } @@ -176,7 +180,11 @@ class HooksRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables) + globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution, + __bru: bru }; } @@ -199,7 +207,11 @@ class HooksRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables) + globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution, + __bru: bru }; } @@ -257,7 +269,11 @@ class HooksRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables) + globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution, + __bru: bru }; } @@ -328,7 +344,11 @@ class HooksRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables) + globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution, + __bru: bru }; } @@ -350,7 +370,12 @@ class HooksRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables) + globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution, + // Include bru reference so callers can read updated values after hook execution + __bru: bru }; } } diff --git a/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-first.bru b/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-first.bru new file mode 100644 index 000000000..d5694eaad --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-first.bru @@ -0,0 +1,45 @@ +meta { + name: legacy-set-next-request-first + type: http + seq: 300 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] Testing legacy bru.setNextRequest() API'); + + // Clear any previous markers + bru.deleteVar('legacy-set-next-skipped-ran'); + + // Mark that this request started + bru.setVar('legacy-set-next-first-ran', 'true'); + + console.log('[beforeRequest] Legacy first request starting'); + }); + + bru.hooks.http.onAfterResponse(({ res }) => { + console.log('[afterResponse] Using legacy bru.setNextRequest() to jump to target'); + + // Use the legacy API to jump to the target request + bru.setNextRequest('legacy-set-next-request-target'); + + console.log('[afterResponse] Called bru.setNextRequest("legacy-set-next-request-target")'); + }); +} + +tests { + test("first request should execute successfully", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("first request marker should be set", function() { + const ran = bru.getVar('legacy-set-next-first-ran'); + expect(ran).to.equal('true'); + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-skipped.bru b/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-skipped.bru new file mode 100644 index 000000000..63b59dec7 --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-skipped.bru @@ -0,0 +1,30 @@ +meta { + name: legacy-set-next-request-skipped + type: http + seq: 301 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] ERROR: This request should have been skipped by legacy setNextRequest!'); + + // If this request runs, set an error marker + bru.setVar('legacy-set-next-skipped-ran', 'true'); + + console.log('[beforeRequest] Setting error marker - legacy setNextRequest jump failed'); + }); +} + +tests { + test("this request should be skipped due to legacy setNextRequest jump", function() { + // If we get here, the request ran when it should have been skipped + const ran = bru.getVar('legacy-set-next-skipped-ran'); + expect(ran).to.be.undefined; + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-target.bru b/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-target.bru new file mode 100644 index 000000000..549da1f90 --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/legacy-set-next-request-target.bru @@ -0,0 +1,43 @@ +meta { + name: legacy-set-next-request-target + type: http + seq: 302 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] Target request reached via legacy bru.setNextRequest() jump'); + + // Mark that target was reached + bru.setVar('legacy-set-next-target-reached', 'true'); + + console.log('[beforeRequest] Legacy target request starting'); + }); +} + +tests { + test("target request should execute successfully", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("target should have been reached via legacy API", function() { + const reached = bru.getVar('legacy-set-next-target-reached'); + expect(reached).to.equal('true'); + }); + + test("first request should have run before jump", function() { + const firstRan = bru.getVar('legacy-set-next-first-ran'); + expect(firstRan).to.equal('true'); + }); + + test("skipped request should NOT have run", function() { + const skippedRan = bru.getVar('legacy-set-next-skipped-ran'); + expect(skippedRan).to.be.undefined; + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/set-next-null-should-not-run.bru b/packages/bruno-tests/hooks-comprehensive-tests/set-next-null-should-not-run.bru new file mode 100644 index 000000000..d8cee5ecb --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/set-next-null-should-not-run.bru @@ -0,0 +1,30 @@ +meta { + name: set-next-null-should-not-run + type: http + seq: 901 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] ERROR: This request should NOT have run after setNextRequest(null)!'); + + // If this request runs, set an error marker + bru.setVar('set-next-null-error', 'Request ran when setNextRequest(null) should have stopped the runner'); + + console.log('[beforeRequest] Setting error marker - setNextRequest(null) failed'); + }); +} + +tests { + test("this request should not run - setNextRequest(null) should have stopped the runner", function() { + // If we get here, the request ran when it should have been stopped + const error = bru.getVar('set-next-null-error'); + expect(error).to.be.undefined; + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/set-next-null-trigger.bru b/packages/bruno-tests/hooks-comprehensive-tests/set-next-null-trigger.bru new file mode 100644 index 000000000..fb333ac1f --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/set-next-null-trigger.bru @@ -0,0 +1,45 @@ +meta { + name: set-next-null-trigger + type: http + seq: 900 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] Testing bru.runner.setNextRequest(null) API'); + + // Clear any previous error markers + bru.deleteVar('set-next-null-error'); + + // Mark that this request started + bru.setVar('set-next-null-trigger-ran', 'true'); + + console.log('[beforeRequest] setNextRequest(null) trigger starting'); + }); + + bru.hooks.http.onAfterResponse(({ res }) => { + console.log('[afterResponse] Setting next request to null to stop runner gracefully'); + + // Setting nextRequest to null should stop the runner gracefully + bru.runner.setNextRequest(null); + + console.log('[afterResponse] Called bru.runner.setNextRequest(null) - runner should stop'); + }); +} + +tests { + test("trigger request should execute successfully", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("trigger marker should be set", function() { + const ran = bru.getVar('set-next-null-trigger-ran'); + expect(ran).to.equal('true'); + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-first.bru b/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-first.bru new file mode 100644 index 000000000..014a63ebc --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-first.bru @@ -0,0 +1,45 @@ +meta { + name: set-next-request-first + type: http + seq: 200 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] Testing bru.runner.setNextRequest() API'); + + // Clear any previous markers + bru.deleteVar('set-next-request-skipped-ran'); + + // Mark that this request started + bru.setVar('set-next-request-first-ran', 'true'); + + console.log('[beforeRequest] First request starting'); + }); + + bru.hooks.http.onAfterResponse(({ res }) => { + console.log('[afterResponse] Setting next request to jump to target'); + + // Jump to the target request, skipping the intermediate request + bru.runner.setNextRequest('set-next-request-target'); + + console.log('[afterResponse] Called bru.runner.setNextRequest("set-next-request-target")'); + }); +} + +tests { + test("first request should execute successfully", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("first request marker should be set", function() { + const ran = bru.getVar('set-next-request-first-ran'); + expect(ran).to.equal('true'); + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-skipped.bru b/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-skipped.bru new file mode 100644 index 000000000..89d8a6379 --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-skipped.bru @@ -0,0 +1,30 @@ +meta { + name: set-next-request-skipped + type: http + seq: 201 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] ERROR: This request should have been skipped by setNextRequest!'); + + // If this request runs, set an error marker + bru.setVar('set-next-request-skipped-ran', 'true'); + + console.log('[beforeRequest] Setting error marker - setNextRequest jump failed'); + }); +} + +tests { + test("this request should be skipped due to setNextRequest jump", function() { + // If we get here, the request ran when it should have been skipped + const ran = bru.getVar('set-next-request-skipped-ran'); + expect(ran).to.be.undefined; + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-target.bru b/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-target.bru new file mode 100644 index 000000000..04451b732 --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/set-next-request-target.bru @@ -0,0 +1,43 @@ +meta { + name: set-next-request-target + type: http + seq: 202 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] Target request reached via setNextRequest jump'); + + // Mark that target was reached + bru.setVar('set-next-request-target-reached', 'true'); + + console.log('[beforeRequest] Target request starting'); + }); +} + +tests { + test("target request should execute successfully", function() { + expect(res.getStatus()).to.equal(200); + }); + + test("target should have been reached", function() { + const reached = bru.getVar('set-next-request-target-reached'); + expect(reached).to.equal('true'); + }); + + test("first request should have run before jump", function() { + const firstRan = bru.getVar('set-next-request-first-ran'); + expect(firstRan).to.equal('true'); + }); + + test("skipped request should NOT have run", function() { + const skippedRan = bru.getVar('set-next-request-skipped-ran'); + expect(skippedRan).to.be.undefined; + }); +} diff --git a/packages/bruno-tests/hooks-comprehensive-tests/skip-request-in-before-hook.bru b/packages/bruno-tests/hooks-comprehensive-tests/skip-request-in-before-hook.bru new file mode 100644 index 000000000..37649c50d --- /dev/null +++ b/packages/bruno-tests/hooks-comprehensive-tests/skip-request-in-before-hook.bru @@ -0,0 +1,32 @@ +meta { + name: skip-request-in-before-hook + type: http + seq: 100 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:hooks { + bru.hooks.http.onBeforeRequest(({ req }) => { + console.log('[beforeRequest] Testing bru.runner.skipRequest() API'); + + // Set a marker to confirm the hook executed + bru.setVar('skip-request-hook-executed', 'true'); + + // Skip this request - the main request should not execute + bru.runner.skipRequest(); + + console.log('[beforeRequest] Called bru.runner.skipRequest() - request should be skipped'); + }); +} + +tests { + test("hook should have executed before skip", function() { + const hookExecuted = bru.getVar('skip-request-hook-executed'); + expect(hookExecuted).to.equal('true'); + }); +} diff --git a/tests/scripting/hooks/hooks.spec.ts b/tests/scripting/hooks/hooks.spec.ts index cfeb7690e..a72cfa7ec 100644 --- a/tests/scripting/hooks/hooks.spec.ts +++ b/tests/scripting/hooks/hooks.spec.ts @@ -10,10 +10,10 @@ test.describe.serial('Hooks feature', () => { await runCollection(page, 'hooks-comprehensive-tests'); await validateRunnerResults(page, { - totalRequests: 45, - passed: 45, + totalRequests: 51, + passed: 50, failed: 0, - skipped: 0 + skipped: 1 }); }); }); @@ -26,10 +26,10 @@ test.describe.serial('Hooks feature', () => { await runCollection(page, 'hooks-comprehensive-tests'); await validateRunnerResults(page, { - totalRequests: 45, - passed: 45, + totalRequests: 51, + passed: 50, failed: 0, - skipped: 0 + skipped: 1 }); }); });