@gjsify/http2 0.1.15 → 0.3.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.
@@ -0,0 +1,303 @@
1
+ // GJS-only integration tests for http2 server + client (Soup.Server / Soup.Session backed)
2
+ // These tests only run on GJS since they require Soup 3.0.
3
+ // Wrapped in on('Gjs') — not executed on Node.js.
4
+
5
+ import { describe, it, expect, on } from '@gjsify/unit';
6
+ import http2 from 'node:http2';
7
+
8
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
9
+
10
+ type AnyServer = ReturnType<typeof http2.createServer>;
11
+ type AnyStream = ReturnType<ReturnType<typeof http2.connect>['request']>;
12
+
13
+ function withServer(
14
+ handler: (req: any, res: any) => void,
15
+ ): Promise<{ server: AnyServer; port: number }> {
16
+ return new Promise((resolve, reject) => {
17
+ const server = http2.createServer(handler);
18
+ server.once('error', reject);
19
+ server.listen(0, () => {
20
+ const port = (server.address() as any)?.port;
21
+ if (!port) return reject(new Error('Could not get server port'));
22
+ resolve({ server, port });
23
+ });
24
+ });
25
+ }
26
+
27
+ function collectBody(stream: AnyStream): Promise<string> {
28
+ return new Promise((resolve, reject) => {
29
+ const chunks: Buffer[] = [];
30
+ (stream as any).on('data', (chunk: Buffer) => chunks.push(chunk));
31
+ (stream as any).on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
32
+ (stream as any).on('error', reject);
33
+ });
34
+ }
35
+
36
+ // ─── Tests ────────────────────────────────────────────────────────────────────
37
+
38
+ export default async () => {
39
+ await on('Gjs', async () => {
40
+
41
+ await describe('http2.createServer()', async () => {
42
+ await it('returns an server instance with listen/close', async () => {
43
+ const server = http2.createServer();
44
+ expect(server).toBeDefined();
45
+ expect(typeof (server as any).listen).toBe('function');
46
+ expect(typeof (server as any).close).toBe('function');
47
+ });
48
+
49
+ await it('listen() starts listening and emits listening', async () => {
50
+ const server = http2.createServer();
51
+ await new Promise<void>((resolve, reject) => {
52
+ (server as any).once('error', reject);
53
+ (server as any).listen(0, resolve);
54
+ });
55
+ expect((server as any).listening).toBeTruthy();
56
+ const addr = (server as any).address();
57
+ expect(addr).toBeDefined();
58
+ expect(addr.port > 0).toBeTruthy();
59
+ (server as any).close();
60
+ });
61
+
62
+ await it('close() stops listening', async () => {
63
+ const server = http2.createServer();
64
+ await new Promise<void>((res) => (server as any).listen(0, res));
65
+ await new Promise<void>((res) => (server as any).close(res));
66
+ expect((server as any).listening).toBeFalsy();
67
+ });
68
+ });
69
+
70
+ await describe('http2 compat API: request event', async () => {
71
+ await it('emits request with req and res objects', async () => {
72
+ const { server, port } = await withServer((req, res) => {
73
+ expect(req).toBeDefined();
74
+ expect(res).toBeDefined();
75
+ expect(typeof req.method).toBe('string');
76
+ res.writeHead(200);
77
+ res.end();
78
+ });
79
+
80
+ const session = http2.connect(`http://localhost:${port}`);
81
+ const stream = session.request({ ':method': 'GET', ':path': '/' }, { endStream: true } as any);
82
+
83
+ await new Promise<void>((resolve, reject) => {
84
+ (stream as any).on('response', () => resolve());
85
+ (stream as any).on('error', reject);
86
+ setTimeout(() => reject(new Error('timeout')), 5000);
87
+ });
88
+
89
+ session.close();
90
+ (server as any).close();
91
+ });
92
+
93
+ await it('req.method and req.url are populated', async () => {
94
+ let capturedMethod = '';
95
+ let capturedUrl = '';
96
+
97
+ const { server, port } = await withServer((req, res) => {
98
+ capturedMethod = req.method;
99
+ capturedUrl = req.url;
100
+ res.writeHead(204);
101
+ res.end();
102
+ });
103
+
104
+ const session = http2.connect(`http://localhost:${port}`);
105
+ const stream = session.request(
106
+ { ':method': 'GET', ':path': '/hello?foo=bar' },
107
+ { endStream: true } as any,
108
+ );
109
+
110
+ await new Promise<void>((resolve, reject) => {
111
+ (stream as any).on('response', () => resolve());
112
+ (stream as any).on('error', reject);
113
+ setTimeout(() => reject(new Error('timeout')), 5000);
114
+ });
115
+
116
+ expect(capturedMethod).toBe('GET');
117
+ expect(capturedUrl).toBe('/hello?foo=bar');
118
+
119
+ session.close();
120
+ (server as any).close();
121
+ });
122
+
123
+ await it('req.headers contains custom request headers', async () => {
124
+ let capturedHeaders: Record<string, string | string[]> = {};
125
+
126
+ const { server, port } = await withServer((req, res) => {
127
+ capturedHeaders = req.headers;
128
+ res.writeHead(200);
129
+ res.end();
130
+ });
131
+
132
+ const session = http2.connect(`http://localhost:${port}`);
133
+ const stream = session.request(
134
+ { ':method': 'GET', ':path': '/', 'x-custom': 'test-value' },
135
+ { endStream: true } as any,
136
+ );
137
+
138
+ await new Promise<void>((resolve, reject) => {
139
+ (stream as any).on('response', () => resolve());
140
+ (stream as any).on('error', reject);
141
+ setTimeout(() => reject(new Error('timeout')), 5000);
142
+ });
143
+
144
+ expect(capturedHeaders['x-custom']).toBe('test-value');
145
+
146
+ session.close();
147
+ (server as any).close();
148
+ });
149
+ });
150
+
151
+ await describe('http2 compat API: response body', async () => {
152
+ await it('response body text is received by client stream', async () => {
153
+ const { server, port } = await withServer((_req, res) => {
154
+ res.writeHead(200, { 'content-type': 'text/plain' });
155
+ res.end('Hello HTTP/2');
156
+ });
157
+
158
+ const session = http2.connect(`http://localhost:${port}`);
159
+ const stream = session.request(
160
+ { ':method': 'GET', ':path': '/' },
161
+ { endStream: true } as any,
162
+ );
163
+
164
+ await new Promise<void>((resolve, reject) => {
165
+ (stream as any).on('response', () => resolve());
166
+ (stream as any).on('error', reject);
167
+ setTimeout(() => reject(new Error('timeout')), 5000);
168
+ });
169
+ const body = await collectBody(stream);
170
+
171
+ expect(body).toBe('Hello HTTP/2');
172
+
173
+ session.close();
174
+ (server as any).close();
175
+ });
176
+
177
+ await it(':status is included in response headers', async () => {
178
+ const { server, port } = await withServer((_req, res) => {
179
+ res.writeHead(201);
180
+ res.end('created');
181
+ });
182
+
183
+ const session = http2.connect(`http://localhost:${port}`);
184
+ const stream = session.request(
185
+ { ':method': 'POST', ':path': '/items' },
186
+ { endStream: true } as any,
187
+ );
188
+
189
+ let responseHeaders: Record<string, string | string[]> = {};
190
+ await new Promise<void>((resolve, reject) => {
191
+ (stream as any).on('response', (headers: any) => {
192
+ responseHeaders = headers;
193
+ resolve();
194
+ });
195
+ (stream as any).on('error', reject);
196
+ setTimeout(() => reject(new Error('timeout')), 5000);
197
+ });
198
+ await collectBody(stream);
199
+
200
+ expect(responseHeaders[':status']).toBe('201');
201
+
202
+ session.close();
203
+ (server as any).close();
204
+ });
205
+ });
206
+
207
+ await describe('http2 compat API: request body', async () => {
208
+ await it('server receives POST body via async iteration on req', async () => {
209
+ let capturedBody = '';
210
+
211
+ const { server, port } = await withServer(async (req, res) => {
212
+ const chunks: Buffer[] = [];
213
+ for await (const chunk of req) {
214
+ chunks.push(chunk as Buffer);
215
+ }
216
+ capturedBody = Buffer.concat(chunks).toString('utf8');
217
+ res.writeHead(200);
218
+ res.end('ok');
219
+ });
220
+
221
+ const session = http2.connect(`http://localhost:${port}`);
222
+ const stream = session.request({
223
+ ':method': 'POST',
224
+ ':path': '/upload',
225
+ 'content-type': 'text/plain',
226
+ });
227
+
228
+ (stream as any).write('Hello');
229
+ (stream as any).end(' World');
230
+
231
+ await new Promise<void>((resolve, reject) => {
232
+ (stream as any).on('response', () => resolve());
233
+ (stream as any).on('error', reject);
234
+ setTimeout(() => reject(new Error('timeout')), 5000);
235
+ });
236
+
237
+ expect(capturedBody).toBe('Hello World');
238
+
239
+ session.close();
240
+ (server as any).close();
241
+ });
242
+ });
243
+
244
+ await describe('http2 session API: stream event', async () => {
245
+ await it('server emits stream event with headers', async () => {
246
+ let streamEventFired = false;
247
+
248
+ const server = http2.createServer();
249
+ (server as any).on('stream', (stream: any, headers: any) => {
250
+ streamEventFired = true;
251
+ expect(stream).toBeDefined();
252
+ expect(typeof stream.respond).toBe('function');
253
+ expect(typeof headers).toBe('object');
254
+ stream.respond({ ':status': 200, 'content-type': 'text/plain' });
255
+ stream.end('stream API response');
256
+ });
257
+
258
+ await new Promise<void>((res) => (server as any).listen(0, res));
259
+ const port = (server as any).address()?.port ?? 0;
260
+
261
+ const session = http2.connect(`http://localhost:${port}`);
262
+ const stream = session.request(
263
+ { ':method': 'GET', ':path': '/' },
264
+ { endStream: true } as any,
265
+ );
266
+
267
+ const body = await new Promise<string>((resolve, reject) => {
268
+ const chunks: Buffer[] = [];
269
+ (stream as any).on('response', () => {});
270
+ (stream as any).on('data', (chunk: Buffer) => chunks.push(chunk));
271
+ (stream as any).on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
272
+ (stream as any).on('error', reject);
273
+ setTimeout(() => reject(new Error('timeout')), 5000);
274
+ });
275
+
276
+ expect(body).toBe('stream API response');
277
+ expect(streamEventFired).toBeTruthy();
278
+
279
+ session.close();
280
+ (server as any).close();
281
+ });
282
+ });
283
+
284
+ await describe('http2.connect()', async () => {
285
+ await it('returns a session with request() method', async () => {
286
+ const session = http2.connect('http://localhost:19999');
287
+ expect(session).toBeDefined();
288
+ expect(typeof session.request).toBe('function');
289
+ expect(typeof session.close).toBe('function');
290
+ session.close();
291
+ });
292
+
293
+ await it('session.request() returns a stream with on()', async () => {
294
+ const session = http2.connect('http://localhost:19999');
295
+ const stream = session.request({ ':method': 'GET', ':path': '/' });
296
+ expect(stream).toBeDefined();
297
+ expect(typeof (stream as any).on).toBe('function');
298
+ session.close();
299
+ });
300
+ });
301
+
302
+ });
303
+ };