diff --git a/packages/bruno-app/src/utils/codemirror/autocomplete.js b/packages/bruno-app/src/utils/codemirror/autocomplete.js index ed61a0880c8..1f672ae8d83 100644 --- a/packages/bruno-app/src/utils/codemirror/autocomplete.js +++ b/packages/bruno-app/src/utils/codemirror/autocomplete.js @@ -43,7 +43,6 @@ const STATIC_API_HINTS = { 'req.headerList.get(name)', 'req.headerList.one(name)', 'req.headerList.all()', - 'req.headerList.idx(index)', 'req.headerList.count()', 'req.headerList.has(name)', 'req.headerList.has(name, value)', @@ -88,7 +87,6 @@ const STATIC_API_HINTS = { 'res.headerList.get(name)', 'res.headerList.one(name)', 'res.headerList.all()', - 'res.headerList.idx(index)', 'res.headerList.count()', 'res.headerList.has(name)', 'res.headerList.has(name, value)', diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js index 1e96c2e9b42..0482aaae8cf 100644 --- a/packages/bruno-converters/src/postman/postman-translations.js +++ b/packages/bruno-converters/src/postman/postman-translations.js @@ -34,8 +34,48 @@ const replacements = { 'pm\\.environment\\.toObject\\(': 'bru.getAllEnvVars(', 'pm\\.environment\\.clear\\(': 'bru.deleteAllEnvVars(', 'pm\\.variables\\.toObject\\(': 'bru.getAllVars(', + // Request header PropertyList methods 'pm\\.request\\.headers\\.remove\\(': 'req.deleteHeader(', + 'pm\\.request\\.headers\\.get\\(': 'req.headerList.get(', + 'pm\\.request\\.headers\\.has\\(': 'req.headerList.has(', + 'pm\\.request\\.headers\\.one\\(': 'req.headerList.one(', + 'pm\\.request\\.headers\\.all\\(': 'req.headerList.all(', + 'pm\\.request\\.headers\\.count\\(': 'req.headerList.count(', + 'pm\\.request\\.headers\\.indexOf\\(': 'req.headerList.indexOf(', + 'pm\\.request\\.headers\\.find\\(': 'req.headerList.find(', + 'pm\\.request\\.headers\\.filter\\(': 'req.headerList.filter(', + 'pm\\.request\\.headers\\.each\\(': 'req.headerList.forEach(', + 'pm\\.request\\.headers\\.map\\(': 'req.headerList.map(', + 'pm\\.request\\.headers\\.reduce\\(': 'req.headerList.reduce(', + 'pm\\.request\\.headers\\.toObject\\(': 'req.headerList.toObject(', + 'pm\\.request\\.headers\\.clear\\(': 'req.headerList.clear(', + 'pm\\.request\\.headers\\.add\\(': 'req.headerList.append(', + 'pm\\.request\\.headers\\.upsert\\(': 'req.headerList.set(', + 'pm\\.request\\.headers\\.toString\\(': 'req.headerList.toString(', + 'pm\\.request\\.headers\\.toJSON\\(': 'req.headerList.toJSON(', + 'pm\\.request\\.headers\\.populate\\(': 'req.headerList.populate(', + 'pm\\.request\\.headers\\.repopulate\\(': 'req.headerList.repopulate(', + 'pm\\.request\\.headers\\.assimilate\\(': 'req.headerList.assimilate(', + // Lossy: positional inserts map to append (position irrelevant for headers) + // Note: regex can't drop the second arg, so it passes through as-is + 'pm\\.request\\.headers\\.prepend\\(': 'req.headerList.append(', + 'pm\\.request\\.headers\\.insert\\(': 'req.headerList.append(', + 'pm\\.request\\.headers\\.insertAfter\\(': 'req.headerList.append(', + // Response header PropertyList methods 'pm\\.response\\.headers\\.get\\(': 'res.getHeader(', + 'pm\\.response\\.headers\\.has\\(': 'res.headerList.has(', + 'pm\\.response\\.headers\\.one\\(': 'res.headerList.one(', + 'pm\\.response\\.headers\\.all\\(': 'res.headerList.all(', + 'pm\\.response\\.headers\\.count\\(': 'res.headerList.count(', + 'pm\\.response\\.headers\\.indexOf\\(': 'res.headerList.indexOf(', + 'pm\\.response\\.headers\\.find\\(': 'res.headerList.find(', + 'pm\\.response\\.headers\\.filter\\(': 'res.headerList.filter(', + 'pm\\.response\\.headers\\.each\\(': 'res.headerList.forEach(', + 'pm\\.response\\.headers\\.map\\(': 'res.headerList.map(', + 'pm\\.response\\.headers\\.reduce\\(': 'res.headerList.reduce(', + 'pm\\.response\\.headers\\.toObject\\(': 'res.headerList.toObject(', + 'pm\\.response\\.headers\\.toString\\(': 'res.headerList.toString(', + 'pm\\.response\\.headers\\.toJSON\\(': 'res.headerList.toJSON(', 'pm\\.response\\.to\\.have\\.jsonSchema\\(': 'expect(res.getBody()).to.have.jsonSchema(', 'pm\\.response\\.to\\.not\\.have\\.jsonSchema\\(': 'expect(res.getBody()).to.not.have.jsonSchema(', 'pm\\.response\\.not\\.to\\.have\\.jsonSchema\\(': 'expect(res.getBody()).not.to.have.jsonSchema(', diff --git a/packages/bruno-converters/src/utils/bruno-to-postman-translator.js b/packages/bruno-converters/src/utils/bruno-to-postman-translator.js index 65fc2bbca7e..f77f862dfd9 100644 --- a/packages/bruno-converters/src/utils/bruno-to-postman-translator.js +++ b/packages/bruno-converters/src/utils/bruno-to-postman-translator.js @@ -90,6 +90,8 @@ const simpleTranslations = { 'req.headerList.map': 'pm.request.headers.map', 'req.headerList.reduce': 'pm.request.headers.reduce', 'req.headerList.toObject': 'pm.request.headers.toObject', + 'req.headerList.toString': 'pm.request.headers.toString', + 'req.headerList.toJSON': 'pm.request.headers.toJSON', 'req.headerList.append': 'pm.request.headers.add', 'req.headerList.set': 'pm.request.headers.upsert', 'req.headerList.delete': 'pm.request.headers.remove', @@ -130,6 +132,8 @@ const simpleTranslations = { 'res.headerList.map': 'pm.response.headers.map', 'res.headerList.reduce': 'pm.response.headers.reduce', 'res.headerList.toObject': 'pm.response.headers.toObject', + 'res.headerList.toString': 'pm.response.headers.toString', + 'res.headerList.toJSON': 'pm.response.headers.toJSON', // Cookies jar 'bru.cookies.jar': 'pm.cookies.jar', 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 fa9ae4dc680..65ae1fac104 100644 --- a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js +++ b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js @@ -65,6 +65,8 @@ const simpleTranslations = { 'pm.request.headers.map': 'req.headerList.map', 'pm.request.headers.reduce': 'req.headerList.reduce', 'pm.request.headers.toObject': 'req.headerList.toObject', + 'pm.request.headers.toString': 'req.headerList.toString', + 'pm.request.headers.toJSON': 'req.headerList.toJSON', 'pm.request.headers.clear': 'req.headerList.clear', // Response headers PropertyList methods (read-only) @@ -79,6 +81,8 @@ const simpleTranslations = { 'pm.response.headers.map': 'res.headerList.map', 'pm.response.headers.reduce': 'res.headerList.reduce', 'pm.response.headers.toObject': 'res.headerList.toObject', + 'pm.response.headers.toString': 'res.headerList.toString', + 'pm.response.headers.toJSON': 'res.headerList.toJSON', // Request properties (pm.request.*) 'pm.request.url.getHost': 'req.getHost', @@ -409,6 +413,32 @@ const complexTransformations = [ } }, + // Lossy: positional header inserts → append (only keep the first arg, drop positional ref) + // pm.request.headers.prepend(item) -> req.headerList.append(item) + { + pattern: 'pm.request.headers.prepend', + transform: (path, j) => { + const args = path.parent.value.arguments; + return j.callExpression(j.identifier('req.headerList.append'), args.length > 0 ? [args[0]] : []); + } + }, + // pm.request.headers.insert(item, before) -> req.headerList.append(item) + { + pattern: 'pm.request.headers.insert', + transform: (path, j) => { + const args = path.parent.value.arguments; + return j.callExpression(j.identifier('req.headerList.append'), args.length > 0 ? [args[0]] : []); + } + }, + // pm.request.headers.insertAfter(item, after) -> req.headerList.append(item) + { + pattern: 'pm.request.headers.insertAfter', + transform: (path, j) => { + const args = path.parent.value.arguments; + return j.callExpression(j.identifier('req.headerList.append'), args.length > 0 ? [args[0]] : []); + } + }, + // pm.response.to.be.ok -> expect(res.getStatus()).to.be.within(200, 299) { pattern: 'pm.response.to.be.ok', diff --git a/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js b/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js index 63305a3285f..3a38779ef69 100644 --- a/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js +++ b/packages/bruno-converters/tests/bruno/bruno-to-postman-translations/request.test.js @@ -292,6 +292,18 @@ console.log("Headers:", JSON.stringify(pm.request.headers)); expect(translatedCode).toBe('const obj = pm.request.headers.toObject();'); }); + it('should translate req.headerList.toString to pm.request.headers.toString', () => { + const code = 'const str = req.headerList.toString();'; + const translatedCode = translateBruToPostman(code); + expect(translatedCode).toBe('const str = pm.request.headers.toString();'); + }); + + it('should translate req.headerList.toJSON to pm.request.headers.toJSON', () => { + const code = 'const json = req.headerList.toJSON();'; + const translatedCode = translateBruToPostman(code); + expect(translatedCode).toBe('const json = pm.request.headers.toJSON();'); + }); + it('should translate req.headerList.set to pm.request.headers.upsert', () => { const code = 'req.headerList.set({key: "X-Custom", value: "updated"});'; const translatedCode = translateBruToPostman(code); diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js index 01f8ff0c3e8..bd057524078 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js +++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/request.test.js @@ -201,9 +201,39 @@ describe('Request Translation', () => { expect(translatedCode).toBe('req.headerList.clear();'); }); + it('should translate pm.request.headers.prepend to req.headerList.append', () => { + const code = 'pm.request.headers.prepend({key: "X-First", value: "1"});'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('req.headerList.append({key: "X-First", value: "1"});'); + }); + + it('should translate pm.request.headers.insert to req.headerList.append (drops positional ref)', () => { + const code = 'pm.request.headers.insert({key: "X-Mid", value: "2"}, "ref");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('req.headerList.append({key: "X-Mid", value: "2"});'); + }); + + it('should translate pm.request.headers.insertAfter to req.headerList.append (drops positional ref)', () => { + const code = 'pm.request.headers.insertAfter({key: "X-After", value: "3"}, "ref");'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('req.headerList.append({key: "X-After", value: "3"});'); + }); + it('should translate pm.request.headers.toObject to req.headerList.toObject', () => { const code = 'const obj = pm.request.headers.toObject();'; const translatedCode = translateCode(code); expect(translatedCode).toBe('const obj = req.headerList.toObject();'); }); + + it('should translate pm.request.headers.toString to req.headerList.toString', () => { + const code = 'const str = pm.request.headers.toString();'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('const str = req.headerList.toString();'); + }); + + it('should translate pm.request.headers.toJSON to req.headerList.toJSON', () => { + const code = 'const json = pm.request.headers.toJSON();'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('const json = req.headerList.toJSON();'); + }); }); 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 5a08a540dd8..fdc70673e3d 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 @@ -894,4 +894,16 @@ describe('Response Translation', () => { const translatedCode = translateCode(code); expect(translatedCode).toBe('const obj = res.headerList.toObject();'); }); + + it('should translate pm.response.headers.toString to res.headerList.toString', () => { + const code = 'const str = pm.response.headers.toString();'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('const str = res.headerList.toString();'); + }); + + it('should translate pm.response.headers.toJSON to res.headerList.toJSON', () => { + const code = 'const json = pm.response.headers.toJSON();'; + const translatedCode = translateCode(code); + expect(translatedCode).toBe('const json = res.headerList.toJSON();'); + }); }); diff --git a/packages/bruno-js/src/header-list.js b/packages/bruno-js/src/header-list.js index 8f5cade6a00..dd593b5766d 100644 --- a/packages/bruno-js/src/header-list.js +++ b/packages/bruno-js/src/header-list.js @@ -1,4 +1,3 @@ -const PropertyList = require('./property-list'); const ReadOnlyPropertyList = require('./readonly-property-list'); /** @@ -78,7 +77,7 @@ const ReadOnlyPropertyList = require('./readonly-property-list'); * | `repopulate(items)` | Clears all, then populates with new items | * | `assimilate(source, prune?)` | Merges headers; prune removes items not in source | */ -class HeaderList extends PropertyList { +class HeaderList extends ReadOnlyPropertyList { #req; #writable; @@ -151,6 +150,12 @@ class HeaderList extends PropertyList { return { key: str.substring(0, idx).trim(), value: str.substring(idx + 1).trim() }; } + // ── Blocked inherited methods ───────────────────────────────────────── + // These are inherited from ReadOnlyPropertyList but are not part of + // the HeaderList API. Set to undefined so they are not callable. + idx = undefined; + each = undefined; + // ── Read method overrides (case-insensitive) ────────────────────────── /** diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js index e4ce5ac6ff6..23027d3b6c8 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js @@ -38,7 +38,7 @@ const addBrunoRequestShimToContext = (vm, req) => { const { evalCode: headersEvalCode } = createPropertyListBridge(vm, req.headerList, headerListObj, { globalPath: 'globalThis.req.headerList', syncReadMethods: ['get', 'has', 'count', 'indexOf', 'toObject', 'toString'], - syncReadObjectMethods: ['one', 'all', 'idx', 'toJSON'], + syncReadObjectMethods: ['one', 'all', 'toJSON'], syncWriteMethods: ['append', 'set', 'delete', 'clear', 'populate', 'repopulate', 'assimilate'], withIterators: true }); @@ -197,7 +197,7 @@ const addBrunoRequestShimToContext = (vm, req) => { // Wrapped in a block to avoid const redeclaration conflicts with other evalCode blocks // The bridge generates `each` (shared with CookieList); alias `forEach` for HeaderList's MDN-style API if (headersEvalCode) { - vm.evalCode(`{ ${headersEvalCode} globalThis.req.headerList.forEach = globalThis.req.headerList.each; }`); + vm.evalCode(`{ ${headersEvalCode} globalThis.req.headerList.forEach = globalThis.req.headerList.each; delete globalThis.req.headerList.each; }`); } }; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js index 329e198dab2..93b9ebcd85e 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js @@ -63,7 +63,7 @@ const addBrunoResponseShimToContext = (vm, res) => { const bridge = createPropertyListBridge(vm, res.headerList, headerListObj, { globalPath: 'globalThis.res.headerList', syncReadMethods: ['get', 'has', 'count', 'indexOf', 'toObject', 'toString'], - syncReadObjectMethods: ['one', 'all', 'idx', 'toJSON'], + syncReadObjectMethods: ['one', 'all', 'toJSON'], syncWriteMethods: ['append', 'set', 'delete', 'clear', 'populate', 'repopulate', 'assimilate'], withIterators: true }); @@ -133,7 +133,7 @@ const addBrunoResponseShimToContext = (vm, res) => { // Wrapped in a block to avoid const redeclaration conflicts with req.headerList's evalCode // The bridge generates `each` (shared with CookieList); alias `forEach` for HeaderList's MDN-style API if (resHeadersEvalCode) { - vm.evalCode(`{ ${resHeadersEvalCode} globalThis.res.headerList.forEach = globalThis.res.headerList.each; }`); + vm.evalCode(`{ ${resHeadersEvalCode} globalThis.res.headerList.forEach = globalThis.res.headerList.each; delete globalThis.res.headerList.each; }`); } }; diff --git a/packages/bruno-js/tests/header-list.spec.js b/packages/bruno-js/tests/header-list.spec.js index bf1f10d56b4..6bb1614d5ba 100644 --- a/packages/bruno-js/tests/header-list.spec.js +++ b/packages/bruno-js/tests/header-list.spec.js @@ -1,5 +1,4 @@ const HeaderList = require('../src/header-list'); -const PropertyList = require('../src/property-list'); const ReadOnlyPropertyList = require('../src/readonly-property-list'); const BrunoRequest = require('../src/bruno-request'); const BrunoResponse = require('../src/bruno-response'); @@ -19,10 +18,9 @@ describe('HeaderList (req.headerList)', () => { // ── Inheritance ──────────────────────────────────────────────────────── - test('extends PropertyList and ReadOnlyPropertyList', () => { + test('extends ReadOnlyPropertyList', () => { const { list } = createReqHeaders(); expect(list).toBeInstanceOf(ReadOnlyPropertyList); - expect(list).toBeInstanceOf(PropertyList); expect(list).toBeInstanceOf(HeaderList); }); @@ -73,17 +71,6 @@ describe('HeaderList (req.headerList)', () => { expect(a1).not.toBe(a2); }); - test('idx() returns header at position', () => { - const { list } = createReqHeaders(); - expect(list.idx(0)).toEqual({ key: 'Content-Type', value: 'application/json' }); - expect(list.idx(2)).toEqual({ key: 'Accept', value: '*/*' }); - }); - - test('idx() returns undefined for out-of-bounds', () => { - const { list } = createReqHeaders(); - expect(list.idx(10)).toBeUndefined(); - }); - test('count() returns number of headers', () => { const { list } = createReqHeaders(); expect(list.count()).toBe(3); @@ -923,11 +910,6 @@ describe('Response Headers (res.headerList)', () => { expect(headerList.count()).toBe(3); }); - test('idx() returns header at position', () => { - const { headerList } = createResHeaders(); - expect(headerList.idx(1)).toEqual({ key: 'x-request-id', value: 'abc-123' }); - }); - test('indexOf() finds structurally-equal header', () => { const { headerList } = createResHeaders(); expect(headerList.indexOf({ key: 'content-type', value: 'application/json' })).toBe(0); diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/read-methods.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/read-methods.bru index 764568b2f4d..9c54cae20b7 100644 --- a/packages/bruno-tests/collection/scripting/api/req/headerList/read-methods.bru +++ b/packages/bruno-tests/collection/scripting/api/req/headerList/read-methods.bru @@ -44,13 +44,6 @@ tests { expect(keys).to.include('x-custom'); }); - test("req.headerList.idx(index)", function() { - const first = req.headerList.idx(0); - expect(first).to.have.property('key'); - expect(first).to.have.property('value'); - expect(req.headerList.idx(-1)).to.be.undefined; - }); - test("req.headerList.count()", function() { expect(req.headerList.count()).to.be.at.least(3); }); diff --git a/packages/bruno-tests/collection/scripting/api/res/headerList/read-methods.bru b/packages/bruno-tests/collection/scripting/api/res/headerList/read-methods.bru index 3553e3645e3..ffb840dee41 100644 --- a/packages/bruno-tests/collection/scripting/api/res/headerList/read-methods.bru +++ b/packages/bruno-tests/collection/scripting/api/res/headerList/read-methods.bru @@ -40,13 +40,6 @@ tests { expect(keys).to.include('x-powered-by'); }); - test("res.headerList.idx(index)", function() { - const first = res.headerList.idx(0); - expect(first).to.have.property('key'); - expect(first).to.have.property('value'); - expect(res.headerList.idx(-1)).to.be.undefined; - }); - test("res.headerList.count()", function() { expect(res.headerList.count()).to.be.at.least(1); });