@gjsify/http 0.0.4 → 0.1.0
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/README.md +30 -1
- package/lib/esm/client-request.js +228 -0
- package/lib/esm/constants.js +105 -0
- package/lib/esm/incoming-message.js +59 -0
- package/lib/esm/index.js +112 -2
- package/lib/esm/server.js +371 -0
- package/lib/types/client-request.d.ts +65 -0
- package/lib/types/constants.d.ts +2 -0
- package/lib/types/incoming-message.d.ts +25 -0
- package/lib/types/index.d.ts +102 -0
- package/lib/types/server.d.ts +102 -0
- package/package.json +24 -18
- package/src/client-request.ts +307 -0
- package/src/client.spec.ts +538 -0
- package/src/constants.ts +33 -0
- package/src/extended.spec.ts +620 -0
- package/src/incoming-message.ts +71 -0
- package/src/index.spec.ts +1359 -67
- package/src/index.ts +164 -20
- package/src/server.ts +489 -0
- package/src/streaming.spec.ts +588 -0
- package/src/test.mts +6 -1
- package/src/timeout.spec.ts +668 -0
- package/src/upgrade.spec.ts +256 -0
- package/tsconfig.json +23 -10
- package/tsconfig.tsbuildinfo +1 -0
- package/lib/cjs/index.js +0 -18
- package/test.gjs.js +0 -34832
- package/test.gjs.mjs +0 -34751
- package/test.gjs.mjs.meta.json +0 -1
- package/test.node.js +0 -1278
- package/test.node.mjs +0 -358
- package/tsconfig.types.json +0 -8
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
// Extended HTTP tests — header validation, STATUS_CODES depth, Agent details,
|
|
2
|
+
// IncomingMessage/ServerResponse properties
|
|
3
|
+
// Ported from refs/node-test/parallel/test-http-*.js
|
|
4
|
+
// Original: MIT license, Node.js contributors
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from '@gjsify/unit';
|
|
7
|
+
import * as http from 'node:http';
|
|
8
|
+
|
|
9
|
+
export default async () => {
|
|
10
|
+
|
|
11
|
+
// ===================== STATUS_CODES comprehensive =====================
|
|
12
|
+
await describe('http.STATUS_CODES comprehensive', async () => {
|
|
13
|
+
await it('should be a non-empty object', async () => {
|
|
14
|
+
expect(typeof http.STATUS_CODES).toBe('object');
|
|
15
|
+
expect(Object.keys(http.STATUS_CODES).length).toBeGreaterThan(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// 1xx Informational
|
|
19
|
+
await it('100 should be Continue', async () => {
|
|
20
|
+
expect(http.STATUS_CODES[100]).toBe('Continue');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await it('101 should be Switching Protocols', async () => {
|
|
24
|
+
expect(http.STATUS_CODES[101]).toBe('Switching Protocols');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await it('102 should be Processing', async () => {
|
|
28
|
+
expect(http.STATUS_CODES[102]).toBe('Processing');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await it('103 should be Early Hints', async () => {
|
|
32
|
+
expect(http.STATUS_CODES[103]).toBe('Early Hints');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 2xx Success
|
|
36
|
+
await it('200 should be OK', async () => {
|
|
37
|
+
expect(http.STATUS_CODES[200]).toBe('OK');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await it('201 should be Created', async () => {
|
|
41
|
+
expect(http.STATUS_CODES[201]).toBe('Created');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await it('202 should be Accepted', async () => {
|
|
45
|
+
expect(http.STATUS_CODES[202]).toBe('Accepted');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await it('204 should be No Content', async () => {
|
|
49
|
+
expect(http.STATUS_CODES[204]).toBe('No Content');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await it('206 should be Partial Content', async () => {
|
|
53
|
+
expect(http.STATUS_CODES[206]).toBe('Partial Content');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await it('207 should be Multi-Status', async () => {
|
|
57
|
+
expect(http.STATUS_CODES[207]).toBe('Multi-Status');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 3xx Redirection
|
|
61
|
+
await it('301 should be Moved Permanently', async () => {
|
|
62
|
+
expect(http.STATUS_CODES[301]).toBe('Moved Permanently');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await it('302 should be Found', async () => {
|
|
66
|
+
expect(http.STATUS_CODES[302]).toBe('Found');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await it('303 should be See Other', async () => {
|
|
70
|
+
expect(http.STATUS_CODES[303]).toBe('See Other');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await it('304 should be Not Modified', async () => {
|
|
74
|
+
expect(http.STATUS_CODES[304]).toBe('Not Modified');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await it('307 should be Temporary Redirect', async () => {
|
|
78
|
+
expect(http.STATUS_CODES[307]).toBe('Temporary Redirect');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await it('308 should be Permanent Redirect', async () => {
|
|
82
|
+
expect(http.STATUS_CODES[308]).toBe('Permanent Redirect');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 4xx Client Errors
|
|
86
|
+
await it('400 should be Bad Request', async () => {
|
|
87
|
+
expect(http.STATUS_CODES[400]).toBe('Bad Request');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await it('401 should be Unauthorized', async () => {
|
|
91
|
+
expect(http.STATUS_CODES[401]).toBe('Unauthorized');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await it('403 should be Forbidden', async () => {
|
|
95
|
+
expect(http.STATUS_CODES[403]).toBe('Forbidden');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await it('404 should be Not Found', async () => {
|
|
99
|
+
expect(http.STATUS_CODES[404]).toBe('Not Found');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await it('405 should be Method Not Allowed', async () => {
|
|
103
|
+
expect(http.STATUS_CODES[405]).toBe('Method Not Allowed');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await it('408 should be Request Timeout', async () => {
|
|
107
|
+
expect(http.STATUS_CODES[408]).toBe('Request Timeout');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await it('409 should be Conflict', async () => {
|
|
111
|
+
expect(http.STATUS_CODES[409]).toBe('Conflict');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await it('410 should be Gone', async () => {
|
|
115
|
+
expect(http.STATUS_CODES[410]).toBe('Gone');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await it('413 should be Payload Too Large', async () => {
|
|
119
|
+
expect(http.STATUS_CODES[413]).toBe('Payload Too Large');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await it('415 should be Unsupported Media Type', async () => {
|
|
123
|
+
expect(http.STATUS_CODES[415]).toBe('Unsupported Media Type');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await it('418 should be I\'m a Teapot', async () => {
|
|
127
|
+
expect(http.STATUS_CODES[418]).toBe("I'm a Teapot");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await it('422 should be Unprocessable Entity', async () => {
|
|
131
|
+
expect(http.STATUS_CODES[422]).toBe('Unprocessable Entity');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await it('429 should be Too Many Requests', async () => {
|
|
135
|
+
expect(http.STATUS_CODES[429]).toBe('Too Many Requests');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 5xx Server Errors
|
|
139
|
+
await it('500 should be Internal Server Error', async () => {
|
|
140
|
+
expect(http.STATUS_CODES[500]).toBe('Internal Server Error');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await it('501 should be Not Implemented', async () => {
|
|
144
|
+
expect(http.STATUS_CODES[501]).toBe('Not Implemented');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await it('502 should be Bad Gateway', async () => {
|
|
148
|
+
expect(http.STATUS_CODES[502]).toBe('Bad Gateway');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await it('503 should be Service Unavailable', async () => {
|
|
152
|
+
expect(http.STATUS_CODES[503]).toBe('Service Unavailable');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await it('504 should be Gateway Timeout', async () => {
|
|
156
|
+
expect(http.STATUS_CODES[504]).toBe('Gateway Timeout');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await it('all values should be strings', async () => {
|
|
160
|
+
for (const [code, text] of Object.entries(http.STATUS_CODES)) {
|
|
161
|
+
expect(typeof text).toBe('string');
|
|
162
|
+
expect((text as string).length).toBeGreaterThan(0);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await it('all keys should be numeric strings', async () => {
|
|
167
|
+
for (const code of Object.keys(http.STATUS_CODES)) {
|
|
168
|
+
expect(Number.isInteger(Number(code))).toBe(true);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await it('should have at least 60 status codes', async () => {
|
|
173
|
+
expect(Object.keys(http.STATUS_CODES).length).toBeGreaterThan(59);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await it('unknown status code should be undefined', async () => {
|
|
177
|
+
expect((http.STATUS_CODES as any)[999]).toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ===================== METHODS comprehensive =====================
|
|
182
|
+
await describe('http.METHODS comprehensive', async () => {
|
|
183
|
+
await it('should be an array', async () => {
|
|
184
|
+
expect(Array.isArray(http.METHODS)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await it('should contain GET', async () => {
|
|
188
|
+
expect(http.METHODS).toContain('GET');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await it('should contain POST', async () => {
|
|
192
|
+
expect(http.METHODS).toContain('POST');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await it('should contain PUT', async () => {
|
|
196
|
+
expect(http.METHODS).toContain('PUT');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await it('should contain DELETE', async () => {
|
|
200
|
+
expect(http.METHODS).toContain('DELETE');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await it('should contain PATCH', async () => {
|
|
204
|
+
expect(http.METHODS).toContain('PATCH');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await it('should contain HEAD', async () => {
|
|
208
|
+
expect(http.METHODS).toContain('HEAD');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await it('should contain OPTIONS', async () => {
|
|
212
|
+
expect(http.METHODS).toContain('OPTIONS');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await it('should contain CONNECT', async () => {
|
|
216
|
+
expect(http.METHODS).toContain('CONNECT');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
await it('should contain TRACE', async () => {
|
|
220
|
+
expect(http.METHODS).toContain('TRACE');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await it('should be sorted alphabetically', async () => {
|
|
224
|
+
const sorted = [...http.METHODS].sort();
|
|
225
|
+
for (let i = 0; i < http.METHODS.length; i++) {
|
|
226
|
+
expect(http.METHODS[i]).toBe(sorted[i]);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await it('should have no duplicates', async () => {
|
|
231
|
+
const unique = new Set(http.METHODS);
|
|
232
|
+
expect(unique.size).toBe(http.METHODS.length);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await it('all methods should be uppercase strings', async () => {
|
|
236
|
+
for (const m of http.METHODS) {
|
|
237
|
+
expect(typeof m).toBe('string');
|
|
238
|
+
expect(m).toBe(m.toUpperCase());
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await it('should have at least 9 standard methods', async () => {
|
|
243
|
+
expect(http.METHODS.length).toBeGreaterThan(8);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ===================== validateHeaderName =====================
|
|
248
|
+
await describe('http.validateHeaderName extended', async () => {
|
|
249
|
+
await it('should accept standard header names', async () => {
|
|
250
|
+
const valid = [
|
|
251
|
+
'Content-Type', 'Accept', 'Authorization', 'Cache-Control',
|
|
252
|
+
'X-Custom-Header', 'x-lowercase', 'UPPERCASE',
|
|
253
|
+
];
|
|
254
|
+
for (const name of valid) {
|
|
255
|
+
expect(() => http.validateHeaderName(name)).not.toThrow();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await it('should accept headers with digits', async () => {
|
|
260
|
+
expect(() => http.validateHeaderName('X-Request-Id-123')).not.toThrow();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await it('should accept headers with special token chars', async () => {
|
|
264
|
+
// RFC 7230 token chars: !#$%&'*+-.^_`|~
|
|
265
|
+
expect(() => http.validateHeaderName('X-Header!')).not.toThrow();
|
|
266
|
+
expect(() => http.validateHeaderName('X-Header#')).not.toThrow();
|
|
267
|
+
expect(() => http.validateHeaderName('X-Header^')).not.toThrow();
|
|
268
|
+
expect(() => http.validateHeaderName('X-Header~')).not.toThrow();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await it('should reject empty string', async () => {
|
|
272
|
+
expect(() => http.validateHeaderName('')).toThrow();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
await it('should reject header with space', async () => {
|
|
276
|
+
expect(() => http.validateHeaderName('Invalid Header')).toThrow();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await it('should reject header with colon', async () => {
|
|
280
|
+
expect(() => http.validateHeaderName('Invalid:Header')).toThrow();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await it('should reject non-string values', async () => {
|
|
284
|
+
expect(() => http.validateHeaderName(123 as any)).toThrow();
|
|
285
|
+
expect(() => http.validateHeaderName(null as any)).toThrow();
|
|
286
|
+
expect(() => http.validateHeaderName(undefined as any)).toThrow();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await it('should reject header with control characters', async () => {
|
|
290
|
+
expect(() => http.validateHeaderName('X-Header\x00')).toThrow();
|
|
291
|
+
expect(() => http.validateHeaderName('X-Header\x0D')).toThrow();
|
|
292
|
+
expect(() => http.validateHeaderName('X-Header\x0A')).toThrow();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// ===================== validateHeaderValue =====================
|
|
297
|
+
await describe('http.validateHeaderValue extended', async () => {
|
|
298
|
+
await it('should accept normal string values', async () => {
|
|
299
|
+
expect(() => http.validateHeaderValue('Content-Type', 'text/html')).not.toThrow();
|
|
300
|
+
expect(() => http.validateHeaderValue('Accept', 'application/json')).not.toThrow();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await it('should accept numeric values', async () => {
|
|
304
|
+
expect(() => http.validateHeaderValue('Content-Length', '1234')).not.toThrow();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await it('should accept empty string value', async () => {
|
|
308
|
+
expect(() => http.validateHeaderValue('X-Empty', '')).not.toThrow();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await it('should accept values with tabs', async () => {
|
|
312
|
+
expect(() => http.validateHeaderValue('X-Tab', 'val\t')).not.toThrow();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await it('should reject undefined value', async () => {
|
|
316
|
+
expect(() => http.validateHeaderValue('X-Header', undefined as any)).toThrow();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await it('should reject value with NUL character', async () => {
|
|
320
|
+
expect(() => http.validateHeaderValue('X-Header', 'val\x00ue')).toThrow();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await it('should reject value with bare CR', async () => {
|
|
324
|
+
expect(() => http.validateHeaderValue('X-Header', 'val\rue')).toThrow();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await it('should reject value with bare LF', async () => {
|
|
328
|
+
expect(() => http.validateHeaderValue('X-Header', 'val\nue')).toThrow();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// ===================== maxHeaderSize =====================
|
|
333
|
+
await describe('http.maxHeaderSize', async () => {
|
|
334
|
+
await it('should be a number', async () => {
|
|
335
|
+
expect(typeof http.maxHeaderSize).toBe('number');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await it('should be positive', async () => {
|
|
339
|
+
expect(http.maxHeaderSize).toBeGreaterThan(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await it('should be at least 8KB', async () => {
|
|
343
|
+
expect(http.maxHeaderSize).toBeGreaterThan(8191);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ===================== Agent extended =====================
|
|
348
|
+
await describe('http.Agent extended', async () => {
|
|
349
|
+
await it('should be constructable', async () => {
|
|
350
|
+
const agent = new http.Agent();
|
|
351
|
+
expect(agent).toBeDefined();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
await it('should accept keepAlive option', async () => {
|
|
355
|
+
const agent = new http.Agent({ keepAlive: true });
|
|
356
|
+
expect(agent).toBeDefined();
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await it('should accept maxSockets option', async () => {
|
|
360
|
+
const agent = new http.Agent({ maxSockets: 5 });
|
|
361
|
+
expect(agent).toBeDefined();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await it('should accept timeout option', async () => {
|
|
365
|
+
const agent = new http.Agent({ timeout: 10000 });
|
|
366
|
+
expect(agent).toBeDefined();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await it('should accept maxFreeSockets option', async () => {
|
|
370
|
+
const agent = new http.Agent({ maxFreeSockets: 128 });
|
|
371
|
+
expect(agent).toBeDefined();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await it('should accept scheduling option', async () => {
|
|
375
|
+
const agent = new http.Agent({ scheduling: 'fifo' });
|
|
376
|
+
expect(agent).toBeDefined();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
await it('should have destroy method', async () => {
|
|
380
|
+
const agent = new http.Agent();
|
|
381
|
+
expect(typeof agent.destroy).toBe('function');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await it('destroy should not throw', async () => {
|
|
385
|
+
const agent = new http.Agent();
|
|
386
|
+
expect(() => agent.destroy()).not.toThrow();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await it('maxSockets should default to Infinity', async () => {
|
|
390
|
+
const agent = new http.Agent();
|
|
391
|
+
expect(agent.maxSockets).toBe(Infinity);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await it('two agents should be independent', async () => {
|
|
395
|
+
const a1 = new http.Agent();
|
|
396
|
+
const a2 = new http.Agent();
|
|
397
|
+
expect(a1).not.toBe(a2);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
await it('should have getName method', async () => {
|
|
401
|
+
const agent = new http.Agent();
|
|
402
|
+
expect(typeof agent.getName).toBe('function');
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// ===================== globalAgent =====================
|
|
407
|
+
await describe('http.globalAgent extended', async () => {
|
|
408
|
+
await it('should be defined', async () => {
|
|
409
|
+
expect(http.globalAgent).toBeDefined();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
await it('should be an Agent instance', async () => {
|
|
413
|
+
expect(http.globalAgent instanceof http.Agent).toBe(true);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
await it('should have maxSockets as Infinity', async () => {
|
|
417
|
+
expect(http.globalAgent.maxSockets).toBe(Infinity);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await it('should be singleton', async () => {
|
|
421
|
+
expect(http.globalAgent).toBe(http.globalAgent);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// ===================== OutgoingMessage properties =====================
|
|
426
|
+
await describe('http.OutgoingMessage extended', async () => {
|
|
427
|
+
await it('should export OutgoingMessage class', async () => {
|
|
428
|
+
expect(typeof http.OutgoingMessage).toBe('function');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
await it('should be constructable', async () => {
|
|
432
|
+
const msg = new http.OutgoingMessage();
|
|
433
|
+
expect(msg).toBeDefined();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
await it('should have setHeader method', async () => {
|
|
437
|
+
const msg = new http.OutgoingMessage();
|
|
438
|
+
expect(typeof msg.setHeader).toBe('function');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await it('should have getHeader method', async () => {
|
|
442
|
+
const msg = new http.OutgoingMessage();
|
|
443
|
+
expect(typeof msg.getHeader).toBe('function');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await it('should have removeHeader method', async () => {
|
|
447
|
+
const msg = new http.OutgoingMessage();
|
|
448
|
+
expect(typeof msg.removeHeader).toBe('function');
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await it('should have hasHeader method', async () => {
|
|
452
|
+
const msg = new http.OutgoingMessage();
|
|
453
|
+
expect(typeof msg.hasHeader).toBe('function');
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await it('should have getHeaderNames method', async () => {
|
|
457
|
+
const msg = new http.OutgoingMessage();
|
|
458
|
+
expect(typeof msg.getHeaderNames).toBe('function');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
await it('should have getHeaders method', async () => {
|
|
462
|
+
const msg = new http.OutgoingMessage();
|
|
463
|
+
expect(typeof msg.getHeaders).toBe('function');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
await it('setHeader/getHeader roundtrip', async () => {
|
|
467
|
+
const msg = new http.OutgoingMessage();
|
|
468
|
+
msg.setHeader('Content-Type', 'text/plain');
|
|
469
|
+
expect(msg.getHeader('Content-Type')).toBe('text/plain');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await it('headers should be case-insensitive', async () => {
|
|
473
|
+
const msg = new http.OutgoingMessage();
|
|
474
|
+
msg.setHeader('Content-Type', 'text/html');
|
|
475
|
+
expect(msg.getHeader('content-type')).toBe('text/html');
|
|
476
|
+
expect(msg.getHeader('CONTENT-TYPE')).toBe('text/html');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
await it('hasHeader should return boolean', async () => {
|
|
480
|
+
const msg = new http.OutgoingMessage();
|
|
481
|
+
expect(msg.hasHeader('X-Nope')).toBe(false);
|
|
482
|
+
msg.setHeader('X-Yep', 'yes');
|
|
483
|
+
expect(msg.hasHeader('X-Yep')).toBe(true);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
await it('removeHeader should remove the header', async () => {
|
|
487
|
+
const msg = new http.OutgoingMessage();
|
|
488
|
+
msg.setHeader('X-Remove', 'val');
|
|
489
|
+
expect(msg.hasHeader('X-Remove')).toBe(true);
|
|
490
|
+
msg.removeHeader('X-Remove');
|
|
491
|
+
expect(msg.hasHeader('X-Remove')).toBe(false);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
await it('getHeaderNames should return array of names', async () => {
|
|
495
|
+
const msg = new http.OutgoingMessage();
|
|
496
|
+
msg.setHeader('Content-Type', 'text/html');
|
|
497
|
+
msg.setHeader('X-Custom', 'foo');
|
|
498
|
+
const names = msg.getHeaderNames();
|
|
499
|
+
expect(Array.isArray(names)).toBe(true);
|
|
500
|
+
expect(names.length).toBe(2);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
await it('getHeaders should return headers object', async () => {
|
|
504
|
+
const msg = new http.OutgoingMessage();
|
|
505
|
+
msg.setHeader('Content-Type', 'text/html');
|
|
506
|
+
msg.setHeader('X-Num', '42');
|
|
507
|
+
const headers = msg.getHeaders();
|
|
508
|
+
expect(typeof headers).toBe('object');
|
|
509
|
+
expect(headers['content-type']).toBe('text/html');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
await it('should have headersSent property', async () => {
|
|
513
|
+
const msg = new http.OutgoingMessage();
|
|
514
|
+
expect(typeof msg.headersSent).toBe('boolean');
|
|
515
|
+
expect(msg.headersSent).toBe(false);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
await it('should have writableEnded property', async () => {
|
|
519
|
+
const msg = new http.OutgoingMessage();
|
|
520
|
+
expect(typeof msg.writableEnded).toBe('boolean');
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
await it('should have writableFinished property', async () => {
|
|
524
|
+
const msg = new http.OutgoingMessage();
|
|
525
|
+
expect(typeof msg.writableFinished).toBe('boolean');
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
await it('setHeader should overwrite existing header', async () => {
|
|
529
|
+
const msg = new http.OutgoingMessage();
|
|
530
|
+
msg.setHeader('X-Test', 'first');
|
|
531
|
+
msg.setHeader('X-Test', 'second');
|
|
532
|
+
expect(msg.getHeader('X-Test')).toBe('second');
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
await it('setHeader should accept array values', async () => {
|
|
536
|
+
const msg = new http.OutgoingMessage();
|
|
537
|
+
msg.setHeader('Set-Cookie', ['a=1', 'b=2']);
|
|
538
|
+
const val = msg.getHeader('Set-Cookie');
|
|
539
|
+
expect(Array.isArray(val)).toBe(true);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
await it('should have appendHeader method', async () => {
|
|
543
|
+
const msg = new http.OutgoingMessage();
|
|
544
|
+
expect(typeof msg.appendHeader).toBe('function');
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// ===================== IncomingMessage properties =====================
|
|
549
|
+
await describe('http.IncomingMessage', async () => {
|
|
550
|
+
await it('should export IncomingMessage class', async () => {
|
|
551
|
+
expect(typeof http.IncomingMessage).toBe('function');
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
await it('should export ServerResponse class', async () => {
|
|
555
|
+
expect(typeof http.ServerResponse).toBe('function');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await it('should export ClientRequest class', async () => {
|
|
559
|
+
expect(typeof http.ClientRequest).toBe('function');
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ===================== createServer =====================
|
|
564
|
+
await describe('http.createServer extended', async () => {
|
|
565
|
+
await it('should be a function', async () => {
|
|
566
|
+
expect(typeof http.createServer).toBe('function');
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
await it('should return a Server', async () => {
|
|
570
|
+
const server = http.createServer();
|
|
571
|
+
expect(server).toBeDefined();
|
|
572
|
+
expect(typeof server.listen).toBe('function');
|
|
573
|
+
expect(typeof server.close).toBe('function');
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
await it('should accept options object', async () => {
|
|
577
|
+
const server = http.createServer({});
|
|
578
|
+
expect(server).toBeDefined();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await it('should accept requestListener', async () => {
|
|
582
|
+
const server = http.createServer((_req, _res) => {});
|
|
583
|
+
expect(server).toBeDefined();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
await it('should accept options and requestListener', async () => {
|
|
587
|
+
const server = http.createServer({}, (_req, _res) => {});
|
|
588
|
+
expect(server).toBeDefined();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
await it('server should have setTimeout method', async () => {
|
|
592
|
+
const server = http.createServer();
|
|
593
|
+
expect(typeof server.setTimeout).toBe('function');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
await it('server should have address method', async () => {
|
|
597
|
+
const server = http.createServer();
|
|
598
|
+
expect(typeof server.address).toBe('function');
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
await it('server should have maxConnections property', async () => {
|
|
602
|
+
const server = http.createServer();
|
|
603
|
+
expect(typeof server.maxConnections === 'number' || server.maxConnections === undefined).toBe(true);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
await it('server should have listening property', async () => {
|
|
607
|
+
const server = http.createServer();
|
|
608
|
+
expect(typeof server.listening).toBe('boolean');
|
|
609
|
+
expect(server.listening).toBe(false);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
await it('server should be an EventEmitter', async () => {
|
|
613
|
+
const server = http.createServer();
|
|
614
|
+
expect(typeof server.on).toBe('function');
|
|
615
|
+
expect(typeof server.emit).toBe('function');
|
|
616
|
+
expect(typeof server.once).toBe('function');
|
|
617
|
+
expect(typeof server.removeListener).toBe('function');
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// IncomingMessage — Readable stream representing an HTTP request (server) or response (client).
|
|
2
|
+
// Reference: Node.js lib/_http_incoming.js
|
|
3
|
+
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
5
|
+
import { Buffer } from 'node:buffer';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* IncomingMessage — Readable stream for HTTP request (server-side) or response (client-side).
|
|
9
|
+
*/
|
|
10
|
+
export class IncomingMessage extends Readable {
|
|
11
|
+
httpVersion = '1.1';
|
|
12
|
+
httpVersionMajor = 1;
|
|
13
|
+
httpVersionMinor = 1;
|
|
14
|
+
headers: Record<string, string | string[]> = {};
|
|
15
|
+
rawHeaders: string[] = [];
|
|
16
|
+
method?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
statusCode?: number;
|
|
19
|
+
statusMessage?: string;
|
|
20
|
+
complete = false;
|
|
21
|
+
socket: any = null;
|
|
22
|
+
aborted = false;
|
|
23
|
+
|
|
24
|
+
private _timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_read(_size: number): void {
|
|
31
|
+
// Data is pushed externally via _pushBody or _pushStream
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Finish the readable stream with the body data (used by server-side handler). */
|
|
35
|
+
_pushBody(body: Uint8Array | null): void {
|
|
36
|
+
if (body && body.length > 0) {
|
|
37
|
+
this.push(Buffer.from(body));
|
|
38
|
+
}
|
|
39
|
+
this.push(null);
|
|
40
|
+
this.complete = true;
|
|
41
|
+
// Clear timeout when request body is complete
|
|
42
|
+
if (this._timeoutTimer) {
|
|
43
|
+
clearTimeout(this._timeoutTimer);
|
|
44
|
+
this._timeoutTimer = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setTimeout(msecs: number, callback?: () => void): this {
|
|
49
|
+
if (this._timeoutTimer) {
|
|
50
|
+
clearTimeout(this._timeoutTimer);
|
|
51
|
+
this._timeoutTimer = null;
|
|
52
|
+
}
|
|
53
|
+
if (callback) this.once('timeout', callback);
|
|
54
|
+
if (msecs > 0) {
|
|
55
|
+
this._timeoutTimer = setTimeout(() => {
|
|
56
|
+
this._timeoutTimer = null;
|
|
57
|
+
this.emit('timeout');
|
|
58
|
+
}, msecs);
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
destroy(error?: Error): this {
|
|
64
|
+
if (this._timeoutTimer) {
|
|
65
|
+
clearTimeout(this._timeoutTimer);
|
|
66
|
+
this._timeoutTimer = null;
|
|
67
|
+
}
|
|
68
|
+
this.aborted = true;
|
|
69
|
+
return super.destroy(error) as this;
|
|
70
|
+
}
|
|
71
|
+
}
|