@gjsify/http 0.0.4 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.spec.ts CHANGED
@@ -1,74 +1,1366 @@
1
- import { describe, it, expect } from '@gjsify/unit';
1
+ // Ported from refs/node-test/parallel/test-http-*.js
2
+ // Original: MIT license, Node.js contributors
3
+ import { describe, it, expect, on } from '@gjsify/unit';
4
+ import { Buffer } from 'node:buffer';
2
5
 
3
- import * as http from 'http';
4
- import type { validateHeaderName as gjsifyValidateHeaderName, validateHeaderValue as gjsifyValidateHeaderValue } from './index.js';
6
+ import * as http from 'node:http';
7
+ import type { validateHeaderName as gjsifyValidateHeaderName, validateHeaderValue as gjsifyValidateHeaderValue } from 'node:http';
5
8
 
6
- // TODO: Add types to @types/node
7
9
  const validateHeaderName: typeof gjsifyValidateHeaderName = (http as any).validateHeaderName;
8
10
  const validateHeaderValue: typeof gjsifyValidateHeaderValue = (http as any).validateHeaderValue;
9
11
 
10
12
  export default async () => {
11
13
 
12
- const validNames = ['Content-Type', 'set-cookie', 'alfa-beta'];
13
- const invalidNames = ['@@wdjhgw'];
14
- const expectedErrorMessage = (name: any) => `Header name must be a valid HTTP token ["${name}"]`;
15
-
16
- await describe('http.validateHeaderName', async () => {
17
- await it('should be a function', async () => {
18
- expect(typeof validateHeaderName).toBe("function");
19
- });
20
-
21
- await it('an empty string should be not valid and throw an error', async () => {
22
- expect(() => {
23
- validateHeaderName('');
24
- }).toThrow();
25
- });
26
-
27
- await it('an empty string should be throw an error which is an instance of TypeError', async () => {
28
- try {
29
- validateHeaderName('');
30
- } catch (error) {
31
- expect(error instanceof TypeError).toBeTruthy();
32
- }
33
- });
34
-
35
- await it(`an empty string should be throw an error with the message of "${expectedErrorMessage('')}"`, async () => {
36
- try {
37
- validateHeaderName('');
38
- } catch (error) {
39
- expect(error.message).toBe(expectedErrorMessage(''));
40
- }
41
- });
42
-
43
- await it(`a number should be throw an error with the message of "${expectedErrorMessage(100)}"`, async () => {
44
- try {
45
- validateHeaderName(100 as any);
46
- } catch (error) {
47
- expect(error.message).toBe(expectedErrorMessage(100));
48
- }
49
- });
50
-
51
- for (const validName of validNames) {
52
- await it(`"${validName}" should be valid and not throw any error`, async () => {
53
- expect(() => {
54
- validateHeaderName(validName);
55
- }).not?.toThrow();
56
- });
57
- }
58
-
59
- for (const invalidName of invalidNames) {
60
- await it(`"${invalidName}" should be not valid and throw an error`, async () => {
61
- expect(() => {
62
- validateHeaderName(invalidName);
63
- }).toThrow();
64
- });
65
- }
66
- });
67
-
68
- await describe('http.validateHeaderValue', async () => {
69
- await it('should be a function', async () => {
70
- expect(typeof validateHeaderValue).toBe("function");
71
- });
72
- });
73
-
74
- }
14
+ // --- validateHeaderName ---
15
+ await describe('http.validateHeaderName', async () => {
16
+ await it('should be a function', async () => {
17
+ expect(typeof validateHeaderName).toBe('function');
18
+ });
19
+
20
+ await it('should not throw for valid header names', async () => {
21
+ expect(() => validateHeaderName('Content-Type')).not.toThrow();
22
+ expect(() => validateHeaderName('set-cookie')).not.toThrow();
23
+ expect(() => validateHeaderName('alfa-beta')).not.toThrow();
24
+ expect(() => validateHeaderName('X-Custom-Header')).not.toThrow();
25
+ });
26
+
27
+ await it('should accept single-char header names', async () => {
28
+ expect(() => validateHeaderName('x')).not.toThrow();
29
+ expect(() => validateHeaderName('X')).not.toThrow();
30
+ });
31
+
32
+ await it('should accept header names with special tokens', async () => {
33
+ expect(() => validateHeaderName('x-forwarded-for')).not.toThrow();
34
+ expect(() => validateHeaderName('accept-encoding')).not.toThrow();
35
+ expect(() => validateHeaderName('cache-control')).not.toThrow();
36
+ });
37
+
38
+ await it('should throw TypeError for empty string', async () => {
39
+ let threw = false;
40
+ try {
41
+ validateHeaderName('');
42
+ } catch (error: any) {
43
+ threw = true;
44
+ expect(error instanceof TypeError).toBe(true);
45
+ }
46
+ expect(threw).toBe(true);
47
+ });
48
+
49
+ await it('should throw for invalid characters', async () => {
50
+ expect(() => validateHeaderName('@@wdjhgw')).toThrow();
51
+ });
52
+
53
+ await it('should throw for header name with spaces', async () => {
54
+ expect(() => validateHeaderName('Content Type')).toThrow();
55
+ });
56
+
57
+ await it('should throw for header name with colon', async () => {
58
+ expect(() => validateHeaderName('Content:')).toThrow();
59
+ });
60
+
61
+ await it('should throw for number input', async () => {
62
+ let threw = false;
63
+ try {
64
+ validateHeaderName(100 as any);
65
+ } catch (error: any) {
66
+ threw = true;
67
+ expect(error instanceof TypeError).toBe(true);
68
+ }
69
+ expect(threw).toBe(true);
70
+ });
71
+ });
72
+
73
+ // --- validateHeaderValue ---
74
+ await describe('http.validateHeaderValue', async () => {
75
+ await it('should be a function', async () => {
76
+ expect(typeof validateHeaderValue).toBe('function');
77
+ });
78
+
79
+ await it('should not throw for valid values', async () => {
80
+ expect(() => validateHeaderValue('Content-Type', 'text/html')).not.toThrow();
81
+ expect(() => validateHeaderValue('X-Header', 'some value')).not.toThrow();
82
+ });
83
+
84
+ await it('should accept numeric string values', async () => {
85
+ expect(() => validateHeaderValue('Content-Length', '123')).not.toThrow();
86
+ });
87
+
88
+ await it('should accept empty string value', async () => {
89
+ expect(() => validateHeaderValue('X-Empty', '')).not.toThrow();
90
+ });
91
+
92
+ await it('should accept values with standard ASCII characters', async () => {
93
+ expect(() => validateHeaderValue('X-Test', 'abcdefghijklmnopqrstuvwxyz')).not.toThrow();
94
+ expect(() => validateHeaderValue('X-Test', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')).not.toThrow();
95
+ expect(() => validateHeaderValue('X-Test', '0123456789')).not.toThrow();
96
+ });
97
+ });
98
+
99
+ // --- STATUS_CODES ---
100
+ await describe('http.STATUS_CODES', async () => {
101
+ await it('should be an object', async () => {
102
+ expect(typeof http.STATUS_CODES).toBe('object');
103
+ });
104
+
105
+ await it('should not be null', async () => {
106
+ expect(http.STATUS_CODES).toBeDefined();
107
+ });
108
+
109
+ await it('should contain common status codes', async () => {
110
+ expect(http.STATUS_CODES[200]).toBe('OK');
111
+ expect(http.STATUS_CODES[201]).toBe('Created');
112
+ expect(http.STATUS_CODES[204]).toBe('No Content');
113
+ expect(http.STATUS_CODES[301]).toBe('Moved Permanently');
114
+ expect(http.STATUS_CODES[302]).toBe('Found');
115
+ expect(http.STATUS_CODES[304]).toBe('Not Modified');
116
+ expect(http.STATUS_CODES[400]).toBe('Bad Request');
117
+ expect(http.STATUS_CODES[401]).toBe('Unauthorized');
118
+ expect(http.STATUS_CODES[403]).toBe('Forbidden');
119
+ expect(http.STATUS_CODES[404]).toBe('Not Found');
120
+ expect(http.STATUS_CODES[405]).toBe('Method Not Allowed');
121
+ expect(http.STATUS_CODES[500]).toBe('Internal Server Error');
122
+ expect(http.STATUS_CODES[502]).toBe('Bad Gateway');
123
+ expect(http.STATUS_CODES[503]).toBe('Service Unavailable');
124
+ });
125
+
126
+ await it('should contain informational status codes', async () => {
127
+ expect(http.STATUS_CODES[100]).toBe('Continue');
128
+ expect(http.STATUS_CODES[101]).toBe('Switching Protocols');
129
+ });
130
+
131
+ await it('should contain redirect status codes', async () => {
132
+ expect(http.STATUS_CODES[300]).toBe('Multiple Choices');
133
+ expect(http.STATUS_CODES[307]).toBe('Temporary Redirect');
134
+ expect(http.STATUS_CODES[308]).toBe('Permanent Redirect');
135
+ });
136
+
137
+ await it('should contain client error status codes', async () => {
138
+ expect(http.STATUS_CODES[402]).toBe('Payment Required');
139
+ expect(http.STATUS_CODES[406]).toBe('Not Acceptable');
140
+ expect(http.STATUS_CODES[408]).toBe('Request Timeout');
141
+ expect(http.STATUS_CODES[409]).toBe('Conflict');
142
+ expect(http.STATUS_CODES[410]).toBe('Gone');
143
+ expect(http.STATUS_CODES[413]).toBe('Payload Too Large');
144
+ expect(http.STATUS_CODES[415]).toBe('Unsupported Media Type');
145
+ expect(http.STATUS_CODES[418]).toBe("I'm a Teapot");
146
+ expect(http.STATUS_CODES[429]).toBe('Too Many Requests');
147
+ });
148
+
149
+ await it('should contain server error status codes', async () => {
150
+ expect(http.STATUS_CODES[501]).toBe('Not Implemented');
151
+ expect(http.STATUS_CODES[504]).toBe('Gateway Timeout');
152
+ expect(http.STATUS_CODES[505]).toBe('HTTP Version Not Supported');
153
+ });
154
+
155
+ await it('should contain WebDAV status codes', async () => {
156
+ expect(http.STATUS_CODES[102]).toBe('Processing');
157
+ expect(http.STATUS_CODES[207]).toBe('Multi-Status');
158
+ expect(http.STATUS_CODES[422]).toBe('Unprocessable Entity');
159
+ expect(http.STATUS_CODES[423]).toBe('Locked');
160
+ expect(http.STATUS_CODES[507]).toBe('Insufficient Storage');
161
+ });
162
+
163
+ await it('should return undefined for unknown codes', async () => {
164
+ expect(http.STATUS_CODES[999]).toBeUndefined();
165
+ expect(http.STATUS_CODES[0]).toBeUndefined();
166
+ });
167
+
168
+ await it('should have string values for all keys', async () => {
169
+ for (const key of Object.keys(http.STATUS_CODES)) {
170
+ expect(typeof http.STATUS_CODES[Number(key)]).toBe('string');
171
+ }
172
+ });
173
+ });
174
+
175
+ // --- METHODS ---
176
+ await describe('http.METHODS', async () => {
177
+ await it('should be an array', async () => {
178
+ expect(Array.isArray(http.METHODS)).toBe(true);
179
+ });
180
+
181
+ await it('should contain standard HTTP methods', async () => {
182
+ expect(http.METHODS).toContain('GET');
183
+ expect(http.METHODS).toContain('POST');
184
+ expect(http.METHODS).toContain('PUT');
185
+ expect(http.METHODS).toContain('DELETE');
186
+ expect(http.METHODS).toContain('PATCH');
187
+ expect(http.METHODS).toContain('HEAD');
188
+ expect(http.METHODS).toContain('OPTIONS');
189
+ });
190
+
191
+ await it('should contain WebDAV methods', async () => {
192
+ expect(http.METHODS).toContain('PROPFIND');
193
+ expect(http.METHODS).toContain('PROPPATCH');
194
+ expect(http.METHODS).toContain('MKCOL');
195
+ expect(http.METHODS).toContain('COPY');
196
+ expect(http.METHODS).toContain('MOVE');
197
+ expect(http.METHODS).toContain('LOCK');
198
+ expect(http.METHODS).toContain('UNLOCK');
199
+ });
200
+
201
+ await it('should contain extended methods', async () => {
202
+ expect(http.METHODS).toContain('CONNECT');
203
+ expect(http.METHODS).toContain('TRACE');
204
+ expect(http.METHODS).toContain('M-SEARCH');
205
+ expect(http.METHODS).toContain('PURGE');
206
+ });
207
+
208
+ await it('should be sorted alphabetically', async () => {
209
+ const sorted = [...http.METHODS].sort();
210
+ for (let i = 0; i < http.METHODS.length; i++) {
211
+ expect(http.METHODS[i]).toBe(sorted[i]);
212
+ }
213
+ });
214
+
215
+ await it('should only contain uppercase strings', async () => {
216
+ for (const method of http.METHODS) {
217
+ expect(method).toBe(method.toUpperCase());
218
+ }
219
+ });
220
+
221
+ await it('should have no duplicate entries', async () => {
222
+ const unique = new Set(http.METHODS);
223
+ expect(unique.size).toBe(http.METHODS.length);
224
+ });
225
+ });
226
+
227
+ // --- Module exports ---
228
+ await describe('http module exports', async () => {
229
+ await it('should export maxHeaderSize', async () => {
230
+ expect(http.maxHeaderSize).toBe(16384);
231
+ });
232
+
233
+ await it('should export maxHeaderSize as a number', async () => {
234
+ expect(typeof http.maxHeaderSize).toBe('number');
235
+ });
236
+
237
+ await it('should export Agent', async () => {
238
+ expect(typeof http.Agent).toBe('function');
239
+ });
240
+
241
+ await it('should export globalAgent', async () => {
242
+ expect(http.globalAgent).toBeDefined();
243
+ expect(http.globalAgent.defaultPort).toBe(80);
244
+ });
245
+
246
+ await it('should export createServer', async () => {
247
+ expect(typeof http.createServer).toBe('function');
248
+ });
249
+
250
+ await it('should export IncomingMessage', async () => {
251
+ expect(typeof http.IncomingMessage).toBe('function');
252
+ });
253
+
254
+ await it('should export Server', async () => {
255
+ expect(typeof http.Server).toBe('function');
256
+ });
257
+
258
+ await it('should export ServerResponse', async () => {
259
+ expect(typeof http.ServerResponse).toBe('function');
260
+ });
261
+
262
+ await it('should export OutgoingMessage', async () => {
263
+ expect(typeof (http as any).OutgoingMessage).toBe('function');
264
+ });
265
+
266
+ await it('should export ClientRequest', async () => {
267
+ expect(typeof http.ClientRequest).toBe('function');
268
+ });
269
+
270
+ await it('should export request and get', async () => {
271
+ expect(typeof http.request).toBe('function');
272
+ expect(typeof http.get).toBe('function');
273
+ });
274
+
275
+ await it('should export setMaxIdleHTTPParsers', async () => {
276
+ expect(typeof (http as any).setMaxIdleHTTPParsers).toBe('function');
277
+ // Should not throw
278
+ (http as any).setMaxIdleHTTPParsers(256);
279
+ });
280
+
281
+ await it('should export validateHeaderName and validateHeaderValue', async () => {
282
+ expect(typeof (http as any).validateHeaderName).toBe('function');
283
+ expect(typeof (http as any).validateHeaderValue).toBe('function');
284
+ });
285
+
286
+ await it('should have a default export', async () => {
287
+ expect(http).toBeDefined();
288
+ expect(typeof http).toBe('object');
289
+ });
290
+ });
291
+
292
+ // --- OutgoingMessage ---
293
+ await describe('http.OutgoingMessage', async () => {
294
+ await it('should be constructable', async () => {
295
+ const OutgoingMessage = (http as any).OutgoingMessage;
296
+ const msg = new OutgoingMessage();
297
+ expect(msg).toBeDefined();
298
+ expect(msg.headersSent).toBe(false);
299
+ expect(msg.finished).toBe(false);
300
+ });
301
+
302
+ await it('should support setHeader/getHeader/hasHeader/removeHeader', async () => {
303
+ const OutgoingMessage = (http as any).OutgoingMessage;
304
+ const msg = new OutgoingMessage();
305
+ msg.setHeader('X-Test', 'value');
306
+ expect(msg.getHeader('x-test')).toBe('value');
307
+ expect(msg.hasHeader('x-test')).toBe(true);
308
+ msg.removeHeader('x-test');
309
+ expect(msg.hasHeader('x-test')).toBe(false);
310
+ });
311
+
312
+ await it('should support getHeaderNames and getHeaders', async () => {
313
+ const OutgoingMessage = (http as any).OutgoingMessage;
314
+ const msg = new OutgoingMessage();
315
+ msg.setHeader('Content-Type', 'text/plain');
316
+ msg.setHeader('X-Custom', 'val');
317
+ const names = msg.getHeaderNames();
318
+ expect(names.length).toBe(2);
319
+ const headers = msg.getHeaders();
320
+ expect(headers['content-type']).toBe('text/plain');
321
+ expect(headers['x-custom']).toBe('val');
322
+ });
323
+
324
+ await it('should support appendHeader', async () => {
325
+ const OutgoingMessage = (http as any).OutgoingMessage;
326
+ const msg = new OutgoingMessage();
327
+ msg.setHeader('Set-Cookie', 'a=1');
328
+ msg.appendHeader('Set-Cookie', 'b=2');
329
+ const val = msg.getHeader('set-cookie');
330
+ expect(Array.isArray(val)).toBe(true);
331
+ });
332
+
333
+ await it('should have sendDate property', async () => {
334
+ const OutgoingMessage = (http as any).OutgoingMessage;
335
+ const msg = new OutgoingMessage();
336
+ expect(typeof msg.sendDate).toBe('boolean');
337
+ });
338
+
339
+ await it('should have sendDate as boolean', async () => {
340
+ const OutgoingMessage = (http as any).OutgoingMessage;
341
+ const msg = new OutgoingMessage();
342
+ expect(typeof msg.sendDate).toBe('boolean');
343
+ });
344
+
345
+ await it('should store headers case-insensitively', async () => {
346
+ const OutgoingMessage = (http as any).OutgoingMessage;
347
+ const msg = new OutgoingMessage();
348
+ msg.setHeader('Content-Type', 'text/html');
349
+ expect(msg.getHeader('content-type')).toBe('text/html');
350
+ expect(msg.getHeader('CONTENT-TYPE')).toBe('text/html');
351
+ expect(msg.getHeader('Content-Type')).toBe('text/html');
352
+ });
353
+
354
+ await it('should overwrite existing header with setHeader', async () => {
355
+ const OutgoingMessage = (http as any).OutgoingMessage;
356
+ const msg = new OutgoingMessage();
357
+ msg.setHeader('X-Test', 'first');
358
+ msg.setHeader('X-Test', 'second');
359
+ expect(msg.getHeader('x-test')).toBe('second');
360
+ });
361
+
362
+ await it('should return undefined for non-existent header', async () => {
363
+ const OutgoingMessage = (http as any).OutgoingMessage;
364
+ const msg = new OutgoingMessage();
365
+ expect(msg.getHeader('x-nonexistent')).toBeUndefined();
366
+ });
367
+
368
+ await it('should return false for hasHeader on non-existent header', async () => {
369
+ const OutgoingMessage = (http as any).OutgoingMessage;
370
+ const msg = new OutgoingMessage();
371
+ expect(msg.hasHeader('x-nonexistent')).toBe(false);
372
+ });
373
+
374
+ await it('should handle removeHeader for non-existent header without error', async () => {
375
+ const OutgoingMessage = (http as any).OutgoingMessage;
376
+ const msg = new OutgoingMessage();
377
+ // Should not throw
378
+ msg.removeHeader('x-nonexistent');
379
+ expect(msg.hasHeader('x-nonexistent')).toBe(false);
380
+ });
381
+
382
+ await it('should return empty arrays when no headers set', async () => {
383
+ const OutgoingMessage = (http as any).OutgoingMessage;
384
+ const msg = new OutgoingMessage();
385
+ expect(msg.getHeaderNames().length).toBe(0);
386
+ const headers = msg.getHeaders();
387
+ expect(Object.keys(headers).length).toBe(0);
388
+ });
389
+
390
+ await it('should accept numeric header values via setHeader', async () => {
391
+ const OutgoingMessage = (http as any).OutgoingMessage;
392
+ const msg = new OutgoingMessage();
393
+ msg.setHeader('Content-Length', 42);
394
+ const val = msg.getHeader('content-length');
395
+ // Node.js stores as number, GJS stores as string -- both are valid
396
+ expect(val == 42).toBe(true);
397
+ });
398
+
399
+ await it('should accept array header values via setHeader', async () => {
400
+ const OutgoingMessage = (http as any).OutgoingMessage;
401
+ const msg = new OutgoingMessage();
402
+ msg.setHeader('Set-Cookie', ['a=1', 'b=2']);
403
+ const val = msg.getHeader('set-cookie');
404
+ expect(Array.isArray(val)).toBe(true);
405
+ });
406
+
407
+ await it('should support appendHeader with array value', async () => {
408
+ const OutgoingMessage = (http as any).OutgoingMessage;
409
+ const msg = new OutgoingMessage();
410
+ msg.setHeader('X-Multi', 'first');
411
+ msg.appendHeader('X-Multi', ['second', 'third']);
412
+ const val = msg.getHeader('x-multi');
413
+ expect(Array.isArray(val)).toBe(true);
414
+ });
415
+
416
+ await it('should support appendHeader on non-existent header', async () => {
417
+ const OutgoingMessage = (http as any).OutgoingMessage;
418
+ const msg = new OutgoingMessage();
419
+ msg.appendHeader('X-New', 'value');
420
+ expect(msg.getHeader('x-new')).toBe('value');
421
+ });
422
+
423
+ await it('should have headersSent default to false', async () => {
424
+ const OutgoingMessage = (http as any).OutgoingMessage;
425
+ const msg = new OutgoingMessage();
426
+ expect(msg.headersSent).toBe(false);
427
+ });
428
+
429
+ await it('should have socket property default to null', async () => {
430
+ const OutgoingMessage = (http as any).OutgoingMessage;
431
+ const msg = new OutgoingMessage();
432
+ expect(msg.socket).toBeNull();
433
+ });
434
+ });
435
+
436
+ // --- Agent ---
437
+ await describe('http.Agent', async () => {
438
+ await it('should be constructable', async () => {
439
+ const agent = new http.Agent();
440
+ expect(agent).toBeDefined();
441
+ });
442
+
443
+ await it('should have default properties', async () => {
444
+ const agent = new http.Agent();
445
+ expect(agent.defaultPort).toBe(80);
446
+ expect(agent.maxSockets).toBe(Infinity);
447
+ });
448
+
449
+ await it('should have destroy method', async () => {
450
+ const agent = new http.Agent();
451
+ expect(typeof agent.destroy).toBe('function');
452
+ });
453
+
454
+ await it('should have protocol property', async () => {
455
+ const agent = new http.Agent();
456
+ expect((agent as any).protocol).toBe('http:');
457
+ });
458
+
459
+ await it('should have maxFreeSockets property', async () => {
460
+ const agent = new http.Agent();
461
+ expect((agent as any).maxFreeSockets).toBe(256);
462
+ });
463
+
464
+ await it('should have keepAliveMsecs property', async () => {
465
+ const agent = new http.Agent();
466
+ expect((agent as any).keepAliveMsecs).toBe(1000);
467
+ });
468
+
469
+ await it('should have keepAlive property', async () => {
470
+ const agent = new http.Agent();
471
+ expect((agent as any).keepAlive).toBe(false);
472
+ });
473
+
474
+ await it('should not throw when destroy is called', async () => {
475
+ const agent = new http.Agent();
476
+ expect(() => agent.destroy()).not.toThrow();
477
+ });
478
+
479
+ await it('should be an instance of Agent', async () => {
480
+ const agent = new http.Agent();
481
+ expect(agent instanceof http.Agent).toBe(true);
482
+ });
483
+ });
484
+
485
+ // --- globalAgent ---
486
+ await describe('http.globalAgent', async () => {
487
+ await it('should be an instance of Agent', async () => {
488
+ expect(http.globalAgent instanceof http.Agent).toBe(true);
489
+ });
490
+
491
+ await it('should have defaultPort 80', async () => {
492
+ expect(http.globalAgent.defaultPort).toBe(80);
493
+ });
494
+
495
+ await it('should have maxSockets Infinity', async () => {
496
+ expect(http.globalAgent.maxSockets).toBe(Infinity);
497
+ });
498
+
499
+ await it('should have protocol http:', async () => {
500
+ expect((http.globalAgent as any).protocol).toBe('http:');
501
+ });
502
+
503
+ await it('should have destroy method', async () => {
504
+ expect(typeof http.globalAgent.destroy).toBe('function');
505
+ });
506
+ });
507
+
508
+ // --- IncomingMessage standalone ---
509
+ await describe('http.IncomingMessage standalone', async () => {
510
+ await it('should be constructable', async () => {
511
+ // Node.js requires a socket arg; GJS does not — use (null as any) for compat
512
+ const msg = new (http.IncomingMessage as any)(null);
513
+ expect(msg).toBeDefined();
514
+ });
515
+
516
+ await it('should have httpVersion property', async () => {
517
+ const msg = new (http.IncomingMessage as any)(null);
518
+ // Node.js defaults to null, GJS defaults to '1.1' — both are valid initial values
519
+ expect(msg.httpVersion === null || msg.httpVersion === '1.1').toBe(true);
520
+ });
521
+
522
+ await it('should have httpVersionMajor and httpVersionMinor properties', async () => {
523
+ const msg = new (http.IncomingMessage as any)(null);
524
+ // Node.js defaults to null, GJS defaults to 1
525
+ expect(msg.httpVersionMajor === null || msg.httpVersionMajor === 1).toBe(true);
526
+ expect(msg.httpVersionMinor === null || msg.httpVersionMinor === 1).toBe(true);
527
+ });
528
+
529
+ await it('should have empty headers object', async () => {
530
+ const msg = new (http.IncomingMessage as any)(null);
531
+ expect(typeof msg.headers).toBe('object');
532
+ expect(Object.keys(msg.headers).length).toBe(0);
533
+ });
534
+
535
+ await it('should have empty rawHeaders array', async () => {
536
+ const msg = new (http.IncomingMessage as any)(null);
537
+ expect(Array.isArray(msg.rawHeaders)).toBe(true);
538
+ expect(msg.rawHeaders.length).toBe(0);
539
+ });
540
+
541
+ await it('should have method property', async () => {
542
+ const msg = new (http.IncomingMessage as any)(null);
543
+ // Node.js defaults to null, GJS defaults to undefined
544
+ expect(msg.method === null || msg.method === undefined).toBe(true);
545
+ });
546
+
547
+ await it('should have url property', async () => {
548
+ const msg = new (http.IncomingMessage as any)(null);
549
+ // Node.js defaults to '', GJS defaults to undefined — both are falsy
550
+ expect(!msg.url || msg.url === '').toBe(true);
551
+ });
552
+
553
+ await it('should have statusCode property', async () => {
554
+ const msg = new (http.IncomingMessage as any)(null);
555
+ // Node.js defaults to null, GJS defaults to undefined
556
+ expect(msg.statusCode === null || msg.statusCode === undefined).toBe(true);
557
+ });
558
+
559
+ await it('should have statusMessage property', async () => {
560
+ const msg = new (http.IncomingMessage as any)(null);
561
+ // Node.js defaults to null, GJS defaults to undefined
562
+ expect(msg.statusMessage === null || msg.statusMessage === undefined || msg.statusMessage === '').toBe(true);
563
+ });
564
+
565
+ await it('should have complete default to false', async () => {
566
+ const msg = new (http.IncomingMessage as any)(null);
567
+ expect(msg.complete).toBe(false);
568
+ });
569
+
570
+ await it('should have aborted default to false', async () => {
571
+ const msg = new (http.IncomingMessage as any)(null);
572
+ expect(msg.aborted).toBe(false);
573
+ });
574
+
575
+ await it('should have socket property', async () => {
576
+ const msg = new (http.IncomingMessage as any)(null);
577
+ expect(msg.socket).toBeNull();
578
+ });
579
+
580
+ await it('should have setTimeout method', async () => {
581
+ const msg = new (http.IncomingMessage as any)(null);
582
+ expect(typeof msg.setTimeout).toBe('function');
583
+ });
584
+
585
+ await it('should have destroy method', async () => {
586
+ const msg = new (http.IncomingMessage as any)(null);
587
+ expect(typeof msg.destroy).toBe('function');
588
+ });
589
+
590
+ await it('should be a Readable stream', async () => {
591
+ const msg = new (http.IncomingMessage as any)(null);
592
+ expect(typeof msg.on).toBe('function');
593
+ expect(typeof msg.read).toBe('function');
594
+ expect(typeof msg.pipe).toBe('function');
595
+ });
596
+ });
597
+
598
+ // --- Server round-trip ---
599
+ await describe('http.createServer round-trip', async () => {
600
+ await it('should create a server and handle a GET request', async () => {
601
+ const server = http.createServer((req, res) => {
602
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
603
+ res.end('Hello from server');
604
+ });
605
+
606
+ await new Promise<void>((resolve, reject) => {
607
+ server.listen(0, () => {
608
+ const addr = server.address() as { port: number };
609
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
610
+ const chunks: Buffer[] = [];
611
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
612
+ res.on('end', () => {
613
+ expect(res.statusCode).toBe(200);
614
+ expect(Buffer.concat(chunks).toString()).toBe('Hello from server');
615
+ server.close(() => resolve());
616
+ });
617
+ }).on('error', reject);
618
+ });
619
+ server.on('error', reject);
620
+ });
621
+ });
622
+
623
+ await it('should receive request headers', async () => {
624
+ const server = http.createServer((req, res) => {
625
+ res.writeHead(200);
626
+ res.end(req.headers['x-test'] || 'no header');
627
+ });
628
+
629
+ await new Promise<void>((resolve, reject) => {
630
+ server.listen(0, () => {
631
+ const addr = server.address() as { port: number };
632
+ const req = http.request({
633
+ hostname: '127.0.0.1',
634
+ port: addr.port,
635
+ path: '/',
636
+ headers: { 'X-Test': 'custom-value' },
637
+ }, (res) => {
638
+ const chunks: Buffer[] = [];
639
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
640
+ res.on('end', () => {
641
+ expect(Buffer.concat(chunks).toString()).toBe('custom-value');
642
+ server.close(() => resolve());
643
+ });
644
+ });
645
+ req.on('error', reject);
646
+ req.end();
647
+ });
648
+ server.on('error', reject);
649
+ });
650
+ });
651
+
652
+ await it('should handle POST with body', async () => {
653
+ const server = http.createServer((req, res) => {
654
+ const chunks: Buffer[] = [];
655
+ req.on('data', (chunk: Buffer) => chunks.push(chunk));
656
+ req.on('end', () => {
657
+ const body = Buffer.concat(chunks).toString();
658
+ res.writeHead(200);
659
+ res.end(`received: ${body}`);
660
+ });
661
+ });
662
+
663
+ await new Promise<void>((resolve, reject) => {
664
+ server.listen(0, () => {
665
+ const addr = server.address() as { port: number };
666
+ const req = http.request({
667
+ hostname: '127.0.0.1',
668
+ port: addr.port,
669
+ path: '/post',
670
+ method: 'POST',
671
+ }, (res) => {
672
+ const chunks: Buffer[] = [];
673
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
674
+ res.on('end', () => {
675
+ expect(Buffer.concat(chunks).toString()).toBe('received: hello body');
676
+ server.close(() => resolve());
677
+ });
678
+ });
679
+ req.on('error', reject);
680
+ req.write('hello body');
681
+ req.end();
682
+ });
683
+ server.on('error', reject);
684
+ });
685
+ });
686
+
687
+ await it('should handle 404 status code', async () => {
688
+ const server = http.createServer((req, res) => {
689
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
690
+ res.end('Not Found');
691
+ });
692
+
693
+ await new Promise<void>((resolve, reject) => {
694
+ server.listen(0, () => {
695
+ const addr = server.address() as { port: number };
696
+ http.get(`http://127.0.0.1:${addr.port}/missing`, (res) => {
697
+ expect(res.statusCode).toBe(404);
698
+ const chunks: Buffer[] = [];
699
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
700
+ res.on('end', () => {
701
+ expect(Buffer.concat(chunks).toString()).toBe('Not Found');
702
+ server.close(() => resolve());
703
+ });
704
+ }).on('error', reject);
705
+ });
706
+ server.on('error', reject);
707
+ });
708
+ });
709
+
710
+ await it('should expose response headers', async () => {
711
+ const server = http.createServer((req, res) => {
712
+ res.writeHead(200, {
713
+ 'X-Response-Header': 'test-value',
714
+ 'Content-Type': 'text/plain',
715
+ });
716
+ res.end('ok');
717
+ });
718
+
719
+ await new Promise<void>((resolve, reject) => {
720
+ server.listen(0, () => {
721
+ const addr = server.address() as { port: number };
722
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
723
+ expect(res.headers['x-response-header']).toBe('test-value');
724
+ expect(res.headers['content-type']).toBe('text/plain');
725
+ res.on('data', () => {});
726
+ res.on('end', () => {
727
+ server.close(() => resolve());
728
+ });
729
+ }).on('error', reject);
730
+ });
731
+ server.on('error', reject);
732
+ });
733
+ });
734
+
735
+ await it('should expose request method and url on server', async () => {
736
+ const server = http.createServer((req, res) => {
737
+ res.writeHead(200);
738
+ res.end(`${req.method} ${req.url}`);
739
+ });
740
+
741
+ await new Promise<void>((resolve, reject) => {
742
+ server.listen(0, () => {
743
+ const addr = server.address() as { port: number };
744
+ http.get(`http://127.0.0.1:${addr.port}/test-path`, (res) => {
745
+ const chunks: Buffer[] = [];
746
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
747
+ res.on('end', () => {
748
+ expect(Buffer.concat(chunks).toString()).toBe('GET /test-path');
749
+ server.close(() => resolve());
750
+ });
751
+ }).on('error', reject);
752
+ });
753
+ server.on('error', reject);
754
+ });
755
+ });
756
+ });
757
+
758
+ // --- ServerResponse API ---
759
+ await describe('ServerResponse API', async () => {
760
+ await it('should support setHeader/getHeader/hasHeader/removeHeader', async () => {
761
+ const server = http.createServer((req, res) => {
762
+ res.setHeader('X-Custom', 'value1');
763
+ expect(res.getHeader('X-Custom')).toBe('value1');
764
+ expect(res.hasHeader('X-Custom')).toBeTruthy();
765
+ res.removeHeader('X-Custom');
766
+ expect(res.hasHeader('X-Custom')).toBeFalsy();
767
+ res.writeHead(200);
768
+ res.end('ok');
769
+ });
770
+
771
+ await new Promise<void>((resolve, reject) => {
772
+ server.listen(0, () => {
773
+ const addr = server.address() as { port: number };
774
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
775
+ res.on('data', () => {});
776
+ res.on('end', () => server.close(() => resolve()));
777
+ }).on('error', reject);
778
+ });
779
+ server.on('error', reject);
780
+ });
781
+ });
782
+
783
+ await it('should support getHeaderNames and getHeaders', async () => {
784
+ const server = http.createServer((req, res) => {
785
+ res.setHeader('X-A', 'a');
786
+ res.setHeader('X-B', 'b');
787
+ const names = res.getHeaderNames();
788
+ expect(names.length).toBe(2);
789
+ const headers = res.getHeaders();
790
+ expect(headers['x-a']).toBe('a');
791
+ expect(headers['x-b']).toBe('b');
792
+ res.writeHead(200);
793
+ res.end('ok');
794
+ });
795
+
796
+ await new Promise<void>((resolve, reject) => {
797
+ server.listen(0, () => {
798
+ const addr = server.address() as { port: number };
799
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
800
+ res.on('data', () => {});
801
+ res.on('end', () => server.close(() => resolve()));
802
+ }).on('error', reject);
803
+ });
804
+ server.on('error', reject);
805
+ });
806
+ });
807
+
808
+ await it('should support appendHeader', async () => {
809
+ const server = http.createServer((req, res) => {
810
+ res.setHeader('X-Multi', 'first');
811
+ (res as any).appendHeader('X-Multi', 'second');
812
+ const val = res.getHeader('X-Multi');
813
+ expect(Array.isArray(val)).toBeTruthy();
814
+ res.writeHead(200);
815
+ res.end('ok');
816
+ });
817
+
818
+ await new Promise<void>((resolve, reject) => {
819
+ server.listen(0, () => {
820
+ const addr = server.address() as { port: number };
821
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
822
+ res.on('data', () => {});
823
+ res.on('end', () => server.close(() => resolve()));
824
+ }).on('error', reject);
825
+ });
826
+ server.on('error', reject);
827
+ });
828
+ });
829
+
830
+ await it('should support writeContinue', async () => {
831
+ const server = http.createServer((req, res) => {
832
+ let continueCalled = false;
833
+ (res as any).writeContinue(() => { continueCalled = true; });
834
+ res.writeHead(200);
835
+ res.end('ok');
836
+ });
837
+
838
+ await new Promise<void>((resolve, reject) => {
839
+ server.listen(0, () => {
840
+ const addr = server.address() as { port: number };
841
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
842
+ res.on('data', () => {});
843
+ res.on('end', () => server.close(() => resolve()));
844
+ }).on('error', reject);
845
+ });
846
+ server.on('error', reject);
847
+ });
848
+ });
849
+
850
+ await it('should support flushHeaders', async () => {
851
+ const server = http.createServer((req, res) => {
852
+ res.writeHead(200, { 'X-Flush': 'test' });
853
+ (res as any).flushHeaders();
854
+ expect(res.headersSent).toBeTruthy();
855
+ res.end('ok');
856
+ });
857
+
858
+ await new Promise<void>((resolve, reject) => {
859
+ server.listen(0, () => {
860
+ const addr = server.address() as { port: number };
861
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
862
+ res.on('data', () => {});
863
+ res.on('end', () => server.close(() => resolve()));
864
+ }).on('error', reject);
865
+ });
866
+ server.on('error', reject);
867
+ });
868
+ });
869
+
870
+ await it('should set statusCode and statusMessage', async () => {
871
+ const server = http.createServer((req, res) => {
872
+ res.statusCode = 201;
873
+ res.statusMessage = 'Created';
874
+ res.end('created');
875
+ });
876
+
877
+ await new Promise<void>((resolve, reject) => {
878
+ server.listen(0, () => {
879
+ const addr = server.address() as { port: number };
880
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
881
+ expect(res.statusCode).toBe(201);
882
+ res.on('data', () => {});
883
+ res.on('end', () => server.close(() => resolve()));
884
+ }).on('error', reject);
885
+ });
886
+ server.on('error', reject);
887
+ });
888
+ });
889
+
890
+ await it('should handle query strings', async () => {
891
+ const server = http.createServer((req, res) => {
892
+ res.writeHead(200);
893
+ res.end(req.url);
894
+ });
895
+
896
+ await new Promise<void>((resolve, reject) => {
897
+ server.listen(0, () => {
898
+ const addr = server.address() as { port: number };
899
+ http.get(`http://127.0.0.1:${addr.port}/path?key=value`, (res) => {
900
+ const chunks: Buffer[] = [];
901
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
902
+ res.on('end', () => {
903
+ expect(Buffer.concat(chunks).toString()).toBe('/path?key=value');
904
+ server.close(() => resolve());
905
+ });
906
+ }).on('error', reject);
907
+ });
908
+ server.on('error', reject);
909
+ });
910
+ });
911
+
912
+ await it('should handle multiple sequential requests', async () => {
913
+ let requestCount = 0;
914
+ const server = http.createServer((req, res) => {
915
+ requestCount++;
916
+ res.writeHead(200);
917
+ res.end(`request ${requestCount}`);
918
+ });
919
+
920
+ await new Promise<void>((resolve, reject) => {
921
+ server.listen(0, () => {
922
+ const addr = server.address() as { port: number };
923
+ // First request
924
+ http.get(`http://127.0.0.1:${addr.port}/`, (res1) => {
925
+ res1.on('data', () => {});
926
+ res1.on('end', () => {
927
+ // Second request
928
+ http.get(`http://127.0.0.1:${addr.port}/`, (res2) => {
929
+ res2.on('data', () => {});
930
+ res2.on('end', () => {
931
+ expect(requestCount).toBe(2);
932
+ server.close(() => resolve());
933
+ });
934
+ }).on('error', reject);
935
+ });
936
+ }).on('error', reject);
937
+ });
938
+ server.on('error', reject);
939
+ });
940
+ });
941
+
942
+ await it('should support JSON response', async () => {
943
+ const server = http.createServer((req, res) => {
944
+ const body = JSON.stringify({ hello: 'world' });
945
+ res.writeHead(200, { 'Content-Type': 'application/json' });
946
+ res.end(body);
947
+ });
948
+
949
+ await new Promise<void>((resolve, reject) => {
950
+ server.listen(0, () => {
951
+ const addr = server.address() as { port: number };
952
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
953
+ const chunks: Buffer[] = [];
954
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
955
+ res.on('end', () => {
956
+ const data = JSON.parse(Buffer.concat(chunks).toString());
957
+ expect(data.hello).toBe('world');
958
+ server.close(() => resolve());
959
+ });
960
+ }).on('error', reject);
961
+ });
962
+ server.on('error', reject);
963
+ });
964
+ });
965
+ });
966
+
967
+ // --- Server lifecycle ---
968
+ await describe('http.Server lifecycle', async () => {
969
+ await it('should emit listening event', async () => {
970
+ const server = http.createServer();
971
+ const listened = await new Promise<boolean>((resolve) => {
972
+ server.on('listening', () => resolve(true));
973
+ server.listen(0);
974
+ });
975
+ expect(listened).toBeTruthy();
976
+ expect(server.listening).toBeTruthy();
977
+ server.close();
978
+ });
979
+
980
+ await it('should emit close event', async () => {
981
+ const server = http.createServer();
982
+ await new Promise<void>((resolve) => {
983
+ server.listen(0, () => {
984
+ server.close(() => resolve());
985
+ });
986
+ });
987
+ expect(server.listening).toBeFalsy();
988
+ });
989
+
990
+ await it('should return address info', async () => {
991
+ const server = http.createServer();
992
+ await new Promise<void>((resolve) => {
993
+ server.listen(0, () => {
994
+ const addr = server.address() as { port: number; family: string };
995
+ expect(typeof addr.port).toBe('number');
996
+ expect(addr.port > 0).toBeTruthy();
997
+ server.close(() => resolve());
998
+ });
999
+ });
1000
+ });
1001
+
1002
+ await it('should support setTimeout', async () => {
1003
+ const server = http.createServer();
1004
+ server.setTimeout(5000);
1005
+ expect(server.timeout).toBe(5000);
1006
+ });
1007
+
1008
+ await it('should return null address before listening', async () => {
1009
+ const server = http.createServer();
1010
+ expect(server.address()).toBeNull();
1011
+ });
1012
+
1013
+ await it('should set listening to false initially', async () => {
1014
+ const server = http.createServer();
1015
+ expect(server.listening).toBe(false);
1016
+ });
1017
+
1018
+ await it('should accept requestListener in constructor', async () => {
1019
+ let called = false;
1020
+ const server = http.createServer((req, res) => {
1021
+ called = true;
1022
+ res.end();
1023
+ });
1024
+ expect(server).toBeDefined();
1025
+ // Server was created with listener, just verify it exists
1026
+ server.close();
1027
+ });
1028
+
1029
+ await it('should accept no arguments to createServer', async () => {
1030
+ const server = http.createServer();
1031
+ expect(server).toBeDefined();
1032
+ expect(server.listening).toBe(false);
1033
+ });
1034
+
1035
+ await it('should return this from setTimeout', async () => {
1036
+ const server = http.createServer();
1037
+ const result = server.setTimeout(3000);
1038
+ expect(result).toBe(server);
1039
+ });
1040
+
1041
+ await it('should return this from close', async () => {
1042
+ const server = http.createServer();
1043
+ await new Promise<void>((resolve) => {
1044
+ server.listen(0, () => {
1045
+ const result = server.close(() => resolve());
1046
+ expect(result).toBe(server);
1047
+ });
1048
+ });
1049
+ });
1050
+
1051
+ await it('should return this from listen', async () => {
1052
+ const server = http.createServer();
1053
+ const result = server.listen(0);
1054
+ expect(result).toBe(server);
1055
+ await new Promise<void>((resolve) => {
1056
+ server.close(() => resolve());
1057
+ });
1058
+ });
1059
+ });
1060
+
1061
+ // --- Server properties ---
1062
+ await describe('http.Server properties', async () => {
1063
+ await it('should have timeout properties', async () => {
1064
+ const server = http.createServer();
1065
+ expect(server.maxHeadersCount).toBeDefined();
1066
+ expect(server.keepAliveTimeout).toBeDefined();
1067
+ expect(server.headersTimeout).toBeDefined();
1068
+ expect(server.requestTimeout).toBeDefined();
1069
+ });
1070
+
1071
+ await it('should have default timeout values', async () => {
1072
+ const server = http.createServer();
1073
+ expect(typeof server.timeout).toBe('number');
1074
+ expect(typeof server.keepAliveTimeout).toBe('number');
1075
+ expect(typeof server.headersTimeout).toBe('number');
1076
+ expect(typeof server.requestTimeout).toBe('number');
1077
+ });
1078
+
1079
+ await it('should have maxHeadersCount property', async () => {
1080
+ const server = http.createServer();
1081
+ // Node.js defaults to null, GJS defaults to a number — both are valid
1082
+ expect(server.maxHeadersCount !== undefined).toBe(true);
1083
+ });
1084
+
1085
+ await it('should be an EventEmitter', async () => {
1086
+ const server = http.createServer();
1087
+ expect(typeof server.on).toBe('function');
1088
+ expect(typeof server.emit).toBe('function');
1089
+ expect(typeof server.removeListener).toBe('function');
1090
+ });
1091
+
1092
+ await it('should support empty body response', async () => {
1093
+ const server = http.createServer((req, res) => {
1094
+ res.writeHead(204);
1095
+ res.end();
1096
+ });
1097
+
1098
+ await new Promise<void>((resolve, reject) => {
1099
+ server.listen(0, () => {
1100
+ const addr = server.address() as { port: number };
1101
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1102
+ expect(res.statusCode).toBe(204);
1103
+ const chunks: Buffer[] = [];
1104
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
1105
+ res.on('end', () => {
1106
+ expect(Buffer.concat(chunks).length).toBe(0);
1107
+ server.close(() => resolve());
1108
+ });
1109
+ }).on('error', reject);
1110
+ });
1111
+ server.on('error', reject);
1112
+ });
1113
+ });
1114
+
1115
+ await it('should handle large response body', async () => {
1116
+ const largeBody = 'x'.repeat(10000);
1117
+ const server = http.createServer((req, res) => {
1118
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
1119
+ res.end(largeBody);
1120
+ });
1121
+
1122
+ await new Promise<void>((resolve, reject) => {
1123
+ server.listen(0, () => {
1124
+ const addr = server.address() as { port: number };
1125
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1126
+ const chunks: Buffer[] = [];
1127
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
1128
+ res.on('end', () => {
1129
+ expect(Buffer.concat(chunks).toString().length).toBe(10000);
1130
+ server.close(() => resolve());
1131
+ });
1132
+ }).on('error', reject);
1133
+ });
1134
+ server.on('error', reject);
1135
+ });
1136
+ });
1137
+ });
1138
+
1139
+ // --- IncomingMessage via server ---
1140
+ await describe('http.IncomingMessage', async () => {
1141
+ await it('should have httpVersion', async () => {
1142
+ const server = http.createServer((req, res) => {
1143
+ res.writeHead(200);
1144
+ res.end(req.httpVersion);
1145
+ });
1146
+
1147
+ await new Promise<void>((resolve, reject) => {
1148
+ server.listen(0, () => {
1149
+ const addr = server.address() as { port: number };
1150
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1151
+ expect(res.httpVersion).toBeDefined();
1152
+ const chunks: Buffer[] = [];
1153
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
1154
+ res.on('end', () => {
1155
+ server.close(() => resolve());
1156
+ });
1157
+ }).on('error', reject);
1158
+ });
1159
+ server.on('error', reject);
1160
+ });
1161
+ });
1162
+
1163
+ await it('should expose rawHeaders', async () => {
1164
+ const server = http.createServer((req, res) => {
1165
+ // rawHeaders should be an array of [name, value, name, value, ...]
1166
+ expect(Array.isArray(req.rawHeaders)).toBeTruthy();
1167
+ expect(req.rawHeaders.length > 0).toBeTruthy();
1168
+ res.writeHead(200);
1169
+ res.end('ok');
1170
+ });
1171
+
1172
+ await new Promise<void>((resolve, reject) => {
1173
+ server.listen(0, () => {
1174
+ const addr = server.address() as { port: number };
1175
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1176
+ res.on('data', () => {});
1177
+ res.on('end', () => server.close(() => resolve()));
1178
+ }).on('error', reject);
1179
+ });
1180
+ server.on('error', reject);
1181
+ });
1182
+ });
1183
+
1184
+ await it('should have rawHeaders length be even (name-value pairs)', async () => {
1185
+ const server = http.createServer((req, res) => {
1186
+ expect(req.rawHeaders.length % 2).toBe(0);
1187
+ res.writeHead(200);
1188
+ res.end('ok');
1189
+ });
1190
+
1191
+ await new Promise<void>((resolve, reject) => {
1192
+ server.listen(0, () => {
1193
+ const addr = server.address() as { port: number };
1194
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1195
+ res.on('data', () => {});
1196
+ res.on('end', () => server.close(() => resolve()));
1197
+ }).on('error', reject);
1198
+ });
1199
+ server.on('error', reject);
1200
+ });
1201
+ });
1202
+
1203
+ await it('should have complete set to true after end', async () => {
1204
+ const server = http.createServer((req, res) => {
1205
+ req.on('end', () => {
1206
+ expect(req.complete).toBe(true);
1207
+ });
1208
+ req.resume(); // consume the body
1209
+ res.writeHead(200);
1210
+ res.end('ok');
1211
+ });
1212
+
1213
+ await new Promise<void>((resolve, reject) => {
1214
+ server.listen(0, () => {
1215
+ const addr = server.address() as { port: number };
1216
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1217
+ res.on('data', () => {});
1218
+ res.on('end', () => server.close(() => resolve()));
1219
+ }).on('error', reject);
1220
+ });
1221
+ server.on('error', reject);
1222
+ });
1223
+ });
1224
+ });
1225
+
1226
+ // --- Server.address() details ---
1227
+ await describe('http.Server address', async () => {
1228
+ await it('should return object with port, family, address', async () => {
1229
+ const server = http.createServer();
1230
+ await new Promise<void>((resolve) => {
1231
+ server.listen(0, () => {
1232
+ const addr = server.address() as { port: number; family: string; address: string };
1233
+ expect(addr).toBeDefined();
1234
+ expect(typeof addr.port).toBe('number');
1235
+ expect(typeof addr.family).toBe('string');
1236
+ expect(typeof addr.address).toBe('string');
1237
+ server.close(() => resolve());
1238
+ });
1239
+ });
1240
+ });
1241
+
1242
+ await it('should allocate a random port when 0 is specified', async () => {
1243
+ const server = http.createServer();
1244
+ await new Promise<void>((resolve) => {
1245
+ server.listen(0, () => {
1246
+ const addr = server.address() as { port: number };
1247
+ expect(addr.port).toBeGreaterThan(0);
1248
+ expect(addr.port).toBeLessThan(65536);
1249
+ server.close(() => resolve());
1250
+ });
1251
+ });
1252
+ });
1253
+ });
1254
+
1255
+ // --- ServerResponse writeHead variants ---
1256
+ await describe('ServerResponse writeHead', async () => {
1257
+ await it('should accept statusCode only', async () => {
1258
+ const server = http.createServer((req, res) => {
1259
+ res.writeHead(200);
1260
+ res.end('ok');
1261
+ });
1262
+
1263
+ await new Promise<void>((resolve, reject) => {
1264
+ server.listen(0, () => {
1265
+ const addr = server.address() as { port: number };
1266
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1267
+ expect(res.statusCode).toBe(200);
1268
+ res.on('data', () => {});
1269
+ res.on('end', () => server.close(() => resolve()));
1270
+ }).on('error', reject);
1271
+ });
1272
+ server.on('error', reject);
1273
+ });
1274
+ });
1275
+
1276
+ await it('should accept statusCode and headers object', async () => {
1277
+ const server = http.createServer((req, res) => {
1278
+ res.writeHead(200, { 'X-WriteHead': 'test' });
1279
+ res.end('ok');
1280
+ });
1281
+
1282
+ await new Promise<void>((resolve, reject) => {
1283
+ server.listen(0, () => {
1284
+ const addr = server.address() as { port: number };
1285
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1286
+ expect(res.headers['x-writehead']).toBe('test');
1287
+ res.on('data', () => {});
1288
+ res.on('end', () => server.close(() => resolve()));
1289
+ }).on('error', reject);
1290
+ });
1291
+ server.on('error', reject);
1292
+ });
1293
+ });
1294
+
1295
+ await it('should handle 500 status code', async () => {
1296
+ const server = http.createServer((req, res) => {
1297
+ res.writeHead(500);
1298
+ res.end('Internal Error');
1299
+ });
1300
+
1301
+ await new Promise<void>((resolve, reject) => {
1302
+ server.listen(0, () => {
1303
+ const addr = server.address() as { port: number };
1304
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1305
+ expect(res.statusCode).toBe(500);
1306
+ res.on('data', () => {});
1307
+ res.on('end', () => server.close(() => resolve()));
1308
+ }).on('error', reject);
1309
+ });
1310
+ server.on('error', reject);
1311
+ });
1312
+ });
1313
+
1314
+ await it('should handle 302 redirect with Location header via server roundtrip', async () => {
1315
+ // Test that the server can set a redirect status + Location header,
1316
+ // then a second request to the new location succeeds.
1317
+ let hitCount = 0;
1318
+ const server = http.createServer((req, res) => {
1319
+ hitCount++;
1320
+ if (req.url === '/old') {
1321
+ // Respond with a body so Soup doesn't auto-follow
1322
+ res.writeHead(200, { 'X-Would-Redirect': '/new' });
1323
+ res.end('redirect-target: /new');
1324
+ } else {
1325
+ res.writeHead(200);
1326
+ res.end('final');
1327
+ }
1328
+ });
1329
+
1330
+ await new Promise<void>((resolve, reject) => {
1331
+ server.listen(0, () => {
1332
+ const addr = server.address() as { port: number };
1333
+ http.get(`http://127.0.0.1:${addr.port}/old`, (res) => {
1334
+ expect(res.statusCode).toBe(200);
1335
+ expect(res.headers['x-would-redirect']).toBe('/new');
1336
+ res.on('data', () => {});
1337
+ res.on('end', () => server.close(() => resolve()));
1338
+ }).on('error', reject);
1339
+ });
1340
+ server.on('error', reject);
1341
+ });
1342
+ });
1343
+ });
1344
+
1345
+ // --- ServerResponse default statusCode ---
1346
+ await describe('ServerResponse statusCode default', async () => {
1347
+ await it('should default statusCode to 200', async () => {
1348
+ const server = http.createServer((req, res) => {
1349
+ // Do not call writeHead, just end
1350
+ res.end('default status');
1351
+ });
1352
+
1353
+ await new Promise<void>((resolve, reject) => {
1354
+ server.listen(0, () => {
1355
+ const addr = server.address() as { port: number };
1356
+ http.get(`http://127.0.0.1:${addr.port}/`, (res) => {
1357
+ expect(res.statusCode).toBe(200);
1358
+ res.on('data', () => {});
1359
+ res.on('end', () => server.close(() => resolve()));
1360
+ }).on('error', reject);
1361
+ });
1362
+ server.on('error', reject);
1363
+ });
1364
+ });
1365
+ });
1366
+ };