@gjsify/dgram 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.
@@ -0,0 +1,1165 @@
1
+ // Ported from refs/node-test/parallel/test-dgram-connect.js,
2
+ // test-dgram-bind.js, test-dgram-address.js
3
+ // Original: MIT license, Node.js contributors
4
+ import { describe, it, expect, on } from '@gjsify/unit';
5
+ import dgram, { createSocket, Socket } from 'node:dgram';
6
+
7
+ export default async () => {
8
+ await describe('dgram', async () => {
9
+
10
+ // --- Module exports ---
11
+ await describe('exports', async () => {
12
+ await it('should export createSocket as a function', async () => {
13
+ expect(typeof createSocket).toBe('function');
14
+ });
15
+
16
+ await it('should export Socket class', async () => {
17
+ expect(typeof Socket).toBe('function');
18
+ });
19
+
20
+ await it('should have exports on the default export', async () => {
21
+ expect(typeof dgram.createSocket).toBe('function');
22
+ expect(typeof dgram.Socket).toBe('function');
23
+ });
24
+ });
25
+
26
+ // --- Socket creation ---
27
+ await describe('createSocket', async () => {
28
+ await it('should create a udp4 socket', async () => {
29
+ const socket = createSocket('udp4');
30
+ expect(socket).toBeDefined();
31
+ expect(socket.type).toBe('udp4');
32
+ expect(typeof socket.close).toBe('function');
33
+ expect(typeof socket.send).toBe('function');
34
+ expect(typeof socket.bind).toBe('function');
35
+ expect(typeof socket.address).toBe('function');
36
+ socket.close();
37
+ });
38
+
39
+ await it('should create a udp6 socket', async () => {
40
+ const socket = createSocket('udp6');
41
+ expect(socket).toBeDefined();
42
+ expect(socket.type).toBe('udp6');
43
+ socket.close();
44
+ });
45
+
46
+ await it('should create socket with options object', async () => {
47
+ const socket = createSocket({ type: 'udp4', reuseAddr: true });
48
+ expect(socket).toBeDefined();
49
+ expect(socket.type).toBe('udp4');
50
+ socket.close();
51
+ });
52
+
53
+ await it('should create socket with callback option', async () => {
54
+ let called = false;
55
+ const socket = createSocket({ type: 'udp4' }, () => { called = true; });
56
+ expect(socket).toBeDefined();
57
+ socket.close();
58
+ });
59
+ });
60
+
61
+ // --- Socket methods ---
62
+ await describe('Socket methods', async () => {
63
+ await it('should have multicast methods', async () => {
64
+ const socket = createSocket('udp4');
65
+ expect(typeof socket.setBroadcast).toBe('function');
66
+ expect(typeof socket.setTTL).toBe('function');
67
+ expect(typeof socket.setMulticastTTL).toBe('function');
68
+ expect(typeof socket.setMulticastLoopback).toBe('function');
69
+ expect(typeof socket.addMembership).toBe('function');
70
+ expect(typeof socket.dropMembership).toBe('function');
71
+ socket.close();
72
+ });
73
+
74
+ await it('should have ref/unref methods that return socket', async () => {
75
+ const socket = createSocket('udp4');
76
+ expect(socket.ref()).toBe(socket);
77
+ expect(socket.unref()).toBe(socket);
78
+ socket.close();
79
+ });
80
+
81
+ await it('should have remoteAddress method or property', async () => {
82
+ const socket = createSocket('udp4');
83
+ // remoteAddress is a method in Node.js, available after connect()
84
+ expect(typeof socket.remoteAddress === 'function' || socket.remoteAddress === undefined).toBe(true);
85
+ socket.close();
86
+ });
87
+
88
+ await it('should have setRecvBufferSize and setSendBufferSize', async () => {
89
+ const socket = createSocket('udp4');
90
+ expect(typeof socket.setRecvBufferSize === 'function' || socket.setRecvBufferSize === undefined).toBe(true);
91
+ expect(typeof socket.setSendBufferSize === 'function' || socket.setSendBufferSize === undefined).toBe(true);
92
+ socket.close();
93
+ });
94
+ });
95
+
96
+ // --- Close event ---
97
+ await describe('close', async () => {
98
+ await it('should emit close event', async () => {
99
+ const socket = createSocket('udp4');
100
+ let closed = false;
101
+ socket.on('close', () => { closed = true; });
102
+ socket.close();
103
+ await new Promise<void>((r) => setTimeout(r, 10));
104
+ expect(closed).toBe(true);
105
+ });
106
+
107
+ await it('should accept callback in close()', async () => {
108
+ const socket = createSocket('udp4');
109
+ const result = await new Promise<string>((resolve) => {
110
+ socket.close(() => resolve('closed'));
111
+ });
112
+ expect(result).toBe('closed');
113
+ });
114
+ });
115
+
116
+ // --- Bind ---
117
+ await describe('bind', async () => {
118
+ await it('should bind and emit listening event', async () => {
119
+ const socket = createSocket('udp4');
120
+ let listening = false;
121
+
122
+ const result = await Promise.race([
123
+ new Promise<string>((resolve) => {
124
+ socket.on('listening', () => { listening = true; resolve('listening'); });
125
+ socket.on('error', () => resolve('error'));
126
+ socket.bind(0);
127
+ }),
128
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
129
+ ]);
130
+
131
+ if (result === 'listening') {
132
+ expect(listening).toBe(true);
133
+ const addr = socket.address();
134
+ expect(typeof addr.port).toBe('number');
135
+ expect(addr.port).toBeGreaterThan(0);
136
+ expect(typeof addr.address).toBe('string');
137
+ }
138
+ socket.close();
139
+ });
140
+
141
+ await it('should bind with options object', async () => {
142
+ const socket = createSocket('udp4');
143
+
144
+ const result = await Promise.race([
145
+ new Promise<string>((resolve) => {
146
+ socket.on('listening', () => resolve('listening'));
147
+ socket.on('error', () => resolve('error'));
148
+ socket.bind({ port: 0 });
149
+ }),
150
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
151
+ ]);
152
+
153
+ if (result === 'listening') {
154
+ const addr = socket.address();
155
+ expect(addr.port).toBeGreaterThan(0);
156
+ }
157
+ socket.close();
158
+ });
159
+ });
160
+
161
+ // --- UDP send/receive (Node.js only, GJS needs MainLoop) ---
162
+ await on('Node.js', async () => {
163
+ await describe('UDP send', async () => {
164
+ await it('should send UDP message without error', async () => {
165
+ const server = createSocket('udp4');
166
+ const client = createSocket('udp4');
167
+
168
+ await new Promise<void>((resolve, reject) => {
169
+ server.on('listening', () => {
170
+ const serverAddr = server.address();
171
+ client.send('hello', serverAddr.port, '127.0.0.1', (err) => {
172
+ expect(err).toBeNull();
173
+ server.close();
174
+ client.close();
175
+ resolve();
176
+ });
177
+ });
178
+ server.on('error', reject);
179
+ server.bind(0);
180
+ });
181
+ });
182
+
183
+ await it('should send Buffer', async () => {
184
+ const server = createSocket('udp4');
185
+ const client = createSocket('udp4');
186
+
187
+ await new Promise<void>((resolve, reject) => {
188
+ server.on('listening', () => {
189
+ const serverAddr = server.address();
190
+ client.send(Buffer.from('buffer test'), serverAddr.port, '127.0.0.1', (err) => {
191
+ expect(err).toBeNull();
192
+ server.close();
193
+ client.close();
194
+ resolve();
195
+ });
196
+ });
197
+ server.on('error', reject);
198
+ server.bind(0);
199
+ });
200
+ });
201
+
202
+ await it('should send Uint8Array', async () => {
203
+ const server = createSocket('udp4');
204
+ const client = createSocket('udp4');
205
+
206
+ await new Promise<void>((resolve, reject) => {
207
+ server.on('listening', () => {
208
+ const serverAddr = server.address();
209
+ client.send(new Uint8Array([72, 101, 108, 108, 111]), serverAddr.port, '127.0.0.1', (err) => {
210
+ expect(err).toBeNull();
211
+ server.close();
212
+ client.close();
213
+ resolve();
214
+ });
215
+ });
216
+ server.on('error', reject);
217
+ server.bind(0);
218
+ });
219
+ });
220
+
221
+ await it('should receive UDP message', async () => {
222
+ const server = createSocket('udp4');
223
+ const client = createSocket('udp4');
224
+
225
+ await new Promise<void>((resolve, reject) => {
226
+ server.on('message', (msg, rinfo) => {
227
+ expect(msg.toString()).toBe('hello udp');
228
+ expect(typeof rinfo.address).toBe('string');
229
+ expect(typeof rinfo.port).toBe('number');
230
+ expect(rinfo.port).toBeGreaterThan(0);
231
+ server.close();
232
+ client.close();
233
+ resolve();
234
+ });
235
+ server.on('listening', () => {
236
+ const serverAddr = server.address();
237
+ client.send('hello udp', serverAddr.port, '127.0.0.1');
238
+ });
239
+ server.on('error', reject);
240
+ server.bind(0);
241
+ });
242
+ });
243
+
244
+ await it('should report correct rinfo family', async () => {
245
+ const server = createSocket('udp4');
246
+ const client = createSocket('udp4');
247
+
248
+ await new Promise<void>((resolve, reject) => {
249
+ server.on('message', (_msg, rinfo) => {
250
+ expect(rinfo.family).toBe('IPv4');
251
+ expect(rinfo.address).toBe('127.0.0.1');
252
+ server.close();
253
+ client.close();
254
+ resolve();
255
+ });
256
+ server.on('listening', () => {
257
+ const serverAddr = server.address();
258
+ client.send('test', serverAddr.port, '127.0.0.1');
259
+ });
260
+ server.on('error', reject);
261
+ server.bind(0);
262
+ });
263
+ });
264
+ });
265
+ });
266
+
267
+ // --- Additional tests ---
268
+
269
+ await describe('createSocket with reuseAddr option', async () => {
270
+ await it('should create a socket with reuseAddr true', async () => {
271
+ const socket = createSocket({ type: 'udp4', reuseAddr: true });
272
+ expect(socket).toBeDefined();
273
+ expect(socket.type).toBe('udp4');
274
+ socket.close();
275
+ });
276
+
277
+ await it('should create a socket with reuseAddr false', async () => {
278
+ const socket = createSocket({ type: 'udp4', reuseAddr: false });
279
+ expect(socket).toBeDefined();
280
+ socket.close();
281
+ });
282
+ });
283
+
284
+ await describe('Socket.address() structure', async () => {
285
+ await it('should return object with port, address, and family after bind', async () => {
286
+ const socket = createSocket('udp4');
287
+
288
+ const result = await Promise.race([
289
+ new Promise<string>((resolve) => {
290
+ socket.on('listening', () => resolve('listening'));
291
+ socket.on('error', () => resolve('error'));
292
+ socket.bind(0);
293
+ }),
294
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
295
+ ]);
296
+
297
+ if (result === 'listening') {
298
+ const addr = socket.address();
299
+ expect(typeof addr.port).toBe('number');
300
+ expect(typeof addr.address).toBe('string');
301
+ expect(typeof addr.family).toBe('string');
302
+ expect(addr.port).toBeGreaterThan(0);
303
+ }
304
+ socket.close();
305
+ });
306
+
307
+ // Ported from refs/node-test/parallel/test-dgram-address.js
308
+ await it('should return family IPv4 for udp4 socket after bind', async () => {
309
+ const socket = createSocket('udp4');
310
+
311
+ const result = await Promise.race([
312
+ new Promise<string>((resolve) => {
313
+ socket.on('listening', () => resolve('listening'));
314
+ socket.on('error', () => resolve('error'));
315
+ socket.bind(0, '127.0.0.1');
316
+ }),
317
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
318
+ ]);
319
+
320
+ if (result === 'listening') {
321
+ const addr = socket.address();
322
+ expect(addr.family).toBe('IPv4');
323
+ expect(addr.address).toBe('127.0.0.1');
324
+ expect(addr.port).toBeGreaterThan(0);
325
+ }
326
+ socket.close();
327
+ });
328
+
329
+ // Ported from refs/node-test/parallel/test-dgram-bind-default-address.js
330
+ await it('should bind to 0.0.0.0 by default for udp4', async () => {
331
+ const socket = createSocket('udp4');
332
+
333
+ const result = await Promise.race([
334
+ new Promise<string>((resolve) => {
335
+ socket.on('listening', () => resolve('listening'));
336
+ socket.on('error', () => resolve('error'));
337
+ socket.bind(0);
338
+ }),
339
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
340
+ ]);
341
+
342
+ if (result === 'listening') {
343
+ const addr = socket.address();
344
+ expect(addr.address).toBe('0.0.0.0');
345
+ expect(addr.port).toBeGreaterThan(0);
346
+ }
347
+ socket.close();
348
+ });
349
+ });
350
+
351
+ await describe('Socket method existence', async () => {
352
+ await it('should have setTTL method', async () => {
353
+ const socket = createSocket('udp4');
354
+ expect(typeof socket.setTTL).toBe('function');
355
+ socket.close();
356
+ });
357
+
358
+ await it('should have setBroadcast method', async () => {
359
+ const socket = createSocket('udp4');
360
+ expect(typeof socket.setBroadcast).toBe('function');
361
+ socket.close();
362
+ });
363
+
364
+ await it('should have setMulticastInterface method', async () => {
365
+ const socket = createSocket('udp4');
366
+ expect(typeof socket.setMulticastInterface).toBe('function');
367
+ socket.close();
368
+ });
369
+
370
+ await it('should have getRecvBufferSize and getSendBufferSize', async () => {
371
+ const socket = createSocket('udp4');
372
+ expect(typeof socket.getRecvBufferSize).toBe('function');
373
+ expect(typeof socket.getSendBufferSize).toBe('function');
374
+ socket.close();
375
+ });
376
+
377
+ await it('should have connect and disconnect methods', async () => {
378
+ const socket = createSocket('udp4');
379
+ expect(typeof socket.connect).toBe('function');
380
+ expect(typeof socket.disconnect).toBe('function');
381
+ socket.close();
382
+ });
383
+ });
384
+
385
+ // --- setBroadcast ---
386
+ // Ported from refs/node-test/parallel/test-dgram-setBroadcast.js
387
+ await describe('setBroadcast', async () => {
388
+ await it('should call setBroadcast after bind without error', async () => {
389
+ const socket = createSocket('udp4');
390
+
391
+ const result = await Promise.race([
392
+ new Promise<string>((resolve) => {
393
+ socket.on('listening', () => resolve('listening'));
394
+ socket.on('error', () => resolve('error'));
395
+ socket.bind(0);
396
+ }),
397
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
398
+ ]);
399
+
400
+ if (result === 'listening') {
401
+ expect(() => socket.setBroadcast(true)).not.toThrow();
402
+ expect(() => socket.setBroadcast(false)).not.toThrow();
403
+ }
404
+ socket.close();
405
+ });
406
+ });
407
+
408
+ // --- setTTL ---
409
+ // Ported from refs/node-test/parallel/test-dgram-setTTL.js
410
+ await describe('setTTL', async () => {
411
+ await it('should return the TTL value after bind', async () => {
412
+ const socket = createSocket('udp4');
413
+
414
+ const result = await Promise.race([
415
+ new Promise<string>((resolve) => {
416
+ socket.on('listening', () => resolve('listening'));
417
+ socket.on('error', () => resolve('error'));
418
+ socket.bind(0);
419
+ }),
420
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
421
+ ]);
422
+
423
+ if (result === 'listening') {
424
+ const ttl = socket.setTTL(16);
425
+ expect(ttl).toBe(16);
426
+ }
427
+ socket.close();
428
+ });
429
+ });
430
+
431
+ // --- setMulticastTTL ---
432
+ // Ported from refs/node-test/parallel/test-dgram-multicast-setTTL.js
433
+ await describe('setMulticastTTL', async () => {
434
+ await it('should return the TTL value after bind', async () => {
435
+ const socket = createSocket('udp4');
436
+
437
+ const result = await Promise.race([
438
+ new Promise<string>((resolve) => {
439
+ socket.on('listening', () => resolve('listening'));
440
+ socket.on('error', () => resolve('error'));
441
+ socket.bind(0);
442
+ }),
443
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
444
+ ]);
445
+
446
+ if (result === 'listening') {
447
+ const ttl = socket.setMulticastTTL(16);
448
+ expect(ttl).toBe(16);
449
+ }
450
+ socket.close();
451
+ });
452
+ });
453
+
454
+ // --- setMulticastLoopback ---
455
+ // Ported from refs/node-test/parallel/test-dgram-multicast-loopback.js
456
+ await describe('setMulticastLoopback', async () => {
457
+ await it('should return the flag value after bind', async () => {
458
+ const socket = createSocket('udp4');
459
+
460
+ const result = await Promise.race([
461
+ new Promise<string>((resolve) => {
462
+ socket.on('listening', () => resolve('listening'));
463
+ socket.on('error', () => resolve('error'));
464
+ socket.bind(0);
465
+ }),
466
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
467
+ ]);
468
+
469
+ if (result === 'listening') {
470
+ const val = socket.setMulticastLoopback(true);
471
+ expect(val).toBe(true);
472
+ }
473
+ socket.close();
474
+ });
475
+ });
476
+
477
+ // --- addMembership / dropMembership ---
478
+ await describe('addMembership and dropMembership', async () => {
479
+ await it('should have addMembership as a function', async () => {
480
+ const socket = createSocket('udp4');
481
+ expect(typeof socket.addMembership).toBe('function');
482
+ socket.close();
483
+ });
484
+
485
+ await it('should have dropMembership as a function', async () => {
486
+ const socket = createSocket('udp4');
487
+ expect(typeof socket.dropMembership).toBe('function');
488
+ socket.close();
489
+ });
490
+ });
491
+
492
+ // --- ref / unref ---
493
+ // Ported from refs/node-test/parallel/test-dgram-ref.js, test-dgram-unref.js
494
+ await describe('ref and unref', async () => {
495
+ await it('ref() should return the socket for chaining', async () => {
496
+ const socket = createSocket('udp4');
497
+ const result = socket.ref();
498
+ expect(result).toBe(socket);
499
+ socket.close();
500
+ });
501
+
502
+ await it('unref() should return the socket for chaining', async () => {
503
+ const socket = createSocket('udp4');
504
+ const result = socket.unref();
505
+ expect(result).toBe(socket);
506
+ socket.close();
507
+ });
508
+
509
+ await it('ref() after close should not throw', async () => {
510
+ const socket = createSocket('udp4');
511
+ await new Promise<void>((resolve) => {
512
+ socket.close(() => {
513
+ expect(() => socket.ref()).not.toThrow();
514
+ resolve();
515
+ });
516
+ });
517
+ });
518
+
519
+ await it('unref() after close should not throw', async () => {
520
+ const socket = createSocket('udp4');
521
+ await new Promise<void>((resolve) => {
522
+ socket.close(() => {
523
+ expect(() => socket.unref()).not.toThrow();
524
+ resolve();
525
+ });
526
+ });
527
+ });
528
+
529
+ await it('should allow chaining ref().unref()', async () => {
530
+ const socket = createSocket('udp4');
531
+ const result = socket.ref().unref();
532
+ expect(result).toBe(socket);
533
+ socket.close();
534
+ });
535
+ });
536
+
537
+ // --- close ---
538
+ await describe('close on unbound socket', async () => {
539
+ await it('close() on unbound socket should not throw', async () => {
540
+ const socket = createSocket('udp4');
541
+ // Socket was never bound — close should still work
542
+ expect(() => socket.close()).not.toThrow();
543
+ });
544
+
545
+ await it('multiple close() calls should throw ERR_SOCKET_DGRAM_NOT_RUNNING', async () => {
546
+ const socket = createSocket('udp4');
547
+ socket.close();
548
+ // Second close should throw — socket is no longer running
549
+ let threw = false;
550
+ try {
551
+ socket.close();
552
+ } catch (e: unknown) {
553
+ threw = true;
554
+ expect((e as Error).message).toBe('Not running');
555
+ }
556
+ expect(threw).toBe(true);
557
+ });
558
+
559
+ await it('close() should return the socket', async () => {
560
+ const socket = createSocket('udp4');
561
+ const result = socket.close();
562
+ expect(result).toBe(socket);
563
+ });
564
+ });
565
+
566
+ // --- EventEmitter behavior ---
567
+ await describe('EventEmitter behavior', async () => {
568
+ await it('socket should be an instance of EventEmitter', async () => {
569
+ const socket = createSocket('udp4');
570
+ expect(typeof socket.on).toBe('function');
571
+ expect(typeof socket.once).toBe('function');
572
+ expect(typeof socket.emit).toBe('function');
573
+ expect(typeof socket.removeListener).toBe('function');
574
+ expect(typeof socket.removeAllListeners).toBe('function');
575
+ socket.close();
576
+ });
577
+
578
+ await it('should support adding multiple event listeners', async () => {
579
+ const socket = createSocket('udp4');
580
+ let count = 0;
581
+ socket.on('close', () => { count++; });
582
+ socket.on('close', () => { count++; });
583
+ socket.close();
584
+ await new Promise<void>((r) => setTimeout(r, 20));
585
+ expect(count).toBe(2);
586
+ });
587
+
588
+ await it('should support once() listener that fires only once', async () => {
589
+ const socket = createSocket('udp4');
590
+ let count = 0;
591
+ socket.once('close', () => { count++; });
592
+ socket.close();
593
+ await new Promise<void>((r) => setTimeout(r, 20));
594
+ expect(count).toBe(1);
595
+ });
596
+
597
+ await it('should support removeListener', async () => {
598
+ const socket = createSocket('udp4');
599
+ let called = false;
600
+ const handler = () => { called = true; };
601
+ socket.on('close', handler);
602
+ socket.removeListener('close', handler);
603
+ socket.close();
604
+ await new Promise<void>((r) => setTimeout(r, 20));
605
+ expect(called).toBe(false);
606
+ });
607
+
608
+ await it('should emit listening event on bind', async () => {
609
+ const socket = createSocket('udp4');
610
+ let listening = false;
611
+ socket.on('listening', () => { listening = true; });
612
+
613
+ const result = await Promise.race([
614
+ new Promise<string>((resolve) => {
615
+ socket.on('listening', () => resolve('listening'));
616
+ socket.on('error', () => resolve('error'));
617
+ socket.bind(0);
618
+ }),
619
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
620
+ ]);
621
+
622
+ if (result === 'listening') {
623
+ expect(listening).toBe(true);
624
+ }
625
+ socket.close();
626
+ });
627
+
628
+ await it('should support error event listener', async () => {
629
+ const socket = createSocket('udp4');
630
+ let errorCalled = false;
631
+ socket.on('error', () => { errorCalled = true; });
632
+ // Just verify the listener was registered without error
633
+ expect(typeof socket.listeners('error')).toBe('object');
634
+ socket.close();
635
+ });
636
+ });
637
+
638
+ await describe('createSocket type as string', async () => {
639
+ await it('should accept "udp4" as string type argument', async () => {
640
+ const socket = createSocket('udp4');
641
+ expect(socket).toBeDefined();
642
+ expect(socket.type).toBe('udp4');
643
+ socket.close();
644
+ });
645
+
646
+ await it('should accept "udp6" as string type argument', async () => {
647
+ const socket = createSocket('udp6');
648
+ expect(socket).toBeDefined();
649
+ expect(socket.type).toBe('udp6');
650
+ socket.close();
651
+ });
652
+ });
653
+
654
+ // --- IPv6 socket creation ---
655
+ await describe('IPv6 socket', async () => {
656
+ await it('should create udp6 socket with options object', async () => {
657
+ const socket = createSocket({ type: 'udp6' });
658
+ expect(socket).toBeDefined();
659
+ expect(socket.type).toBe('udp6');
660
+ socket.close();
661
+ });
662
+
663
+ await it('udp6 socket should bind and emit listening', async () => {
664
+ const socket = createSocket('udp6');
665
+
666
+ const result = await Promise.race([
667
+ new Promise<string>((resolve) => {
668
+ socket.on('listening', () => resolve('listening'));
669
+ socket.on('error', () => resolve('error'));
670
+ socket.bind(0);
671
+ }),
672
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
673
+ ]);
674
+
675
+ if (result === 'listening') {
676
+ const addr = socket.address();
677
+ expect(addr.family).toBe('IPv6');
678
+ expect(addr.port).toBeGreaterThan(0);
679
+ }
680
+ socket.close();
681
+ });
682
+ });
683
+
684
+ // --- bind with callback ---
685
+ await describe('bind with callback', async () => {
686
+ await it('should accept callback as second argument to bind(port, cb)', async () => {
687
+ const socket = createSocket('udp4');
688
+
689
+ const result = await Promise.race([
690
+ new Promise<string>((resolve) => {
691
+ socket.bind(0, () => resolve('listening'));
692
+ socket.on('error', () => resolve('error'));
693
+ }),
694
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
695
+ ]);
696
+
697
+ if (result === 'listening') {
698
+ const addr = socket.address();
699
+ expect(addr.port).toBeGreaterThan(0);
700
+ }
701
+ socket.close();
702
+ });
703
+ });
704
+
705
+ // --- setRecvBufferSize / setSendBufferSize ---
706
+ await describe('setRecvBufferSize and setSendBufferSize', async () => {
707
+ await it('should have setRecvBufferSize as a function', async () => {
708
+ const socket = createSocket('udp4');
709
+ expect(typeof socket.setRecvBufferSize).toBe('function');
710
+ socket.close();
711
+ });
712
+
713
+ await it('should have setSendBufferSize as a function', async () => {
714
+ const socket = createSocket('udp4');
715
+ expect(typeof socket.setSendBufferSize).toBe('function');
716
+ socket.close();
717
+ });
718
+
719
+ await it('should not throw when calling setRecvBufferSize after bind', async () => {
720
+ const socket = createSocket('udp4');
721
+
722
+ const result = await Promise.race([
723
+ new Promise<string>((resolve) => {
724
+ socket.on('listening', () => resolve('listening'));
725
+ socket.on('error', () => resolve('error'));
726
+ socket.bind(0);
727
+ }),
728
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
729
+ ]);
730
+
731
+ if (result === 'listening') {
732
+ expect(() => socket.setRecvBufferSize(8192)).not.toThrow();
733
+ }
734
+ socket.close();
735
+ });
736
+
737
+ await it('should not throw when calling setSendBufferSize after bind', async () => {
738
+ const socket = createSocket('udp4');
739
+
740
+ const result = await Promise.race([
741
+ new Promise<string>((resolve) => {
742
+ socket.on('listening', () => resolve('listening'));
743
+ socket.on('error', () => resolve('error'));
744
+ socket.bind(0);
745
+ }),
746
+ new Promise<string>((resolve) => setTimeout(() => resolve('timeout'), 2000)),
747
+ ]);
748
+
749
+ if (result === 'listening') {
750
+ expect(() => socket.setSendBufferSize(8192)).not.toThrow();
751
+ }
752
+ socket.close();
753
+ });
754
+ });
755
+
756
+ // --- connect / disconnect / remoteAddress ---
757
+ // Ported from refs/node-test/parallel/test-dgram-connect.js
758
+ await describe('connect and disconnect', async () => {
759
+ await it('connect should set remoteAddress', async () => {
760
+ const socket = createSocket('udp4');
761
+ await new Promise<void>((resolve) => {
762
+ socket.connect(12345, '127.0.0.1', () => {
763
+ const remote = (socket as any).remoteAddress();
764
+ expect(remote.port).toBe(12345);
765
+ expect(remote.address).toBe('127.0.0.1');
766
+ expect(remote.family).toBe('IPv4');
767
+ socket.close();
768
+ resolve();
769
+ });
770
+ });
771
+ });
772
+
773
+ await it('connect should emit connect event', async () => {
774
+ const socket = createSocket('udp4');
775
+ let connectEmitted = false;
776
+ socket.on('connect', () => { connectEmitted = true; });
777
+
778
+ await new Promise<void>((resolve) => {
779
+ socket.connect(12345, '127.0.0.1', () => {
780
+ expect(connectEmitted).toBe(true);
781
+ socket.close();
782
+ resolve();
783
+ });
784
+ });
785
+ });
786
+
787
+ await it('connect with default host should use 127.0.0.1', async () => {
788
+ const socket = createSocket('udp4');
789
+ await new Promise<void>((resolve) => {
790
+ socket.connect(12345, () => {
791
+ const remote = (socket as any).remoteAddress();
792
+ expect(remote.port).toBe(12345);
793
+ expect(remote.address).toBe('127.0.0.1');
794
+ socket.close();
795
+ resolve();
796
+ });
797
+ });
798
+ });
799
+
800
+ await it('connect twice should throw ERR_SOCKET_DGRAM_IS_CONNECTED', async () => {
801
+ const socket = createSocket('udp4');
802
+ await new Promise<void>((resolve) => {
803
+ socket.connect(12345, '127.0.0.1', () => {
804
+ let threw = false;
805
+ try {
806
+ socket.connect(12345, () => {});
807
+ } catch (e: unknown) {
808
+ threw = true;
809
+ expect((e as NodeJS.ErrnoException).code).toBe('ERR_SOCKET_DGRAM_IS_CONNECTED');
810
+ }
811
+ expect(threw).toBe(true);
812
+ socket.close();
813
+ resolve();
814
+ });
815
+ });
816
+ });
817
+
818
+ await it('disconnect after connect should succeed', async () => {
819
+ const socket = createSocket('udp4');
820
+ await new Promise<void>((resolve) => {
821
+ socket.connect(12345, '127.0.0.1', () => {
822
+ socket.disconnect();
823
+ // remoteAddress() should now throw
824
+ let threw = false;
825
+ try {
826
+ (socket as any).remoteAddress();
827
+ } catch (e: unknown) {
828
+ threw = true;
829
+ expect((e as NodeJS.ErrnoException).code).toBe('ERR_SOCKET_DGRAM_NOT_CONNECTED');
830
+ }
831
+ expect(threw).toBe(true);
832
+ socket.close();
833
+ resolve();
834
+ });
835
+ });
836
+ });
837
+
838
+ await it('disconnect without connect should throw ERR_SOCKET_DGRAM_NOT_CONNECTED', async () => {
839
+ const socket = createSocket('udp4');
840
+ let threw = false;
841
+ try {
842
+ socket.disconnect();
843
+ } catch (e: unknown) {
844
+ threw = true;
845
+ expect((e as NodeJS.ErrnoException).code).toBe('ERR_SOCKET_DGRAM_NOT_CONNECTED');
846
+ }
847
+ expect(threw).toBe(true);
848
+ socket.close();
849
+ });
850
+
851
+ await it('remoteAddress on unconnected socket should throw', async () => {
852
+ const socket = createSocket('udp4');
853
+ let threw = false;
854
+ try {
855
+ (socket as any).remoteAddress();
856
+ } catch (e: unknown) {
857
+ threw = true;
858
+ expect((e as NodeJS.ErrnoException).code).toBe('ERR_SOCKET_DGRAM_NOT_CONNECTED');
859
+ }
860
+ expect(threw).toBe(true);
861
+ socket.close();
862
+ });
863
+
864
+ await it('connect with invalid port should throw ERR_SOCKET_BAD_PORT', async () => {
865
+ const socket = createSocket('udp4');
866
+ const invalidPorts = [0, 65536, -1];
867
+ for (const port of invalidPorts) {
868
+ let threw = false;
869
+ try {
870
+ socket.connect(port);
871
+ } catch (e: unknown) {
872
+ threw = true;
873
+ expect((e as NodeJS.ErrnoException).code).toBe('ERR_SOCKET_BAD_PORT');
874
+ }
875
+ expect(threw).toBe(true);
876
+ }
877
+ socket.close();
878
+ });
879
+
880
+ await it('double disconnect should throw ERR_SOCKET_DGRAM_NOT_CONNECTED', async () => {
881
+ const socket = createSocket('udp4');
882
+ await new Promise<void>((resolve) => {
883
+ socket.connect(12345, '127.0.0.1', () => {
884
+ socket.disconnect();
885
+ let threw = false;
886
+ try {
887
+ socket.disconnect();
888
+ } catch (e: unknown) {
889
+ threw = true;
890
+ expect((e as NodeJS.ErrnoException).code).toBe('ERR_SOCKET_DGRAM_NOT_CONNECTED');
891
+ }
892
+ expect(threw).toBe(true);
893
+ socket.close();
894
+ resolve();
895
+ });
896
+ });
897
+ });
898
+
899
+ await it('connect then disconnect then reconnect should work', async () => {
900
+ const socket = createSocket('udp4');
901
+ await new Promise<void>((resolve) => {
902
+ socket.connect(12345, '127.0.0.1', () => {
903
+ socket.disconnect();
904
+ // Should be able to connect again
905
+ socket.connect(54321, '127.0.0.1', () => {
906
+ const remote = (socket as any).remoteAddress();
907
+ expect(remote.port).toBe(54321);
908
+ socket.close();
909
+ resolve();
910
+ });
911
+ });
912
+ });
913
+ });
914
+ });
915
+
916
+ // --- Node.js-only tests requiring actual socket I/O ---
917
+ await on('Node.js', async () => {
918
+ await describe('UDP send', async () => {
919
+ await it('should send UDP message without error', async () => {
920
+ const server = createSocket('udp4');
921
+ const client = createSocket('udp4');
922
+
923
+ await new Promise<void>((resolve, reject) => {
924
+ server.on('listening', () => {
925
+ const serverAddr = server.address();
926
+ client.send('hello', serverAddr.port, '127.0.0.1', (err) => {
927
+ expect(err).toBeNull();
928
+ server.close();
929
+ client.close();
930
+ resolve();
931
+ });
932
+ });
933
+ server.on('error', reject);
934
+ server.bind(0);
935
+ });
936
+ });
937
+
938
+ await it('should send Buffer', async () => {
939
+ const server = createSocket('udp4');
940
+ const client = createSocket('udp4');
941
+
942
+ await new Promise<void>((resolve, reject) => {
943
+ server.on('listening', () => {
944
+ const serverAddr = server.address();
945
+ client.send(Buffer.from('buffer test'), serverAddr.port, '127.0.0.1', (err) => {
946
+ expect(err).toBeNull();
947
+ server.close();
948
+ client.close();
949
+ resolve();
950
+ });
951
+ });
952
+ server.on('error', reject);
953
+ server.bind(0);
954
+ });
955
+ });
956
+
957
+ await it('should send Uint8Array', async () => {
958
+ const server = createSocket('udp4');
959
+ const client = createSocket('udp4');
960
+
961
+ await new Promise<void>((resolve, reject) => {
962
+ server.on('listening', () => {
963
+ const serverAddr = server.address();
964
+ client.send(new Uint8Array([72, 101, 108, 108, 111]), serverAddr.port, '127.0.0.1', (err) => {
965
+ expect(err).toBeNull();
966
+ server.close();
967
+ client.close();
968
+ resolve();
969
+ });
970
+ });
971
+ server.on('error', reject);
972
+ server.bind(0);
973
+ });
974
+ });
975
+
976
+ await it('should receive UDP message', async () => {
977
+ const server = createSocket('udp4');
978
+ const client = createSocket('udp4');
979
+
980
+ await new Promise<void>((resolve, reject) => {
981
+ server.on('message', (msg, rinfo) => {
982
+ expect(msg.toString()).toBe('hello udp');
983
+ expect(typeof rinfo.address).toBe('string');
984
+ expect(typeof rinfo.port).toBe('number');
985
+ expect(rinfo.port).toBeGreaterThan(0);
986
+ server.close();
987
+ client.close();
988
+ resolve();
989
+ });
990
+ server.on('listening', () => {
991
+ const serverAddr = server.address();
992
+ client.send('hello udp', serverAddr.port, '127.0.0.1');
993
+ });
994
+ server.on('error', reject);
995
+ server.bind(0);
996
+ });
997
+ });
998
+
999
+ await it('should report correct rinfo family', async () => {
1000
+ const server = createSocket('udp4');
1001
+ const client = createSocket('udp4');
1002
+
1003
+ await new Promise<void>((resolve, reject) => {
1004
+ server.on('message', (_msg, rinfo) => {
1005
+ expect(rinfo.family).toBe('IPv4');
1006
+ expect(rinfo.address).toBe('127.0.0.1');
1007
+ server.close();
1008
+ client.close();
1009
+ resolve();
1010
+ });
1011
+ server.on('listening', () => {
1012
+ const serverAddr = server.address();
1013
+ client.send('test', serverAddr.port, '127.0.0.1');
1014
+ });
1015
+ server.on('error', reject);
1016
+ server.bind(0);
1017
+ });
1018
+ });
1019
+ });
1020
+
1021
+ await describe('Multiple sends and send callback', async () => {
1022
+ await it('multiple sends should all succeed', async () => {
1023
+ const server = createSocket('udp4');
1024
+ const client = createSocket('udp4');
1025
+
1026
+ await new Promise<void>((resolve, reject) => {
1027
+ let sendCount = 0;
1028
+ server.on('listening', () => {
1029
+ const serverAddr = server.address();
1030
+ const onSend = (err: Error | null) => {
1031
+ expect(err).toBeNull();
1032
+ sendCount++;
1033
+ if (sendCount === 3) {
1034
+ server.close();
1035
+ client.close();
1036
+ resolve();
1037
+ }
1038
+ };
1039
+ client.send('msg1', serverAddr.port, '127.0.0.1', onSend);
1040
+ client.send('msg2', serverAddr.port, '127.0.0.1', onSend);
1041
+ client.send('msg3', serverAddr.port, '127.0.0.1', onSend);
1042
+ });
1043
+ server.on('error', reject);
1044
+ server.bind(0);
1045
+ });
1046
+ });
1047
+
1048
+ await it('send with callback should invoke callback', async () => {
1049
+ const server = createSocket('udp4');
1050
+ const client = createSocket('udp4');
1051
+
1052
+ const callbackInvoked = await new Promise<boolean>((resolve, reject) => {
1053
+ server.on('listening', () => {
1054
+ const serverAddr = server.address();
1055
+ client.send('callback test', serverAddr.port, '127.0.0.1', (err) => {
1056
+ expect(err).toBeNull();
1057
+ server.close();
1058
+ client.close();
1059
+ resolve(true);
1060
+ });
1061
+ });
1062
+ server.on('error', reject);
1063
+ server.bind(0);
1064
+ });
1065
+
1066
+ expect(callbackInvoked).toBe(true);
1067
+ });
1068
+ });
1069
+
1070
+ // Ported from refs/node-test/parallel/test-dgram-implicit-bind.js
1071
+ await describe('implicit bind on send', async () => {
1072
+ await it('send without explicit bind should auto-bind', async () => {
1073
+ const server = createSocket('udp4');
1074
+ const client = createSocket('udp4');
1075
+
1076
+ await new Promise<void>((resolve, reject) => {
1077
+ server.on('message', (msg) => {
1078
+ expect(msg.toString()).toBe('auto-bind');
1079
+ server.close();
1080
+ client.close();
1081
+ resolve();
1082
+ });
1083
+ server.on('listening', () => {
1084
+ const serverAddr = server.address();
1085
+ // client was never bound — send should auto-bind
1086
+ client.send('auto-bind', serverAddr.port, '127.0.0.1');
1087
+ });
1088
+ server.on('error', reject);
1089
+ server.bind(0);
1090
+ });
1091
+ });
1092
+ });
1093
+
1094
+ await describe('receive Buffer message content', async () => {
1095
+ await it('should receive Buffer data matching what was sent', async () => {
1096
+ const server = createSocket('udp4');
1097
+ const client = createSocket('udp4');
1098
+
1099
+ await new Promise<void>((resolve, reject) => {
1100
+ server.on('message', (msg) => {
1101
+ expect(Buffer.isBuffer(msg)).toBe(true);
1102
+ expect(msg.toString()).toBe('buffer-content');
1103
+ server.close();
1104
+ client.close();
1105
+ resolve();
1106
+ });
1107
+ server.on('listening', () => {
1108
+ const serverAddr = server.address();
1109
+ client.send(Buffer.from('buffer-content'), serverAddr.port, '127.0.0.1');
1110
+ });
1111
+ server.on('error', reject);
1112
+ server.bind(0);
1113
+ });
1114
+ });
1115
+ });
1116
+
1117
+ await describe('rinfo size field', async () => {
1118
+ await it('should include size in rinfo matching message length', async () => {
1119
+ const server = createSocket('udp4');
1120
+ const client = createSocket('udp4');
1121
+ const testMsg = 'size-test';
1122
+
1123
+ await new Promise<void>((resolve, reject) => {
1124
+ server.on('message', (msg, rinfo) => {
1125
+ expect(msg.length).toBe(testMsg.length);
1126
+ // rinfo.size should match message byte length
1127
+ if ('size' in rinfo) {
1128
+ expect((rinfo as any).size).toBe(testMsg.length);
1129
+ }
1130
+ server.close();
1131
+ client.close();
1132
+ resolve();
1133
+ });
1134
+ server.on('listening', () => {
1135
+ const serverAddr = server.address();
1136
+ client.send(testMsg, serverAddr.port, '127.0.0.1');
1137
+ });
1138
+ server.on('error', reject);
1139
+ server.bind(0);
1140
+ });
1141
+ });
1142
+ });
1143
+
1144
+ await describe('close during listening', async () => {
1145
+ // Ported from refs/node-test/parallel/test-dgram-close-in-listening.js
1146
+ await it('should allow close() inside listening callback', async () => {
1147
+ const socket = createSocket('udp4');
1148
+ let closeCalled = false;
1149
+
1150
+ await new Promise<void>((resolve) => {
1151
+ socket.on('listening', () => {
1152
+ socket.close(() => {
1153
+ closeCalled = true;
1154
+ resolve();
1155
+ });
1156
+ });
1157
+ socket.bind(0);
1158
+ });
1159
+
1160
+ expect(closeCalled).toBe(true);
1161
+ });
1162
+ });
1163
+ });
1164
+ });
1165
+ };