@blamejs/blamejs-shop 0.4.101 → 0.4.102

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/vendor/MANIFEST.json +55 -51
  4. package/lib/vendor/blamejs/.gitignore +5 -0
  5. package/lib/vendor/blamejs/CHANGELOG.md +4 -0
  6. package/lib/vendor/blamejs/api-snapshot.json +2 -2
  7. package/lib/vendor/blamejs/lib/sandbox.js +23 -5
  8. package/lib/vendor/blamejs/package.json +1 -1
  9. package/lib/vendor/blamejs/release-notes/v0.15.25.json +18 -0
  10. package/lib/vendor/blamejs/release-notes/v0.15.26.json +18 -0
  11. package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +5 -2
  12. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-open-nofollow.test.js +7 -2
  13. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +30 -10
  14. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +21 -0
  15. package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +22 -17
  16. package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +23 -18
  17. package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +8 -3
  18. package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +22 -0
  19. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +50 -0
  20. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +13 -5
  21. package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +5 -2
  22. package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +20 -9
  23. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +8 -2
  24. package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +34 -15
  25. package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +30 -9
  26. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +15 -3
  27. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +41 -18
  28. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +33 -14
  29. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-throttle-transform.test.js +21 -2
  30. package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +5 -2
  31. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +29 -8
  32. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +42 -23
  33. package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +5 -2
  34. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +20 -0
  35. package/lib/vendor/blamejs/test/layer-0-primitives/network-nts-handshake-byte-cap.test.js +20 -1
  36. package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +5 -2
  37. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +22 -1
  38. package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +8 -2
  39. package/lib/vendor/blamejs/test/layer-0-primitives/parser-verify-hardening.test.js +21 -16
  40. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +5 -2
  41. package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +31 -11
  42. package/lib/vendor/blamejs/test/layer-0-primitives/request-id-async-context.test.js +21 -4
  43. package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +35 -0
  44. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +5 -2
  45. package/lib/vendor/blamejs/test/layer-0-primitives/self-update-poll-asset-digest.test.js +21 -2
  46. package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +29 -10
  47. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +51 -33
  48. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +31 -13
  49. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +41 -21
  50. package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +25 -0
  51. package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +30 -6
  52. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +22 -0
  53. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +10 -6
  54. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +91 -20
  55. package/package.json +1 -1
@@ -240,9 +240,44 @@ async function testByteCapMultibyte() {
240
240
  t2 && t2.code === "sandbox/oversized-result");
241
241
  }
242
242
 
243
+ async function testWorkerHandleReleased() {
244
+ // b.sandbox.run spawns a worker thread and calls worker.terminate() — which
245
+ // is asynchronous — on the result/timeout paths. It must await that
246
+ // termination before settling the caller's promise, otherwise the worker's
247
+ // MessagePort stays alive past the resolve and a long-lived process running
248
+ // sandboxed code repeatedly accumulates leaked MessagePort handles. The
249
+ // timeout path is the widest leak window (the worker is spinning and
250
+ // forcibly terminated, so terminate() takes longest); drive it repeatedly
251
+ // and assert no MessagePort is left active. RED before the fix (the reject
252
+ // raced ahead of the async terminate, leaving the busy worker's port alive).
253
+ function _ports() {
254
+ return process.getActiveResourcesInfo().filter(function (r) { return r === "MessagePort"; }).length;
255
+ }
256
+ var before = _ports();
257
+ // Drive several spinning workers CONCURRENTLY so that, if terminate() is not
258
+ // awaited, all their MessagePorts are mid-close at the instant Promise.all
259
+ // settles — amplifying the leak from one racy handle into N simultaneously
260
+ // open ones, which the synchronous count below catches deterministically.
261
+ var N = 8;
262
+ var runs = [];
263
+ for (var i = 0; i < N; i += 1) {
264
+ runs.push(b.sandbox.run({ source: "while (true) {}", timeoutMs: 150 }).then(
265
+ function () { return "resolved"; },
266
+ function (e) { return e && e.code; }));
267
+ }
268
+ var outcomes = await Promise.all(runs);
269
+ check("all spinning sandbox.run calls time out",
270
+ outcomes.length === N && outcomes.every(function (o) { return o === "sandbox/timeout"; }));
271
+ // The fix awaits worker.terminate() before settling, so every port is closed
272
+ // by the time its promise resolves; with the bug, N ports linger here.
273
+ check("sandbox.run releases the worker MessagePort after timeout-terminate (no handle leak)",
274
+ _ports() <= before);
275
+ }
276
+
243
277
  async function run() {
244
278
  await testByteCapMultibyte();
245
279
  testErrorClassExposed();
280
+ await testWorkerHandleReleased();
246
281
  await testHappyPath();
247
282
  await testAllowedBuiltins();
248
283
  await testBadAllowedRejected();
@@ -813,7 +813,7 @@ function testExports() {
813
813
 
814
814
  // ---- Run all ----
815
815
 
816
- (async function run() {
816
+ async function run() {
817
817
  testDisclosureEncodeDecode();
818
818
  testDisclosureValueComplexShape();
819
819
  testDisclosureBadInput();
@@ -847,4 +847,7 @@ function testExports() {
847
847
  await testHolderAlgFromKeyType();
848
848
  await testKbAlgKtyCrossCheck();
849
849
  testExports();
850
- })().catch(function (e) { console.error(e); process.exit(1); });
850
+ }
851
+
852
+ module.exports = { run: run };
853
+ if (require.main === module) run().catch(function (e) { console.error(e); process.exit(1); });
@@ -75,9 +75,28 @@ async function testPollDigestNullWhenAbsent() {
75
75
  } finally { server.close(); }
76
76
  }
77
77
 
78
+ // selfUpdate.poll dials the releases endpoint through the shared httpClient
79
+ // keep-alive transport pool; a cached client socket finalizes its destroy on a
80
+ // later event-loop turn, past the forked worker's grace window. Reset the pool,
81
+ // then poll until every TCP handle (client sockets + the fixture server's
82
+ // accept socket) has actually drained so none outlives run().
83
+ async function _drainTcpHandles() {
84
+ b.httpClient._resetForTest();
85
+ if (typeof process.getActiveResourcesInfo !== "function") return;
86
+ await helpers.waitUntil(function () {
87
+ return process.getActiveResourcesInfo().filter(function (t) {
88
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
89
+ }).length === 0;
90
+ }, { timeoutMs: 5000, label: "self-update-poll-asset-digest: TCP handle drain after _resetForTest" });
91
+ }
92
+
78
93
  async function run() {
79
- await testPollExposesAssetDigest();
80
- await testPollDigestNullWhenAbsent();
94
+ try {
95
+ await testPollExposesAssetDigest();
96
+ await testPollDigestNullWhenAbsent();
97
+ } finally {
98
+ await _drainTcpHandles();
99
+ }
81
100
  }
82
101
 
83
102
  module.exports = { run: run };
@@ -280,17 +280,36 @@ async function testRollbackMissingBackupRefused() {
280
280
  try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
281
281
  }
282
282
 
283
+ // selfUpdate.poll dials the releases endpoint through the shared httpClient
284
+ // keep-alive transport pool; a cached client socket finalizes its destroy on a
285
+ // later event-loop turn, past the forked worker's grace window. Reset the pool,
286
+ // then poll until every TCP handle (client sockets + any fixture-server accept
287
+ // socket) has actually drained so none outlives run().
288
+ async function _drainTcpHandles() {
289
+ b.httpClient._resetForTest();
290
+ if (typeof process.getActiveResourcesInfo !== "function") return;
291
+ await helpers.waitUntil(function () {
292
+ return process.getActiveResourcesInfo().filter(function (t) {
293
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
294
+ }).length === 0;
295
+ }, { timeoutMs: 5000, label: "self-update: TCP handle drain after _resetForTest" });
296
+ }
297
+
283
298
  async function run() {
284
- testSurface();
285
- testCompareTags();
286
- await testPollRejectsBadOpts();
287
- await testPollAvailableAndUpToDate();
288
- await testPollArrayShape();
289
- await testPollNon2xxRefused();
290
- await testVerifyPassFail();
291
- await testSwapAndRollback();
292
- await testSwapMissingFromRefused();
293
- await testRollbackMissingBackupRefused();
299
+ try {
300
+ testSurface();
301
+ testCompareTags();
302
+ await testPollRejectsBadOpts();
303
+ await testPollAvailableAndUpToDate();
304
+ await testPollArrayShape();
305
+ await testPollNon2xxRefused();
306
+ await testVerifyPassFail();
307
+ await testSwapAndRollback();
308
+ await testSwapMissingFromRefused();
309
+ await testRollbackMissingBackupRefused();
310
+ } finally {
311
+ await _drainTcpHandles();
312
+ }
294
313
  }
295
314
 
296
315
  module.exports = { run: run };
@@ -1017,40 +1017,58 @@ function testCanonicalPathSingleEncodeForS3() {
1017
1017
  sigv4.awsUriEncode("photo-\u{1F600}.jpg", true) === "photo-%F0%9F%98%80.jpg");
1018
1018
  }
1019
1019
 
1020
+ // bucketOps dispatches every request through the shared httpClient keep-alive
1021
+ // transport pool; a cached client socket finalizes its destroy on a later
1022
+ // event-loop turn, past the forked worker's grace window. Reset the pool, then
1023
+ // poll until every TCP handle has actually drained so none outlives run().
1024
+ async function _drainTcpHandles() {
1025
+ b.httpClient._resetForTest();
1026
+ if (typeof process.getActiveResourcesInfo !== "function") return;
1027
+ await helpers.waitUntil(function () {
1028
+ return process.getActiveResourcesInfo().filter(function (t) {
1029
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
1030
+ }).length === 0;
1031
+ }, { timeoutMs: 5000, label: "sigv4-bucket-ops: TCP handle drain after _resetForTest" });
1032
+ }
1033
+
1020
1034
  async function run() {
1021
- testSurface();
1022
- testCanonicalPathSingleEncodeForS3();
1023
- testFactoryValidation();
1024
- testBucketNameValidation();
1025
- testLifecycleXml();
1026
- testCorsXml();
1027
- await testCreateBucketUsEast1NoBody();
1028
- await testCreateBucketOtherRegionSendsLocationConstraint();
1029
- await testCreateBucketAlreadyOwnedMaps();
1030
- await testCreateBucketTakenMaps();
1031
- await testDeleteBucketHappyAndMissing();
1032
- await testDeleteBucketNotEmptyMaps();
1033
- await testListBuckets();
1034
- await testSetLifecycleSendsXml();
1035
- await testSetCorsRulesSendsXml();
1036
- // v0.6.47 — Object Lock
1037
- await testCreateBucketObjectLockEnabled();
1038
- await testSetObjectLockConfiguration();
1039
- await testSetObjectLockConfigurationValidation();
1040
- await testGetObjectLockConfiguration();
1041
- await testSetObjectRetention();
1042
- await testSetObjectRetentionBypassGovernance();
1043
- await testSetObjectRetentionValidation();
1044
- await testGetObjectRetention();
1045
- await testLegalHold();
1046
- // v0.6.51 — not-configured response paths return clean defaults
1047
- await testGetObjectLockConfigurationNotConfigured();
1048
- await testGetObjectRetentionNotConfigured();
1049
- await testGetObjectLegalHoldNotConfigured();
1050
- // v0.6.53 — audit + observability emissions
1051
- await testAuditObservabilityWiring();
1052
- await testAuditSuccessFalseDisablesSuccessAudit();
1053
- await testPerCallActorOverrideHonored();
1035
+ try {
1036
+ testSurface();
1037
+ testCanonicalPathSingleEncodeForS3();
1038
+ testFactoryValidation();
1039
+ testBucketNameValidation();
1040
+ testLifecycleXml();
1041
+ testCorsXml();
1042
+ await testCreateBucketUsEast1NoBody();
1043
+ await testCreateBucketOtherRegionSendsLocationConstraint();
1044
+ await testCreateBucketAlreadyOwnedMaps();
1045
+ await testCreateBucketTakenMaps();
1046
+ await testDeleteBucketHappyAndMissing();
1047
+ await testDeleteBucketNotEmptyMaps();
1048
+ await testListBuckets();
1049
+ await testSetLifecycleSendsXml();
1050
+ await testSetCorsRulesSendsXml();
1051
+ // v0.6.47 — Object Lock
1052
+ await testCreateBucketObjectLockEnabled();
1053
+ await testSetObjectLockConfiguration();
1054
+ await testSetObjectLockConfigurationValidation();
1055
+ await testGetObjectLockConfiguration();
1056
+ await testSetObjectRetention();
1057
+ await testSetObjectRetentionBypassGovernance();
1058
+ await testSetObjectRetentionValidation();
1059
+ await testGetObjectRetention();
1060
+ await testLegalHold();
1061
+ // v0.6.51 — not-configured response paths return clean defaults
1062
+ await testGetObjectLockConfigurationNotConfigured();
1063
+ await testGetObjectRetentionNotConfigured();
1064
+ await testGetObjectLegalHoldNotConfigured();
1065
+ // v0.6.53 — audit + observability emissions
1066
+ await testAuditObservabilityWiring();
1067
+ await testAuditSuccessFalseDisablesSuccessAudit();
1068
+ await testPerCallActorOverrideHonored();
1069
+ } finally {
1070
+ await _drainTcpHandles();
1071
+ }
1054
1072
  }
1055
1073
 
1056
1074
  module.exports = { run: run };
@@ -24,7 +24,6 @@ var sigv4 = require("../../lib/object-store/sigv4");
24
24
  var b = helpers.b;
25
25
  var check = helpers.check;
26
26
  var listenOnRandomPort = helpers.listenOnRandomPort;
27
- void b;
28
27
 
29
28
  function _baseConfig(port, overrides) {
30
29
  var cfg = {
@@ -416,19 +415,38 @@ async function testMultipartFalseRejectsStreams() {
416
415
  }
417
416
  }
418
417
 
418
+ // The sigv4 store dispatches every part / initiate / complete request through
419
+ // the shared httpClient keep-alive transport pool; cached client sockets
420
+ // finalize their destroy on a later event-loop turn, past the forked worker's
421
+ // grace window. Reset the pool, then poll until every TCP handle has actually
422
+ // drained so none outlives run().
423
+ async function _drainTcpHandles() {
424
+ b.httpClient._resetForTest();
425
+ if (typeof process.getActiveResourcesInfo !== "function") return;
426
+ await helpers.waitUntil(function () {
427
+ return process.getActiveResourcesInfo().filter(function (t) {
428
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
429
+ }).length === 0;
430
+ }, { timeoutMs: 5000, label: "sigv4-multipart-sse: TCP handle drain after _resetForTest" });
431
+ }
432
+
419
433
  async function run() {
420
- await testSinglePutRemainsBufferAtThreshold();
421
- await testMultipartAutoDetectAboveThreshold();
422
- await testMultipartFromReadableStream();
423
- await testMultipartAbortsOnPartFailure();
424
- await testMultipartCompleteErrorBodyFails();
425
- await testSseAES256Forwarded();
426
- await testSseKmsForwardedWithKeyId();
427
- await testSseForwardedOnEveryMultipartRequest();
428
- await testSseResponseVerificationFailsOnDroppedHeader();
429
- await testSseValidationRejectsBadValues();
430
- testConfigValidation();
431
- await testMultipartFalseRejectsStreams();
434
+ try {
435
+ await testSinglePutRemainsBufferAtThreshold();
436
+ await testMultipartAutoDetectAboveThreshold();
437
+ await testMultipartFromReadableStream();
438
+ await testMultipartAbortsOnPartFailure();
439
+ await testMultipartCompleteErrorBodyFails();
440
+ await testSseAES256Forwarded();
441
+ await testSseKmsForwardedWithKeyId();
442
+ await testSseForwardedOnEveryMultipartRequest();
443
+ await testSseResponseVerificationFailsOnDroppedHeader();
444
+ await testSseValidationRejectsBadValues();
445
+ testConfigValidation();
446
+ await testMultipartFalseRejectsStreams();
447
+ } finally {
448
+ await _drainTcpHandles();
449
+ }
432
450
  }
433
451
 
434
452
  module.exports = { run: run };
@@ -524,28 +524,48 @@ async function testConditionalEntityTagPrecedence() {
524
524
  }
525
525
  }
526
526
 
527
+ // The _get helper drives each fixture server with a default-agent http.request
528
+ // (keep-alive), and srv.close() runs fire-and-forget. The kept-alive client
529
+ // sockets, the servers' accept sockets, and any in-flight static-file read
530
+ // (FSReqCallback) finalize their teardown on a later event-loop turn — past the
531
+ // forked worker's grace window. Destroy the global-agent socket pool, then poll
532
+ // until every TCP/fs handle has drained so none outlives run().
533
+ async function _drainTcpHandles() {
534
+ http.globalAgent.destroy();
535
+ if (typeof process.getActiveResourcesInfo !== "function") return;
536
+ await helpers.waitUntil(function () {
537
+ return process.getActiveResourcesInfo().filter(function (t) {
538
+ return t === "TCPSocketWrap" || t === "TCPServerWrap" || t === "FSReqCallback";
539
+ }).length === 0;
540
+ }, { timeoutMs: 5000, label: "static: TCP/fs handle drain after globalAgent.destroy" });
541
+ }
542
+
527
543
  async function run() {
528
- await testConditionalEntityTagPrecedence();
529
- await testForceAttachmentDefaultOff();
530
- await testForceAttachmentOnHtml();
531
- await testForceAttachmentOnJs();
532
- await testForceAttachmentTextStillInline();
533
- await testForceAttachmentRasterStillInline();
534
- await testForceAttachmentSvgWithoutSanitizer();
535
- await testForceAttachmentSvgWithSanitizerInlineAllowed();
536
- await testForceAttachmentPdfDefaultDownload();
537
- await testForceAttachmentPdfOptInInline();
538
- await testMountTypeUserContentForcesDownloadByDefault();
539
- await testMountTypeCuratedKeepsInline();
540
- await testMountTypeExplicitOverrideWins();
541
- testMountTypeBadValueThrows();
542
- testRejectsUnknownOpts();
543
- await testOnErrorFiresOnRefusal();
544
- await testOnErrorThrowDoesNotCorruptResponse();
545
- testOnErrorRejectsNonFunction();
546
- await testPathTraversalRefused();
547
- await testNestedFileServed();
548
- await testDirectoryIndexServed();
544
+ try {
545
+ await testConditionalEntityTagPrecedence();
546
+ await testForceAttachmentDefaultOff();
547
+ await testForceAttachmentOnHtml();
548
+ await testForceAttachmentOnJs();
549
+ await testForceAttachmentTextStillInline();
550
+ await testForceAttachmentRasterStillInline();
551
+ await testForceAttachmentSvgWithoutSanitizer();
552
+ await testForceAttachmentSvgWithSanitizerInlineAllowed();
553
+ await testForceAttachmentPdfDefaultDownload();
554
+ await testForceAttachmentPdfOptInInline();
555
+ await testMountTypeUserContentForcesDownloadByDefault();
556
+ await testMountTypeCuratedKeepsInline();
557
+ await testMountTypeExplicitOverrideWins();
558
+ testMountTypeBadValueThrows();
559
+ testRejectsUnknownOpts();
560
+ await testOnErrorFiresOnRefusal();
561
+ await testOnErrorThrowDoesNotCorruptResponse();
562
+ testOnErrorRejectsNonFunction();
563
+ await testPathTraversalRefused();
564
+ await testNestedFileServed();
565
+ await testDirectoryIndexServed();
566
+ } finally {
567
+ await _drainTcpHandles();
568
+ }
549
569
  }
550
570
 
551
571
  module.exports = { run: run };
@@ -3,11 +3,36 @@
3
3
  * b.testing.request — supertest-style chainable HTTP test helper.
4
4
  */
5
5
 
6
+ var http = require("node:http");
6
7
  var helpers = require("../helpers");
7
8
  var b = helpers.b;
8
9
  var check = helpers.check;
9
10
 
11
+ // b.testing.request drives the harness server with a default-agent http.request
12
+ // (keep-alive) and closes the fixture server fire-and-forget. The kept-alive
13
+ // client socket and the server's accept socket finalize their destroy on a
14
+ // later event-loop turn, past the forked worker's grace window. Destroy the
15
+ // global-agent socket pool, then poll until every TCP handle has drained so
16
+ // none outlives run().
17
+ async function _drainTcpHandles() {
18
+ http.globalAgent.destroy();
19
+ if (typeof process.getActiveResourcesInfo !== "function") return;
20
+ await helpers.waitUntil(function () {
21
+ return process.getActiveResourcesInfo().filter(function (t) {
22
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
23
+ }).length === 0;
24
+ }, { timeoutMs: 5000, label: "testing-request: TCP handle drain after globalAgent.destroy" });
25
+ }
26
+
10
27
  async function run() {
28
+ try {
29
+ await _runTests();
30
+ } finally {
31
+ await _drainTcpHandles();
32
+ }
33
+ }
34
+
35
+ async function _runTests() {
11
36
  // ---- Surface ----
12
37
  check("b.testing.request is fn", typeof b.testing.request === "function");
13
38
 
@@ -112,9 +112,11 @@ function testLiveHandshake() {
112
112
  } catch (e) {
113
113
  client.destroy(); server.close(); reject(e); return;
114
114
  }
115
- client.end();
116
- server.close();
117
- resolve();
115
+ // Hand the live handles back so run()'s drain can destroy them and
116
+ // poll until their TCP handles release — a fire-and-forget end()/close()
117
+ // here leaves the client/server sockets finalizing past the worker's
118
+ // post-run grace window.
119
+ resolve({ server: server, client: client });
118
120
  }
119
121
 
120
122
  var server = nodeTls.createServer({
@@ -154,10 +156,32 @@ function testLiveHandshake() {
154
156
  });
155
157
  }
156
158
 
159
+ // Destroy the live TLS client + server handed back by the handshake, then poll
160
+ // until their TCP handles release. Polling drives the real event-loop turns
161
+ // that complete the async socket teardown inside run(), instead of leaving it
162
+ // to finalize in the worker's post-run grace window.
163
+ async function _drainTcpHandles(handles) {
164
+ if (handles) {
165
+ if (handles.client) { try { handles.client.destroy(); } catch (_e) { /* already torn down */ } }
166
+ if (handles.server) { try { handles.server.close(); } catch (_e) { /* already closed */ } }
167
+ }
168
+ if (typeof process.getActiveResourcesInfo !== "function") return;
169
+ await helpers.waitUntil(function () {
170
+ return process.getActiveResourcesInfo().filter(function (t) {
171
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
172
+ }).length === 0;
173
+ }, { timeoutMs: 5000, label: "tls-exporter: TCP handle drain after socket destroy" });
174
+ }
175
+
157
176
  async function run() {
158
- testSurface();
159
- testValidationPaths();
160
- await testLiveHandshake();
177
+ var handles = null;
178
+ try {
179
+ testSurface();
180
+ testValidationPaths();
181
+ handles = await testLiveHandshake();
182
+ } finally {
183
+ await _drainTcpHandles(handles);
184
+ }
161
185
  }
162
186
 
163
187
  module.exports = { run: run };
@@ -736,7 +736,29 @@ async function testStripeNonceStoreConcurrentAtomic() {
736
736
  if (ns.close) await ns.close();
737
737
  }
738
738
 
739
+ // signer.send dispatches through the shared httpClient keep-alive transport
740
+ // pool; a cached client socket finalizes its destroy on a later event-loop
741
+ // turn, past the forked worker's grace window. Reset the pool, then poll until
742
+ // every TCP handle has actually drained so none outlives run().
743
+ async function _drainTcpHandles() {
744
+ b.httpClient._resetForTest();
745
+ if (typeof process.getActiveResourcesInfo !== "function") return;
746
+ await helpers.waitUntil(function () {
747
+ return process.getActiveResourcesInfo().filter(function (t) {
748
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
749
+ }).length === 0;
750
+ }, { timeoutMs: 5000, label: "webhook: TCP handle drain after _resetForTest" });
751
+ }
752
+
739
753
  async function run() {
754
+ try {
755
+ await _runTests();
756
+ } finally {
757
+ await _drainTcpHandles();
758
+ }
759
+ }
760
+
761
+ async function _runTests() {
740
762
  testWebhookSurface();
741
763
  await testHmacRoundtrip();
742
764
  await testHmacBufferBody();
@@ -42,11 +42,12 @@ async function run() {
42
42
  .catch(function (e) { bError = e; });
43
43
 
44
44
  // Bound the wait so a hung B fails the test rather than hanging the suite.
45
- await Promise.race([
46
- Promise.all([aPromise, bPromise]),
47
- helpers.waitUntil(function () { return bResult !== null || bError !== null; },
48
- { timeoutMs: 8000, label: "#127: queued task settles after the slot recycles" }),
49
- ]);
45
+ // withTestTimeout clears its guard timer on settle (a raced waitUntil would
46
+ // keep polling — and leave its in-flight poll timer — once Promise.all won).
47
+ await helpers.withTestTimeout(
48
+ "#127: queued task settles after the slot recycles",
49
+ function () { return Promise.all([aPromise, bPromise]); },
50
+ { timeoutMs: 8000 });
50
51
 
51
52
  check("#127 the timing-out task A is reaped (timeout, as expected)",
52
53
  aErr && aErr.code === "workerpool/timeout");
@@ -55,7 +56,10 @@ async function run() {
55
56
  check("#127 the queued task runs on the replacement worker and returns its result",
56
57
  bResult && bResult.v === 42);
57
58
  } finally {
58
- pool.terminate();
59
+ // Await termination so the worker threads fully exit before run()
60
+ // returns — an un-awaited terminate() leaves the worker's MessagePort
61
+ // and reap timer alive past the assertions.
62
+ await pool.terminate();
59
63
  }
60
64
  }
61
65