@reproapp/node-sdk 0.0.37 → 0.0.38
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 +41 -2
- package/package.json +1 -1
- package/src/index.ts +51 -2
- package/test/mongodb-integration.test.js +164 -0
package/dist/index.js
CHANGED
|
@@ -4092,6 +4092,45 @@ async function maybeCaptureDbValueAsync(params, sink) {
|
|
|
4092
4092
|
}
|
|
4093
4093
|
return result.captureRef;
|
|
4094
4094
|
}
|
|
4095
|
+
function pushUniqueCaptureCandidate(candidates, value) {
|
|
4096
|
+
if (value === undefined)
|
|
4097
|
+
return;
|
|
4098
|
+
if (candidates.some((candidate) => candidate === value))
|
|
4099
|
+
return;
|
|
4100
|
+
candidates.push(value);
|
|
4101
|
+
}
|
|
4102
|
+
function buildResultMetaFullValueCandidates(resultMetaRaw, resultMetaSource) {
|
|
4103
|
+
const candidates = [];
|
|
4104
|
+
pushUniqueCaptureCandidate(candidates, resultMetaRaw);
|
|
4105
|
+
if (resultMetaSource &&
|
|
4106
|
+
typeof resultMetaSource === 'object' &&
|
|
4107
|
+
Object.prototype.hasOwnProperty.call(resultMetaSource, 'result')) {
|
|
4108
|
+
pushUniqueCaptureCandidate(candidates, resultMetaSource.result);
|
|
4109
|
+
}
|
|
4110
|
+
pushUniqueCaptureCandidate(candidates, resultMetaSource);
|
|
4111
|
+
return candidates;
|
|
4112
|
+
}
|
|
4113
|
+
async function maybeCaptureDbValueFromCandidatesAsync(params, sink) {
|
|
4114
|
+
let skippedRef;
|
|
4115
|
+
for (const rawValue of params.rawValues) {
|
|
4116
|
+
const result = await createCapturedValueEntryAsync({
|
|
4117
|
+
target: params.target,
|
|
4118
|
+
rawValue,
|
|
4119
|
+
previewValue: params.previewValue,
|
|
4120
|
+
capture: params.capture,
|
|
4121
|
+
});
|
|
4122
|
+
if (!result)
|
|
4123
|
+
continue;
|
|
4124
|
+
if (result.entry) {
|
|
4125
|
+
sink.push(result.entry);
|
|
4126
|
+
return result.captureRef;
|
|
4127
|
+
}
|
|
4128
|
+
if (!skippedRef) {
|
|
4129
|
+
skippedRef = result.captureRef;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
return skippedRef;
|
|
4133
|
+
}
|
|
4095
4134
|
function reproMiddleware(cfg) {
|
|
4096
4135
|
const masking = normalizeMaskingConfig(cfg.masking);
|
|
4097
4136
|
patchMongoDb(cfg);
|
|
@@ -5285,9 +5324,9 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
|
|
|
5285
5324
|
previewValue: queryPreview,
|
|
5286
5325
|
capture,
|
|
5287
5326
|
}, dbValues);
|
|
5288
|
-
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await
|
|
5327
|
+
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
|
|
5289
5328
|
target: 'db.resultMeta',
|
|
5290
|
-
|
|
5329
|
+
rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
|
|
5291
5330
|
previewValue: resultMetaPreview,
|
|
5292
5331
|
capture,
|
|
5293
5332
|
}, dbValues);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -4955,6 +4955,55 @@ async function maybeCaptureDbValueAsync(
|
|
|
4955
4955
|
return result.captureRef;
|
|
4956
4956
|
}
|
|
4957
4957
|
|
|
4958
|
+
function pushUniqueCaptureCandidate(candidates: any[], value: any): void {
|
|
4959
|
+
if (value === undefined) return;
|
|
4960
|
+
if (candidates.some((candidate) => candidate === value)) return;
|
|
4961
|
+
candidates.push(value);
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4964
|
+
function buildResultMetaFullValueCandidates(resultMetaRaw: any, resultMetaSource: any): any[] {
|
|
4965
|
+
const candidates: any[] = [];
|
|
4966
|
+
pushUniqueCaptureCandidate(candidates, resultMetaRaw);
|
|
4967
|
+
if (
|
|
4968
|
+
resultMetaSource &&
|
|
4969
|
+
typeof resultMetaSource === 'object' &&
|
|
4970
|
+
Object.prototype.hasOwnProperty.call(resultMetaSource, 'result')
|
|
4971
|
+
) {
|
|
4972
|
+
pushUniqueCaptureCandidate(candidates, resultMetaSource.result);
|
|
4973
|
+
}
|
|
4974
|
+
pushUniqueCaptureCandidate(candidates, resultMetaSource);
|
|
4975
|
+
return candidates;
|
|
4976
|
+
}
|
|
4977
|
+
|
|
4978
|
+
async function maybeCaptureDbValueFromCandidatesAsync(
|
|
4979
|
+
params: {
|
|
4980
|
+
target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error';
|
|
4981
|
+
rawValues: any[];
|
|
4982
|
+
previewValue: any;
|
|
4983
|
+
capture: FullValueCaptureContext;
|
|
4984
|
+
},
|
|
4985
|
+
sink: TraceValueBatchEntry[],
|
|
4986
|
+
): Promise<TraceValueCaptureRef | undefined> {
|
|
4987
|
+
let skippedRef: TraceValueCaptureRef | undefined;
|
|
4988
|
+
for (const rawValue of params.rawValues) {
|
|
4989
|
+
const result = await createCapturedValueEntryAsync({
|
|
4990
|
+
target: params.target,
|
|
4991
|
+
rawValue,
|
|
4992
|
+
previewValue: params.previewValue,
|
|
4993
|
+
capture: params.capture,
|
|
4994
|
+
});
|
|
4995
|
+
if (!result) continue;
|
|
4996
|
+
if (result.entry) {
|
|
4997
|
+
sink.push(result.entry);
|
|
4998
|
+
return result.captureRef;
|
|
4999
|
+
}
|
|
5000
|
+
if (!skippedRef) {
|
|
5001
|
+
skippedRef = result.captureRef;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
return skippedRef;
|
|
5005
|
+
}
|
|
5006
|
+
|
|
4958
5007
|
// ===================================================================
|
|
4959
5008
|
// reproMiddleware — unchanged behavior + passive per-request trace
|
|
4960
5009
|
// ===================================================================
|
|
@@ -6241,9 +6290,9 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
|
|
|
6241
6290
|
previewValue: queryPreview,
|
|
6242
6291
|
capture,
|
|
6243
6292
|
}, dbValues);
|
|
6244
|
-
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await
|
|
6293
|
+
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
|
|
6245
6294
|
target: 'db.resultMeta',
|
|
6246
|
-
|
|
6295
|
+
rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
|
|
6247
6296
|
previewValue: resultMetaPreview,
|
|
6248
6297
|
capture,
|
|
6249
6298
|
}, dbValues);
|
|
@@ -133,12 +133,24 @@ function execPre(hooks, name, context) {
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
function execPost(hooks, name, context, args = []) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
hooks.execPost(name, context, args, {}, (error) => (error ? reject(error) : resolve()));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
function dbChanges(events) {
|
|
137
143
|
return events
|
|
138
144
|
.filter((event) => event.event_type === 'db_change')
|
|
139
145
|
.flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
|
|
140
146
|
}
|
|
141
147
|
|
|
148
|
+
function dbValueEntries(events) {
|
|
149
|
+
return events
|
|
150
|
+
.filter((event) => event.event_type === 'db_change')
|
|
151
|
+
.flatMap((event) => Array.isArray(event.payload?.dbValues) ? event.payload.dbValues : []);
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
function requestCaptureManifests(events) {
|
|
143
155
|
return events
|
|
144
156
|
.filter((event) => event.event_type === 'request_capture_manifest')
|
|
@@ -146,6 +158,81 @@ function requestCaptureManifests(events) {
|
|
|
146
158
|
.filter(Boolean);
|
|
147
159
|
}
|
|
148
160
|
|
|
161
|
+
function makeNotificationModuleDocument() {
|
|
162
|
+
const notificationPreferences = Array.from({ length: 24 }, (_, index) => ({
|
|
163
|
+
trigger: 'SubjectScreened',
|
|
164
|
+
enabled: true,
|
|
165
|
+
channels: [
|
|
166
|
+
{
|
|
167
|
+
type: 'email',
|
|
168
|
+
template: {
|
|
169
|
+
subject: `Participant Screening ${index}`,
|
|
170
|
+
html: `<p>Screening notification ${index}</p>`,
|
|
171
|
+
},
|
|
172
|
+
recipients: {
|
|
173
|
+
to: ['noreply@korioclinical.com'],
|
|
174
|
+
cc: [
|
|
175
|
+
`pobrien+dev-conf-csm${index}@korioclinical.com`,
|
|
176
|
+
`mnebot+site-monitor${index}@korioclinical.com`,
|
|
177
|
+
],
|
|
178
|
+
bcc: [`audit-${index}@korioclinical.com`],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
}));
|
|
183
|
+
const notificationTrigger = Array.from({ length: 24 }, (_, index) => ({
|
|
184
|
+
active: true,
|
|
185
|
+
event: 'SubjectScreened',
|
|
186
|
+
visitType: 'Screening',
|
|
187
|
+
subjectType: 'Participant',
|
|
188
|
+
rules: [
|
|
189
|
+
{
|
|
190
|
+
condition: {
|
|
191
|
+
path: 'subject.subjectStatusOption',
|
|
192
|
+
operator: 'equals',
|
|
193
|
+
value: 'Screened',
|
|
194
|
+
},
|
|
195
|
+
action: {
|
|
196
|
+
module: 'NotificationGeneratorService',
|
|
197
|
+
method: 'SubjectScreened',
|
|
198
|
+
order: index,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
__t: 'NotificationModule',
|
|
206
|
+
_id: '69e77c77ab541cfac917b37a',
|
|
207
|
+
active: true,
|
|
208
|
+
environment: 'dev-configure-pulmovant',
|
|
209
|
+
notificationPreferences,
|
|
210
|
+
notification_trigger: notificationTrigger,
|
|
211
|
+
protocol: {
|
|
212
|
+
id: '69e77c77ab541cfac917b36b',
|
|
213
|
+
name: 'RVT-2301-301',
|
|
214
|
+
},
|
|
215
|
+
schema_version: 1,
|
|
216
|
+
sponsor_name: 'Pulmovant',
|
|
217
|
+
study_name: 'RVT-2301-301',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
class NotificationModuleHostDocument {
|
|
222
|
+
constructor(value) {
|
|
223
|
+
this.$__ = {};
|
|
224
|
+
this.value = value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
toObject() {
|
|
228
|
+
return JSON.parse(JSON.stringify(this.value));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
toJSON() {
|
|
232
|
+
return this.toObject();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
149
236
|
async function testMongooseSuppressionDoesNotHideExplicitNativeInsertOne() {
|
|
150
237
|
await withCapturedFetch(async (capturedBodies) => {
|
|
151
238
|
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
@@ -477,6 +564,82 @@ async function testClientScreenNativeMongoInsertOneShapeIsCaptured() {
|
|
|
477
564
|
});
|
|
478
565
|
}
|
|
479
566
|
|
|
567
|
+
async function testLargeMongooseFindOneResultStoresFullResultMetaSidecar() {
|
|
568
|
+
await withCapturedFetch(async (capturedBodies) => {
|
|
569
|
+
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
570
|
+
const sessionId = 'S_mongoose_large_result_meta';
|
|
571
|
+
const actionId = 'A_mongoose_large_result_meta';
|
|
572
|
+
const cfg = {
|
|
573
|
+
...makeConfig(sessionId, actionId),
|
|
574
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const schema = new mongoose.Schema({}, { strict: false });
|
|
578
|
+
reproMongoosePlugin(cfg)(schema);
|
|
579
|
+
|
|
580
|
+
const notificationModule = makeNotificationModuleDocument();
|
|
581
|
+
const rawResult = new NotificationModuleHostDocument(notificationModule);
|
|
582
|
+
const queryContext = {
|
|
583
|
+
model: {
|
|
584
|
+
collection: {
|
|
585
|
+
name: 'configStudy',
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
getFilter: () => ({
|
|
589
|
+
__t: 'NotificationModule',
|
|
590
|
+
active: true,
|
|
591
|
+
protocol: {
|
|
592
|
+
id: '69e77c77ab541cfac917b36b',
|
|
593
|
+
},
|
|
594
|
+
}),
|
|
595
|
+
getUpdate: () => undefined,
|
|
596
|
+
projection: () => undefined,
|
|
597
|
+
getOptions: () => ({ lean: false }),
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
await runCapturedRequest(cfg, sessionId, actionId, async () => {
|
|
601
|
+
await execPre(schema.s.hooks, 'findOne', queryContext);
|
|
602
|
+
await execPost(schema.s.hooks, 'findOne', queryContext, [rawResult]);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
|
|
606
|
+
await waitFor(() => dbValueEntries(flattenEvents(capturedBodies)).some((entry) => entry.target === 'db.resultMeta'));
|
|
607
|
+
await drainSdk();
|
|
608
|
+
|
|
609
|
+
const [change] = dbChanges(flattenEvents(capturedBodies));
|
|
610
|
+
const values = dbValueEntries(flattenEvents(capturedBodies));
|
|
611
|
+
const resultMetaValue = values.find((entry) => entry.id === change.resultMetaValueCapture?.valueId);
|
|
612
|
+
|
|
613
|
+
assert.strictEqual(change.type, 'mongo');
|
|
614
|
+
assert.strictEqual(change.collection, 'configStudy');
|
|
615
|
+
assert.strictEqual(change.op, 'findOne');
|
|
616
|
+
assert.strictEqual(change.query.filter.__t, 'NotificationModule');
|
|
617
|
+
assert.strictEqual(change.resultMeta.docsCount, 1);
|
|
618
|
+
assert.deepStrictEqual(change.resultMeta.result.notificationPreferences[0], {
|
|
619
|
+
__kind: 'omitted',
|
|
620
|
+
reason: 'depth>3',
|
|
621
|
+
});
|
|
622
|
+
assert.deepStrictEqual(change.resultMeta.result.notification_trigger[0], {
|
|
623
|
+
__kind: 'omitted',
|
|
624
|
+
reason: 'depth>3',
|
|
625
|
+
});
|
|
626
|
+
assert.strictEqual(change.resultMetaValueCapture.status, 'stored');
|
|
627
|
+
assert.ok(change.resultMetaValueCapture.valueId, 'expected resultMeta value id');
|
|
628
|
+
assert.ok(resultMetaValue, 'expected matching db.resultMeta sidecar');
|
|
629
|
+
assert.strictEqual(resultMetaValue.target, 'db.resultMeta');
|
|
630
|
+
assert.strictEqual(resultMetaValue.value.__t, 'NotificationModule');
|
|
631
|
+
assert.strictEqual(resultMetaValue.value.notificationPreferences.length, 24);
|
|
632
|
+
assert.strictEqual(
|
|
633
|
+
resultMetaValue.value.notificationPreferences[0].channels[0].recipients.cc[0],
|
|
634
|
+
'pobrien+dev-conf-csm0@korioclinical.com',
|
|
635
|
+
);
|
|
636
|
+
assert.strictEqual(
|
|
637
|
+
resultMetaValue.value.notification_trigger[0].rules[0].action.method,
|
|
638
|
+
'SubjectScreened',
|
|
639
|
+
);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
480
643
|
async function testNativeInsertOneIsCaptured() {
|
|
481
644
|
await withCapturedFetch(async (capturedBodies) => {
|
|
482
645
|
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
@@ -742,6 +905,7 @@ async function testNativeOperationFailureIsCapturedAndRethrown() {
|
|
|
742
905
|
await testReproMiddlewarePatchesPreloadedNativeMongoClientInsertOne();
|
|
743
906
|
await testMongoClientDbCollectionChainIsPatchedWithoutCollectionExport();
|
|
744
907
|
await testClientScreenNativeMongoInsertOneShapeIsCaptured();
|
|
908
|
+
await testLargeMongooseFindOneResultStoresFullResultMetaSidecar();
|
|
745
909
|
await testNativeInsertOneIsCaptured();
|
|
746
910
|
await testCaptureRawNativeInsertOneUsesJsonSafeTransportValues();
|
|
747
911
|
await testNativeUpdateOneUpsertIsCaptured();
|