@reproapp/node-sdk 0.0.26 → 0.0.27

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.
@@ -56,6 +56,25 @@ export const kickIngestQueueDrain = (): void => {
56
56
  }
57
57
  };
58
58
 
59
+ const isCriticalIngestEntry = (entry: LegacyEntry): boolean => (
60
+ entry.requestCaptureManifest !== undefined ||
61
+ entry.sessionCaptureManifest !== undefined ||
62
+ entry.request !== undefined ||
63
+ entry.db !== undefined ||
64
+ entry.trace !== undefined ||
65
+ entry.email !== undefined
66
+ );
67
+
68
+ const resolveBatchPriority = (
69
+ entries: LegacyEntry[],
70
+ options: EnqueueIngestOptions,
71
+ ): QueuedIngestBatch['priority'] => {
72
+ if (options.priority === 'high') {
73
+ return 'high';
74
+ }
75
+ return entries.some(isCriticalIngestEntry) ? 'high' : 'normal';
76
+ };
77
+
59
78
  const resolveIngestUrl = (config: IngestClientConfig): string => {
60
79
  const envBase = typeof process !== 'undefined' ? (process as any)?.env?.REPRO_INGEST_BASE : undefined;
61
80
  const ingestBase = String(envBase || config.ingestBase || DEFAULT_INGEST_BASE).replace(/\/+$/, '');
@@ -185,7 +204,7 @@ const drainIngestQueue = async (): Promise<void> => {
185
204
  }
186
205
 
187
206
  batch.attempts += 1;
188
- if (batch.attempts >= DEFAULT_QUEUE_MAX_ATTEMPTS) {
207
+ if (batch.attempts >= DEFAULT_QUEUE_MAX_ATTEMPTS && batch.priority !== 'high') {
189
208
  ingestQueue.shift();
190
209
  backoffMs = 0;
191
210
  continue;
@@ -215,7 +234,7 @@ export const enqueueIngestEntries = (
215
234
  if (!sessionId || !Array.isArray(entries) || entries.length === 0) {
216
235
  return false;
217
236
  }
218
- const priority = options.priority === 'high' ? 'high' : 'normal';
237
+ const priority = resolveBatchPriority(entries, options);
219
238
  const batch: QueuedIngestBatch = {
220
239
  config: { ...config },
221
240
  sessionId,
@@ -235,13 +254,16 @@ export const enqueueIngestEntries = (
235
254
  }
236
255
  if (DEFAULT_QUEUE_MAX_ITEMS > 0 && ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
237
256
  while (ingestQueue.length > DEFAULT_QUEUE_MAX_ITEMS) {
238
- let removeIndex = ingestQueue.length - 1;
257
+ let removeIndex = -1;
239
258
  for (let index = ingestQueue.length - 1; index >= 0; index -= 1) {
240
259
  if (ingestQueue[index]?.priority !== 'high') {
241
260
  removeIndex = index;
242
261
  break;
243
262
  }
244
263
  }
264
+ if (removeIndex === -1) {
265
+ break;
266
+ }
245
267
  ingestQueue.splice(removeIndex, 1);
246
268
  }
247
269
  }
@@ -0,0 +1,147 @@
1
+ process.env.REPRO_INGEST_QUEUE_MAX_ITEMS = '3';
2
+
3
+ const assert = require('assert');
4
+
5
+ const originalFetch = global.fetch;
6
+ const capturedBodies = [];
7
+
8
+ global.fetch = async (_url, init = {}) => {
9
+ capturedBodies.push(JSON.parse(String(init.body || '{}')));
10
+ return {
11
+ ok: true,
12
+ status: 200,
13
+ json: async () => ({ ok: true }),
14
+ text: async () => '{"ok":true}',
15
+ };
16
+ };
17
+
18
+ const { enqueueIngestEntries, flushIngestQueue } = require('../dist/ingest/client');
19
+
20
+ async function main() {
21
+ const cfg = {
22
+ tenantId: 'TENANT_test',
23
+ appId: 'APP_test',
24
+ appSecret: 'secret',
25
+ serviceName: 'critical-retention-test',
26
+ ingestBase: 'http://127.0.0.1:65535',
27
+ };
28
+ const sessionId = 'S_critical_retention';
29
+
30
+ for (let index = 0; index < 5; index += 1) {
31
+ enqueueIngestEntries(cfg, sessionId, [{
32
+ actionId: 'A_optional',
33
+ message: `optional-${index}`,
34
+ t: Date.now(),
35
+ }]);
36
+ }
37
+
38
+ const criticalEntries = [
39
+ {
40
+ actionId: 'A_critical',
41
+ request: {
42
+ rid: 'R_request',
43
+ method: 'GET',
44
+ url: '/critical-request',
45
+ status: 200,
46
+ },
47
+ t: Date.now(),
48
+ },
49
+ {
50
+ actionId: 'A_critical',
51
+ requestRid: 'R_trace',
52
+ trace: [{
53
+ t: Date.now(),
54
+ type: 'enter',
55
+ fn: 'criticalTrace',
56
+ }],
57
+ traceBatch: {
58
+ rid: 'R_trace',
59
+ index: 0,
60
+ total: 1,
61
+ },
62
+ t: Date.now(),
63
+ },
64
+ {
65
+ actionId: 'A_critical',
66
+ requestRid: 'R_db',
67
+ db: [{
68
+ collection: 'critical_collection',
69
+ op: 'findOne',
70
+ }],
71
+ t: Date.now(),
72
+ },
73
+ {
74
+ actionId: 'A_critical',
75
+ requestRid: 'R_email',
76
+ email: {
77
+ provider: 'critical-email',
78
+ subject: 'critical',
79
+ },
80
+ t: Date.now(),
81
+ },
82
+ {
83
+ actionId: 'A_critical',
84
+ requestRid: 'R_manifest',
85
+ requestCaptureManifest: {
86
+ scopeId: 'req:R_manifest',
87
+ requestRid: 'R_manifest',
88
+ transport: 'http',
89
+ expected: {
90
+ backendRequest: 1,
91
+ traceBatch: 0,
92
+ dbChange: 0,
93
+ emailEvent: 0,
94
+ },
95
+ closedAt: Date.now(),
96
+ },
97
+ t: Date.now(),
98
+ },
99
+ {
100
+ actionId: 'A_critical',
101
+ sessionCaptureManifest: {
102
+ mode: 'staging',
103
+ expected: {
104
+ frontendActionDoc: 0,
105
+ rrwebChunk: 0,
106
+ frontendSessionEnd: 1,
107
+ },
108
+ closedAt: Date.now(),
109
+ },
110
+ t: Date.now(),
111
+ },
112
+ ];
113
+
114
+ for (const entry of criticalEntries) {
115
+ const enqueued = enqueueIngestEntries(cfg, sessionId, [entry]);
116
+ assert.strictEqual(enqueued, true, `critical event was dropped: ${JSON.stringify(Object.keys(entry))}`);
117
+ }
118
+
119
+ await flushIngestQueue();
120
+
121
+ const eventTypes = capturedBodies
122
+ .flatMap((body) => Array.isArray(body?.events) ? body.events : [])
123
+ .map((event) => event?.event_type)
124
+ .sort();
125
+
126
+ assert.deepStrictEqual(eventTypes, [
127
+ 'backend_request',
128
+ 'db_change',
129
+ 'email_event',
130
+ 'request_capture_manifest',
131
+ 'session_capture_manifest',
132
+ 'trace_batch',
133
+ ]);
134
+
135
+ // eslint-disable-next-line no-console
136
+ console.log('ingest critical retention OK');
137
+ }
138
+
139
+ main()
140
+ .catch((error) => {
141
+ // eslint-disable-next-line no-console
142
+ console.error(error);
143
+ process.exitCode = 1;
144
+ })
145
+ .finally(() => {
146
+ global.fetch = originalFetch;
147
+ });
@@ -0,0 +1,57 @@
1
+ const assert = require('assert');
2
+
3
+ const originalFetch = global.fetch;
4
+ let attempts = 0;
5
+
6
+ global.fetch = async () => {
7
+ attempts += 1;
8
+ return {
9
+ ok: attempts > 6,
10
+ status: attempts > 6 ? 200 : 503,
11
+ json: async () => ({ ok: attempts > 6 }),
12
+ text: async () => attempts > 6 ? '{"ok":true}' : '{"ok":false}',
13
+ };
14
+ };
15
+
16
+ const { enqueueIngestEntries, flushIngestQueue } = require('../dist/ingest/client');
17
+
18
+ async function main() {
19
+ const cfg = {
20
+ tenantId: 'TENANT_test',
21
+ appId: 'APP_test',
22
+ appSecret: 'secret',
23
+ serviceName: 'critical-retry-retention-test',
24
+ ingestBase: 'http://127.0.0.1:65535',
25
+ };
26
+
27
+ const enqueued = enqueueIngestEntries(cfg, 'S_critical_retry_retention', [{
28
+ actionId: 'A_critical_retry',
29
+ request: {
30
+ rid: 'R_critical_retry',
31
+ method: 'GET',
32
+ url: '/critical-retry',
33
+ status: 200,
34
+ },
35
+ t: Date.now(),
36
+ }]);
37
+ assert.strictEqual(enqueued, true);
38
+
39
+ for (let index = 0; index < 7; index += 1) {
40
+ await flushIngestQueue();
41
+ }
42
+
43
+ assert.strictEqual(attempts, 7, `critical batch was dropped before eventual success; attempts=${attempts}`);
44
+
45
+ // eslint-disable-next-line no-console
46
+ console.log('ingest critical retry retention OK');
47
+ }
48
+
49
+ main()
50
+ .catch((error) => {
51
+ // eslint-disable-next-line no-console
52
+ console.error(error);
53
+ process.exitCode = 1;
54
+ })
55
+ .finally(() => {
56
+ global.fetch = originalFetch;
57
+ });
@@ -0,0 +1,75 @@
1
+ process.env.REPRO_INGEST_QUEUE_MAX_ITEMS = '3';
2
+
3
+ const assert = require('assert');
4
+
5
+ const originalFetch = global.fetch;
6
+ const capturedBodies = [];
7
+
8
+ global.fetch = async (_url, init = {}) => {
9
+ capturedBodies.push(JSON.parse(String(init.body || '{}')));
10
+ return {
11
+ ok: true,
12
+ status: 200,
13
+ json: async () => ({ ok: true }),
14
+ text: async () => '{"ok":true}',
15
+ };
16
+ };
17
+
18
+ const { enqueueIngestEntries, flushIngestQueue } = require('../dist/ingest/client');
19
+
20
+ async function main() {
21
+ const cfg = {
22
+ tenantId: 'TENANT_test',
23
+ appId: 'APP_test',
24
+ appSecret: 'secret',
25
+ serviceName: 'high-priority-retention-test',
26
+ ingestBase: 'http://127.0.0.1:65535',
27
+ };
28
+ const sessionId = 'S_high_priority_retention';
29
+
30
+ for (let index = 0; index < 5; index += 1) {
31
+ const enqueued = enqueueIngestEntries(cfg, sessionId, [{
32
+ actionId: 'A_high_priority_retention',
33
+ requestRid: `R_high_priority_${index}`,
34
+ requestCaptureManifest: {
35
+ scopeId: `req:R_high_priority_${index}`,
36
+ requestRid: `R_high_priority_${index}`,
37
+ transport: 'http',
38
+ expected: {
39
+ backendRequest: 1,
40
+ traceBatch: 0,
41
+ dbChange: 0,
42
+ emailEvent: 0,
43
+ },
44
+ closedAt: Date.now(),
45
+ },
46
+ t: Date.now(),
47
+ }], { priority: 'high' });
48
+ assert.strictEqual(enqueued, true, `high-priority batch ${index} was dropped`);
49
+ }
50
+
51
+ await flushIngestQueue();
52
+
53
+ const manifests = capturedBodies
54
+ .flatMap((body) => Array.isArray(body?.events) ? body.events : [])
55
+ .filter((event) => event?.event_type === 'request_capture_manifest');
56
+
57
+ assert.strictEqual(manifests.length, 5, JSON.stringify({
58
+ got: manifests.length,
59
+ expected: 5,
60
+ requestIds: manifests.map((event) => event?.request_id),
61
+ }));
62
+
63
+ // eslint-disable-next-line no-console
64
+ console.log('ingest high priority retention OK');
65
+ }
66
+
67
+ main()
68
+ .catch((error) => {
69
+ // eslint-disable-next-line no-console
70
+ console.error(error);
71
+ process.exitCode = 1;
72
+ })
73
+ .finally(() => {
74
+ global.fetch = originalFetch;
75
+ });
@@ -0,0 +1,332 @@
1
+ process.env.REPRO_INGEST_REQUEST_TIMEOUT_MS = '50';
2
+ process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS = '25';
3
+ process.env.REPRO_SDK_BACKGROUND_IDLE_DELAY_MS = '0';
4
+ process.env.REPRO_SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = '0';
5
+ process.env.TRACE_IDLE_FLUSH_MS = '10';
6
+ process.env.TRACE_LINGER_AFTER_FINISH_MS = '0';
7
+
8
+ const assert = require('assert');
9
+ const { EventEmitter } = require('events');
10
+ const { flushIngestQueue } = require('../dist/ingest/client');
11
+ const { reproMiddleware, __reproTestHooks } = require('../dist');
12
+
13
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14
+
15
+ async function waitFor(predicate, timeoutMs = 2000) {
16
+ const deadline = Date.now() + timeoutMs;
17
+ while (Date.now() < deadline) {
18
+ if (predicate()) return;
19
+ await sleep(20);
20
+ }
21
+ assert(predicate(), 'timed out waiting for expected ingest payload');
22
+ }
23
+
24
+ function flattenEvents(posts) {
25
+ return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
26
+ }
27
+
28
+ async function drainSdk() {
29
+ await __reproTestHooks.drainSdkBackgroundQueueForTest();
30
+ await flushIngestQueue();
31
+ }
32
+
33
+ async function withCapturedFetch(fn) {
34
+ const capturedBodies = [];
35
+ const originalFetch = global.fetch;
36
+ global.fetch = async (_url, init) => {
37
+ capturedBodies.push(JSON.parse(String(init?.body ?? '{}')));
38
+ return {
39
+ ok: true,
40
+ status: 202,
41
+ json: async () => ({ ok: true }),
42
+ text: async () => '{"ok":true}',
43
+ };
44
+ };
45
+
46
+ try {
47
+ await fn(capturedBodies);
48
+ } finally {
49
+ await drainSdk();
50
+ global.fetch = originalFetch;
51
+ }
52
+ }
53
+
54
+ function makeConfig(sessionId, actionId) {
55
+ return {
56
+ tenantId: 'TENANT_test',
57
+ appId: 'APP_test',
58
+ appSecret: 'secret',
59
+ captureHeaders: false,
60
+ privacy: { environment: 'dev' },
61
+ ingestBase: 'http://127.0.0.1:65535',
62
+ resolveContext: () => ({ sid: sessionId, aid: actionId }),
63
+ };
64
+ }
65
+
66
+ function makeReqRes(sessionId, actionId) {
67
+ const req = new EventEmitter();
68
+ req.method = 'GET';
69
+ req.url = '/native-mongodb';
70
+ req.originalUrl = '/native-mongodb';
71
+ req.headers = {
72
+ 'x-bug-session-id': sessionId,
73
+ 'x-bug-action-id': actionId,
74
+ };
75
+ req.query = {};
76
+ req.params = {};
77
+ req.body = undefined;
78
+
79
+ const res = new EventEmitter();
80
+ res.statusCode = 200;
81
+ res.getHeader = () => undefined;
82
+ res.getHeaders = () => ({});
83
+ res.json = function json(body) {
84
+ this.emit('finish');
85
+ return body;
86
+ };
87
+ res.send = function send(body) {
88
+ this.emit('finish');
89
+ return body;
90
+ };
91
+ res.write = function write() {
92
+ return true;
93
+ };
94
+ res.end = function end() {
95
+ this.emit('finish');
96
+ return this;
97
+ };
98
+
99
+ return { req, res };
100
+ }
101
+
102
+ async function runCapturedRequest(cfg, sessionId, actionId, fn) {
103
+ const middleware = reproMiddleware(cfg);
104
+ const { req, res } = makeReqRes(sessionId, actionId);
105
+ await new Promise((resolve, reject) => {
106
+ middleware(req, res, (err) => {
107
+ if (err) {
108
+ reject(err);
109
+ return;
110
+ }
111
+ Promise.resolve()
112
+ .then(fn)
113
+ .then(resolve, reject);
114
+ });
115
+ });
116
+ res.end();
117
+ }
118
+
119
+ function dbChanges(events) {
120
+ return events
121
+ .filter((event) => event.event_type === 'db_change')
122
+ .flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
123
+ }
124
+
125
+ async function testNativeInsertOneIsCaptured() {
126
+ await withCapturedFetch(async (capturedBodies) => {
127
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
128
+ const sessionId = 'S_mongo_insert';
129
+ const actionId = 'A_mongo_insert';
130
+ const cfg = makeConfig(sessionId, actionId);
131
+
132
+ class FakeCollection {
133
+ constructor() {
134
+ this.collectionName = 'subjects';
135
+ this.dbName = 'clinical';
136
+ }
137
+
138
+ async insertOne(doc, options) {
139
+ assert.strictEqual(options.session.id, 'native-session-1');
140
+ doc._id = doc._id ?? 'subject-1';
141
+ return { acknowledged: true, insertedId: doc._id };
142
+ }
143
+ }
144
+
145
+ __reproTestHooks.patchMongoCollectionPrototypeForTest(FakeCollection.prototype, cfg);
146
+ const collection = new FakeCollection();
147
+
148
+ await runCapturedRequest(cfg, sessionId, actionId, async () => {
149
+ const result = await collection.insertOne(
150
+ { subjectNumber: '10013038', status: 'screened' },
151
+ {
152
+ session: {
153
+ id: 'native-session-1',
154
+ hasEnded: false,
155
+ inTransaction: () => true,
156
+ },
157
+ },
158
+ );
159
+ assert.deepStrictEqual(result, { acknowledged: true, insertedId: 'subject-1' });
160
+ });
161
+
162
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
163
+ await drainSdk();
164
+
165
+ const [change] = dbChanges(flattenEvents(capturedBodies));
166
+ assert.strictEqual(change.type, 'mongo');
167
+ assert.strictEqual(change.collection, 'subjects');
168
+ assert.strictEqual(change.op, 'insertOne');
169
+ assert.strictEqual(change.pk._id, 'subject-1');
170
+ assert.strictEqual(change.query.driver, 'mongodb');
171
+ assert.strictEqual(change.query.db, 'clinical');
172
+ assert.strictEqual(change.query.doc.subjectNumber, '10013038');
173
+ assert.strictEqual(change.query.options.session.__kind, 'mongodb_client_session');
174
+ assert.strictEqual(change.query.options.session.id, 'native-session-1');
175
+ assert.strictEqual(change.query.options.session.inTransaction, true);
176
+ assert.strictEqual(change.after.subjectNumber, '10013038');
177
+ assert.strictEqual(change.resultMeta.insertedId, 'subject-1');
178
+ assert.strictEqual(change.resultMeta.acknowledged, true);
179
+ });
180
+ }
181
+
182
+ async function testNativeUpdateOneUpsertIsCaptured() {
183
+ await withCapturedFetch(async (capturedBodies) => {
184
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
185
+ const sessionId = 'S_mongo_update';
186
+ const actionId = 'A_mongo_update';
187
+ const cfg = makeConfig(sessionId, actionId);
188
+
189
+ class FakeCollection {
190
+ constructor() {
191
+ this.collectionName = 'subjects';
192
+ this.dbName = 'clinical';
193
+ }
194
+
195
+ async updateOne() {
196
+ return {
197
+ acknowledged: true,
198
+ matchedCount: 0,
199
+ modifiedCount: 0,
200
+ upsertedId: 'subject-2',
201
+ };
202
+ }
203
+ }
204
+
205
+ __reproTestHooks.patchMongoCollectionPrototypeForTest(FakeCollection.prototype, cfg);
206
+ const collection = new FakeCollection();
207
+
208
+ await runCapturedRequest(cfg, sessionId, actionId, async () => {
209
+ await collection.updateOne(
210
+ { _id: 'subject-2' },
211
+ { $set: { status: 'randomized' } },
212
+ { upsert: true },
213
+ );
214
+ });
215
+
216
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
217
+ await drainSdk();
218
+
219
+ const [change] = dbChanges(flattenEvents(capturedBodies));
220
+ assert.strictEqual(change.collection, 'subjects');
221
+ assert.strictEqual(change.op, 'updateOne');
222
+ assert.strictEqual(change.pk._id, 'subject-2');
223
+ assert.strictEqual(change.query.filter._id, 'subject-2');
224
+ assert.strictEqual(change.query.update.$set.status, 'randomized');
225
+ assert.strictEqual(change.query.options.upsert, true);
226
+ assert.strictEqual(change.resultMeta.matched, 0);
227
+ assert.strictEqual(change.resultMeta.modified, 0);
228
+ assert.strictEqual(change.resultMeta.upsertedId, 'subject-2');
229
+ });
230
+ }
231
+
232
+ async function testNativeFindCursorToArrayIsCaptured() {
233
+ await withCapturedFetch(async (capturedBodies) => {
234
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
235
+ const sessionId = 'S_mongo_find';
236
+ const actionId = 'A_mongo_find';
237
+ const cfg = makeConfig(sessionId, actionId);
238
+
239
+ class FakeCollection {
240
+ constructor() {
241
+ this.collectionName = 'subjects';
242
+ this.dbName = 'clinical';
243
+ }
244
+
245
+ find() {
246
+ return {
247
+ async toArray() {
248
+ return [
249
+ { _id: 'subject-3', status: 'screened' },
250
+ { _id: 'subject-4', status: 'randomized' },
251
+ ];
252
+ },
253
+ };
254
+ }
255
+ }
256
+
257
+ __reproTestHooks.patchMongoCollectionPrototypeForTest(FakeCollection.prototype, cfg);
258
+ const collection = new FakeCollection();
259
+
260
+ await runCapturedRequest(cfg, sessionId, actionId, async () => {
261
+ const docs = await collection.find({ siteId: 'site-1001' }, { projection: { status: 1 } }).toArray();
262
+ assert.strictEqual(docs.length, 2);
263
+ });
264
+
265
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
266
+ await drainSdk();
267
+
268
+ const [change] = dbChanges(flattenEvents(capturedBodies));
269
+ assert.strictEqual(change.collection, 'subjects');
270
+ assert.strictEqual(change.op, 'find');
271
+ assert.strictEqual(change.query.filter.siteId, 'site-1001');
272
+ assert.deepStrictEqual(change.query.options.projection, { status: 1 });
273
+ assert.strictEqual(change.query.cursorMethod, 'toArray');
274
+ assert.strictEqual(change.resultMeta.docsCount, 2);
275
+ assert.strictEqual(change.resultMeta.result[0]._id, 'subject-3');
276
+ });
277
+ }
278
+
279
+ async function testNativeOperationFailureIsCapturedAndRethrown() {
280
+ await withCapturedFetch(async (capturedBodies) => {
281
+ __reproTestHooks.resetSdkBackgroundQueuesForTest();
282
+ const sessionId = 'S_mongo_error';
283
+ const actionId = 'A_mongo_error';
284
+ const cfg = makeConfig(sessionId, actionId);
285
+
286
+ class FakeCollection {
287
+ constructor() {
288
+ this.collectionName = 'subjects';
289
+ this.dbName = 'clinical';
290
+ }
291
+
292
+ async deleteOne() {
293
+ const err = new Error('delete failed');
294
+ err.code = 12345;
295
+ err.codeName = 'DeleteFailed';
296
+ throw err;
297
+ }
298
+ }
299
+
300
+ __reproTestHooks.patchMongoCollectionPrototypeForTest(FakeCollection.prototype, cfg);
301
+ const collection = new FakeCollection();
302
+
303
+ await assert.rejects(
304
+ () => runCapturedRequest(cfg, sessionId, actionId, async () => {
305
+ await collection.deleteOne({ _id: 'subject-5' });
306
+ }),
307
+ /delete failed/,
308
+ );
309
+
310
+ await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
311
+ await drainSdk();
312
+
313
+ const [change] = dbChanges(flattenEvents(capturedBodies));
314
+ assert.strictEqual(change.collection, 'subjects');
315
+ assert.strictEqual(change.op, 'deleteOne');
316
+ assert.strictEqual(change.query.filter._id, 'subject-5');
317
+ assert.strictEqual(change.error.message, 'delete failed');
318
+ assert.strictEqual(change.error.code, 12345);
319
+ assert.strictEqual(change.error.codeName, 'DeleteFailed');
320
+ });
321
+ }
322
+
323
+ (async () => {
324
+ await testNativeInsertOneIsCaptured();
325
+ await testNativeUpdateOneUpsertIsCaptured();
326
+ await testNativeFindCursorToArrayIsCaptured();
327
+ await testNativeOperationFailureIsCapturedAndRethrown();
328
+ console.log('mongodb integration tests passed');
329
+ })().catch((err) => {
330
+ console.error(err);
331
+ process.exit(1);
332
+ });