Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions lib/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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':
Expand Down Expand Up @@ -86,7 +82,7 @@ function createHeaders(headers = {}) {
}
);
default:
return target[prop];
return target[typeof prop === 'string' ? prop.toLowerCase() : prop];
}
},
set(target, prop, value) {
Expand Down
15 changes: 3 additions & 12 deletions lib/mockRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 : {};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
};

/**
Expand Down
87 changes: 47 additions & 40 deletions test/lib/headers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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', () => {
Expand All @@ -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;
Expand All @@ -78,60 +85,60 @@ 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;
});
});

describe('#set()', () => {
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');
});
});

describe('#append()', () => {
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;
Expand All @@ -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 = {};
Expand All @@ -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() {
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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);
Expand Down
35 changes: 26 additions & 9 deletions test/lib/mockRequest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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');
});
});

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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');
});
});

Expand Down