Skip to content

Commit 6c3f45d

Browse files
authored
Fix direct access to the headers (#319)
Fixes #318
1 parent 137b5e2 commit 6c3f45d

File tree

4 files changed

+79
-68
lines changed

4 files changed

+79
-68
lines changed

lib/headers.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* (https://developer.mozilla.org/en-US/docs/Web/API/Headers)
44
* while maintaining compatibility with Express.js style header access.
55
*/
6+
const utils = require('./utils');
67

78
/**
89
* Creates a Headers object that implements both Express.js style access
@@ -12,13 +13,8 @@
1213
* @returns {HeaderWebAPI} - A proxy that implements the HeaderWebAPI interface
1314
*/
1415
function createHeaders(headers = {}) {
15-
return new Proxy(headers, {
16+
return new Proxy(utils.convertKeysToLowerCase(headers), {
1617
get(target, prop) {
17-
// Direct property access for Express.js style
18-
if (typeof prop === 'string' && prop in target) {
19-
return target[prop];
20-
}
21-
2218
// Handle Headers interface methods
2319
switch (prop) {
2420
case 'get':
@@ -86,7 +82,7 @@ function createHeaders(headers = {}) {
8682
}
8783
);
8884
default:
89-
return target[prop];
85+
return target[typeof prop === 'string' ? prop.toLowerCase() : prop];
9086
}
9187
},
9288
set(target, prop, value) {

lib/mockRequest.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ const accepts = require('accepts');
3434
const parseRange = require('range-parser');
3535
let { EventEmitter } = require('events');
3636
const querystring = require('querystring');
37-
const utils = require('./utils');
3837
const { createHeaders } = require('./headers');
3938

4039
const standardRequestOptions = [
@@ -76,8 +75,7 @@ function createRequest(options = {}) {
7675
}
7776

7877
// Create headers using the Headers.js module
79-
const originalHeaders = options.headers ? utils.convertKeysToLowerCase(options.headers) : {};
80-
mockRequest.headers = createHeaders(originalHeaders);
78+
mockRequest.headers = createHeaders(options.headers);
8179

8280
mockRequest.body = options.body ? options.body : {};
8381
mockRequest.query = options.query ? options.query : {};
@@ -132,14 +130,7 @@ function createRequest(options = {}) {
132130
* @api public
133131
*/
134132
mockRequest.getHeader = function getHeader(name) {
135-
const headerName = name.toLowerCase();
136-
switch (headerName) {
137-
case 'referer':
138-
case 'referrer':
139-
return mockRequest.headers.referrer || mockRequest.headers.referer;
140-
default:
141-
return mockRequest.headers[headerName];
142-
}
133+
return mockRequest.headers.get(name);
143134
};
144135
mockRequest.header = mockRequest.getHeader;
145136
mockRequest.get = mockRequest.getHeader;
@@ -375,7 +366,7 @@ function createRequest(options = {}) {
375366
* @param value The value associated with the variable
376367
*/
377368
mockRequest._setHeadersVariable = function _setHeadersVariable(variable, value) {
378-
mockRequest.headers[variable.toLowerCase()] = value;
369+
mockRequest.headers[variable] = value;
379370
};
380371

381372
/**

test/lib/headers.spec.js

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,24 @@ describe('Headers', () => {
1616
});
1717

1818
it('should initialize with provided headers', () => {
19-
const initialHeaders = { 'content-type': 'application/json' };
19+
const initialHeaders = { 'Content-Type': 'application/json' };
2020
const headers = createHeaders(initialHeaders);
2121

22-
expect(headers['content-type']).to.equal('application/json');
22+
expect(headers['Content-Type']).to.equal('application/json');
23+
});
24+
25+
it('should allow to directly access headers', () => {
26+
const headers = createHeaders();
27+
headers['Content-Type'] = 'application/json';
28+
29+
expect(headers['Content-Type']).to.equal('application/json');
2330
});
2431
});
2532

2633
describe('Headers Web API Methods', () => {
2734
describe('#get()', () => {
2835
it('should get a header value', () => {
29-
const headers = createHeaders({ 'content-type': 'application/json' });
36+
const headers = createHeaders({ 'Content-Type': 'application/json' });
3037

3138
expect(headers.get('content-type')).to.equal('application/json');
3239
expect(headers.get('Content-Type')).to.equal('application/json');
@@ -35,7 +42,7 @@ describe('Headers', () => {
3542
it('should return undefined for non-existent headers', () => {
3643
const headers = createHeaders();
3744

38-
expect(headers.get('content-type')).to.be.undefined;
45+
expect(headers.get('cContent-Type')).to.be.undefined;
3946
});
4047

4148
it('should handle the referer/referrer special case', () => {
@@ -49,27 +56,27 @@ describe('Headers', () => {
4956

5057
describe('#getAll()', () => {
5158
it('should get all values for a header as an array', () => {
52-
const headers = createHeaders({ 'set-cookie': ['cookie1=value1', 'cookie2=value2'] });
59+
const headers = createHeaders({ 'Set-Cookie': ['cookie1=value1', 'cookie2=value2'] });
5360

54-
expect(headers.getAll('set-cookie')).to.deep.equal(['cookie1=value1', 'cookie2=value2']);
61+
expect(headers.getAll('Set-Cookie')).to.deep.equal(['cookie1=value1', 'cookie2=value2']);
5562
});
5663

5764
it('should return a single value as an array', () => {
58-
const headers = createHeaders({ 'content-type': 'application/json' });
65+
const headers = createHeaders({ 'Content-Type': 'application/json' });
5966

60-
expect(headers.getAll('content-type')).to.deep.equal(['application/json']);
67+
expect(headers.getAll('Content-Type')).to.deep.equal(['application/json']);
6168
});
6269

6370
it('should return an empty array for non-existent headers', () => {
6471
const headers = createHeaders();
6572

66-
expect(headers.getAll('content-type')).to.deep.equal([]);
73+
expect(headers.getAll('Content-Type')).to.deep.equal([]);
6774
});
6875
});
6976

7077
describe('#has()', () => {
7178
it('should check if a header exists', () => {
72-
const headers = createHeaders({ 'content-type': 'application/json' });
79+
const headers = createHeaders({ 'Content-Type': 'application/json' });
7380

7481
expect(headers.has('content-type')).to.be.true;
7582
expect(headers.has('Content-Type')).to.be.true;
@@ -78,60 +85,60 @@ describe('Headers', () => {
7885
it('should return false for non-existent headers', () => {
7986
const headers = createHeaders();
8087

81-
expect(headers.has('content-type')).to.be.false;
88+
expect(headers.has('Content-Type')).to.be.false;
8289
});
8390
});
8491

8592
describe('#set()', () => {
8693
it('should set a header value', () => {
8794
const headers = createHeaders();
8895

89-
headers.set('content-type', 'application/json');
90-
expect(headers['content-type']).to.equal('application/json');
96+
headers.set('Content-Type', 'application/json');
97+
expect(headers['Content-Type']).to.equal('application/json');
9198
});
9299

93100
it('should overwrite existing headers', () => {
94-
const headers = createHeaders({ 'content-type': 'text/html' });
101+
const headers = createHeaders({ 'Content-Type': 'text/html' });
95102

96103
headers.set('Content-Type', 'application/json');
97-
expect(headers['content-type']).to.equal('application/json');
104+
expect(headers['Content-Type']).to.equal('application/json');
98105
});
99106
});
100107

101108
describe('#append()', () => {
102109
it('should append a value to a non-existent header', () => {
103110
const headers = createHeaders();
104111

105-
headers.append('content-type', 'application/json');
106-
expect(headers['content-type']).to.equal('application/json');
112+
headers.append('Content-Type', 'application/json');
113+
expect(headers['Content-Type']).to.equal('application/json');
107114
});
108115

109116
it('should convert a single value to an array when appending', () => {
110-
const headers = createHeaders({ accept: 'text/html' });
117+
const headers = createHeaders({ Accept: 'text/html' });
111118

112-
headers.append('accept', 'application/json');
113-
expect(headers.accept).to.deep.equal(['text/html', 'application/json']);
119+
headers.append('Accept', 'application/json');
120+
expect(headers.Accept).to.deep.equal(['text/html', 'application/json']);
114121
});
115122

116123
it('should append to an existing array of values', () => {
117-
const headers = createHeaders({ 'set-cookie': ['cookie1=value1'] });
124+
const headers = createHeaders({ 'Set-Cookie': ['cookie1=value1'] });
118125

119-
headers.append('set-cookie', 'cookie2=value2');
120-
expect(headers['set-cookie']).to.deep.equal(['cookie1=value1', 'cookie2=value2']);
126+
headers.append('Set-Cookie', 'cookie2=value2');
127+
expect(headers['Set-Cookie']).to.deep.equal(['cookie1=value1', 'cookie2=value2']);
121128
});
122129
});
123130

124131
describe('#delete()', () => {
125132
it('should delete a header', () => {
126-
const headers = createHeaders({ 'content-type': 'application/json' });
133+
const headers = createHeaders({ 'Content-Type': 'application/json' });
127134

128-
headers.delete('content-type');
129-
expect(headers['content-type']).to.be.undefined;
130-
expect('content-type' in headers).to.be.false;
135+
headers.delete('Content-Type');
136+
expect(headers['Content-Type']).to.be.undefined;
137+
expect('Content-Type' in headers).to.be.false;
131138
});
132139

133140
it('should handle case-insensitive deletion', () => {
134-
const headers = createHeaders({ 'content-type': 'application/json' });
141+
const headers = createHeaders({ 'Content-Type': 'application/json' });
135142

136143
headers.delete('Content-Type');
137144
expect('content-type' in headers).to.be.false;
@@ -141,9 +148,9 @@ describe('Headers', () => {
141148
describe('#forEach()', () => {
142149
it('should iterate over all headers', () => {
143150
const headers = createHeaders({
144-
'content-type': 'application/json',
145-
accept: 'text/html',
146-
'x-custom': 'custom-value'
151+
'Content-Type': 'application/json',
152+
Accept: 'text/html',
153+
'X-Custom': 'custom-value'
147154
});
148155

149156
const result = {};
@@ -159,7 +166,7 @@ describe('Headers', () => {
159166
});
160167

161168
it('should respect thisArg parameter', () => {
162-
const headers = createHeaders({ 'content-type': 'application/json' });
169+
const headers = createHeaders({ 'Content-Type': 'application/json' });
163170
const context = { value: 'context' };
164171

165172
headers.forEach(function iterator() {
@@ -172,8 +179,8 @@ describe('Headers', () => {
172179
describe('Iterable Interface', () => {
173180
it('should implement entries() iterator', () => {
174181
const headers = createHeaders({
175-
'content-type': 'application/json',
176-
accept: 'text/html'
182+
'Content-Type': 'application/json',
183+
Accept: 'text/html'
177184
});
178185

179186
const entries = Array.from(headers.entries());
@@ -183,8 +190,8 @@ describe('Headers', () => {
183190

184191
it('should implement keys() iterator', () => {
185192
const headers = createHeaders({
186-
'content-type': 'application/json',
187-
accept: 'text/html'
193+
'Content-Type': 'application/json',
194+
Accept: 'text/html'
188195
});
189196

190197
const keys = Array.from(headers.keys());
@@ -194,8 +201,8 @@ describe('Headers', () => {
194201

195202
it('should implement values() iterator', () => {
196203
const headers = createHeaders({
197-
'content-type': 'application/json',
198-
accept: 'text/html'
204+
'Content-Type': 'application/json',
205+
Accept: 'text/html'
199206
});
200207

201208
const values = Array.from(headers.values());
@@ -205,8 +212,8 @@ describe('Headers', () => {
205212

206213
it('should be iterable with Symbol.iterator', () => {
207214
const headers = createHeaders({
208-
'content-type': 'application/json',
209-
accept: 'text/html'
215+
'Content-Type': 'application/json',
216+
Accept: 'text/html'
210217
});
211218

212219
const entries = Array.from(headers);

test/lib/mockRequest.spec.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,17 @@ describe('mockRequest', () => {
212212
expect(request.getHeader('KEY2')).to.equal('value2');
213213
});
214214

215+
it('should set .headers directly and be accessible via get() and header() case-insensitively', () => {
216+
const request = mockRequest.createRequest();
217+
request.headers.KEY1 = 'value1';
218+
219+
expect(request.header('KEY1')).to.equal('value1');
220+
expect(request.get('KEY1')).to.equal('value1');
221+
expect(request.headers.get('KEY1')).to.equal('value1');
222+
expect(request.getHeader('KEY1')).to.equal('value1');
223+
expect(request.headers.KEY1).to.equal('value1');
224+
});
225+
215226
it('should set .body to options.body', () => {
216227
const options = {
217228
body: {
@@ -304,6 +315,7 @@ describe('mockRequest', () => {
304315
expect(request.header('key')).to.equal('value');
305316
expect(request.headers.get('key')).to.equal('value');
306317
expect(request.getHeader('key')).to.equal('value');
318+
expect(request.headers.key).to.equal('value');
307319
});
308320

309321
it('should return referer, when request as referrer', () => {
@@ -318,6 +330,9 @@ describe('mockRequest', () => {
318330
expect(request.header('referrer')).to.equal('value');
319331
expect(request.headers.get('referrer')).to.equal('value');
320332
expect(request.getHeader('referrer')).to.equal('value');
333+
334+
// direct access keeps the original name
335+
expect(request.headers.referer).to.equal('value');
321336
});
322337

323338
it('should return referrer, when request as referer', () => {
@@ -332,6 +347,9 @@ describe('mockRequest', () => {
332347
expect(request.header('referer')).to.equal('value');
333348
expect(request.headers.get('referer')).to.equal('value');
334349
expect(request.getHeader('referer')).to.equal('value');
350+
351+
// direct access keeps the original name
352+
expect(request.headers.referrer).to.equal('value');
335353
});
336354

337355
it('should not return header, when not set', () => {
@@ -340,6 +358,7 @@ describe('mockRequest', () => {
340358
expect(request.header('key')).to.be.a('undefined');
341359
expect(request.headers.get('key')).to.be.a('undefined');
342360
expect(request.getHeader('key')).to.be.a('undefined');
361+
expect(request.headers.key).to.be.a('undefined');
343362
});
344363
});
345364

@@ -570,6 +589,7 @@ describe('mockRequest', () => {
570589
expect(request.get('key')).to.be.a('undefined');
571590
expect(request.header('key')).to.be.a('undefined');
572591
expect(request.headers.get('key')).to.be.a('undefined');
592+
expect(request.headers.key).to.be.a('undefined');
573593
});
574594

575595
it('should return defaultValue, when not found in params/body/query', () => {
@@ -649,16 +669,13 @@ describe('mockRequest', () => {
649669
describe('._setHeadersVariable()', () => {
650670
it('should set header, when called with key and value', () => {
651671
const request = mockRequest.createRequest();
652-
request._setHeadersVariable('key', 'value');
653-
expect(request.get('key')).to.equal('value');
654-
expect(request.header('key')).to.equal('value');
655-
expect(request.headers.get('key')).to.equal('value');
656-
expect(request.getHeader('key')).to.equal('value');
657-
});
672+
request._setHeadersVariable('Key', 'value');
658673

659-
it('should throw an error, when called with missing arguments', () => {
660-
const request = mockRequest.createRequest();
661-
expect(request._setHeadersVariable).to.throw();
674+
expect(request.get('Key')).to.equal('value');
675+
expect(request.header('Key')).to.equal('value');
676+
expect(request.headers.get('Key')).to.equal('value');
677+
expect(request.getHeader('Key')).to.equal('value');
678+
expect(request.headers.Key).to.equal('value');
662679
});
663680
});
664681

0 commit comments

Comments
 (0)