From 220da6b58e8af6bfeb88cfebf5152b3fa5e69cc9 Mon Sep 17 00:00:00 2001 From: lohit Date: Tue, 22 Apr 2025 16:35:54 +0530 Subject: [PATCH] feat: Moved logic related to html report generation to bruno-common package (#4536) --------- Co-authored-by: lohit jiddimani --- package-lock.json | 68 +- package.json | 1 + packages/bruno-cli/src/commands/run.js | 59 +- packages/bruno-cli/tests/commands/run.spec.js | 67 -- .../bruno-cli/tests/reporters/html.spec.js | 81 --- packages/bruno-common/package.json | 16 +- packages/bruno-common/rollup.config.js | 72 +- packages/bruno-common/src/runner/index.ts | 4 + .../runner/reports/html/generate-report.ts | 39 ++ .../src/runner/reports/html/template.ts | 654 ++++++++++++++++++ .../bruno-common/src/runner/runner-summary.ts | 68 ++ .../bruno-common/src/runner/types/index.ts | 114 +++ .../bruno-common/src/runner/utils/index.ts | 31 + .../bruno-electron/src/ipc/network/index.js | 4 +- 14 files changed, 1040 insertions(+), 238 deletions(-) delete mode 100644 packages/bruno-cli/tests/commands/run.spec.js delete mode 100644 packages/bruno-cli/tests/reporters/html.spec.js create mode 100644 packages/bruno-common/src/runner/index.ts create mode 100644 packages/bruno-common/src/runner/reports/html/generate-report.ts create mode 100644 packages/bruno-common/src/runner/reports/html/template.ts create mode 100644 packages/bruno-common/src/runner/runner-summary.ts create mode 100644 packages/bruno-common/src/runner/types/index.ts create mode 100644 packages/bruno-common/src/runner/utils/index.ts diff --git a/package-lock.json b/package-lock.json index f0c612916..ff174d7e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1475,7 +1474,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -1506,7 +1504,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1524,7 +1521,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/@babel/generator": { @@ -1804,7 +1800,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.9", @@ -7789,7 +7784,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -7812,7 +7806,6 @@ "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "*", @@ -7823,7 +7816,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -11068,7 +11060,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -12679,7 +12670,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -13647,7 +13637,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -24207,7 +24196,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -26304,18 +26293,19 @@ "name": "@usebruno/common", "version": "0.1.0", "license": "MIT", - "dependencies": { - "@faker-js/faker": "^9.7.0" - }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-typescript": "^7.27.0", + "@faker-js/faker": "^9.7.0", "@jest/globals": "^29.7.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^12.1.2", + "@types/jest": "^29.5.14", "babel-jest": "^29.7.0", + "cheerio": "^1.0.0", "moment": "^2.29.4", + "playwright": "^1.52.0", "rollup": "3.29.5", "rollup-plugin-dts": "^5.0.0", "rollup-plugin-peer-deps-external": "^2.2.4", @@ -26708,6 +26698,7 @@ "version": "9.7.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz", "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -26824,6 +26815,21 @@ } } }, + "packages/bruno-common/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "packages/bruno-common/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -26831,6 +26837,38 @@ "dev": true, "license": "MIT" }, + "packages/bruno-common/node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "packages/bruno-common/node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "packages/bruno-common/node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", diff --git a/package.json b/package.json index 074e7aa9b..b8690d75c 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "build:electron:deb": "./scripts/build-electron.sh deb", "build:electron:rpm": "./scripts/build-electron.sh rpm", "build:electron:snap": "./scripts/build-electron.sh snap", + "watch:common": "npm run watch --workspace=packages/bruno-common", "test:codegen": "npm run dev:web & node ./scripts/playwright-codegen.js", "test:e2e": "npx playwright test", "test:report": "npx playwright show-report", diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index ba1e671dc..57e334e86 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -2,6 +2,7 @@ const fs = require('fs'); const chalk = require('chalk'); const path = require('path'); const { forOwn, cloneDeep } = require('lodash'); +const { getRunnerSummary } = require('@usebruno/common/runner'); const { exists, isFile, isDirectory } = require('../utils/filesystem'); const { runSingleRequest } = require('../runner/run-single-request'); const { bruToEnvJson, getEnvVars } = require('../utils/bru'); @@ -16,50 +17,18 @@ const command = 'run [filename]'; const desc = 'Run a request'; const printRunSummary = (results) => { - let totalRequests = 0; - let passedRequests = 0; - let failedRequests = 0; - let skippedRequests = 0; - let totalAssertions = 0; - let passedAssertions = 0; - let failedAssertions = 0; - let totalTests = 0; - let passedTests = 0; - let failedTests = 0; - - for (const result of results) { - totalRequests += 1; - totalTests += result.testResults.length; - totalAssertions += result.assertionResults.length; - let anyFailed = false; - let hasAnyTestsOrAssertions = false; - for (const testResult of result.testResults) { - hasAnyTestsOrAssertions = true; - if (testResult.status === 'pass') { - passedTests += 1; - } else { - anyFailed = true; - failedTests += 1; - } - } - for (const assertionResult of result.assertionResults) { - hasAnyTestsOrAssertions = true; - if (assertionResult.status === 'pass') { - passedAssertions += 1; - } else { - anyFailed = true; - failedAssertions += 1; - } - } - if (!hasAnyTestsOrAssertions && result.skipped) { - skippedRequests += 1; - } - else if (!hasAnyTestsOrAssertions && result.error) { - failedRequests += 1; - } else { - passedRequests += 1; - } - } + const { + totalRequests, + passedRequests, + failedRequests, + skippedRequests, + totalAssertions, + passedAssertions, + failedAssertions, + totalTests, + passedTests, + failedTests + } = getRunnerSummary(results); const maxLength = 12; @@ -99,7 +68,7 @@ const printRunSummary = (results) => { totalTests, passedTests, failedTests - }; + } }; const createCollectionFromPath = (collectionPath) => { diff --git a/packages/bruno-cli/tests/commands/run.spec.js b/packages/bruno-cli/tests/commands/run.spec.js deleted file mode 100644 index 10cdf42b4..000000000 --- a/packages/bruno-cli/tests/commands/run.spec.js +++ /dev/null @@ -1,67 +0,0 @@ -const { describe, it, expect } = require('@jest/globals'); - -const { printRunSummary } = require('../../src/commands/run'); - -describe('printRunSummary', () => { - // Suppress console.log output - jest.spyOn(console, 'log').mockImplementation(() => {}); - - it('should produce the correct summary for a successful run', () => { - const results = [ - { - testResults: [{ status: 'pass' }, { status: 'pass' }, { status: 'pass' }], - assertionResults: [{ status: 'pass' }, { status: 'pass' }], - error: null - }, - { - testResults: [{ status: 'pass' }, { status: 'pass' }], - assertionResults: [{ status: 'pass' }, { status: 'pass' }, { status: 'pass' }], - error: null - } - ]; - - const summary = printRunSummary(results); - - expect(summary.totalRequests).toBe(2); - expect(summary.passedRequests).toBe(2); - expect(summary.failedRequests).toBe(0); - expect(summary.totalAssertions).toBe(5); - expect(summary.passedAssertions).toBe(5); - expect(summary.failedAssertions).toBe(0); - expect(summary.totalTests).toBe(5); - expect(summary.passedTests).toBe(5); - expect(summary.failedTests).toBe(0); - }); - - it('should produce the correct summary for a failed run', () => { - const results = [ - { - testResults: [{ status: 'fail' }, { status: 'pass' }, { status: 'pass' }], - assertionResults: [{ status: 'pass' }, { status: 'fail' }], - error: null - }, - { - testResults: [{ status: 'pass' }, { status: 'fail' }], - assertionResults: [{ status: 'pass' }, { status: 'fail' }, { status: 'fail' }], - error: null - }, - { - testResults: [], - assertionResults: [], - error: new Error('Request failed') - } - ]; - - const summary = printRunSummary(results); - - expect(summary.totalRequests).toBe(3); - expect(summary.passedRequests).toBe(2); - expect(summary.failedRequests).toBe(1); - expect(summary.totalAssertions).toBe(5); - expect(summary.passedAssertions).toBe(2); - expect(summary.failedAssertions).toBe(3); - expect(summary.totalTests).toBe(5); - expect(summary.passedTests).toBe(3); - expect(summary.failedTests).toBe(2); - }); -}); diff --git a/packages/bruno-cli/tests/reporters/html.spec.js b/packages/bruno-cli/tests/reporters/html.spec.js deleted file mode 100644 index b45e57f41..000000000 --- a/packages/bruno-cli/tests/reporters/html.spec.js +++ /dev/null @@ -1,81 +0,0 @@ -const { describe, it, expect } = require('@jest/globals'); -const fs = require('fs'); - -const makeHtmlOutput = require('../../src/reporters/html'); - -describe('makeHtmlOutput', () => { - beforeEach(() => { - jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should produce an html report', () => { - const outputJson = { - summary: { - totalRequests: 1, - passedRequests: 1, - failedRequests: 1, - totalAssertions: 1, - passedAssertions: 1, - failedAssertions: 1, - totalTests: 1, - passedTests: 1, - failedTests: 1 - }, - results: [ - { - description: 'description provided', - suitename: 'Tests/Suite A', - request: { - method: 'GET', - url: 'https://ima.test' - }, - assertionResults: [ - { - lhsExpr: 'res.status', - rhsExpr: 'eq 200', - status: 'pass' - }, - { - lhsExpr: 'res.status', - rhsExpr: 'neq 200', - status: 'fail', - error: 'expected 200 to not equal 200' - } - ], - runtime: 1.2345678 - }, - { - request: { - method: 'GET', - url: 'https://imanother.test' - }, - suitename: 'Tests/Suite B', - testResults: [ - { - lhsExpr: 'res.status', - rhsExpr: 'eq 200', - description: 'A test that passes', - status: 'pass' - }, - { - description: 'A test that fails', - status: 'fail', - error: 'expected 200 to not equal 200', - status: 'fail' - } - ], - runtime: 2.3456789 - } - ] - }; - - makeHtmlOutput(outputJson, '/tmp/testfile.html'); - - const htmlReport = fs.writeFileSync.mock.calls[0][1]; - expect(htmlReport).toContain(JSON.stringify(outputJson, null, 2)); - }); -}); diff --git a/packages/bruno-common/package.json b/packages/bruno-common/package.json index 2664d1a84..a29f586ec 100644 --- a/packages/bruno-common/package.json +++ b/packages/bruno-common/package.json @@ -7,8 +7,14 @@ "types": "dist/index.d.ts", "exports": { ".": { + "require": "./dist/cjs/index.js", "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" + "types": "./dist/index.d.ts" + }, + "./runner": { + "require": "./dist/runner/cjs/index.js", + "import": "./dist/runner/esm/index.js", + "types": "./dist/runner/index.d.ts" } }, "files": [ @@ -21,19 +27,19 @@ "test": "jest", "test:watch": "jest --watch", "prebuild": "npm run clean", - "build": "rollup -c", + "build": "rollup -c rollup.config.js", + "watch": "rollup -c -w", "prepack": "npm run test && npm run build" }, - "dependencies": { - "@faker-js/faker": "^9.7.0" - }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-typescript": "^7.27.0", + "@faker-js/faker": "^9.7.0", "@jest/globals": "^29.7.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^12.1.2", + "@types/jest": "^29.5.14", "babel-jest": "^29.7.0", "moment": "^2.29.4", "rollup": "3.29.5", diff --git a/packages/bruno-common/rollup.config.js b/packages/bruno-common/rollup.config.js index 51fc15a4e..8eec5127f 100644 --- a/packages/bruno-common/rollup.config.js +++ b/packages/bruno-common/rollup.config.js @@ -7,29 +7,53 @@ const peerDepsExternal = require('rollup-plugin-peer-deps-external'); const packageJson = require('./package.json'); -module.exports = [ - { - input: 'src/index.ts', - output: [ - { - file: packageJson.main, - format: 'cjs', - sourcemap: true - }, - { - file: packageJson.module, - format: 'esm', - sourcemap: true +function createBuildConfig({ inputDir, input, cjsOutput, esmOutput }) { + return [ + { + input, + output: [ + { + file: cjsOutput, + format: 'cjs', + sourcemap: true + }, + { + file: esmOutput, + format: 'esm', + sourcemap: true + } + ], + plugins: [ + peerDepsExternal(), + nodeResolve(), + commonjs(), + typescript({ + tsconfig: './tsconfig.json', + include: [inputDir] + }), + terser() + ], + treeshake: { + moduleSideEffects: false } - ], - plugins: [ - peerDepsExternal(), - nodeResolve({ - extensions: ['.css'] - }), - commonjs(), - typescript({ tsconfig: './tsconfig.json' }), - terser() - ] - } + } + ]; +} + +// todo: configure declarations +module.exports = [ + // Main package build + ...createBuildConfig({ + inputDir: 'src/**/*', + input: 'src/index.ts', + cjsOutput: packageJson.main, + esmOutput: packageJson.module + }), + // reports/html + ...createBuildConfig({ + inputDir: 'src/runner/**/*', + input: 'src/runner/index.ts', + cjsOutput: 'dist/runner/cjs/index.js', + esmOutput: 'dist/runner/esm/index.js' + }) ]; diff --git a/packages/bruno-common/src/runner/index.ts b/packages/bruno-common/src/runner/index.ts new file mode 100644 index 000000000..3e3c842b0 --- /dev/null +++ b/packages/bruno-common/src/runner/index.ts @@ -0,0 +1,4 @@ +import { generateHtmlReport } from "./reports/html/generate-report"; +import { getRunnerSummary } from "./runner-summary"; + +export { generateHtmlReport, getRunnerSummary }; \ No newline at end of file diff --git a/packages/bruno-common/src/runner/reports/html/generate-report.ts b/packages/bruno-common/src/runner/reports/html/generate-report.ts new file mode 100644 index 000000000..378a75197 --- /dev/null +++ b/packages/bruno-common/src/runner/reports/html/generate-report.ts @@ -0,0 +1,39 @@ +import { T_RunnerResults } from "../../types"; +import { isHtmlContentType, getContentType, redactImageData, encodeBase64 } from "../../utils"; +import { getRunnerSummary } from "../../runner-summary"; +import htmlTemplateString from "./template"; + +const generateHtmlReport = ({ + runnerResults +}: { + runnerResults: T_RunnerResults[] +}): string => { + const resultsWithSummaryAndCleanData = runnerResults.map(({ iterationIndex, results }) => { + return { + iterationIndex, + results: results.map((result) => { + const { request, response } = result || {}; + const requestContentType = request?.headers ? getContentType(request?.headers) : ''; + const responseContentType = response?.headers ? getContentType(response?.headers) : ''; + return { + ...result, + request: { + ...result.request, + data: request?.data ? redactImageData(request?.data, requestContentType) : request?.data, + isHtml: isHtmlContentType(requestContentType) + }, + response: { + ...result.response, + data: response?.data ? redactImageData(response?.data, responseContentType) : response?.data, + isHtml: isHtmlContentType(responseContentType) + } + } + }), + summary: getRunnerSummary(results) + } + }); + const htmlString = htmlTemplateString(encodeBase64(JSON.stringify(resultsWithSummaryAndCleanData))); + return htmlString; +}; + +export { generateHtmlReport } \ No newline at end of file diff --git a/packages/bruno-common/src/runner/reports/html/template.ts b/packages/bruno-common/src/runner/reports/html/template.ts new file mode 100644 index 000000000..d19e2077f --- /dev/null +++ b/packages/bruno-common/src/runner/reports/html/template.ts @@ -0,0 +1,654 @@ +export const htmlTemplateString = (resutsJsonString: string) =>` + + + + + + + + Bruno + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +`; + +export default htmlTemplateString; diff --git a/packages/bruno-common/src/runner/runner-summary.ts b/packages/bruno-common/src/runner/runner-summary.ts new file mode 100644 index 000000000..5862729bb --- /dev/null +++ b/packages/bruno-common/src/runner/runner-summary.ts @@ -0,0 +1,68 @@ +import { T_RunnerRequestExecutionResult, T_RunSummary } from "./types"; + +// todo: this is generic, not specific to html, can be moved out of the report/html sub-package +export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_RunSummary => { + let totalRequests = 0; + let passedRequests = 0; + let failedRequests = 0; + let errorRequests = 0; + let skippedRequests = 0; + let totalAssertions = 0; + let passedAssertions = 0; + let failedAssertions = 0; + let totalTests = 0; + let passedTests = 0; + let failedTests = 0; + + for (const result of results || []) { + const { status, testResults, assertionResults } = result; + totalRequests += 1; + totalTests += Number(testResults?.length) || 0; + totalAssertions += Number(assertionResults?.length) || 0; + + if (status === 'skipped') { + skippedRequests += 1; + continue; + } + + let anyFailed = false; + for (const testResult of testResults || []) { + if (testResult.status === "pass") { + passedTests += 1; + } else { + anyFailed = true; + failedTests += 1; + } + } + for (const assertionResult of assertionResults || []) { + if (assertionResult.status === "pass") { + passedAssertions += 1; + } else { + anyFailed = true; + failedAssertions += 1; + } + } + + if (!anyFailed && status !== "error") { + passedRequests += 1; + } else if (anyFailed) { + failedRequests += 1; + } else { + errorRequests += 1; + } + } + + return { + totalRequests, + passedRequests, + failedRequests, + errorRequests, + skippedRequests, + totalAssertions, + passedAssertions, + failedAssertions, + totalTests, + passedTests, + failedTests, + }; +}; \ No newline at end of file diff --git a/packages/bruno-common/src/runner/types/index.ts b/packages/bruno-common/src/runner/types/index.ts new file mode 100644 index 000000000..d9c36e32c --- /dev/null +++ b/packages/bruno-common/src/runner/types/index.ts @@ -0,0 +1,114 @@ +// assertion results types +type T_AssertionPassResult = { + lhsExpr: string; + rhsExpr: string; + rhsOperand: string; + operator: string; + status: string; +} + +type T_AssertionFailResult = { + lhsExpr: string; + rhsExpr: string; + rhsOperand: string; + operator: string; + status: string; + error: string; +} + +type T_AssertionResult = T_AssertionPassResult | T_AssertionFailResult; + +// test results types +type T_TestPassResult = { + status: string; + description: string; + uid?: string; +}; + +type T_TestFailResult = { + status: string; + description: string; + error: string; + uid?: string; +}; + +type T_TestResult = T_TestPassResult | T_TestFailResult; + +type T_EmptyRequest = { + method?: null | undefined; + url?: null | undefined; + headers?: null | undefined; + data?: null | undefined; + isHtml?: boolean | undefined; +} + +// request types +type T_Request = { + method: string; + url: string; + headers: Record; + data: string | object | null | boolean | number; + isHtml?: boolean; +}; + +type T_EmptyResponse = { + status?: null | undefined; + statusText?: null | undefined; + headers?: null | undefined; + data?: null | undefined; + responseTime?: number | undefined; + isHtml?: boolean | undefined; +} + +type T_SkippedResponse = { + status?: string | null | undefined; + statusText?: string | null | undefined; + headers?: null | undefined; + data?: null | undefined; + responseTime?: number | undefined; + isHtml?: boolean | undefined; +} + +// response types +type T_Response = { + status: number | string; + statusText: string; + headers: Record; + data: string | object | null | boolean | number; + isHtml?: boolean; +}; + +// result type +export type T_RunnerRequestExecutionResult = { + iterationIndex: number; + name: string; + path: string; + request: T_EmptyRequest | T_Request; + response: T_EmptyResponse | T_Response | T_SkippedResponse; + status: null | undefined | string; + error: null | undefined | string; + assertionResults?: T_AssertionResult[]; + testResults?: T_TestResult[]; + runDuration: number; +} + +export type T_RunnerResults = { + iterationIndex: number; + iterationData?: any; // todo - csv/json row data + results: T_RunnerRequestExecutionResult[]; +} + +// run summary type +export type T_RunSummary = { + totalRequests: number; + passedRequests: number; + failedRequests: number; + errorRequests: number; + skippedRequests: number; + totalAssertions: number; + passedAssertions: number; + failedAssertions: number; + totalTests: number; + passedTests: number; + failedTests: number; +} \ No newline at end of file diff --git a/packages/bruno-common/src/runner/utils/index.ts b/packages/bruno-common/src/runner/utils/index.ts new file mode 100644 index 000000000..76f1f774c --- /dev/null +++ b/packages/bruno-common/src/runner/utils/index.ts @@ -0,0 +1,31 @@ +export const encodeBase64 = (str: string) => { + const bytes = new TextEncoder().encode(str); + const binary = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), ''); + return btoa(binary); +} + +export const decodeBase64 = (base64: string) => { + const binary = atob(base64); + const bytes = Uint8Array.from(binary, c => c.charCodeAt(0)); + return new TextDecoder().decode(bytes); +} + +export const getContentType = (headers: Record): string => { + if (!headers || typeof headers !== 'object') { + return ''; + } + const contentType = Object.entries(headers) + .find(([key]) => key.toLowerCase() === 'content-type')?.[1]; + return typeof contentType === 'string' ? contentType : ''; +}; + +export const isHtmlContentType = (contentType: string) => { + return contentType?.includes("html"); +}; + +export const redactImageData = (data: string | object | number | boolean, contentType: string) => { + if (contentType?.includes("image")) { + return "Response content redacted (image data)"; + } + return data; +} \ 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 b59285f23..8fddd2d98 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -1003,7 +1003,9 @@ const registerNetworkIpc = (mainWindow) => { responseReceived: { status: 'skipped', statusText: 'request skipped via pre-request script', - data: null + data: null, + responseTime: 0, + headers: null }, ...eventData });