Skip to content

Commit 6ceb236

Browse files
gautam-rlReflexclaude
authored
fix(smoketests): instrument node:http2 in verify-http2.mjs, not undici (#814)
Co-authored-by: Reflex <reflex@runloop.ai> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 5ee1cf9 commit 6ceb236

1 file changed

Lines changed: 24 additions & 17 deletions

File tree

tests/smoketests/scripts/verify-http2.mjs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
/**
2-
* Plain-node verification harness for the HTTP/2 (undici) transport.
2+
* Plain-node verification harness for the HTTP/2 (node:http2) transport.
33
*
44
* Runs OUTSIDE jest against the BUILT package — which is exactly how real
55
* clients consume the SDK, and the only place a `"type": "commonjs"` interop
66
* regression would surface. It proves three things a green smoke run cannot:
77
*
8-
* 1. h2 is actually NEGOTIATED (not a silent HTTP/1.1 fallback) — asserted by
9-
* reading the TLS socket's ALPN protocol via undici's diagnostics channel.
8+
* 1. h2 is actually NEGOTIATED — asserted by reading the TLS socket's ALPN
9+
* protocol on every `http2.connect` session the SDK opens.
1010
* 2. A success response body parses (exercises Response.json()).
1111
* 3. A non-2xx response REJECTS with a readable error and does NOT crash the
1212
* process — the exact failure mode of the old got adapter on a 401.
1313
*
1414
* Usage: RUNLOOP_API_KEY=... [RUNLOOP_BASE_URL=...] node tests/smoketests/scripts/verify-http2.mjs
1515
* Exit code 0 = all checks passed, 1 = a check failed, 2 = misconfigured.
1616
*/
17-
import diagnostics_channel from 'node:diagnostics_channel';
17+
import http2 from 'node:http2';
1818
import { createRequire } from 'node:module';
1919

20+
// Capture the negotiated ALPN protocol for every H2 session the transport opens.
21+
// `node:http2` is a singleton module shared between this ESM harness and the CJS
22+
// dist bundle, so wrapping `connect` here instruments the SDK's own sessions.
23+
// Must be installed BEFORE the SDK is required.
24+
const alpnSeen = [];
25+
let connectCount = 0;
26+
const realConnect = http2.connect;
27+
http2.connect = function (...args) {
28+
connectCount++;
29+
const session = realConnect.apply(this, args);
30+
session.on('connect', () => {
31+
const proto = session.socket?.alpnProtocol;
32+
if (proto) alpnSeen.push(proto);
33+
});
34+
return session;
35+
};
36+
2037
const require = createRequire(import.meta.url);
2138
const distPath = new URL('../../../dist/index.js', import.meta.url).pathname;
2239
const { Runloop } = require(distPath);
@@ -28,17 +45,6 @@ if (!apiKey) {
2845
process.exit(2);
2946
}
3047

31-
// Capture the negotiated ALPN protocol for every undici connection. Channels are
32-
// keyed by name globally, so this catches the SDK's own undici regardless of
33-
// which module instance created the connection.
34-
const alpnSeen = [];
35-
let connectCount = 0;
36-
diagnostics_channel.subscribe('undici:client:connected', (msg) => {
37-
connectCount++;
38-
const proto = msg?.socket?.alpnProtocol;
39-
if (proto) alpnSeen.push(proto);
40-
});
41-
4248
let failures = 0;
4349
const check = (cond, label) => {
4450
console.log(`${cond ? 'PASS' : 'FAIL'}: ${label}`);
@@ -80,8 +86,9 @@ try {
8086

8187
// ── Pass D: HTTP/2 multiplexing — many concurrent requests reuse few connections ──
8288
// The whole point of the bounded H2 pool: N concurrent requests share a small number of
83-
// TLS sessions instead of one connection per request. Default config (pipelining=1) or the
84-
// pre-fix undici Agent would open ~N connections here.
89+
// TLS sessions instead of one connection per request. The pool eagerly opens
90+
// minConnections (4) sessions and only scales beyond that under stream saturation,
91+
// so a fresh client serving 25 concurrent requests should open exactly 4.
8592
try {
8693
const N = 25;
8794
const before = connectCount;

0 commit comments

Comments
 (0)