diff --git a/packages/bruno-converters/src/utils/postman-status-assertions.js b/packages/bruno-converters/src/utils/postman-status-assertions.js new file mode 100644 index 000000000..16212ca35 --- /dev/null +++ b/packages/bruno-converters/src/utils/postman-status-assertions.js @@ -0,0 +1,51 @@ +const j = require('jscodeshift'); + +/** + * Generates data-driven status assertion entries for pm.response.to.be.* + * Each assertion gets positive, to.not.be, and to.be.not variants. + */ +export const buildStatusAssertionEntries = () => { + const buildStatusTransform = (chain, litArgs) => (path) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier(chain) + ), + litArgs.map((v) => j.literal(v)) + ); + }; + + // Only replaces the first 'to.' — safe because all chains start with 'to.' and contain no other 'to.' + const negateChain = (chain) => chain.replace('to.', 'to.not.'); + + const statusAssertions = [ + // Range-based assertions + { name: 'ok', chain: 'to.be.within', args: [200, 299] }, + { name: 'success', chain: 'to.be.within', args: [200, 299] }, + { name: 'info', chain: 'to.be.within', args: [100, 199] }, + { name: 'redirection', chain: 'to.be.within', args: [300, 399] }, + { name: 'clientError', chain: 'to.be.within', args: [400, 499] }, + { name: 'serverError', chain: 'to.be.within', args: [500, 599] }, + { name: 'error', chain: 'to.be.at.least', args: [400] }, + // Specific status code assertions + { name: 'accepted', chain: 'to.equal', args: [202] }, + { name: 'badRequest', chain: 'to.equal', args: [400] }, + { name: 'unauthorized', chain: 'to.equal', args: [401] }, + { name: 'forbidden', chain: 'to.equal', args: [403] }, + { name: 'notFound', chain: 'to.equal', args: [404] }, + { name: 'rateLimited', chain: 'to.equal', args: [429] } + ]; + + const entries = []; + + // Generate positive + negated entries for each status assertion + statusAssertions.forEach(({ name, chain, args }) => { + entries.push( + { pattern: `pm.response.to.be.${name}`, transform: buildStatusTransform(chain, args) }, + { pattern: `pm.response.to.not.be.${name}`, transform: buildStatusTransform(negateChain(chain), args) }, + { pattern: `pm.response.to.be.not.${name}`, transform: buildStatusTransform(negateChain(chain), args) } + ); + }); + + return entries; +}; diff --git a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js index e99782997..065ed20d3 100644 --- a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js +++ b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js @@ -2,6 +2,7 @@ import sendRequestTransformer from './send-request-transformer'; import { getMemberExpressionString } from './ast-utils'; const j = require('jscodeshift'); const cloneDeep = require('lodash/cloneDeep'); +import { buildStatusAssertionEntries } from './postman-status-assertions'; // Simple 1:1 translations for straightforward replacements // TODO: Restore the commented-out translations once the UI update fixes are live. @@ -211,87 +212,60 @@ const complexTransformations = [ return j.callExpression(j.identifier('res.getHeader'), path.parent.value.arguments); } }, - // Handle pm.response.to.have.status - { - pattern: 'pm.response.to.have.status', + // pm.response.to[.not].have.status -> expect(res.getStatus()).to[.not].equal(arg) + ...['to.have.status', 'to.not.have.status', 'to.have.not.status'].map((pattern) => ({ + pattern: `pm.response.${pattern}`, transform: (path, j) => { - const callExpr = path.parent.value; - - const args = callExpr.arguments; - - // Create: expect(res.getStatus()).to.equal(arg) + const negated = pattern.includes('.not.'); return j.callExpression( j.memberExpression( - j.callExpression( - j.identifier('expect'), - [ - j.callExpression( - j.identifier('res.getStatus'), - [] - ) - ] - ), - j.identifier('to.equal') + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), + j.identifier(negated ? 'to.not.equal' : 'to.equal') ), - args + path.parent.value.arguments ); } - }, + })), - // handle 'pm.response.to.have.header' to expect(res.getHeaders()).to.have.property(args) - { - pattern: 'pm.response.to.have.header', + // pm.response.to[.not].have.header -> expect(res.getHeaders()).to[.not].have.property(args) + // Header names are lowercased because axios normalizes response headers to lowercase + ...['to.have.header', 'to.not.have.header', 'to.have.not.header'].map((pattern) => ({ + pattern: `pm.response.${pattern}`, transform: (path, j) => { - const callExpr = path.parent.value; - - const args = callExpr.arguments; + const args = path.parent.value.arguments; + const negated = pattern.includes('.not.'); if (args.length > 0) { - // Apply toLowerCase() to the first argument args[0] = j.callExpression( - j.memberExpression( - args[0], - j.identifier('toLowerCase') - ), + j.memberExpression(args[0], j.identifier('toLowerCase')), [] ); } - // Create: expect(res.getHeaders()).to.have.property(args) return j.callExpression( j.memberExpression( - j.callExpression( - j.identifier('expect'), - [ - j.callExpression( - j.identifier('res.getHeaders'), - [] - ) - ] - ), - j.identifier('to.have.property') + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getHeaders'), [])]), + j.identifier(negated ? 'to.not.have.property' : 'to.have.property') ), args ); } - }, - // handle pm.response.to.have.body to expect(res.getBody()).to.equal(arg) - { - pattern: 'pm.response.to.have.body', + })), + + // pm.response.to[.not].have.body -> expect(res.getBody()).to[.not].equal(arg) + ...['to.have.body', 'to.not.have.body', 'to.have.not.body'].map((pattern) => ({ + pattern: `pm.response.${pattern}`, transform: (path, j) => { - const callExpr = path.parent.value; - - const args = callExpr.arguments; - + const negated = pattern.includes('.not.'); return j.callExpression( j.memberExpression( - j.callExpression(j.identifier('expect'), [j.identifier('res.getBody()')]), - j.identifier('to.equal') + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier(negated ? 'to.not.equal' : 'to.equal') ), - args + path.parent.value.arguments ); } - }, + })), // Handle pm.execution.setNextRequest(null) { @@ -439,90 +413,6 @@ const complexTransformations = [ } }, - // pm.response.to.be.ok -> expect(res.getStatus()).to.be.within(200, 299) - { - pattern: 'pm.response.to.be.ok', - transform: (path, j) => { - return j.callExpression( - j.memberExpression( - j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), - j.identifier('to.be.within') - ), - [j.literal(200), j.literal(299)] - ); - } - }, - - // pm.response.to.be.success -> expect(res.getStatus()).to.be.within(200, 299) - { - pattern: 'pm.response.to.be.success', - transform: (path, j) => { - return j.callExpression( - j.memberExpression( - j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), - j.identifier('to.be.within') - ), - [j.literal(200), j.literal(299)] - ); - } - }, - - // pm.response.to.be.redirection -> expect(res.getStatus()).to.be.within(300, 399) - { - pattern: 'pm.response.to.be.redirection', - transform: (path, j) => { - return j.callExpression( - j.memberExpression( - j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), - j.identifier('to.be.within') - ), - [j.literal(300), j.literal(399)] - ); - } - }, - - // pm.response.to.be.clientError -> expect(res.getStatus()).to.be.within(400, 499) - { - pattern: 'pm.response.to.be.clientError', - transform: (path, j) => { - return j.callExpression( - j.memberExpression( - j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), - j.identifier('to.be.within') - ), - [j.literal(400), j.literal(499)] - ); - } - }, - - // pm.response.to.be.serverError -> expect(res.getStatus()).to.be.within(500, 599) - { - pattern: 'pm.response.to.be.serverError', - transform: (path, j) => { - return j.callExpression( - j.memberExpression( - j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), - j.identifier('to.be.within') - ), - [j.literal(500), j.literal(599)] - ); - } - }, - - // pm.response.to.be.error -> expect(res.getStatus()).to.be.at.least(400) - { - pattern: 'pm.response.to.be.error', - transform: (path, j) => { - return j.callExpression( - j.memberExpression( - j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getStatus'), [])]), - j.identifier('to.be.at.least') - ), - [j.literal(400)] - ); - } - }, - // pm.response.to.have.jsonBody(...) -> expect(res.getBody()).to.have.jsonBody(...) { pattern: 'pm.response.to.have.jsonBody', @@ -655,7 +545,49 @@ const complexTransformations = [ const args = callExpr.arguments; return j.callExpression(j.identifier('res.getHeader'), args); } - } + }, + + // pm.response.to.be.withBody -> expect(res.getBody()).to.not.equal(undefined) + // Uses undefined check instead of truthiness (.to.be.ok) so falsy bodies (false, 0, null) pass correctly + { + pattern: 'pm.response.to.be.withBody', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier('to.not.equal') + ), + [j.identifier('undefined')] + ); + } + }, + { + pattern: 'pm.response.to.not.be.withBody', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier('to.equal') + ), + [j.identifier('undefined')] + ); + } + }, + { + pattern: 'pm.response.to.be.not.withBody', + transform: (path, j) => { + return j.callExpression( + j.memberExpression( + j.callExpression(j.identifier('expect'), [j.callExpression(j.identifier('res.getBody'), [])]), + j.identifier('to.equal') + ), + [j.identifier('undefined')] + ); + } + }, + + // --- Data-driven status assertions (pm.response.to.be.*) --- + ...buildStatusAssertionEntries() ]; // Create a map for complex transformations to enable O(1) lookups diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js index 176b9632e..f57000d3d 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/response.test.js @@ -906,4 +906,299 @@ describe('Response Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe('const json = res.headerList.toJSON();'); }); + + // --- New status assertions --- + + it('should translate pm.response.to.be.info', () => { + const code = 'pm.response.to.be.info;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(100, 199)'); + }); + + it('should translate pm.response.to.be.accepted', () => { + const code = 'pm.response.to.be.accepted;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(202)'); + }); + + it('should translate pm.response.to.be.badRequest', () => { + const code = 'pm.response.to.be.badRequest;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(400)'); + }); + + it('should translate pm.response.to.be.unauthorized', () => { + const code = 'pm.response.to.be.unauthorized;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(401)'); + }); + + it('should translate pm.response.to.be.forbidden', () => { + const code = 'pm.response.to.be.forbidden;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(403)'); + }); + + it('should translate pm.response.to.be.notFound', () => { + const code = 'pm.response.to.be.notFound;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(404)'); + }); + + it('should translate pm.response.to.be.rateLimited', () => { + const code = 'pm.response.to.be.rateLimited;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(429)'); + }); + + it('should translate pm.response.to.be.withBody', () => { + const code = 'pm.response.to.be.withBody;'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getBody()).to.not.equal(undefined);'); + }); + + it('should translate withBody using undefined check (not truthiness) so falsy bodies work', () => { + const code = 'pm.response.to.be.withBody;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('to.not.equal(undefined)'); + }); + + it('should handle new status assertions inside test blocks', () => { + const code = ` + pm.test("Status checks", function() { + pm.response.to.be.info; + pm.response.to.be.accepted; + pm.response.to.be.badRequest; + pm.response.to.be.unauthorized; + pm.response.to.be.forbidden; + pm.response.to.be.notFound; + pm.response.to.be.rateLimited; + }); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('test("Status checks", function() {'); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(100, 199)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(202)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(400)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(401)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(403)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(404)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.equal(429)'); + }); + + // --- .not negation for to.be.* assertions --- + + it('should translate pm.response.to.not.be.ok', () => { + const code = 'pm.response.to.not.be.ok;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(200, 299)'); + }); + + it('should translate pm.response.to.be.not.ok (alternate position)', () => { + const code = 'pm.response.to.be.not.ok;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(200, 299)'); + }); + + it('should translate pm.response.to.be.not.forbidden (alternate position)', () => { + const code = 'pm.response.to.be.not.forbidden;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(403)'); + }); + + it('should translate pm.response.to.be.not.serverError (alternate position)', () => { + const code = 'pm.response.to.be.not.serverError;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(500, 599)'); + }); + + it('should translate pm.response.to.be.not.withBody (alternate position)', () => { + const code = 'pm.response.to.be.not.withBody;'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getBody()).to.equal(undefined);'); + }); + + it('should translate pm.response.to.not.be.success', () => { + const code = 'pm.response.to.not.be.success;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(200, 299)'); + }); + + it('should translate pm.response.to.not.be.serverError', () => { + const code = 'pm.response.to.not.be.serverError;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(500, 599)'); + }); + + it('should translate pm.response.to.not.be.clientError', () => { + const code = 'pm.response.to.not.be.clientError;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(400, 499)'); + }); + + it('should translate pm.response.to.not.be.redirection', () => { + const code = 'pm.response.to.not.be.redirection;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(300, 399)'); + }); + + it('should translate pm.response.to.not.be.error', () => { + const code = 'pm.response.to.not.be.error;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.at.least(400)'); + }); + + it('should translate pm.response.to.not.be.info', () => { + const code = 'pm.response.to.not.be.info;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(100, 199)'); + }); + + it('should translate pm.response.to.not.be.accepted', () => { + const code = 'pm.response.to.not.be.accepted;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(202)'); + }); + + it('should translate pm.response.to.not.be.badRequest', () => { + const code = 'pm.response.to.not.be.badRequest;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(400)'); + }); + + it('should translate pm.response.to.not.be.unauthorized', () => { + const code = 'pm.response.to.not.be.unauthorized;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(401)'); + }); + + it('should translate pm.response.to.not.be.forbidden', () => { + const code = 'pm.response.to.not.be.forbidden;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(403)'); + }); + + it('should translate pm.response.to.not.be.notFound', () => { + const code = 'pm.response.to.not.be.notFound;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(404)'); + }); + + it('should translate pm.response.to.not.be.rateLimited', () => { + const code = 'pm.response.to.not.be.rateLimited;'; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(429)'); + }); + + it('should translate pm.response.to.not.be.withBody', () => { + const code = 'pm.response.to.not.be.withBody;'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getBody()).to.equal(undefined);'); + }); + + it('should handle negated assertions inside test blocks', () => { + const code = ` + pm.test("Response is not a server error", function() { + pm.response.to.not.be.serverError; + pm.response.to.not.be.clientError; + }); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('test("Response is not a server error", function() {'); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(500, 599)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(400, 499)'); + }); + + it('should handle mixed positive and negated assertions', () => { + const code = ` + pm.test("Mixed assertions", function() { + pm.response.to.be.success; + pm.response.to.not.be.serverError; + }); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.be.within(200, 299)'); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(500, 599)'); + }); + + it('should handle negated assertions with aliases', () => { + const code = ` + const resp = pm.response; + resp.to.not.be.serverError; + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.be.within(500, 599)'); + }); + + // --- .not negation for to.have.* assertions --- + + it('should translate pm.response.to.not.have.status', () => { + const code = 'pm.response.to.not.have.status(404);'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getStatus()).to.not.equal(404);'); + }); + + it('should translate pm.response.to.not.have.header', () => { + const code = 'pm.response.to.not.have.header("X-Error");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getHeaders()).to.not.have.property("X-Error".toLowerCase());'); + }); + + it('should translate pm.response.to.not.have.header with value', () => { + const code = 'pm.response.to.not.have.header("Content-Type", "text/plain");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getHeaders()).to.not.have.property("Content-Type".toLowerCase(), "text/plain");'); + }); + + it('should translate pm.response.to.not.have.body', () => { + const code = 'pm.response.to.not.have.body("error");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getBody()).to.not.equal("error");'); + }); + + // --- to.have.not.* (alternate .not position) --- + + it('should translate pm.response.to.have.not.status (alternate position)', () => { + const code = 'pm.response.to.have.not.status(404);'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getStatus()).to.not.equal(404);'); + }); + + it('should translate pm.response.to.have.not.header (alternate position)', () => { + const code = 'pm.response.to.have.not.header("X-Error");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getHeaders()).to.not.have.property("X-Error".toLowerCase());'); + }); + + it('should translate pm.response.to.have.not.body (alternate position)', () => { + const code = 'pm.response.to.have.not.body("error");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('expect(res.getBody()).to.not.equal("error");'); + }); + + it('should handle negated to.have.* assertions inside test blocks', () => { + const code = ` + pm.test("Negative assertions", function() { + pm.response.to.not.have.status(500); + pm.response.to.not.have.header("X-Error"); + pm.response.to.not.have.body("error"); + }); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toContain('test("Negative assertions", function() {'); + expect(translatedCode).toContain('expect(res.getStatus()).to.not.equal(500)'); + expect(translatedCode).toContain('expect(res.getHeaders()).to.not.have.property("X-Error".toLowerCase())'); + expect(translatedCode).toContain('expect(res.getBody()).to.not.equal("error")'); + }); + + it('should handle negated to.have.status with alias', () => { + const code = ` + const resp = pm.response; + resp.to.not.have.status(404); + `; + const translatedCode = translateCode(code); + expect(translatedCode).toBe(` + expect(res.getStatus()).to.not.equal(404); + `); + }); });