diff --git a/packages/bruno-app/src/utils/codemirror/autocomplete.js b/packages/bruno-app/src/utils/codemirror/autocomplete.js index 1f672ae8d83..2e79fdaa7ef 100644 --- a/packages/bruno-app/src/utils/codemirror/autocomplete.js +++ b/packages/bruno-app/src/utils/codemirror/autocomplete.js @@ -49,15 +49,15 @@ const STATIC_API_HINTS = { 'req.headerList.find(fn)', 'req.headerList.filter(fn)', 'req.headerList.indexOf(item)', - 'req.headerList.forEach(fn)', + 'req.headerList.each(fn)', 'req.headerList.map(fn)', 'req.headerList.reduce(fn, initialValue)', 'req.headerList.toObject()', 'req.headerList.toString()', 'req.headerList.toJSON()', - 'req.headerList.append(headerObj)', - 'req.headerList.set(name, value)', - 'req.headerList.delete(predicate)', + 'req.headerList.add(headerObj)', + 'req.headerList.upsert(headerObj)', + 'req.headerList.remove(predicate)', 'req.headerList.clear()', 'req.headerList.populate(items)', 'req.headerList.repopulate(items)', @@ -93,7 +93,7 @@ const STATIC_API_HINTS = { 'res.headerList.find(fn)', 'res.headerList.filter(fn)', 'res.headerList.indexOf(item)', - 'res.headerList.forEach(fn)', + 'res.headerList.each(fn)', 'res.headerList.map(fn)', 'res.headerList.reduce(fn, initialValue)', 'res.headerList.toObject()', diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js index 0482aaae8cf..9c60f07e227 100644 --- a/packages/bruno-converters/src/postman/postman-translations.js +++ b/packages/bruno-converters/src/postman/postman-translations.js @@ -44,23 +44,23 @@ const replacements = { '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\\.each\\(': 'req.headerList.each(', '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\\.add\\(': 'req.headerList.add(', + 'pm\\.request\\.headers\\.upsert\\(': 'req.headerList.upsert(', '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) + // Lossy: positional inserts map to add (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(', + 'pm\\.request\\.headers\\.prepend\\(': 'req.headerList.add(', + 'pm\\.request\\.headers\\.insert\\(': 'req.headerList.add(', + 'pm\\.request\\.headers\\.insertAfter\\(': 'req.headerList.add(', // Response header PropertyList methods 'pm\\.response\\.headers\\.get\\(': 'res.getHeader(', 'pm\\.response\\.headers\\.has\\(': 'res.headerList.has(', @@ -70,7 +70,7 @@ const replacements = { '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\\.each\\(': 'res.headerList.each(', 'pm\\.response\\.headers\\.map\\(': 'res.headerList.map(', 'pm\\.response\\.headers\\.reduce\\(': 'res.headerList.reduce(', 'pm\\.response\\.headers\\.toObject\\(': 'res.headerList.toObject(', 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 f77f862dfd9..5ff3a6aa81a 100644 --- a/packages/bruno-converters/src/utils/bruno-to-postman-translator.js +++ b/packages/bruno-converters/src/utils/bruno-to-postman-translator.js @@ -86,15 +86,15 @@ const simpleTranslations = { 'req.headerList.indexOf': 'pm.request.headers.indexOf', 'req.headerList.find': 'pm.request.headers.find', 'req.headerList.filter': 'pm.request.headers.filter', - 'req.headerList.forEach': 'pm.request.headers.each', + 'req.headerList.each': 'pm.request.headers.each', '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', + 'req.headerList.add': 'pm.request.headers.add', + 'req.headerList.upsert': 'pm.request.headers.upsert', + 'req.headerList.remove': 'pm.request.headers.remove', 'req.headerList.clear': 'pm.request.headers.clear', 'req.headerList.populate': 'pm.request.headers.populate', 'req.headerList.repopulate': 'pm.request.headers.repopulate', @@ -128,7 +128,7 @@ const simpleTranslations = { 'res.headerList.indexOf': 'pm.response.headers.indexOf', 'res.headerList.find': 'pm.response.headers.find', 'res.headerList.filter': 'pm.response.headers.filter', - 'res.headerList.forEach': 'pm.response.headers.each', + 'res.headerList.each': 'pm.response.headers.each', 'res.headerList.map': 'pm.response.headers.map', 'res.headerList.reduce': 'pm.response.headers.reduce', 'res.headerList.toObject': 'pm.response.headers.toObject', 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 65ae1fac104..e99782997cc 100644 --- a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js +++ b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js @@ -61,7 +61,7 @@ const simpleTranslations = { '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.each': 'req.headerList.each', 'pm.request.headers.map': 'req.headerList.map', 'pm.request.headers.reduce': 'req.headerList.reduce', 'pm.request.headers.toObject': 'req.headerList.toObject', @@ -77,7 +77,7 @@ const simpleTranslations = { '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.each': 'res.headerList.each', 'pm.response.headers.map': 'res.headerList.map', 'pm.response.headers.reduce': 'res.headerList.reduce', 'pm.response.headers.toObject': 'res.headerList.toObject', @@ -414,28 +414,28 @@ const complexTransformations = [ }, // Lossy: positional header inserts → append (only keep the first arg, drop positional ref) - // pm.request.headers.prepend(item) -> req.headerList.append(item) + // pm.request.headers.prepend(item) -> req.headerList.add(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]] : []); + return j.callExpression(j.identifier('req.headerList.add'), args.length > 0 ? [args[0]] : []); } }, - // pm.request.headers.insert(item, before) -> req.headerList.append(item) + // pm.request.headers.insert(item, before) -> req.headerList.add(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]] : []); + return j.callExpression(j.identifier('req.headerList.add'), args.length > 0 ? [args[0]] : []); } }, - // pm.request.headers.insertAfter(item, after) -> req.headerList.append(item) + // pm.request.headers.insertAfter(item, after) -> req.headerList.add(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]] : []); + return j.callExpression(j.identifier('req.headerList.add'), args.length > 0 ? [args[0]] : []); } }, 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 3a38779ef69..165970276a1 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 @@ -256,14 +256,14 @@ console.log("Headers:", JSON.stringify(pm.request.headers)); expect(translatedCode).toBe('const custom = pm.request.headers.filter(h => h.key.startsWith("X-"));'); }); - it('should translate req.headerList.append to pm.request.headers.add', () => { - const code = 'req.headerList.append({key: "X-Custom", value: "test"});'; + it('should translate req.headerList.add to pm.request.headers.add', () => { + const code = 'req.headerList.add({key: "X-Custom", value: "test"});'; const translatedCode = translateBruToPostman(code); expect(translatedCode).toBe('pm.request.headers.add({key: "X-Custom", value: "test"});'); }); - it('should translate req.headerList.delete to pm.request.headers.remove', () => { - const code = 'req.headerList.delete("Authorization");'; + it('should translate req.headerList.remove to pm.request.headers.remove', () => { + const code = 'req.headerList.remove("Authorization");'; const translatedCode = translateBruToPostman(code); expect(translatedCode).toBe('pm.request.headers.remove("Authorization");'); }); @@ -304,8 +304,8 @@ console.log("Headers:", JSON.stringify(pm.request.headers)); 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"});'; + it('should translate req.headerList.upsert to pm.request.headers.upsert', () => { + const code = 'req.headerList.upsert({key: "X-Custom", value: "updated"});'; const translatedCode = translateBruToPostman(code); expect(translatedCode).toBe('pm.request.headers.upsert({key: "X-Custom", value: "updated"});'); }); 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 bd057524078..979ad88c16e 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 @@ -177,10 +177,10 @@ describe('Request Translation', () => { expect(translatedCode).toBe('const allHeaders = req.headerList.all();'); }); - it('should translate pm.request.headers.each to req.headerList.forEach', () => { + it('should translate pm.request.headers.each to req.headerList.each', () => { const code = 'pm.request.headers.each(h => console.log(h.key));'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('req.headerList.forEach(h => console.log(h.key));'); + expect(translatedCode).toBe('req.headerList.each(h => console.log(h.key));'); }); it('should translate pm.request.headers.filter to req.headerList.filter', () => { @@ -201,22 +201,22 @@ describe('Request Translation', () => { expect(translatedCode).toBe('req.headerList.clear();'); }); - it('should translate pm.request.headers.prepend to req.headerList.append', () => { + it('should translate pm.request.headers.prepend to req.headerList.add', () => { 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"});'); + expect(translatedCode).toBe('req.headerList.add({key: "X-First", value: "1"});'); }); - it('should translate pm.request.headers.insert to req.headerList.append (drops positional ref)', () => { + it('should translate pm.request.headers.insert to req.headerList.add (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"});'); + expect(translatedCode).toBe('req.headerList.add({key: "X-Mid", value: "2"});'); }); - it('should translate pm.request.headers.insertAfter to req.headerList.append (drops positional ref)', () => { + it('should translate pm.request.headers.insertAfter to req.headerList.add (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"});'); + expect(translatedCode).toBe('req.headerList.add({key: "X-After", value: "3"});'); }); it('should translate pm.request.headers.toObject to req.headerList.toObject', () => { 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 fdc70673e3d..176b9632e38 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 @@ -871,10 +871,10 @@ describe('Response Translation', () => { expect(translatedCode).toBe('const allHeaders = res.headerList.all();'); }); - it('should translate pm.response.headers.each to res.headerList.forEach', () => { + it('should translate pm.response.headers.each to res.headerList.each', () => { const code = 'pm.response.headers.each(h => console.log(h.key));'; const translatedCode = translateCode(code); - expect(translatedCode).toBe('res.headerList.forEach(h => console.log(h.key));'); + expect(translatedCode).toBe('res.headerList.each(h => console.log(h.key));'); }); it('should translate pm.response.headers.filter to res.headerList.filter', () => { diff --git a/packages/bruno-js/src/header-list.js b/packages/bruno-js/src/header-list.js index dd593b5766d..a78b27db45c 100644 --- a/packages/bruno-js/src/header-list.js +++ b/packages/bruno-js/src/header-list.js @@ -53,7 +53,7 @@ const ReadOnlyPropertyList = require('./readonly-property-list'); * * | Method | Description | * |------------------------------|----------------------------------------------| - * | `forEach(fn, context?)` | Calls `fn(header, index)` for every header | + * | `each(fn, context?)` | Calls `fn(header, index)` for every header | * | `map(fn, context?)` | Returns a new array of mapped values | * | `reduce(fn, initial?, context?)` | Reduces headers to a single value | * @@ -69,9 +69,9 @@ const ReadOnlyPropertyList = require('./readonly-property-list'); * * | Method | Description | * |-----------------------------------|----------------------------------------------------------| - * | `append(headerObj\|name, value?)` | Sets a header; accepts `{key,value}`, `"Key: Value"`, or `(name, value)` | - * | `set(headerObj\|name, value?)` | Sets (or replaces) a header; returns true/false/null | - * | `delete(predicate, context?)` | Deletes header(s) by name, predicate, or object | + * | `add(headerObj\|name, value?)` | Sets a header; accepts `{key,value}`, `"Key: Value"`, or `(name, value)` | + * | `upsert(headerObj\|name, value?)` | Sets (or replaces) a header; returns true/false/null | + * | `remove(predicate, context?)` | Deletes header(s) by name, predicate, or object | * | `clear()` | Removes **all** headers (enabled and disabled) | * | `populate(items\|string)` | Adds items, skipping keys that already exist | * | `repopulate(items)` | Clears all, then populates with new items | @@ -151,10 +151,9 @@ class HeaderList extends ReadOnlyPropertyList { } // ── 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 is inherited from ReadOnlyPropertyList but not part of the + // HeaderList API. Set to undefined so it is not callable. idx = undefined; - each = undefined; // ── Read method overrides (case-insensitive) ────────────────────────── @@ -215,7 +214,7 @@ class HeaderList extends ReadOnlyPropertyList { // ── Iteration overrides (optional context binding) ───────────────── /** @param {Function} fn @param {*} [context] */ - forEach(fn, context) { + each(fn, context) { super.each(context !== undefined ? fn.bind(context) : fn); } @@ -245,25 +244,21 @@ class HeaderList extends ReadOnlyPropertyList { // ── Write methods (direct request config manipulation) ──────────────── /** - * Append a header. Accepts a { key, value } object, a "Key: Value" string, - * or two arguments (name, value). - * - * Note: Unlike MDN's Headers.append(), this does not create duplicate keys - * (Bruno does not support multiple headers with the same name). Instead it - * delegates to set(), which overwrites any existing header with the same key. + * Add a header. Accepts a { key, value } object, a "Key: Value" string, + * or two arguments (name, value). Delegates to upsert(). * * @param {object|string} itemOrName - Header object, "Key: Value" string, or header name * @param {string} [value] - Header value (when using two-arg form) */ - append(itemOrName, value) { + add(itemOrName, value) { if (typeof itemOrName === 'string' && value !== undefined) { - this.set({ key: itemOrName, value }); + this.upsert({ key: itemOrName, value }); return; } if (typeof itemOrName === 'string') { itemOrName = HeaderList.#parseHeaderString(itemOrName); } - this.set(itemOrName); + this.upsert(itemOrName); } /** @@ -273,7 +268,7 @@ class HeaderList extends ReadOnlyPropertyList { * @param {string} [value] - Header value (when using two-arg form) * @returns {boolean|null} `true` if added, `false` if updated, `null` if input was nil */ - set(itemOrName, value) { + upsert(itemOrName, value) { this.#assertWritable(); let item = itemOrName; if (typeof itemOrName === 'string') { @@ -300,12 +295,12 @@ class HeaderList extends ReadOnlyPropertyList { } /** - * Delete header(s) matching a predicate, key string, or item reference. + * Remove header(s) matching a predicate, key string, or item reference. * String and object removal are case-insensitive. * @param {Function|string|object} predicate * @param {*} [context] - Bind `this` for function predicates */ - delete(predicate, context) { + remove(predicate, context) { this.#assertWritable(); if (typeof predicate === 'function') { const bound = context !== undefined ? predicate.bind(context) : predicate; @@ -403,7 +398,7 @@ class HeaderList extends ReadOnlyPropertyList { for (const line of lines) { const parsed = HeaderList.#parseHeaderString(line); if (parsed && !this.has(parsed.key)) { - this.append(parsed); + this.add(parsed); } } return; @@ -411,7 +406,7 @@ class HeaderList extends ReadOnlyPropertyList { const list = Array.isArray(items) ? items : []; for (const item of list) { if (item && item.key && !this.has(item.key)) { - this.append(item); + this.add(item); } } } @@ -480,7 +475,7 @@ class HeaderList extends ReadOnlyPropertyList { } // Merge source items into this list for (const item of items) { - this.append(item); + this.add(item); } // Prune: remove items from this list that are not in source if (prune && items.length > 0) { 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 23027d3b6c8..13eefc48f6e 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js @@ -39,7 +39,7 @@ const addBrunoRequestShimToContext = (vm, req) => { globalPath: 'globalThis.req.headerList', syncReadMethods: ['get', 'has', 'count', 'indexOf', 'toObject', 'toString'], syncReadObjectMethods: ['one', 'all', 'toJSON'], - syncWriteMethods: ['append', 'set', 'delete', 'clear', 'populate', 'repopulate', 'assimilate'], + syncWriteMethods: ['add', 'upsert', 'remove', 'clear', 'populate', 'repopulate', 'assimilate'], withIterators: true }); vm.setProp(reqObject, 'headerList', headerListObj); @@ -195,9 +195,8 @@ const addBrunoRequestShimToContext = (vm, req) => { // Evaluate iterator code after req is on global (iterators reference globalThis.req.headerList) // 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; delete globalThis.req.headerList.each; }`); + vm.evalCode(`{ ${headersEvalCode} }`); } }; 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 93b9ebcd85e..d2c807f02fa 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js @@ -64,7 +64,7 @@ const addBrunoResponseShimToContext = (vm, res) => { globalPath: 'globalThis.res.headerList', syncReadMethods: ['get', 'has', 'count', 'indexOf', 'toObject', 'toString'], syncReadObjectMethods: ['one', 'all', 'toJSON'], - syncWriteMethods: ['append', 'set', 'delete', 'clear', 'populate', 'repopulate', 'assimilate'], + syncWriteMethods: ['add', 'upsert', 'remove', 'clear', 'populate', 'repopulate', 'assimilate'], withIterators: true }); resHeadersEvalCode = bridge.evalCode; @@ -131,9 +131,8 @@ const addBrunoResponseShimToContext = (vm, res) => { // Evaluate iterator code after res is on global (iterators reference globalThis.res.headerList) // 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; delete globalThis.res.headerList.each; }`); + vm.evalCode(`{ ${resHeadersEvalCode} }`); } }; diff --git a/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js b/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js index 8ba2a2a4517..d8162c2ddbe 100644 --- a/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js +++ b/packages/bruno-js/src/sandbox/quickjs/utils/property-list-bridge.js @@ -167,22 +167,18 @@ const createPropertyListBridge = (vm, nativeList, targetObj, options) => { ${globalPath}.reduce = (fn, ...rest) => { const ctx = rest.length > 1 ? rest[1] : undefined; const b = ctx !== undefined ? fn.bind(ctx) : fn; return rest.length > 0 ? _allNative().reduce(b, rest[0]) : _allNative().reduce(b); };\n`; } - // Override `remove`/`delete` when present in syncWriteMethods so function predicates work in-VM. + // Override `remove` when it's a syncWriteMethod so function predicates work in-VM. // The native bridge can't serialize function handles (vm.dump fails on functions). - // Instead: pull items via all(), run the predicate in-VM, call native method(key) per match. - // Both names are supported: CookieList uses `remove`, HeaderList uses `delete`. - if (withIterators) { - for (const name of ['remove', 'delete']) { - if (!syncWriteMethods.includes(name)) continue; - evalCode += `const _${name}Native = ${globalPath}.${name}; - ${globalPath}.${name} = (predicate) => { - if (typeof predicate === 'function') { - _allNative().filter(predicate).forEach(item => _${name}Native(item.key)); - } else { - _${name}Native(predicate); - } - };\n`; - } + // Instead: pull items via all(), run the predicate in-VM, call native remove(key) per match. + if (withIterators && syncWriteMethods.includes('remove')) { + evalCode += `const _removeNative = ${globalPath}.remove; + ${globalPath}.remove = (predicate) => { + if (typeof predicate === 'function') { + _allNative().filter(predicate).forEach(item => _removeNative(item.key)); + } else { + _removeNative(predicate); + } + };\n`; } return { evalCode }; diff --git a/packages/bruno-js/tests/header-list.spec.js b/packages/bruno-js/tests/header-list.spec.js index 6bb1614d5ba..48ca0fa1641 100644 --- a/packages/bruno-js/tests/header-list.spec.js +++ b/packages/bruno-js/tests/header-list.spec.js @@ -29,6 +29,21 @@ describe('HeaderList (req.headerList)', () => { expect(ReadOnlyPropertyList.isPropertyList(list)).toBe(true); }); + // ── Blocked inherited methods ───────────────────────────────────────── + + test('idx is undefined (blocked from ReadOnlyPropertyList)', () => { + const { list } = createReqHeaders(); + expect(list.idx).toBeUndefined(); + }); + + test('positional methods do not exist (not inherited from PropertyList)', () => { + const { list } = createReqHeaders(); + expect(list.prepend).toBeUndefined(); + expect(list.insert).toBeUndefined(); + expect(list.insertAfter).toBeUndefined(); + expect(list.append).toBeUndefined(); + }); + // ── Read methods ────────────────────────────────────────────────────── describe('read methods', () => { @@ -138,10 +153,10 @@ describe('HeaderList (req.headerList)', () => { // ── Iteration methods ───────────────────────────────────────────────── describe('iteration methods', () => { - test('forEach() iterates over all headers', () => { + test('each() iterates over all headers', () => { const { list } = createReqHeaders(); const keys = []; - list.forEach((h) => keys.push(h.key)); + list.each((h) => keys.push(h.key)); expect(keys).toEqual(['Content-Type', 'Authorization', 'Accept']); }); @@ -271,113 +286,119 @@ describe('HeaderList (req.headerList)', () => { // ── Write methods ───────────────────────────────────────────────────── - describe('append()', () => { - test('appends a new header to the request', () => { + describe('add()', () => { + test('adds a new header to the request', () => { const { list, rawReq } = createReqHeaders(); - list.append({ key: 'X-Custom', value: 'test' }); + list.add({ key: 'X-Custom', value: 'test' }); expect(rawReq.headers['X-Custom']).toBe('test'); expect(list.get('X-Custom')).toBe('test'); }); test('overwrites existing header', () => { const { list, rawReq } = createReqHeaders(); - list.append({ key: 'Content-Type', value: 'text/plain' }); + list.add({ key: 'Content-Type', value: 'text/plain' }); expect(rawReq.headers['Content-Type']).toBe('text/plain'); }); test('accepts a "Key: Value" string', () => { const { list, rawReq } = createReqHeaders({}); - list.append('X-Custom: my-value'); + list.add('X-Custom: my-value'); expect(rawReq.headers['X-Custom']).toBe('my-value'); }); test('accepts two-arg form (name, value)', () => { const { list, rawReq } = createReqHeaders({}); - list.append('X-Custom', 'my-value'); + list.add('X-Custom', 'my-value'); expect(rawReq.headers['X-Custom']).toBe('my-value'); }); + test('two-arg form overwrites existing header', () => { + const { list, rawReq } = createReqHeaders(); + list.add('Content-Type', 'text/plain'); + expect(rawReq.headers['Content-Type']).toBe('text/plain'); + }); + test('ignores malformed string (no colon)', () => { const { list } = createReqHeaders({}); const countBefore = list.count(); - list.append('no-colon-here'); + list.add('no-colon-here'); expect(list.count()).toBe(countBefore); }); test('ignores null/undefined input', () => { const { list } = createReqHeaders(); const countBefore = list.count(); - list.append(null); - list.append(undefined); + list.add(null); + list.add(undefined); expect(list.count()).toBe(countBefore); }); test('ignores object without key property', () => { const { list } = createReqHeaders(); const countBefore = list.count(); - list.append({ value: 'no-key' }); + list.add({ value: 'no-key' }); expect(list.count()).toBe(countBefore); }); }); - describe('set()', () => { + describe('upsert()', () => { test('sets a new header with object', () => { const { list, rawReq } = createReqHeaders(); - list.set({ key: 'X-New', value: 'val' }); + list.upsert({ key: 'X-New', value: 'val' }); expect(rawReq.headers['X-New']).toBe('val'); }); test('sets a new header with two-arg form', () => { const { list, rawReq } = createReqHeaders(); - list.set('X-New', 'val'); + list.upsert('X-New', 'val'); expect(rawReq.headers['X-New']).toBe('val'); }); test('replaces existing header', () => { const { list, rawReq } = createReqHeaders(); - list.set({ key: 'Content-Type', value: 'text/html' }); + list.upsert({ key: 'Content-Type', value: 'text/html' }); expect(rawReq.headers['Content-Type']).toBe('text/html'); expect(list.get('Content-Type')).toBe('text/html'); }); test('replaces existing header with two-arg form', () => { const { list, rawReq } = createReqHeaders(); - list.set('Content-Type', 'text/html'); + list.upsert('Content-Type', 'text/html'); expect(rawReq.headers['Content-Type']).toBe('text/html'); }); test('with missing value sets header to undefined', () => { const { list, rawReq } = createReqHeaders({}); - list.set({ key: 'X-Foo' }); + list.upsert({ key: 'X-Foo' }); expect(rawReq.headers['X-Foo']).toBeUndefined(); expect(list.count()).toBe(1); }); }); - describe('delete()', () => { + describe('remove()', () => { test('removes header by key string', () => { const { list, rawReq } = createReqHeaders(); - list.delete('Accept'); + list.remove('Accept'); expect(rawReq.headers['Accept']).toBeUndefined(); expect(list.has('Accept')).toBe(false); }); test('removes header by predicate function', () => { const { list, rawReq } = createReqHeaders(); - list.delete((h) => h.key === 'Authorization'); + list.remove((h) => h.key === 'Authorization'); expect(rawReq.headers['Authorization']).toBeUndefined(); expect(list.has('Authorization')).toBe(false); }); test('removes header by object reference', () => { const { list, rawReq } = createReqHeaders(); - list.delete({ key: 'Accept', value: '*/*' }); + list.remove({ key: 'Accept', value: '*/*' }); expect(rawReq.headers['Accept']).toBeUndefined(); }); test('removes multiple headers matching predicate', () => { const { list, rawReq } = createReqHeaders(); - list.delete((h) => h.key.startsWith('A')); + list.remove((h) => h.key.startsWith('A')); expect(rawReq.headers['Authorization']).toBeUndefined(); expect(rawReq.headers['Accept']).toBeUndefined(); expect(rawReq.headers['Content-Type']).toBe('application/json'); @@ -385,22 +406,22 @@ describe('HeaderList (req.headerList)', () => { test('tracks removed headers in __headersToDelete', () => { const { list, rawReq } = createReqHeaders(); - list.delete('Accept'); + list.remove('Accept'); expect(rawReq.__headersToDelete).toContain('Accept'); }); test('no-op for non-existent key', () => { const { list } = createReqHeaders(); const countBefore = list.count(); - list.delete('X-Does-Not-Exist'); + list.remove('X-Does-Not-Exist'); expect(list.count()).toBe(countBefore); }); test('no-op for null/undefined predicate', () => { const { list } = createReqHeaders(); const countBefore = list.count(); - list.delete(null); - list.delete(undefined); + list.remove(null); + list.remove(undefined); expect(list.count()).toBe(countBefore); }); @@ -412,7 +433,7 @@ describe('HeaderList (req.headerList)', () => { disabledHeaders: [{ name: 'B', value: '2' }] }; const brunoReq = new BrunoRequest(rawReq); - brunoReq.headerList.delete('B'); + brunoReq.headerList.remove('B'); expect(rawReq.disabledHeaders).toHaveLength(0); expect(brunoReq.headerList.has('B')).toBe(false); }); @@ -425,7 +446,7 @@ describe('HeaderList (req.headerList)', () => { disabledHeaders: [{ name: 'B', value: '2' }] }; const brunoReq = new BrunoRequest(rawReq); - brunoReq.headerList.delete((h) => h.disabled); + brunoReq.headerList.remove((h) => h.disabled); expect(rawReq.disabledHeaders).toHaveLength(0); expect(brunoReq.headerList.count()).toBe(1); }); @@ -689,16 +710,16 @@ describe('HeaderList (req.headerList)', () => { expect(list.indexOf('X-Nonexistent')).toBe(-1); }); - test('delete() by string is case-insensitive', () => { + test('remove() by string is case-insensitive', () => { const { list, rawReq } = createReqHeaders(); - list.delete('content-type'); + list.remove('content-type'); expect(rawReq.headers['Content-Type']).toBeUndefined(); expect(rawReq.__headersToDelete).toContain('Content-Type'); }); - test('set() replaces existing header case-insensitively', () => { + test('upsert() replaces existing header case-insensitively', () => { const { list, rawReq } = createReqHeaders(); - list.set({ key: 'content-type', value: 'text/plain' }); + list.upsert({ key: 'content-type', value: 'text/plain' }); expect(rawReq.headers['content-type']).toBe('text/plain'); expect(rawReq.headers['Content-Type']).toBeUndefined(); // Header was re-added with new casing, so it should NOT be in __headersToDelete @@ -710,10 +731,10 @@ describe('HeaderList (req.headerList)', () => { // ── Context parameter ───────────────────────────────────────────────── describe('context parameter on iteration methods', () => { - test('forEach(fn, context) binds this', () => { + test('each(fn, context) binds this', () => { const { list } = createReqHeaders({ A: '1' }); const ctx = { collected: [] }; - list.forEach(function (h) { this.collected.push(h.key); }, ctx); + list.each(function (h) { this.collected.push(h.key); }, ctx); expect(ctx.collected).toContain('A'); }); @@ -748,10 +769,10 @@ describe('HeaderList (req.headerList)', () => { expect(result).toBe('|A|B'); }); - test('delete(fn, context) binds this', () => { + test('remove(fn, context) binds this', () => { const { list, rawReq } = createReqHeaders({ A: '1', B: '2' }); const ctx = { target: 'A' }; - list.delete(function (h) { return h.key === this.target; }, ctx); + list.remove(function (h) { return h.key === this.target; }, ctx); expect(rawReq.headers['A']).toBeUndefined(); expect(rawReq.headers['B']).toBe('2'); }); @@ -759,7 +780,7 @@ describe('HeaderList (req.headerList)', () => { test('works without context (no binding)', () => { const { list } = createReqHeaders({ A: '1', B: '2' }); const keys = []; - list.forEach((h) => keys.push(h.key)); + list.each((h) => keys.push(h.key)); expect(keys).toContain('A'); expect(keys).toContain('B'); }); @@ -767,22 +788,22 @@ describe('HeaderList (req.headerList)', () => { // ── set() return values ──────────────────────────────────────────── - describe('set() return values', () => { + describe('upsert() return values', () => { test('returns true when adding a new header', () => { const { list } = createReqHeaders({}); - expect(list.set({ key: 'X-New', value: 'val' })).toBe(true); + expect(list.upsert({ key: 'X-New', value: 'val' })).toBe(true); }); test('returns false when updating an existing header', () => { const { list } = createReqHeaders({ 'X-Existing': 'old' }); - expect(list.set({ key: 'X-Existing', value: 'new' })).toBe(false); + expect(list.upsert({ key: 'X-Existing', value: 'new' })).toBe(false); }); test('returns null for nil input', () => { const { list } = createReqHeaders(); - expect(list.set(null)).toBeNull(); - expect(list.set(undefined)).toBeNull(); - expect(list.set({ value: 'no-key' })).toBeNull(); + expect(list.upsert(null)).toBeNull(); + expect(list.upsert(undefined)).toBeNull(); + expect(list.upsert({ value: 'no-key' })).toBeNull(); }); }); @@ -947,10 +968,10 @@ describe('Response Headers (res.headerList)', () => { // ── Iteration methods ───────────────────────────────────────────────── describe('iteration methods', () => { - test('forEach() iterates over all headers', () => { + test('each() iterates over all headers', () => { const { headerList } = createResHeaders(); const keys = []; - headerList.forEach((h) => keys.push(h.key)); + headerList.each((h) => keys.push(h.key)); expect(keys).toEqual(['content-type', 'x-request-id', 'cache-control']); }); @@ -1023,10 +1044,10 @@ describe('Response Headers (res.headerList)', () => { test('response headers are read-only (write methods throw)', () => { const { headerList } = createResHeaders(); - expect(() => headerList.append({ key: 'X-New', value: 'val' })).toThrow('read-only'); - expect(() => headerList.delete('content-type')).toThrow('read-only'); + expect(() => headerList.add({ key: 'X-New', value: 'val' })).toThrow('read-only'); + expect(() => headerList.remove('content-type')).toThrow('read-only'); expect(() => headerList.clear()).toThrow('read-only'); - expect(() => headerList.set({ key: 'X-New', value: 'val' })).toThrow('read-only'); + expect(() => headerList.upsert({ key: 'X-New', value: 'val' })).toThrow('read-only'); expect(() => headerList.populate([])).toThrow('read-only'); expect(() => headerList.assimilate([])).toThrow('read-only'); }); diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/add.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/add.bru new file mode 100644 index 00000000000..b7e4186147c --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/req/headerList/add.bru @@ -0,0 +1,40 @@ +meta { + name: add + type: http + seq: 5 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +headers { + bruno: is-awesome +} + +assert { + res.status: eq 200 + res.body: eq pong +} + +script:pre-request { + req.headerList.add({ key: 'x-added', value: 'via-add' }); + req.headerList.upsert({ key: 'x-upserted', value: 'via-upsert' }); + req.headerList.upsert({ key: 'bruno', value: 'is-the-best' }); +} + +tests { + test("req.headerList.add(item)", function() { + expect(req.getHeader('x-added')).to.equal('via-add'); + }); + + test("req.headerList.upsert(item) - new header", function() { + expect(req.getHeader('x-upserted')).to.equal('via-upsert'); + }); + + test("req.headerList.upsert(item) - overwrite existing", function() { + expect(req.getHeader('bruno')).to.equal('is-the-best'); + }); +} diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/append.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/append.bru deleted file mode 100644 index 57bd859a4de..00000000000 --- a/packages/bruno-tests/collection/scripting/api/req/headerList/append.bru +++ /dev/null @@ -1,40 +0,0 @@ -meta { - name: append - type: http - seq: 5 -} - -get { - url: {{host}}/ping - body: none - auth: none -} - -headers { - bruno: is-awesome -} - -assert { - res.status: eq 200 - res.body: eq pong -} - -script:pre-request { - req.headerList.append({ key: 'x-added', value: 'via-append' }); - req.headerList.set({ key: 'x-set', value: 'via-set' }); - req.headerList.set({ key: 'bruno', value: 'is-the-best' }); -} - -tests { - test("req.headerList.append(item)", function() { - expect(req.getHeader('x-added')).to.equal('via-append'); - }); - - test("req.headerList.set(item) - new header", function() { - expect(req.getHeader('x-set')).to.equal('via-set'); - }); - - test("req.headerList.set(item) - overwrite existing", function() { - expect(req.getHeader('bruno')).to.equal('is-the-best'); - }); -} diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/case-insensitive-write.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/case-insensitive-write.bru index e5320b04720..2c5c19e478c 100644 --- a/packages/bruno-tests/collection/scripting/api/req/headerList/case-insensitive-write.bru +++ b/packages/bruno-tests/collection/scripting/api/req/headerList/case-insensitive-write.bru @@ -21,18 +21,18 @@ assert { } script:pre-request { - req.headerList.set({ key: 'x-custom', value: 'updated' }); - req.headerList.delete('x-remove-me'); + req.headerList.upsert({ key: 'x-custom', value: 'updated' }); + req.headerList.remove('x-remove-me'); } tests { - test("set() replaces header case-insensitively", function() { + test("upsert() replaces header case-insensitively", function() { expect(req.headerList.get('x-custom')).to.equal('updated'); expect(req.getHeader('X-Custom')).to.be.undefined; expect(req.getHeader('x-custom')).to.equal('updated'); }); - test("delete() deletes header case-insensitively", function() { + test("remove() deletes header case-insensitively", function() { expect(req.headerList.has('X-Remove-Me')).to.be.false; expect(req.headerList.has('x-remove-me')).to.be.false; }); diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/context-binding.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/context-binding.bru index 42ec4c9f85b..2b4efceb15a 100644 --- a/packages/bruno-tests/collection/scripting/api/req/headerList/context-binding.bru +++ b/packages/bruno-tests/collection/scripting/api/req/headerList/context-binding.bru @@ -22,9 +22,9 @@ assert { } tests { - test("forEach(fn, context) binds this", function() { + test("each(fn, context) binds this", function() { var ctx = { keys: [] }; - req.headerList.forEach(function(h) { + req.headerList.each(function(h) { this.keys.push(h.key); }, ctx); expect(ctx.keys).to.include('bruno'); diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/iteration-methods.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/iteration-methods.bru index e07cea46333..afb385376b7 100644 --- a/packages/bruno-tests/collection/scripting/api/req/headerList/iteration-methods.bru +++ b/packages/bruno-tests/collection/scripting/api/req/headerList/iteration-methods.bru @@ -21,9 +21,9 @@ assert { } tests { - test("req.headerList.forEach(fn)", function() { + test("req.headerList.each(fn)", function() { const keys = []; - req.headerList.forEach((header) => { + req.headerList.each((header) => { keys.push(header.key); }); expect(keys).to.include('bruno'); diff --git a/packages/bruno-tests/collection/scripting/api/req/headerList/delete.bru b/packages/bruno-tests/collection/scripting/api/req/headerList/remove.bru similarity index 56% rename from packages/bruno-tests/collection/scripting/api/req/headerList/delete.bru rename to packages/bruno-tests/collection/scripting/api/req/headerList/remove.bru index 4c26a90ebc3..56c4978ec38 100644 --- a/packages/bruno-tests/collection/scripting/api/req/headerList/delete.bru +++ b/packages/bruno-tests/collection/scripting/api/req/headerList/remove.bru @@ -1,5 +1,5 @@ meta { - name: delete + name: remove type: http seq: 6 } @@ -23,25 +23,25 @@ assert { } script:pre-request { - req.headerList.delete('bruno'); - req.headerList.delete(h => h.key === 'della'); - req.headerList.delete({ key: 'x-custom', value: 'test-value' }); + req.headerList.remove('bruno'); + req.headerList.remove(h => h.key === 'della'); + req.headerList.remove({ key: 'x-custom', value: 'test-value' }); } tests { - test("req.headerList.delete(name) - by string", function() { + test("req.headerList.remove(name) - by string", function() { expect(req.getHeader('bruno')).to.be.undefined; }); - test("req.headerList.delete(predicate) - by function", function() { + test("req.headerList.remove(predicate) - by function", function() { expect(req.getHeader('della')).to.be.undefined; }); - test("req.headerList.delete(object) - by object", function() { + test("req.headerList.remove(object) - by object", function() { expect(req.getHeader('x-custom')).to.be.undefined; }); - test("req.headerList.delete does not affect other headers", function() { + test("req.headerList.remove does not affect other headers", function() { expect(req.getHeader('x-extra')).to.equal('extra-value'); }); } diff --git a/packages/bruno-tests/collection/scripting/api/res/headerList/iteration-methods.bru b/packages/bruno-tests/collection/scripting/api/res/headerList/iteration-methods.bru index 295eb7b75ea..08eb19d5ce1 100644 --- a/packages/bruno-tests/collection/scripting/api/res/headerList/iteration-methods.bru +++ b/packages/bruno-tests/collection/scripting/api/res/headerList/iteration-methods.bru @@ -21,9 +21,9 @@ assert { } tests { - test("res.headerList.forEach(fn)", function() { + test("res.headerList.each(fn)", function() { const keys = []; - res.headerList.forEach((header) => { + res.headerList.each((header) => { keys.push(header.key); }); expect(keys).to.include('x-powered-by');