diff --git a/lib/headers.js b/lib/headers.js index 47f8a60..2d8c96e 100644 --- a/lib/headers.js +++ b/lib/headers.js @@ -3,6 +3,7 @@ * (https://developer.mozilla.org/en-US/docs/Web/API/Headers) * while maintaining compatibility with Express.js style header access. */ +const utils = require('./utils'); /** * Creates a Headers object that implements both Express.js style access @@ -12,13 +13,8 @@ * @returns {HeaderWebAPI} - A proxy that implements the HeaderWebAPI interface */ function createHeaders(headers = {}) { - return new Proxy(headers, { + return new Proxy(utils.convertKeysToLowerCase(headers), { get(target, prop) { - // Direct property access for Express.js style - if (typeof prop === 'string' && prop in target) { - return target[prop]; - } - // Handle Headers interface methods switch (prop) { case 'get': @@ -86,7 +82,7 @@ function createHeaders(headers = {}) { } ); default: - return target[prop]; + return target[typeof prop === 'string' ? prop.toLowerCase() : prop]; } }, set(target, prop, value) { diff --git a/lib/mockRequest.js b/lib/mockRequest.js index 02d9425..34de081 100644 --- a/lib/mockRequest.js +++ b/lib/mockRequest.js @@ -34,7 +34,6 @@ const accepts = require('accepts'); const parseRange = require('range-parser'); let { EventEmitter } = require('events'); const querystring = require('querystring'); -const utils = require('./utils'); const { createHeaders } = require('./headers'); const standardRequestOptions = [ @@ -76,8 +75,7 @@ function createRequest(options = {}) { } // Create headers using the Headers.js module - const originalHeaders = options.headers ? utils.convertKeysToLowerCase(options.headers) : {}; - mockRequest.headers = createHeaders(originalHeaders); + mockRequest.headers = createHeaders(options.headers); mockRequest.body = options.body ? options.body : {}; mockRequest.query = options.query ? options.query : {}; @@ -132,14 +130,7 @@ function createRequest(options = {}) { * @api public */ mockRequest.getHeader = function getHeader(name) { - const headerName = name.toLowerCase(); - switch (headerName) { - case 'referer': - case 'referrer': - return mockRequest.headers.referrer || mockRequest.headers.referer; - default: - return mockRequest.headers[headerName]; - } + return mockRequest.headers.get(name); }; mockRequest.header = mockRequest.getHeader; mockRequest.get = mockRequest.getHeader; @@ -375,7 +366,7 @@ function createRequest(options = {}) { * @param value The value associated with the variable */ mockRequest._setHeadersVariable = function _setHeadersVariable(variable, value) { - mockRequest.headers[variable.toLowerCase()] = value; + mockRequest.headers[variable] = value; }; /** diff --git a/test/lib/headers.spec.js b/test/lib/headers.spec.js index 671a0cd..1435690 100644 --- a/test/lib/headers.spec.js +++ b/test/lib/headers.spec.js @@ -16,17 +16,24 @@ describe('Headers', () => { }); it('should initialize with provided headers', () => { - const initialHeaders = { 'content-type': 'application/json' }; + const initialHeaders = { 'Content-Type': 'application/json' }; const headers = createHeaders(initialHeaders); - expect(headers['content-type']).to.equal('application/json'); + expect(headers['Content-Type']).to.equal('application/json'); + }); + + it('should allow to directly access headers', () => { + const headers = createHeaders(); + headers['Content-Type'] = 'application/json'; + + expect(headers['Content-Type']).to.equal('application/json'); }); }); describe('Headers Web API Methods', () => { describe('#get()', () => { it('should get a header value', () => { - const headers = createHeaders({ 'content-type': 'application/json' }); + const headers = createHeaders({ 'Content-Type': 'application/json' }); expect(headers.get('content-type')).to.equal('application/json'); expect(headers.get('Content-Type')).to.equal('application/json'); @@ -35,7 +42,7 @@ describe('Headers', () => { it('should return undefined for non-existent headers', () => { const headers = createHeaders(); - expect(headers.get('content-type')).to.be.undefined; + expect(headers.get('cContent-Type')).to.be.undefined; }); it('should handle the referer/referrer special case', () => { @@ -49,27 +56,27 @@ describe('Headers', () => { describe('#getAll()', () => { it('should get all values for a header as an array', () => { - const headers = createHeaders({ 'set-cookie': ['cookie1=value1', 'cookie2=value2'] }); + const headers = createHeaders({ 'Set-Cookie': ['cookie1=value1', 'cookie2=value2'] }); - expect(headers.getAll('set-cookie')).to.deep.equal(['cookie1=value1', 'cookie2=value2']); + expect(headers.getAll('Set-Cookie')).to.deep.equal(['cookie1=value1', 'cookie2=value2']); }); it('should return a single value as an array', () => { - const headers = createHeaders({ 'content-type': 'application/json' }); + const headers = createHeaders({ 'Content-Type': 'application/json' }); - expect(headers.getAll('content-type')).to.deep.equal(['application/json']); + expect(headers.getAll('Content-Type')).to.deep.equal(['application/json']); }); it('should return an empty array for non-existent headers', () => { const headers = createHeaders(); - expect(headers.getAll('content-type')).to.deep.equal([]); + expect(headers.getAll('Content-Type')).to.deep.equal([]); }); }); describe('#has()', () => { it('should check if a header exists', () => { - const headers = createHeaders({ 'content-type': 'application/json' }); + const headers = createHeaders({ 'Content-Type': 'application/json' }); expect(headers.has('content-type')).to.be.true; expect(headers.has('Content-Type')).to.be.true; @@ -78,7 +85,7 @@ describe('Headers', () => { it('should return false for non-existent headers', () => { const headers = createHeaders(); - expect(headers.has('content-type')).to.be.false; + expect(headers.has('Content-Type')).to.be.false; }); }); @@ -86,15 +93,15 @@ describe('Headers', () => { it('should set a header value', () => { const headers = createHeaders(); - headers.set('content-type', 'application/json'); - expect(headers['content-type']).to.equal('application/json'); + headers.set('Content-Type', 'application/json'); + expect(headers['Content-Type']).to.equal('application/json'); }); it('should overwrite existing headers', () => { - const headers = createHeaders({ 'content-type': 'text/html' }); + const headers = createHeaders({ 'Content-Type': 'text/html' }); headers.set('Content-Type', 'application/json'); - expect(headers['content-type']).to.equal('application/json'); + expect(headers['Content-Type']).to.equal('application/json'); }); }); @@ -102,36 +109,36 @@ describe('Headers', () => { it('should append a value to a non-existent header', () => { const headers = createHeaders(); - headers.append('content-type', 'application/json'); - expect(headers['content-type']).to.equal('application/json'); + headers.append('Content-Type', 'application/json'); + expect(headers['Content-Type']).to.equal('application/json'); }); it('should convert a single value to an array when appending', () => { - const headers = createHeaders({ accept: 'text/html' }); + const headers = createHeaders({ Accept: 'text/html' }); - headers.append('accept', 'application/json'); - expect(headers.accept).to.deep.equal(['text/html', 'application/json']); + headers.append('Accept', 'application/json'); + expect(headers.Accept).to.deep.equal(['text/html', 'application/json']); }); it('should append to an existing array of values', () => { - const headers = createHeaders({ 'set-cookie': ['cookie1=value1'] }); + const headers = createHeaders({ 'Set-Cookie': ['cookie1=value1'] }); - headers.append('set-cookie', 'cookie2=value2'); - expect(headers['set-cookie']).to.deep.equal(['cookie1=value1', 'cookie2=value2']); + headers.append('Set-Cookie', 'cookie2=value2'); + expect(headers['Set-Cookie']).to.deep.equal(['cookie1=value1', 'cookie2=value2']); }); }); describe('#delete()', () => { it('should delete a header', () => { - const headers = createHeaders({ 'content-type': 'application/json' }); + const headers = createHeaders({ 'Content-Type': 'application/json' }); - headers.delete('content-type'); - expect(headers['content-type']).to.be.undefined; - expect('content-type' in headers).to.be.false; + headers.delete('Content-Type'); + expect(headers['Content-Type']).to.be.undefined; + expect('Content-Type' in headers).to.be.false; }); it('should handle case-insensitive deletion', () => { - const headers = createHeaders({ 'content-type': 'application/json' }); + const headers = createHeaders({ 'Content-Type': 'application/json' }); headers.delete('Content-Type'); expect('content-type' in headers).to.be.false; @@ -141,9 +148,9 @@ describe('Headers', () => { describe('#forEach()', () => { it('should iterate over all headers', () => { const headers = createHeaders({ - 'content-type': 'application/json', - accept: 'text/html', - 'x-custom': 'custom-value' + 'Content-Type': 'application/json', + Accept: 'text/html', + 'X-Custom': 'custom-value' }); const result = {}; @@ -159,7 +166,7 @@ describe('Headers', () => { }); it('should respect thisArg parameter', () => { - const headers = createHeaders({ 'content-type': 'application/json' }); + const headers = createHeaders({ 'Content-Type': 'application/json' }); const context = { value: 'context' }; headers.forEach(function iterator() { @@ -172,8 +179,8 @@ describe('Headers', () => { describe('Iterable Interface', () => { it('should implement entries() iterator', () => { const headers = createHeaders({ - 'content-type': 'application/json', - accept: 'text/html' + 'Content-Type': 'application/json', + Accept: 'text/html' }); const entries = Array.from(headers.entries()); @@ -183,8 +190,8 @@ describe('Headers', () => { it('should implement keys() iterator', () => { const headers = createHeaders({ - 'content-type': 'application/json', - accept: 'text/html' + 'Content-Type': 'application/json', + Accept: 'text/html' }); const keys = Array.from(headers.keys()); @@ -194,8 +201,8 @@ describe('Headers', () => { it('should implement values() iterator', () => { const headers = createHeaders({ - 'content-type': 'application/json', - accept: 'text/html' + 'Content-Type': 'application/json', + Accept: 'text/html' }); const values = Array.from(headers.values()); @@ -205,8 +212,8 @@ describe('Headers', () => { it('should be iterable with Symbol.iterator', () => { const headers = createHeaders({ - 'content-type': 'application/json', - accept: 'text/html' + 'Content-Type': 'application/json', + Accept: 'text/html' }); const entries = Array.from(headers); diff --git a/test/lib/mockRequest.spec.ts b/test/lib/mockRequest.spec.ts index 7032d61..3ef173e 100644 --- a/test/lib/mockRequest.spec.ts +++ b/test/lib/mockRequest.spec.ts @@ -212,6 +212,17 @@ describe('mockRequest', () => { expect(request.getHeader('KEY2')).to.equal('value2'); }); + it('should set .headers directly and be accessible via get() and header() case-insensitively', () => { + const request = mockRequest.createRequest(); + request.headers.KEY1 = 'value1'; + + expect(request.header('KEY1')).to.equal('value1'); + expect(request.get('KEY1')).to.equal('value1'); + expect(request.headers.get('KEY1')).to.equal('value1'); + expect(request.getHeader('KEY1')).to.equal('value1'); + expect(request.headers.KEY1).to.equal('value1'); + }); + it('should set .body to options.body', () => { const options = { body: { @@ -304,6 +315,7 @@ describe('mockRequest', () => { expect(request.header('key')).to.equal('value'); expect(request.headers.get('key')).to.equal('value'); expect(request.getHeader('key')).to.equal('value'); + expect(request.headers.key).to.equal('value'); }); it('should return referer, when request as referrer', () => { @@ -318,6 +330,9 @@ describe('mockRequest', () => { expect(request.header('referrer')).to.equal('value'); expect(request.headers.get('referrer')).to.equal('value'); expect(request.getHeader('referrer')).to.equal('value'); + + // direct access keeps the original name + expect(request.headers.referer).to.equal('value'); }); it('should return referrer, when request as referer', () => { @@ -332,6 +347,9 @@ describe('mockRequest', () => { expect(request.header('referer')).to.equal('value'); expect(request.headers.get('referer')).to.equal('value'); expect(request.getHeader('referer')).to.equal('value'); + + // direct access keeps the original name + expect(request.headers.referrer).to.equal('value'); }); it('should not return header, when not set', () => { @@ -340,6 +358,7 @@ describe('mockRequest', () => { expect(request.header('key')).to.be.a('undefined'); expect(request.headers.get('key')).to.be.a('undefined'); expect(request.getHeader('key')).to.be.a('undefined'); + expect(request.headers.key).to.be.a('undefined'); }); }); @@ -570,6 +589,7 @@ describe('mockRequest', () => { expect(request.get('key')).to.be.a('undefined'); expect(request.header('key')).to.be.a('undefined'); expect(request.headers.get('key')).to.be.a('undefined'); + expect(request.headers.key).to.be.a('undefined'); }); it('should return defaultValue, when not found in params/body/query', () => { @@ -649,16 +669,13 @@ describe('mockRequest', () => { describe('._setHeadersVariable()', () => { it('should set header, when called with key and value', () => { const request = mockRequest.createRequest(); - request._setHeadersVariable('key', 'value'); - expect(request.get('key')).to.equal('value'); - expect(request.header('key')).to.equal('value'); - expect(request.headers.get('key')).to.equal('value'); - expect(request.getHeader('key')).to.equal('value'); - }); + request._setHeadersVariable('Key', 'value'); - it('should throw an error, when called with missing arguments', () => { - const request = mockRequest.createRequest(); - expect(request._setHeadersVariable).to.throw(); + expect(request.get('Key')).to.equal('value'); + expect(request.header('Key')).to.equal('value'); + expect(request.headers.get('Key')).to.equal('value'); + expect(request.getHeader('Key')).to.equal('value'); + expect(request.headers.Key).to.equal('value'); }); });