refactor: revert HeaderList method names to PropertyList conventions (#7931)

* refactor: update headerList methods and translations for consistency

- Renamed methods in req.headerList and res.headerList from 'forEach' to 'each' for consistency with the new API.
- Updated method translations in the Postman converters to reflect the new method names: 'append' to 'add', 'set' to 'upsert', and 'delete' to 'remove'.
- Adjusted related tests to ensure they validate the new method names and functionality.
- Removed deprecated test cases for 'append' and 'set', replacing them with tests for 'add' and 'upsert'.
- Enhanced documentation to clarify the changes in method names and their usage.

* test: add new tests for HeaderList methods and behavior

- Introduced tests to verify that the 'idx' property is undefined in HeaderList, ensuring compliance with the updated API.
- Added tests to confirm that positional methods (prepend, insert, insertAfter) do not exist in HeaderList, reflecting the recent refactor.
- Implemented a test to check that the two-argument form of the 'add' method correctly overwrites existing headers, enhancing the robustness of header management tests.
This commit is contained in:
sanish chirayath
2026-05-06 22:55:21 +05:30
committed by GitHub
parent d39d5ef575
commit f8bf1460bd
19 changed files with 203 additions and 193 deletions

View File

@@ -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()',

View File

@@ -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(',

View File

@@ -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',

View File

@@ -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]] : []);
}
},

View File

@@ -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"});');
});

View File

@@ -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', () => {

View File

@@ -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', () => {

View File

@@ -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) {

View File

@@ -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} }`);
}
};

View File

@@ -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} }`);
}
};

View File

@@ -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 };

View File

@@ -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');
});

View File

@@ -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');
});
}

View File

@@ -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');
});
}

View File

@@ -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;
});

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');
});
}

View File

@@ -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');