From 86901c1e89ecc9a0752b12a932b5b60761aa24be Mon Sep 17 00:00:00 2001 From: Pooja Date: Thu, 7 Aug 2025 15:50:03 +0530 Subject: [PATCH] fix: test only flag in cli to inclue pre and post test (#5216) --- packages/bruno-cli/src/commands/run.js | 16 +- packages/bruno-cli/src/utils/request.js | 44 +++ packages/bruno-cli/tests/utils/common.spec.js | 309 ++++++++++++++++++ 3 files changed, 365 insertions(+), 4 deletions(-) create mode 100644 packages/bruno-cli/src/utils/request.js create mode 100644 packages/bruno-cli/tests/utils/common.spec.js diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 1ca689fe2..e3b7aa3ea 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -14,6 +14,7 @@ const { getOptions } = require('../utils/bru'); const { parseDotEnv, parseEnvironment } = require('@usebruno/filestore'); const constants = require('../constants'); const { findItemInCollection, createCollectionJsonFromPathname, getCallStack } = require('../utils/collection'); +const { hasExecutableTestInScript } = require('../utils/request'); const command = 'run [paths...]'; const desc = 'Run one or more requests/folders'; @@ -467,10 +468,17 @@ const handler = async function (argv) { requestItems = getCallStack(resolvedPaths, collection, { recursive }); if (testsOnly) { - requestItems = requestItems.filter((iter) => { - const requestHasTests = iter.request?.tests; - const requestHasActiveAsserts = iter.request?.assertions.some((x) => x.enabled) || false; - return requestHasTests || requestHasActiveAsserts; + requestItems = requestItems.filter((item) => { + const requestHasTests = hasExecutableTestInScript(item.request?.tests); + const requestHasActiveAsserts = item.request?.assertions.some((x) => x.enabled) || false; + + const preRequestScript = item.request?.script?.req; + const requestHasPreRequestTests = hasExecutableTestInScript(preRequestScript); + + const postResponseScript = item.request?.script?.res; + const requestHasPostResponseTests = hasExecutableTestInScript(postResponseScript); + + return requestHasTests || requestHasActiveAsserts || requestHasPreRequestTests || requestHasPostResponseTests; }); } diff --git a/packages/bruno-cli/src/utils/request.js b/packages/bruno-cli/src/utils/request.js new file mode 100644 index 000000000..6a75a3cc7 --- /dev/null +++ b/packages/bruno-cli/src/utils/request.js @@ -0,0 +1,44 @@ + +// Check for meaningful test() calls (not commented out or in strings) +const hasExecutableTestInScript = (script) => { + if (!script) return false; + + // Remove single-line comments (// ...) and multi-line comments (/* ... */) + let cleanScript = script + .replace(/\/\/.*$/gm, '') // Remove line comments + .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove block comments + + // Remove string literals to avoid matching test() inside strings + cleanScript = cleanScript + .replace(/"(?:[^"\\]|\\.)*"/g, '""') // Remove double-quoted strings + .replace(/'(?:[^'\\]|\\.)*'/g, "''") // Remove single-quoted strings + .replace(/`(?:[^`\\]|\\.)*`/g, '``'); // Remove template literals + + // Look for standalone test() calls (not object method calls like obj.test()) + // Find all test( occurrences and check they're not preceded by dots + let hasValidTest = false; + let searchFrom = 0; + + while (true) { + const index = cleanScript.indexOf('test', searchFrom); + if (index === -1) break; + + // Check if this looks like test( with optional whitespace + const afterTest = cleanScript.substring(index + 4); + if (/^\s*\(/.test(afterTest)) { + // Found test( - check if it's not preceded by a dot + if (index === 0 || cleanScript[index - 1] !== '.') { + hasValidTest = true; + break; + } + } + + searchFrom = index + 1; + } + + return hasValidTest; +}; + +module.exports = { + hasExecutableTestInScript +}; \ No newline at end of file diff --git a/packages/bruno-cli/tests/utils/common.spec.js b/packages/bruno-cli/tests/utils/common.spec.js new file mode 100644 index 000000000..bd4c1e7e0 --- /dev/null +++ b/packages/bruno-cli/tests/utils/common.spec.js @@ -0,0 +1,309 @@ +const { describe, it, expect } = require('@jest/globals'); +const { hasExecutableTestInScript } = require('../../src/utils/request'); + +describe('hasExecutableTestInScript', () => { + describe('should return true for valid test() calls', () => { + it('should detect basic test calls', () => { + const script = ` + test("should work", function() { + expect(true).to.be.true; + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect indented test calls', () => { + const script = ` + if (true) { + test("indented test", function() { + expect(1).to.equal(1); + }); + } + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls with extra whitespace', () => { + const script = `test ("with spaces", function() { });`; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls after assignments', () => { + const script = ` + const result = test("assignment test", function() { + expect("hello").to.be.a("string"); + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls in conditionals', () => { + const script = ` + if (condition) { + test("conditional test", function() { + expect(true).to.be.true; + }); + } + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls in arrays', () => { + const script = ` + const tests = [ + test("array test", function() { + expect(Array.isArray([])).to.be.true; + }) + ]; + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls in ternary operators', () => { + const script = ` + const result = condition ? test("ternary test", function() { + expect(true).to.be.true; + }) : null; + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls after semicolons', () => { + const script = ` + const data = res.data; test("after semicolon", function() { + expect(data).to.be.an("object"); + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls in object values', () => { + const script = ` + const config = { + validation: test("object value test", function() { + expect(true).to.be.true; + }) + }; + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect multiple test calls', () => { + const script = ` + test("first test", function() { + expect(1).to.equal(1); + }); + + test("second test", function() { + expect(2).to.equal(2); + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should detect test calls at start of script', () => { + const script = `test("at start", function() { expect(true).to.be.true; });`; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + }); + + describe('should return false for invalid test() calls', () => { + it('should ignore commented out test calls with //', () => { + const script = ` + // test("commented test", function() { + // expect(true).to.be.true; + // }); + console.log("no real tests here"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore commented out test calls with /* */', () => { + const script = ` + /* test("block commented test", function() { + expect(true).to.be.true; + }); */ + console.log("no real tests here"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore test() in double-quoted strings', () => { + const script = ` + console.log("This contains test() but should not match"); + console.log("Remember to test() your API"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore test() in single-quoted strings', () => { + const script = ` + console.log('Single quote test() should not match'); + const message = 'Use test() for validation'; + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore test() in template literals', () => { + const script = ` + console.log(\`Template literal test() should not match\`); + const message = \`Remember to test() your code\`; + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore object method calls', () => { + const script = ` + const obj = { test: function() { return "not a real test"; } }; + obj.test("This is a method call"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore this.test() calls', () => { + const script = ` + this.test("Another method call"); + this.test(); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore complex object chain calls', () => { + const script = ` + api.client.test("Should not match"); + user.test.endpoint("Chained method"); + window.test("Should not match"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should ignore object methods in variables', () => { + const script = ` + const validator = { + test: function(value) { return value > 0; } + }; + validator.test(42); + + const tester = { test: () => "mock" }; + tester.test("method call"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should return false for empty scripts', () => { + expect(hasExecutableTestInScript('')).toBe(false); + expect(hasExecutableTestInScript(null)).toBe(false); + expect(hasExecutableTestInScript(undefined)).toBe(false); + }); + + it('should return false for scripts with no test calls', () => { + const script = ` + bru.setVar("userId", "12345"); + console.log("Setting up request"); + const data = res.data; + bru.setVar("responseData", data); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should return false when test is part of other words', () => { + const script = ` + const testing = "value"; + const protest = "demo"; + const fastest = "speed"; + console.log("contest results"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + }); + + describe('should handle mixed scenarios correctly', () => { + it('should return true when valid test exists among invalid ones', () => { + const script = ` + // test("commented out"); + console.log("test() in string"); + obj.test("method call"); + + test("real test", function() { + expect(true).to.be.true; + }); + + api.client.test("another method"); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should return false when only invalid tests exist', () => { + const script = ` + // test("commented out test", function() { + // expect(true).to.be.true; + // }); + + console.log("test() inside string"); + console.log('test() in single quotes'); + console.log(\`test() in template\`); + + const obj = { test: () => "mock" }; + obj.test("method call"); + this.test("another method"); + api.client.test("chained method"); + + bru.setVar("test", "variable name"); + `; + expect(hasExecutableTestInScript(script)).toBe(false); + }); + + it('should handle complex nested quotes correctly', () => { + const script = ` + console.log("String with 'nested quotes' and test() call"); + console.log('String with "nested quotes" and test() call'); + + test("real test with \\"escaped quotes\\"", function() { + expect(true).to.be.true; + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should handle multi-line comments correctly', () => { + const script = ` + /* + * This is a multi-line comment with + * test("commented test", function() { + * expect(true).to.be.true; + * }); + */ + + test("real test", function() { + expect(true).to.be.true; + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + + it('should handle inline comments correctly', () => { + const script = ` + const data = res.data; // test("inline comment") + test("real test", function() { // this is a real test + expect(data).to.be.an("object"); + }); + `; + expect(hasExecutableTestInScript(script)).toBe(true); + }); + }); + + describe('edge cases', () => { + it('should handle test calls immediately after dots (edge case)', () => { + const script = ` + // This should not match because it's after a dot + console.test("should not match"); + + // But this should match because there's a space + console. test("should match due to space"); + `; + // Note: Our current implementation would consider the second one valid + // because there's a space between the dot and test + expect(hasExecutableTestInScript(script)).toBe(true); + }); + }); +}); \ No newline at end of file