@reproapp/node-sdk 0.0.28 → 0.0.30

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/dist/index.js CHANGED
@@ -295,6 +295,7 @@ const REQUEST_START_HEADER = 'x-bug-request-start';
295
295
  const REPRO_QUERY_SESSION_PARAM = '__repro_sid';
296
296
  const REPRO_QUERY_ACTION_PARAM = '__repro_aid';
297
297
  const REPRO_QUERY_START_PARAM = '__repro_start';
298
+ const REPRO_REQUEST_MIDDLEWARE_RAN = Symbol.for('__repro_request_middleware_ran');
298
299
  const KAFKA_HEADER_TRACE_ID = 'x-repro-trace-id';
299
300
  const KAFKA_HEADER_SPAN_ID = 'x-repro-span-id';
300
301
  const KAFKA_HEADER_PARENT_SPAN_ID = 'x-repro-parent-span-id';
@@ -1028,10 +1029,6 @@ function scheduleTraceMaterializerIdleShutdown() {
1028
1029
  catch { }
1029
1030
  }
1030
1031
  }, TRACE_MATERIALIZE_WORKER_IDLE_SHUTDOWN_MS);
1031
- try {
1032
- traceMaterializerIdleTimer.unref?.();
1033
- }
1034
- catch { }
1035
1032
  }
1036
1033
  function rejectTraceMaterializerPending(error) {
1037
1034
  for (const [taskId, pending] of traceMaterializerPending.entries()) {
@@ -1373,11 +1370,43 @@ function waitForSessionDrain(sessionId) {
1373
1370
  if (remaining <= 0)
1374
1371
  return Promise.resolve();
1375
1372
  return new Promise(resolve => {
1373
+ let settled = false;
1374
+ let timeout = null;
1375
+ const removeWaiter = (waiter) => {
1376
+ const list = sessionWaiters.get(sessionId);
1377
+ if (!list)
1378
+ return;
1379
+ const next = list.filter(fn => fn !== waiter);
1380
+ if (next.length) {
1381
+ sessionWaiters.set(sessionId, next);
1382
+ }
1383
+ else {
1384
+ sessionWaiters.delete(sessionId);
1385
+ }
1386
+ };
1387
+ const done = () => {
1388
+ if (settled)
1389
+ return;
1390
+ settled = true;
1391
+ if (timeout) {
1392
+ try {
1393
+ clearTimeout(timeout);
1394
+ }
1395
+ catch { }
1396
+ timeout = null;
1397
+ }
1398
+ removeWaiter(done);
1399
+ resolve();
1400
+ };
1376
1401
  const list = sessionWaiters.get(sessionId) || [];
1377
- list.push(resolve);
1402
+ list.push(done);
1378
1403
  sessionWaiters.set(sessionId, list);
1379
1404
  if (SESSION_DRAIN_TIMEOUT_MS > 0 && Number.isFinite(SESSION_DRAIN_TIMEOUT_MS)) {
1380
- setTimeout(resolve, SESSION_DRAIN_TIMEOUT_MS);
1405
+ timeout = setTimeout(done, SESSION_DRAIN_TIMEOUT_MS);
1406
+ try {
1407
+ timeout.unref?.();
1408
+ }
1409
+ catch { }
1381
1410
  }
1382
1411
  });
1383
1412
  }
@@ -3999,6 +4028,22 @@ function reproMiddleware(cfg) {
3999
4028
  const masking = normalizeMaskingConfig(cfg.masking);
4000
4029
  patchMongoDb(cfg);
4001
4030
  return async function (req, res, next) {
4031
+ const reqAny = req;
4032
+ if (reqAny?.[REPRO_REQUEST_MIDDLEWARE_RAN]) {
4033
+ return next();
4034
+ }
4035
+ try {
4036
+ Object.defineProperty(reqAny, REPRO_REQUEST_MIDDLEWARE_RAN, {
4037
+ value: true,
4038
+ configurable: true,
4039
+ });
4040
+ }
4041
+ catch {
4042
+ try {
4043
+ reqAny[REPRO_REQUEST_MIDDLEWARE_RAN] = true;
4044
+ }
4045
+ catch { }
4046
+ }
4002
4047
  const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
4003
4048
  const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
4004
4049
  const queryTs = readRequestQueryNumber(req, REPRO_QUERY_START_PARAM);
@@ -4035,30 +4080,50 @@ function reproMiddleware(cfg) {
4035
4080
  const maskReq = { method: String(req.method || 'GET').toUpperCase(), path: urlPathOnly, key };
4036
4081
  const requestHeaders = sanitizeHeaders(req.headers, cfg.captureHeaders);
4037
4082
  beginSessionRequest(sid);
4038
- // ---- response body capture (unchanged) ----
4083
+ // ---- response body capture ----
4039
4084
  let capturedBody = undefined;
4040
- const origJson = res.json.bind(res);
4041
- res.json = (body) => { capturedBody = body; return origJson(body); };
4042
- const origSend = res.send.bind(res);
4043
- res.send = (body) => {
4044
- if (capturedBody === undefined) {
4045
- capturedBody = coerceBodyToStorable(body, res.getHeader?.('content-type'));
4046
- }
4047
- return origSend(body);
4048
- };
4049
- const origWrite = res.write.bind(res);
4050
- const origEnd = res.end.bind(res);
4085
+ const originalJson = res.json;
4086
+ if (typeof originalJson === 'function') {
4087
+ const origJson = originalJson.bind(res);
4088
+ res.json = (body) => { capturedBody = body; return origJson(body); };
4089
+ }
4090
+ const originalSend = res.send;
4091
+ if (typeof originalSend === 'function') {
4092
+ const origSend = originalSend.bind(res);
4093
+ res.send = (body) => {
4094
+ if (capturedBody === undefined) {
4095
+ capturedBody = coerceBodyToStorable(body, res.getHeader?.('content-type'));
4096
+ }
4097
+ return origSend(body);
4098
+ };
4099
+ }
4100
+ const origWrite = typeof res.write === 'function'
4101
+ ? res.write.bind(res)
4102
+ : null;
4103
+ const origEnd = typeof res.end === 'function'
4104
+ ? res.end.bind(res)
4105
+ : null;
4051
4106
  const chunks = [];
4052
- res.write = (chunk, ...args) => { try {
4053
- if (chunk != null)
4054
- chunks.push(chunk);
4107
+ if (origWrite) {
4108
+ res.write = (chunk, ...args) => {
4109
+ try {
4110
+ if (chunk != null)
4111
+ chunks.push(chunk);
4112
+ }
4113
+ catch { }
4114
+ return origWrite(chunk, ...args);
4115
+ };
4055
4116
  }
4056
- catch { } return origWrite(chunk, ...args); };
4057
- res.end = (chunk, ...args) => { try {
4058
- if (chunk != null)
4059
- chunks.push(chunk);
4117
+ if (origEnd) {
4118
+ res.end = (chunk, ...args) => {
4119
+ try {
4120
+ if (chunk != null)
4121
+ chunks.push(chunk);
4122
+ }
4123
+ catch { }
4124
+ return origEnd(chunk, ...args);
4125
+ };
4060
4126
  }
4061
- catch { } return origEnd(chunk, ...args); };
4062
4127
  // ---- our ALS (unchanged) ----
4063
4128
  const tracerApiWithTrace = __TRACER__;
4064
4129
  const runInTrace = (fn) => {
@@ -4546,6 +4611,7 @@ function nativeMongoOpsForMongooseOp(op) {
4546
4611
  return [op];
4547
4612
  }
4548
4613
  function reproMongoosePlugin(cfg) {
4614
+ patchMongoDb(cfg);
4549
4615
  return function (schema) {
4550
4616
  // -------- pre/post save (unchanged) --------
4551
4617
  schema.pre('save', { document: true }, function (next) {
@@ -7942,6 +8008,50 @@ function patchMongoCollectionPrototype(proto, cfg) {
7942
8008
  patchMongoCollectionMethod(proto, op, true, cfg);
7943
8009
  }
7944
8010
  }
8011
+ function patchMongoCollectionInstance(collection, cfg) {
8012
+ if (!collection || typeof collection !== 'object')
8013
+ return collection;
8014
+ patchMongoCollectionPrototype(Object.getPrototypeOf(collection), cfg);
8015
+ patchMongoCollectionPrototype(collection, cfg);
8016
+ return collection;
8017
+ }
8018
+ function patchMongoDbPrototype(proto, cfg) {
8019
+ if (!proto)
8020
+ return;
8021
+ const marker = '__repro_mongodb_db_collection_patched';
8022
+ if (proto[marker])
8023
+ return;
8024
+ const original = proto.collection;
8025
+ if (typeof original !== 'function')
8026
+ return;
8027
+ proto[marker] = true;
8028
+ proto.collection = function reproPatchedMongoDbCollection(...args) {
8029
+ const collection = original.apply(this, args);
8030
+ return patchMongoCollectionInstance(collection, cfg);
8031
+ };
8032
+ }
8033
+ function patchMongoDbInstance(db, cfg) {
8034
+ if (!db || typeof db !== 'object')
8035
+ return db;
8036
+ patchMongoDbPrototype(Object.getPrototypeOf(db), cfg);
8037
+ patchMongoDbPrototype(db, cfg);
8038
+ return db;
8039
+ }
8040
+ function patchMongoClientPrototype(proto, cfg) {
8041
+ if (!proto)
8042
+ return;
8043
+ const marker = '__repro_mongodb_client_db_patched';
8044
+ if (proto[marker])
8045
+ return;
8046
+ const original = proto.db;
8047
+ if (typeof original !== 'function')
8048
+ return;
8049
+ proto[marker] = true;
8050
+ proto.db = function reproPatchedMongoClientDb(...args) {
8051
+ const db = original.apply(this, args);
8052
+ return patchMongoDbInstance(db, cfg);
8053
+ };
8054
+ }
7945
8055
  function patchMongoDbFromModuleExports(mongoModule, cfg) {
7946
8056
  if (!mongoModule)
7947
8057
  return;
@@ -7956,6 +8066,28 @@ function patchMongoDbFromModuleExports(mongoModule, cfg) {
7956
8066
  for (const ctor of constructors) {
7957
8067
  patchMongoCollectionPrototype(ctor.prototype, cfg);
7958
8068
  }
8069
+ const dbConstructors = new Set();
8070
+ const clientConstructors = new Set();
8071
+ const addDbCtor = (candidate) => {
8072
+ if (typeof candidate === 'function' && candidate.prototype) {
8073
+ dbConstructors.add(candidate);
8074
+ }
8075
+ };
8076
+ const addClientCtor = (candidate) => {
8077
+ if (typeof candidate === 'function' && candidate.prototype) {
8078
+ clientConstructors.add(candidate);
8079
+ }
8080
+ };
8081
+ addDbCtor(mongoModule?.Db);
8082
+ addDbCtor(mongoModule?.default?.Db);
8083
+ addClientCtor(mongoModule?.MongoClient);
8084
+ addClientCtor(mongoModule?.default?.MongoClient);
8085
+ for (const ctor of dbConstructors) {
8086
+ patchMongoDbPrototype(ctor.prototype, cfg);
8087
+ }
8088
+ for (const ctor of clientConstructors) {
8089
+ patchMongoClientPrototype(ctor.prototype, cfg);
8090
+ }
7959
8091
  }
7960
8092
  function installMongoDbRequireHook() {
7961
8093
  if (mongoDbRequireHookInstalled)
@@ -7997,9 +8129,13 @@ const REPRO_AUTO_MIDDLEWARE_INSTALLED = Symbol.for('__repro_auto_middleware_inst
7997
8129
  const REPRO_AUTO_NEST_FACTORY_PATCHED = Symbol.for('__repro_auto_nest_factory_patched');
7998
8130
  const REPRO_AUTO_EXPRESS_LISTEN_PATCHED = Symbol.for('__repro_auto_express_listen_patched');
7999
8131
  const REPRO_AUTO_NEST_LISTEN_PATCHED = Symbol.for('__repro_auto_nest_listen_patched');
8132
+ const REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED = Symbol.for('__repro_auto_http_create_server_patched');
8133
+ const REPRO_AUTO_HTTP_SERVER_PATCHED = Symbol.for('__repro_auto_http_server_patched');
8134
+ const REPRO_AUTO_HTTP_LISTENER_WRAPPED = Symbol.for('__repro_auto_http_listener_wrapped');
8000
8135
  const REPRO_AUTO_MONGOOSE_PLUGIN_PATCHED = Symbol.for('__repro_auto_mongoose_plugin_patched');
8001
8136
  let reproAutoMiddlewareConfig = null;
8002
8137
  const mongoosePluginConfigs = [];
8138
+ const httpRequestListenerWrappers = new WeakMap();
8003
8139
  let frameworkRequireHookInstalled = false;
8004
8140
  let mongooseRequireHookInstalled = false;
8005
8141
  function hasMarker(target, marker) {
@@ -8104,6 +8240,81 @@ function patchExpressFromModuleExports(expressModule) {
8104
8240
  setMarker(wrappedListen, REPRO_AUTO_EXPRESS_LISTEN_PATCHED);
8105
8241
  application.listen = wrappedListen;
8106
8242
  }
8243
+ function wrapHttpRequestListener(listener, cfg) {
8244
+ if (typeof listener !== 'function')
8245
+ return listener;
8246
+ if (hasMarker(listener, REPRO_AUTO_HTTP_LISTENER_WRAPPED))
8247
+ return listener;
8248
+ const existing = httpRequestListenerWrappers.get(listener);
8249
+ if (existing)
8250
+ return existing;
8251
+ const middleware = reproMiddleware(cfg);
8252
+ const wrapped = function reproPatchedHttpRequestListener(req, res) {
8253
+ return middleware(req, res, (err) => {
8254
+ if (err) {
8255
+ try {
8256
+ if (!res.headersSent) {
8257
+ res.statusCode = 500;
8258
+ res.end('Internal Server Error');
8259
+ return undefined;
8260
+ }
8261
+ }
8262
+ catch { }
8263
+ throw err;
8264
+ }
8265
+ return listener.call(this, req, res);
8266
+ });
8267
+ };
8268
+ setMarker(wrapped, REPRO_AUTO_HTTP_LISTENER_WRAPPED);
8269
+ httpRequestListenerWrappers.set(listener, wrapped);
8270
+ return wrapped;
8271
+ }
8272
+ function patchHttpServerRequestListenerMethods(server) {
8273
+ const cfg = reproAutoMiddlewareConfig;
8274
+ if (!cfg || !server || hasMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED))
8275
+ return;
8276
+ for (const methodName of ['on', 'addListener', 'prependListener', 'once']) {
8277
+ const original = server[methodName];
8278
+ if (typeof original !== 'function')
8279
+ continue;
8280
+ server[methodName] = function reproPatchedHttpServerListenerMethod(...args) {
8281
+ if (args[0] === 'request' && typeof args[1] === 'function' && reproAutoMiddlewareConfig) {
8282
+ args[1] = wrapHttpRequestListener(args[1], reproAutoMiddlewareConfig);
8283
+ }
8284
+ return original.apply(this, args);
8285
+ };
8286
+ }
8287
+ setMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED);
8288
+ }
8289
+ function patchHttpCreateServerFromModuleExports(httpModule) {
8290
+ const original = httpModule?.createServer;
8291
+ if (!httpModule || typeof original !== 'function')
8292
+ return;
8293
+ if (hasMarker(original, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED))
8294
+ return;
8295
+ const wrappedCreateServer = function reproPatchedHttpCreateServer(...args) {
8296
+ const cfg = reproAutoMiddlewareConfig;
8297
+ if (cfg) {
8298
+ const listenerIndex = typeof args[0] === 'function'
8299
+ ? 0
8300
+ : typeof args[1] === 'function'
8301
+ ? 1
8302
+ : -1;
8303
+ if (listenerIndex >= 0) {
8304
+ args[listenerIndex] = wrapHttpRequestListener(args[listenerIndex], cfg);
8305
+ }
8306
+ }
8307
+ const server = original.apply(this, args);
8308
+ patchHttpServerRequestListenerMethods(server);
8309
+ return server;
8310
+ };
8311
+ setMarker(wrappedCreateServer, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED);
8312
+ httpModule.createServer = wrappedCreateServer;
8313
+ }
8314
+ function patchHttpServerAutoAttach() {
8315
+ patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:http') ?? requireOptionalHostModule('http'));
8316
+ patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:https') ?? requireOptionalHostModule('https'));
8317
+ }
8107
8318
  function patchKnownFrameworkModules() {
8108
8319
  const nestModules = new Set();
8109
8320
  const resolvedNest = requireOptionalHostModule('@nestjs/core');
@@ -8407,6 +8618,7 @@ async function initRepro(cfg) {
8407
8618
  };
8408
8619
  shareRuntimePrivacyState(reproAutoMiddlewareConfig, integrationCfg);
8409
8620
  installFrameworkRequireHook();
8621
+ patchHttpServerAutoAttach();
8410
8622
  patchKnownFrameworkModules();
8411
8623
  patchHttpClients({
8412
8624
  resolveContext: cfg.resolveContext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "build": "tsc -p tsconfig.json",
13
13
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
14
14
  "prepublishOnly": "npm run build",
15
- "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js",
15
+ "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js",
16
16
  "test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js"
17
17
  },
18
18
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -313,6 +313,7 @@ const REQUEST_START_HEADER = 'x-bug-request-start';
313
313
  const REPRO_QUERY_SESSION_PARAM = '__repro_sid';
314
314
  const REPRO_QUERY_ACTION_PARAM = '__repro_aid';
315
315
  const REPRO_QUERY_START_PARAM = '__repro_start';
316
+ const REPRO_REQUEST_MIDDLEWARE_RAN = Symbol.for('__repro_request_middleware_ran');
316
317
  const KAFKA_HEADER_TRACE_ID = 'x-repro-trace-id';
317
318
  const KAFKA_HEADER_SPAN_ID = 'x-repro-span-id';
318
319
  const KAFKA_HEADER_PARENT_SPAN_ID = 'x-repro-parent-span-id';
@@ -1429,7 +1430,6 @@ function scheduleTraceMaterializerIdleShutdown(): void {
1429
1430
  try { void worker.terminate?.(); } catch {}
1430
1431
  }
1431
1432
  }, TRACE_MATERIALIZE_WORKER_IDLE_SHUTDOWN_MS);
1432
- try { (traceMaterializerIdleTimer as any).unref?.(); } catch {}
1433
1433
  }
1434
1434
 
1435
1435
  function rejectTraceMaterializerPending(error: Error): void {
@@ -1745,11 +1745,34 @@ function waitForSessionDrain(sessionId: string): Promise<void> {
1745
1745
  if (remaining <= 0) return Promise.resolve();
1746
1746
 
1747
1747
  return new Promise(resolve => {
1748
+ let settled = false;
1749
+ let timeout: NodeJS.Timeout | null = null;
1750
+ const removeWaiter = (waiter: () => void) => {
1751
+ const list = sessionWaiters.get(sessionId);
1752
+ if (!list) return;
1753
+ const next = list.filter(fn => fn !== waiter);
1754
+ if (next.length) {
1755
+ sessionWaiters.set(sessionId, next);
1756
+ } else {
1757
+ sessionWaiters.delete(sessionId);
1758
+ }
1759
+ };
1760
+ const done = () => {
1761
+ if (settled) return;
1762
+ settled = true;
1763
+ if (timeout) {
1764
+ try { clearTimeout(timeout); } catch {}
1765
+ timeout = null;
1766
+ }
1767
+ removeWaiter(done);
1768
+ resolve();
1769
+ };
1748
1770
  const list = sessionWaiters.get(sessionId) || [];
1749
- list.push(resolve);
1771
+ list.push(done);
1750
1772
  sessionWaiters.set(sessionId, list);
1751
1773
  if (SESSION_DRAIN_TIMEOUT_MS > 0 && Number.isFinite(SESSION_DRAIN_TIMEOUT_MS)) {
1752
- setTimeout(resolve, SESSION_DRAIN_TIMEOUT_MS);
1774
+ timeout = setTimeout(done, SESSION_DRAIN_TIMEOUT_MS);
1775
+ try { (timeout as any).unref?.(); } catch {}
1753
1776
  }
1754
1777
  });
1755
1778
  }
@@ -4894,6 +4917,19 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
4894
4917
  const masking = normalizeMaskingConfig(cfg.masking);
4895
4918
  patchMongoDb(cfg);
4896
4919
  return async function (req: Request, res: Response, next: NextFunction) {
4920
+ const reqAny = req as any;
4921
+ if (reqAny?.[REPRO_REQUEST_MIDDLEWARE_RAN]) {
4922
+ return next();
4923
+ }
4924
+ try {
4925
+ Object.defineProperty(reqAny, REPRO_REQUEST_MIDDLEWARE_RAN, {
4926
+ value: true,
4927
+ configurable: true,
4928
+ });
4929
+ } catch {
4930
+ try { reqAny[REPRO_REQUEST_MIDDLEWARE_RAN] = true; } catch {}
4931
+ }
4932
+
4897
4933
  const querySid = readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM);
4898
4934
  const queryAid = readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM);
4899
4935
  const queryTs = readRequestQueryNumber(req, REPRO_QUERY_START_PARAM);
@@ -4933,24 +4969,44 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
4933
4969
  const requestHeaders = sanitizeHeaders(req.headers, cfg.captureHeaders);
4934
4970
  beginSessionRequest(sid);
4935
4971
 
4936
- // ---- response body capture (unchanged) ----
4972
+ // ---- response body capture ----
4937
4973
  let capturedBody: any = undefined;
4938
- const origJson = res.json.bind(res as any);
4939
- (res as any).json = (body: any) => { capturedBody = body; return origJson(body); };
4940
-
4941
- const origSend = res.send.bind(res as any);
4942
- (res as any).send = (body: any) => {
4943
- if (capturedBody === undefined) {
4944
- capturedBody = coerceBodyToStorable(body, res.getHeader?.('content-type'));
4945
- }
4946
- return origSend(body);
4947
- };
4974
+ const originalJson = (res as any).json;
4975
+ if (typeof originalJson === 'function') {
4976
+ const origJson = originalJson.bind(res as any);
4977
+ (res as any).json = (body: any) => { capturedBody = body; return origJson(body); };
4978
+ }
4979
+
4980
+ const originalSend = (res as any).send;
4981
+ if (typeof originalSend === 'function') {
4982
+ const origSend = originalSend.bind(res as any);
4983
+ (res as any).send = (body: any) => {
4984
+ if (capturedBody === undefined) {
4985
+ capturedBody = coerceBodyToStorable(body, res.getHeader?.('content-type'));
4986
+ }
4987
+ return origSend(body);
4988
+ };
4989
+ }
4948
4990
 
4949
- const origWrite = (res as any).write.bind(res as any);
4950
- const origEnd = (res as any).end.bind(res as any);
4991
+ const origWrite = typeof (res as any).write === 'function'
4992
+ ? (res as any).write.bind(res as any)
4993
+ : null;
4994
+ const origEnd = typeof (res as any).end === 'function'
4995
+ ? (res as any).end.bind(res as any)
4996
+ : null;
4951
4997
  const chunks: Array<Buffer | string> = [];
4952
- (res as any).write = (chunk: any, ...args: any[]) => { try { if (chunk != null) chunks.push(chunk); } catch {} return origWrite(chunk, ...args); };
4953
- (res as any).end = (chunk?: any, ...args: any[]) => { try { if (chunk != null) chunks.push(chunk); } catch {} return origEnd(chunk, ...args); };
4998
+ if (origWrite) {
4999
+ (res as any).write = (chunk: any, ...args: any[]) => {
5000
+ try { if (chunk != null) chunks.push(chunk); } catch {}
5001
+ return origWrite(chunk, ...args);
5002
+ };
5003
+ }
5004
+ if (origEnd) {
5005
+ (res as any).end = (chunk?: any, ...args: any[]) => {
5006
+ try { if (chunk != null) chunks.push(chunk); } catch {}
5007
+ return origEnd(chunk, ...args);
5008
+ };
5009
+ }
4954
5010
 
4955
5011
  // ---- our ALS (unchanged) ----
4956
5012
  const tracerApiWithTrace = __TRACER__ as (TracerApi & { withTrace?: (id: string, fn: () => void) => void }) | null;
@@ -5490,6 +5546,7 @@ function nativeMongoOpsForMongooseOp(op: string): string[] {
5490
5546
  }
5491
5547
 
5492
5548
  export function reproMongoosePlugin(cfg: ReproMiddlewareConfig) {
5549
+ patchMongoDb(cfg);
5493
5550
  return function (schema: Schema) {
5494
5551
  // -------- pre/post save (unchanged) --------
5495
5552
  schema.pre('save', { document: true }, function (next) {
@@ -9185,6 +9242,46 @@ function patchMongoCollectionPrototype(proto: any, cfg: MongoDbPatchConfig): voi
9185
9242
  }
9186
9243
  }
9187
9244
 
9245
+ function patchMongoCollectionInstance(collection: any, cfg: MongoDbPatchConfig): any {
9246
+ if (!collection || typeof collection !== 'object') return collection;
9247
+ patchMongoCollectionPrototype(Object.getPrototypeOf(collection), cfg);
9248
+ patchMongoCollectionPrototype(collection, cfg);
9249
+ return collection;
9250
+ }
9251
+
9252
+ function patchMongoDbPrototype(proto: any, cfg: MongoDbPatchConfig): void {
9253
+ if (!proto) return;
9254
+ const marker = '__repro_mongodb_db_collection_patched';
9255
+ if (proto[marker]) return;
9256
+ const original = proto.collection;
9257
+ if (typeof original !== 'function') return;
9258
+ proto[marker] = true;
9259
+ proto.collection = function reproPatchedMongoDbCollection(this: any, ...args: any[]) {
9260
+ const collection = original.apply(this, args);
9261
+ return patchMongoCollectionInstance(collection, cfg);
9262
+ };
9263
+ }
9264
+
9265
+ function patchMongoDbInstance(db: any, cfg: MongoDbPatchConfig): any {
9266
+ if (!db || typeof db !== 'object') return db;
9267
+ patchMongoDbPrototype(Object.getPrototypeOf(db), cfg);
9268
+ patchMongoDbPrototype(db, cfg);
9269
+ return db;
9270
+ }
9271
+
9272
+ function patchMongoClientPrototype(proto: any, cfg: MongoDbPatchConfig): void {
9273
+ if (!proto) return;
9274
+ const marker = '__repro_mongodb_client_db_patched';
9275
+ if (proto[marker]) return;
9276
+ const original = proto.db;
9277
+ if (typeof original !== 'function') return;
9278
+ proto[marker] = true;
9279
+ proto.db = function reproPatchedMongoClientDb(this: any, ...args: any[]) {
9280
+ const db = original.apply(this, args);
9281
+ return patchMongoDbInstance(db, cfg);
9282
+ };
9283
+ }
9284
+
9188
9285
  function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig): void {
9189
9286
  if (!mongoModule) return;
9190
9287
  const constructors = new Set<any>();
@@ -9200,6 +9297,31 @@ function patchMongoDbFromModuleExports(mongoModule: any, cfg: MongoDbPatchConfig
9200
9297
  for (const ctor of constructors) {
9201
9298
  patchMongoCollectionPrototype(ctor.prototype, cfg);
9202
9299
  }
9300
+
9301
+ const dbConstructors = new Set<any>();
9302
+ const clientConstructors = new Set<any>();
9303
+ const addDbCtor = (candidate: any) => {
9304
+ if (typeof candidate === 'function' && candidate.prototype) {
9305
+ dbConstructors.add(candidate);
9306
+ }
9307
+ };
9308
+ const addClientCtor = (candidate: any) => {
9309
+ if (typeof candidate === 'function' && candidate.prototype) {
9310
+ clientConstructors.add(candidate);
9311
+ }
9312
+ };
9313
+
9314
+ addDbCtor(mongoModule?.Db);
9315
+ addDbCtor(mongoModule?.default?.Db);
9316
+ addClientCtor(mongoModule?.MongoClient);
9317
+ addClientCtor(mongoModule?.default?.MongoClient);
9318
+
9319
+ for (const ctor of dbConstructors) {
9320
+ patchMongoDbPrototype(ctor.prototype, cfg);
9321
+ }
9322
+ for (const ctor of clientConstructors) {
9323
+ patchMongoClientPrototype(ctor.prototype, cfg);
9324
+ }
9203
9325
  }
9204
9326
 
9205
9327
  function installMongoDbRequireHook(): void {
@@ -9243,10 +9365,14 @@ const REPRO_AUTO_MIDDLEWARE_INSTALLED = Symbol.for('__repro_auto_middleware_inst
9243
9365
  const REPRO_AUTO_NEST_FACTORY_PATCHED = Symbol.for('__repro_auto_nest_factory_patched');
9244
9366
  const REPRO_AUTO_EXPRESS_LISTEN_PATCHED = Symbol.for('__repro_auto_express_listen_patched');
9245
9367
  const REPRO_AUTO_NEST_LISTEN_PATCHED = Symbol.for('__repro_auto_nest_listen_patched');
9368
+ const REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED = Symbol.for('__repro_auto_http_create_server_patched');
9369
+ const REPRO_AUTO_HTTP_SERVER_PATCHED = Symbol.for('__repro_auto_http_server_patched');
9370
+ const REPRO_AUTO_HTTP_LISTENER_WRAPPED = Symbol.for('__repro_auto_http_listener_wrapped');
9246
9371
  const REPRO_AUTO_MONGOOSE_PLUGIN_PATCHED = Symbol.for('__repro_auto_mongoose_plugin_patched');
9247
9372
 
9248
9373
  let reproAutoMiddlewareConfig: ReproMiddlewareConfig | null = null;
9249
9374
  const mongoosePluginConfigs: ReproMiddlewareConfig[] = [];
9375
+ const httpRequestListenerWrappers = new WeakMap<Function, Function>();
9250
9376
  let frameworkRequireHookInstalled = false;
9251
9377
  let mongooseRequireHookInstalled = false;
9252
9378
 
@@ -9347,6 +9473,82 @@ function patchExpressFromModuleExports(expressModule: any): void {
9347
9473
  application.listen = wrappedListen;
9348
9474
  }
9349
9475
 
9476
+ function wrapHttpRequestListener(listener: any, cfg: ReproMiddlewareConfig): any {
9477
+ if (typeof listener !== 'function') return listener;
9478
+ if (hasMarker(listener, REPRO_AUTO_HTTP_LISTENER_WRAPPED)) return listener;
9479
+
9480
+ const existing = httpRequestListenerWrappers.get(listener);
9481
+ if (existing) return existing;
9482
+
9483
+ const middleware = reproMiddleware(cfg);
9484
+ const wrapped = function reproPatchedHttpRequestListener(this: any, req: any, res: any) {
9485
+ return middleware(req, res, (err?: any) => {
9486
+ if (err) {
9487
+ try {
9488
+ if (!res.headersSent) {
9489
+ res.statusCode = 500;
9490
+ res.end('Internal Server Error');
9491
+ return undefined;
9492
+ }
9493
+ } catch {}
9494
+ throw err;
9495
+ }
9496
+ return listener.call(this, req, res);
9497
+ });
9498
+ };
9499
+ setMarker(wrapped, REPRO_AUTO_HTTP_LISTENER_WRAPPED);
9500
+ httpRequestListenerWrappers.set(listener, wrapped);
9501
+ return wrapped;
9502
+ }
9503
+
9504
+ function patchHttpServerRequestListenerMethods(server: any): void {
9505
+ const cfg = reproAutoMiddlewareConfig;
9506
+ if (!cfg || !server || hasMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED)) return;
9507
+
9508
+ for (const methodName of ['on', 'addListener', 'prependListener', 'once']) {
9509
+ const original = server[methodName];
9510
+ if (typeof original !== 'function') continue;
9511
+ server[methodName] = function reproPatchedHttpServerListenerMethod(this: any, ...args: any[]) {
9512
+ if (args[0] === 'request' && typeof args[1] === 'function' && reproAutoMiddlewareConfig) {
9513
+ args[1] = wrapHttpRequestListener(args[1], reproAutoMiddlewareConfig);
9514
+ }
9515
+ return original.apply(this, args);
9516
+ };
9517
+ }
9518
+ setMarker(server, REPRO_AUTO_HTTP_SERVER_PATCHED);
9519
+ }
9520
+
9521
+ function patchHttpCreateServerFromModuleExports(httpModule: any): void {
9522
+ const original = httpModule?.createServer;
9523
+ if (!httpModule || typeof original !== 'function') return;
9524
+ if (hasMarker(original, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED)) return;
9525
+
9526
+ const wrappedCreateServer = function reproPatchedHttpCreateServer(this: any, ...args: any[]) {
9527
+ const cfg = reproAutoMiddlewareConfig;
9528
+ if (cfg) {
9529
+ const listenerIndex =
9530
+ typeof args[0] === 'function'
9531
+ ? 0
9532
+ : typeof args[1] === 'function'
9533
+ ? 1
9534
+ : -1;
9535
+ if (listenerIndex >= 0) {
9536
+ args[listenerIndex] = wrapHttpRequestListener(args[listenerIndex], cfg);
9537
+ }
9538
+ }
9539
+ const server = original.apply(this, args);
9540
+ patchHttpServerRequestListenerMethods(server);
9541
+ return server;
9542
+ };
9543
+ setMarker(wrappedCreateServer, REPRO_AUTO_HTTP_CREATE_SERVER_PATCHED);
9544
+ httpModule.createServer = wrappedCreateServer;
9545
+ }
9546
+
9547
+ function patchHttpServerAutoAttach(): void {
9548
+ patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:http') ?? requireOptionalHostModule('http'));
9549
+ patchHttpCreateServerFromModuleExports(requireOptionalHostModule('node:https') ?? requireOptionalHostModule('https'));
9550
+ }
9551
+
9350
9552
  function patchKnownFrameworkModules(): void {
9351
9553
  const nestModules = new Set<any>();
9352
9554
  const resolvedNest = requireOptionalHostModule('@nestjs/core');
@@ -9654,6 +9856,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
9654
9856
  shareRuntimePrivacyState(reproAutoMiddlewareConfig, integrationCfg);
9655
9857
 
9656
9858
  installFrameworkRequireHook();
9859
+ patchHttpServerAutoAttach();
9657
9860
  patchKnownFrameworkModules();
9658
9861
 
9659
9862
  patchHttpClients({
@@ -0,0 +1,170 @@
1
+ process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '50';
2
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '25';
3
+ process.env.TRACE_IDLE_FLUSH_MS = '10';
4
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
5
+
6
+ const assert = require('node:assert/strict');
7
+ const http = require('node:http');
8
+ const mongodb = require('mongodb');
9
+
10
+ const originalNativeMongoInsertOne = mongodb.Collection.prototype.insertOne;
11
+ mongodb.Collection.prototype.insertOne = async function reproFakeNativeMongoInsertOne(doc, options) {
12
+ assert.equal(options.session.id, 'auto-http-native-session-1');
13
+ doc._id = doc._id ?? 'subject-auto-http-1';
14
+ return { acknowledged: true, insertedId: doc._id };
15
+ };
16
+
17
+ const { initRepro, reproMiddleware } = require('../dist');
18
+ const { flushIngestQueue } = require('../dist/ingest/client');
19
+
20
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
21
+
22
+ async function waitFor(predicate, timeoutMs = 3000) {
23
+ const deadline = Date.now() + timeoutMs;
24
+ while (Date.now() < deadline) {
25
+ if (predicate()) return;
26
+ await sleep(25);
27
+ }
28
+ assert(predicate(), 'timed out waiting for expected condition');
29
+ }
30
+
31
+ function flattenEvents(posts) {
32
+ return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
33
+ }
34
+
35
+ function backendRequests(posts, sessionId) {
36
+ return flattenEvents(posts).filter((event) => {
37
+ return event?.event_type === 'backend_request' && event?.session_id === sessionId;
38
+ });
39
+ }
40
+
41
+ function dbChanges(posts, sessionId) {
42
+ return flattenEvents(posts)
43
+ .filter((event) => event?.event_type === 'db_change' && event?.session_id === sessionId)
44
+ .flatMap((event) => Array.isArray(event?.payload?.db) ? event.payload.db : []);
45
+ }
46
+
47
+ async function listen(server) {
48
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
49
+ return server.address().port;
50
+ }
51
+
52
+ async function close(server) {
53
+ await new Promise((resolve, reject) => {
54
+ server.close((err) => (err ? reject(err) : resolve()));
55
+ });
56
+ }
57
+
58
+ async function request(port, sessionId, actionId) {
59
+ await new Promise((resolve, reject) => {
60
+ const req = http.request({
61
+ host: '127.0.0.1',
62
+ port,
63
+ path: '/subjects',
64
+ method: 'POST',
65
+ headers: {
66
+ 'content-type': 'application/json',
67
+ 'x-bug-session-id': sessionId,
68
+ 'x-bug-action-id': actionId,
69
+ 'x-bug-request-start': String(Date.now()),
70
+ },
71
+ }, (res) => {
72
+ res.resume();
73
+ res.on('end', resolve);
74
+ });
75
+ req.on('error', reject);
76
+ req.end(JSON.stringify({ ok: true }));
77
+ });
78
+ }
79
+
80
+ async function main() {
81
+ const capturedBodies = [];
82
+ const originalFetch = global.fetch;
83
+ global.fetch = async (_url, init = {}) => {
84
+ capturedBodies.push(JSON.parse(String(init.body || '{}')));
85
+ return {
86
+ ok: true,
87
+ status: 202,
88
+ json: async () => ({ ok: true }),
89
+ text: async () => '{"ok":true}',
90
+ };
91
+ };
92
+
93
+ const cfg = {
94
+ tenantId: 'TENANT_test',
95
+ appId: 'APP_test',
96
+ appSecret: 'secret',
97
+ appName: 'auto-http-test',
98
+ serviceName: 'Auto HTTP Test',
99
+ captureHeaders: false,
100
+ privacy: { environment: 'dev', captureRaw: true },
101
+ ingestBase: 'http://127.0.0.1:65535',
102
+ };
103
+
104
+ let autoServer;
105
+ let doubleServer;
106
+ try {
107
+ await initRepro(cfg);
108
+
109
+ const subjectsCollection = new mongodb.MongoClient('mongodb://127.0.0.1:27017/clinical')
110
+ .db('clinical')
111
+ .collection('subjects');
112
+
113
+ autoServer = http.createServer(async (_req, res) => {
114
+ await subjectsCollection.insertOne(
115
+ { subjectNumber: '10013038', status: 'screened' },
116
+ {
117
+ session: {
118
+ id: 'auto-http-native-session-1',
119
+ hasEnded: false,
120
+ inTransaction: () => true,
121
+ },
122
+ },
123
+ );
124
+ res.setHeader('content-type', 'application/json');
125
+ res.end(JSON.stringify({ ok: true, mode: 'auto' }));
126
+ });
127
+ const autoPort = await listen(autoServer);
128
+ await request(autoPort, 'S_auto_http_only', 'A_auto_http_only');
129
+ await waitFor(() => backendRequests(capturedBodies, 'S_auto_http_only').length === 1);
130
+ await waitFor(() => dbChanges(capturedBodies, 'S_auto_http_only').length === 1);
131
+ assert.equal(backendRequests(capturedBodies, 'S_auto_http_only').length, 1);
132
+ const [nativeMongoChange] = dbChanges(capturedBodies, 'S_auto_http_only');
133
+ assert.equal(nativeMongoChange.type, 'mongo');
134
+ assert.equal(nativeMongoChange.collection, 'subjects');
135
+ assert.equal(nativeMongoChange.op, 'insertOne');
136
+ assert.equal(nativeMongoChange.pk._id, 'subject-auto-http-1');
137
+ assert.equal(nativeMongoChange.query.driver, 'mongodb');
138
+ assert.equal(nativeMongoChange.query.db, 'clinical');
139
+ assert.equal(nativeMongoChange.query.doc.subjectNumber, '10013038');
140
+ assert.equal(nativeMongoChange.query.options.session.__kind, 'mongodb_client_session');
141
+ assert.equal(nativeMongoChange.query.options.session.id, 'auto-http-native-session-1');
142
+ assert.equal(nativeMongoChange.after.subjectNumber, '10013038');
143
+ assert.equal(nativeMongoChange.resultMeta.insertedId, 'subject-auto-http-1');
144
+
145
+ const manual = reproMiddleware(cfg);
146
+ doubleServer = http.createServer((req, res) => {
147
+ manual(req, res, () => {
148
+ res.setHeader('content-type', 'application/json');
149
+ res.end(JSON.stringify({ ok: true, mode: 'manual-too' }));
150
+ });
151
+ });
152
+ const doublePort = await listen(doubleServer);
153
+ await request(doublePort, 'S_auto_http_no_duplicate', 'A_auto_http_no_duplicate');
154
+ await waitFor(() => backendRequests(capturedBodies, 'S_auto_http_no_duplicate').length === 1);
155
+ assert.equal(backendRequests(capturedBodies, 'S_auto_http_no_duplicate').length, 1);
156
+
157
+ console.log('initRepro auto HTTP server middleware OK');
158
+ } finally {
159
+ if (autoServer?.listening) await close(autoServer);
160
+ if (doubleServer?.listening) await close(doubleServer);
161
+ await flushIngestQueue();
162
+ mongodb.Collection.prototype.insertOne = originalNativeMongoInsertOne;
163
+ global.fetch = originalFetch;
164
+ }
165
+ }
166
+
167
+ main().catch((error) => {
168
+ console.error(error);
169
+ process.exit(1);
170
+ });
@@ -176,6 +176,83 @@ async function testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne() {
176
176
  });
177
177
  }
178
178
 
179
+ async function testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport() {
180
+ await withCapturedFetch(async (capturedBodies) => {
181
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
182
+ const sessionId = 'S_mongo_client_chain';
183
+ const actionId = 'A_mongo_client_chain';
184
+ const cfg = makeConfig(sessionId, actionId);
185
+
186
+ class FakeCollection {
187
+ constructor(dbName, collectionName) {
188
+ this.dbName = dbName;
189
+ this.collectionName = collectionName;
190
+ }
191
+
192
+ async insertOne(doc, options) {
193
+ assert.strictEqual(options.session.id, 'native-chain-session-1');
194
+ doc._id = doc._id ?? 'subject-chain-1';
195
+ return { acknowledged: true, insertedId: doc._id };
196
+ }
197
+ }
198
+
199
+ class FakeDb {
200
+ constructor(dbName) {
201
+ this.databaseName = dbName;
202
+ }
203
+
204
+ collection(collectionName) {
205
+ return new FakeCollection(this.databaseName, collectionName);
206
+ }
207
+ }
208
+
209
+ class FakeMongoClient {
210
+ db(dbName) {
211
+ return new FakeDb(dbName);
212
+ }
213
+ }
214
+
215
+ __reproTestHooks.patchMongoDbModuleForTest({
216
+ MongoClient: FakeMongoClient,
217
+ Db: FakeDb,
218
+ }, cfg);
219
+
220
+ const dbClient = new FakeMongoClient();
221
+ const subjectsCollection = dbClient.db('clinical').collection('subjects');
222
+
223
+ await runCapturedRequest(cfg, sessionId, actionId, async () => {
224
+ const result = await subjectsCollection.insertOne(
225
+ { subjectNumber: '10013038', status: 'screened' },
226
+ {
227
+ session: {
228
+ id: 'native-chain-session-1',
229
+ hasEnded: false,
230
+ inTransaction: () => true,
231
+ },
232
+ },
233
+ );
234
+ assert.deepStrictEqual(result, { acknowledged: true, insertedId: 'subject-chain-1' });
235
+ });
236
+
237
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
238
+ await drainSdk();
239
+
240
+ const [change] = dbChanges(flattenEvents(capturedBodies));
241
+ assert.strictEqual(change.type, 'mongo');
242
+ assert.strictEqual(change.collection, 'subjects');
243
+ assert.strictEqual(change.op, 'insertOne');
244
+ assert.strictEqual(change.pk._id, 'subject-chain-1');
245
+ assert.strictEqual(change.query.driver, 'mongodb');
246
+ assert.strictEqual(change.query.db, 'clinical');
247
+ assert.strictEqual(change.query.doc.subjectNumber, '10013038');
248
+ assert.strictEqual(change.query.options.session.__kind, 'mongodb_client_session');
249
+ assert.strictEqual(change.query.options.session.id, 'native-chain-session-1');
250
+ assert.strictEqual(change.after.subjectNumber, '10013038');
251
+ assert.strictEqual(change.resultMeta.insertedId, 'subject-chain-1');
252
+ assert.strictEqual(change.resultMeta.acknowledged, true);
253
+ });
254
+ }
255
+
179
256
  async function testNativeInsertOneIsCaptured() {
180
257
  await withCapturedFetch(async (capturedBodies) => {
181
258
  __reproTestHooks.resetSdkBackgroundQueuesForTest();
@@ -376,6 +453,7 @@ async function testNativeOperationFailureIsCapturedAndRethrown() {
376
453
 
377
454
  (async () => {
378
455
  await testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne();
456
+ await testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport();
379
457
  await testNativeInsertOneIsCaptured();
380
458
  await testNativeUpdateOneUpsertIsCaptured();
381
459
  await testNativeFindCursorToArrayIsCaptured();