Skip to content

Commit cc57cb7

Browse files
authored
test: add Headers coverage and benchmark
Add WHATWG Headers unit tests and a fetch/headers benchmark so Node can track the API after the implementation change lands in undici. Refs: nodejs/undici#5699 Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com> PR-URL: #65365 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
1 parent 3ff8c2d commit cc57cb7

3 files changed

Lines changed: 346 additions & 0 deletions

File tree

benchmark/fetch/headers.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
4+
const bench = common.createBenchmark(main, {
5+
n: [1e5],
6+
method: [
7+
'construct-empty',
8+
'construct-object',
9+
'construct-headers',
10+
'get',
11+
'get-common',
12+
'set',
13+
'append',
14+
'has',
15+
'delete',
16+
'iterate',
17+
],
18+
});
19+
20+
const objectInit = {
21+
'Accept': 'application/json',
22+
'Content-Type': 'text/plain',
23+
'User-Agent': 'benchmark',
24+
'Authorization': 'Bearer token',
25+
'Cookie': 'a=1',
26+
'X-Request-Id': 'abc',
27+
'Cache-Control': 'no-cache',
28+
'Host': 'example.com',
29+
};
30+
31+
function main({ n, method }) {
32+
const headers = new Headers(objectInit);
33+
const copySource = new Headers(objectInit);
34+
let result;
35+
36+
bench.start();
37+
switch (method) {
38+
case 'construct-empty':
39+
for (let i = 0; i < n; i++)
40+
new Headers();
41+
break;
42+
case 'construct-object':
43+
for (let i = 0; i < n; i++)
44+
new Headers(objectInit);
45+
break;
46+
case 'construct-headers':
47+
for (let i = 0; i < n; i++)
48+
new Headers(copySource);
49+
break;
50+
case 'get':
51+
for (let i = 0; i < n; i++)
52+
result = headers.get('x-request-id');
53+
break;
54+
case 'get-common':
55+
for (let i = 0; i < n; i++)
56+
result = headers.get('content-type');
57+
break;
58+
case 'set':
59+
for (let i = 0; i < n; i++)
60+
headers.set('x-count', i);
61+
break;
62+
case 'append':
63+
for (let i = 0; i < n; i++) {
64+
const current = new Headers();
65+
current.append('Accept', 'text/html');
66+
current.append('X-Custom', i);
67+
}
68+
break;
69+
case 'has':
70+
for (let i = 0; i < n; i++)
71+
result = headers.has('authorization');
72+
break;
73+
case 'delete': {
74+
for (let i = 0; i < n; i++) {
75+
const current = new Headers(objectInit);
76+
current.delete('content-type');
77+
}
78+
break;
79+
}
80+
case 'iterate':
81+
for (let i = 0; i < n; i++) {
82+
for (const entry of headers)
83+
result = entry;
84+
}
85+
break;
86+
default:
87+
throw new Error(`Unexpected method "${method}"`);
88+
}
89+
bench.end(n);
90+
91+
// Keep a live use so V8 cannot DCE the loop.
92+
if (result === Symbol.for('benchmark-never'))
93+
throw new Error('unreachable');
94+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const runBenchmark = require('../common/benchmark');
6+
7+
runBenchmark('fetch', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 });
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
'use strict';
2+
3+
// Tests below are not from WPT.
4+
5+
require('../common');
6+
const assert = require('assert');
7+
const util = require('util');
8+
9+
{
10+
const headers = new Headers();
11+
assert.strictEqual(headers.get('content-type'), null);
12+
assert.strictEqual(headers.has('content-type'), false);
13+
assert.deepStrictEqual([...headers], []);
14+
assert.deepStrictEqual(headers.getSetCookie(), []);
15+
}
16+
17+
{
18+
const headers = new Headers({
19+
'Content-Type': 'text/plain',
20+
'Accept': 'application/json',
21+
'X-Custom': '1',
22+
});
23+
assert.strictEqual(headers.get('content-type'), 'text/plain');
24+
assert.strictEqual(headers.get('Content-Type'), 'text/plain');
25+
assert.strictEqual(headers.get('ACCEPT'), 'application/json');
26+
assert.ok(headers.has('accept'));
27+
assert.deepStrictEqual([...headers], [
28+
['accept', 'application/json'],
29+
['content-type', 'text/plain'],
30+
['x-custom', '1'],
31+
]);
32+
}
33+
34+
{
35+
const headers = new Headers([
36+
['X-A', '1'],
37+
['x-b', '2'],
38+
['X-A', '3'],
39+
]);
40+
assert.strictEqual(headers.get('x-a'), '1, 3');
41+
assert.deepStrictEqual([...headers], [
42+
['x-a', '1, 3'],
43+
['x-b', '2'],
44+
]);
45+
}
46+
47+
{
48+
const source = new Headers({ 'Content-Type': 'text/html' });
49+
source.append('Set-Cookie', 'a=b');
50+
source.append('Set-Cookie', 'c=d');
51+
const copy = new Headers(source);
52+
assert.strictEqual(copy.get('content-type'), 'text/html');
53+
assert.deepStrictEqual(copy.getSetCookie(), ['a=b', 'c=d']);
54+
assert.deepStrictEqual([...copy], [...source]);
55+
copy.append('X-Copy', 'yes');
56+
assert.strictEqual(source.has('x-copy'), false);
57+
source.append('Set-Cookie', 'e=f');
58+
assert.deepStrictEqual(copy.getSetCookie(), ['a=b', 'c=d']);
59+
}
60+
61+
{
62+
const headers = new Headers();
63+
headers.append('Accept', 'text/html');
64+
headers.append('accept', 'application/json');
65+
assert.strictEqual(headers.get('ACCEPT'), 'text/html, application/json');
66+
headers.set('ACCEPT', 'image/png');
67+
assert.strictEqual(headers.get('accept'), 'image/png');
68+
headers.delete('Accept');
69+
assert.strictEqual(headers.has('accept'), false);
70+
}
71+
72+
{
73+
const headers = new Headers();
74+
headers.append('Cookie', 'a=1');
75+
headers.append('cookie', 'b=2');
76+
assert.strictEqual(headers.get('cookie'), 'a=1; b=2');
77+
}
78+
79+
{
80+
const headers = new Headers();
81+
headers.append('set-cookie', 'a=b');
82+
headers.append('Set-Cookie', 'c=d');
83+
assert.deepStrictEqual(headers.getSetCookie(), ['a=b', 'c=d']);
84+
const cloned = headers.getSetCookie();
85+
cloned.push('e=f');
86+
assert.deepStrictEqual(headers.getSetCookie(), ['a=b', 'c=d']);
87+
headers.set('set-cookie', 'only=one');
88+
assert.deepStrictEqual(headers.getSetCookie(), ['only=one']);
89+
headers.delete('SET-COOKIE');
90+
assert.deepStrictEqual(headers.getSetCookie(), []);
91+
}
92+
93+
{
94+
const headers = new Headers();
95+
headers.set('a', ' value ');
96+
assert.strictEqual(headers.get('a'), 'value');
97+
headers.set('b', '\r\n\t trimmed\t\n');
98+
assert.strictEqual(headers.get('b'), 'trimmed');
99+
headers.set('c', '\r');
100+
assert.strictEqual(headers.get('c'), '');
101+
headers.set('d', '\n');
102+
assert.strictEqual(headers.get('d'), '');
103+
}
104+
105+
{
106+
const headers = new Headers();
107+
headers.set('a', ['b', 'c']);
108+
assert.strictEqual(headers.get('a'), 'b,c');
109+
headers.set('b', null);
110+
assert.strictEqual(headers.get('b'), 'null');
111+
headers.set('c', 1);
112+
assert.strictEqual(headers.get('c'), '1');
113+
}
114+
115+
{
116+
const headers = new Headers({
117+
c: '5',
118+
b: ['3', '4'],
119+
a: ['1', '2'],
120+
});
121+
assert.deepStrictEqual([...headers.entries()], [
122+
['a', '1,2'],
123+
['b', '3,4'],
124+
['c', '5'],
125+
]);
126+
}
127+
128+
{
129+
const init = [
130+
['foo', '123'],
131+
['bar', '456'],
132+
];
133+
const headers = new Headers(init);
134+
for (const [key, val] of headers) {
135+
headers.delete(key);
136+
headers.set(`x-${key}`, val);
137+
}
138+
assert.deepStrictEqual([...headers], [
139+
['foo', '123'],
140+
['x-x-bar', '456'],
141+
]);
142+
}
143+
144+
{
145+
const headers = new Headers([
146+
['b', '2'],
147+
['c', '3'],
148+
['e', '5'],
149+
]);
150+
headers.append('d', '4');
151+
headers.append('a', '1');
152+
headers.append('f', '6');
153+
headers.append('c', '7');
154+
headers.append('abc', '8');
155+
assert.deepStrictEqual([...headers], [
156+
['a', '1'],
157+
['abc', '8'],
158+
['b', '2'],
159+
['c', '3, 7'],
160+
['d', '4'],
161+
['e', '5'],
162+
['f', '6'],
163+
]);
164+
}
165+
166+
{
167+
const headers = new Headers({ 'Content-Type': 'application/json' });
168+
headers.set('Authorization', 'Bearer token');
169+
assert.strictEqual(
170+
util.inspect(headers, { depth: 1 }),
171+
"Headers { 'Content-Type': 'application/json', Authorization: 'Bearer token' }",
172+
);
173+
}
174+
175+
{
176+
const headers = new Headers();
177+
assert.throws(() => headers.get(), TypeError);
178+
assert.throws(() => headers.has(), TypeError);
179+
assert.throws(() => headers.delete(), TypeError);
180+
assert.throws(() => headers.append('a'), TypeError);
181+
assert.throws(() => headers.set('a'), TypeError);
182+
assert.throws(() => headers.append('invalid @ name', 'x'), TypeError);
183+
assert.throws(() => headers.set('a', 'a\nb'), TypeError);
184+
assert.throws(() => headers.set('a', 'a\rb'), TypeError);
185+
assert.throws(() => headers.set('a', 'a\0b'), TypeError);
186+
assert.throws(() => headers.set(Symbol('x'), 'y'), TypeError);
187+
assert.throws(() => headers.set('a', Symbol('y')), TypeError);
188+
assert.throws(() => headers.set('', 'x'), TypeError);
189+
assert.throws(() => headers.set('a', 'héllo\u0100'), TypeError);
190+
assert.throws(() => new Headers(1), TypeError);
191+
assert.throws(() => new Headers('1'), TypeError);
192+
assert.throws(() => new Headers([['undici', 'fetch'], ['fetch']]), TypeError);
193+
}
194+
195+
{
196+
assert.throws(() => Headers.prototype.get.call(null, 'a'), {
197+
name: 'TypeError',
198+
code: 'ERR_INVALID_THIS',
199+
});
200+
assert.throws(() => Headers.prototype.append.call({}, 'a', 'b'), {
201+
name: 'TypeError',
202+
code: 'ERR_INVALID_THIS',
203+
});
204+
}
205+
206+
{
207+
assert.strictEqual(Headers.prototype.append.length, 2);
208+
assert.strictEqual(Headers.prototype.constructor.length, 0);
209+
assert.strictEqual(Headers.prototype.delete.length, 1);
210+
assert.strictEqual(Headers.prototype.get.length, 1);
211+
assert.strictEqual(Headers.prototype.has.length, 1);
212+
assert.strictEqual(Headers.prototype.set.length, 2);
213+
assert.strictEqual(Headers.prototype.entries, Headers.prototype[Symbol.iterator]);
214+
assert.strictEqual(Headers.prototype[Symbol.toStringTag], 'Headers');
215+
assert.strictEqual(Object.prototype.toString.call(Headers.prototype), '[object Headers]');
216+
}
217+
218+
{
219+
const headers = new Headers();
220+
headers.set('content-type', 'text/plain');
221+
assert.strictEqual(headers.delete('content-type'), undefined);
222+
assert.strictEqual(headers.delete('missing'), undefined);
223+
assert.strictEqual(headers.set('a', 'b'), undefined);
224+
}
225+
226+
{
227+
const headers = new Headers();
228+
for (const name of [
229+
'content-type',
230+
'accept',
231+
'user-agent',
232+
'cache-control',
233+
'set-cookie',
234+
]) {
235+
headers.set(name, 'value');
236+
assert.strictEqual(headers.get(name), 'value');
237+
assert.ok(headers.has(name));
238+
}
239+
}
240+
241+
{
242+
const headers = new Headers();
243+
headers.append('fhqwhgads', `a${'\t'.repeat(1000)}a`);
244+
assert.strictEqual(headers.get('fhqwhgads'), `a${'\t'.repeat(1000)}a`);
245+
}

0 commit comments

Comments
 (0)