@reproapp/node-sdk 0.0.31 → 0.0.33
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.d.ts +27 -0
- package/dist/index.js +235 -158
- package/package.json +3 -3
- package/src/index.ts +218 -149
- package/test/mongodb-integration.test.js +71 -1
- package/test/mongodb-mongoose-native-suppression.testcontainers.test.js +158 -0
- package/test/mongodb-suppression-cleanup.test.js +72 -0
|
@@ -0,0 +1,158 @@
|
|
|
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 mongoose = require('mongoose');
|
|
11
|
+
const { GenericContainer, Wait } = require('testcontainers');
|
|
12
|
+
|
|
13
|
+
const { flushIngestQueue } = require('../dist/ingest/client');
|
|
14
|
+
const { reproMiddleware, reproMongoosePlugin, __reproTestHooks } = require('../dist');
|
|
15
|
+
|
|
16
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
|
|
18
|
+
async function waitFor(predicate, timeoutMs = 5000) {
|
|
19
|
+
const deadline = Date.now() + timeoutMs;
|
|
20
|
+
while (Date.now() < deadline) {
|
|
21
|
+
if (predicate()) return;
|
|
22
|
+
await sleep(25);
|
|
23
|
+
}
|
|
24
|
+
assert(predicate(), 'timed out waiting for expected ingest payload');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function flattenEvents(posts) {
|
|
28
|
+
return posts.flatMap((body) => Array.isArray(body?.events) ? body.events : []);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dbChanges(events) {
|
|
32
|
+
return events
|
|
33
|
+
.filter((event) => event.event_type === 'db_change')
|
|
34
|
+
.flatMap((event) => Array.isArray(event.payload?.db) ? event.payload.db : []);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeReqRes(sessionId, actionId) {
|
|
38
|
+
const req = new EventEmitter();
|
|
39
|
+
req.method = 'POST';
|
|
40
|
+
req.url = '/subjects';
|
|
41
|
+
req.originalUrl = '/subjects';
|
|
42
|
+
req.headers = {
|
|
43
|
+
'x-bug-session-id': sessionId,
|
|
44
|
+
'x-bug-action-id': actionId,
|
|
45
|
+
};
|
|
46
|
+
req.query = {};
|
|
47
|
+
req.params = {};
|
|
48
|
+
|
|
49
|
+
const res = new EventEmitter();
|
|
50
|
+
res.statusCode = 200;
|
|
51
|
+
res.getHeader = () => undefined;
|
|
52
|
+
res.getHeaders = () => ({});
|
|
53
|
+
res.write = () => true;
|
|
54
|
+
res.end = function end() {
|
|
55
|
+
this.emit('finish');
|
|
56
|
+
return this;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return { req, res };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function runCapturedRequest(cfg, sessionId, actionId, fn) {
|
|
63
|
+
const middleware = reproMiddleware(cfg);
|
|
64
|
+
const { req, res } = makeReqRes(sessionId, actionId);
|
|
65
|
+
await new Promise((resolve, reject) => {
|
|
66
|
+
middleware(req, res, (err) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
reject(err);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
Promise.resolve().then(fn).then(resolve, reject);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
res.end();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function startMongo() {
|
|
78
|
+
if (process.env.REPRO_MONGO_TEST_URI) {
|
|
79
|
+
return {
|
|
80
|
+
uri: process.env.REPRO_MONGO_TEST_URI,
|
|
81
|
+
stop: async () => {},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const container = await new GenericContainer('mongo:7')
|
|
86
|
+
.withExposedPorts(27017)
|
|
87
|
+
.withTmpFs({ '/data/db': 'rw,noexec,nosuid,size=1g' })
|
|
88
|
+
.withCommand(['mongod', '--bind_ip_all', '--setParameter', 'diagnosticDataCollectionEnabled=false'])
|
|
89
|
+
.withWaitStrategy(Wait.forListeningPorts())
|
|
90
|
+
.start();
|
|
91
|
+
return {
|
|
92
|
+
uri: `mongodb://${container.getHost()}:${container.getMappedPort(27017)}/repro_mongoose_native_suppression`,
|
|
93
|
+
stop: () => container.stop(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const capturedBodies = [];
|
|
99
|
+
const originalFetch = global.fetch;
|
|
100
|
+
global.fetch = async (_url, init) => {
|
|
101
|
+
capturedBodies.push(JSON.parse(String(init?.body ?? '{}')));
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
status: 202,
|
|
105
|
+
json: async () => ({ ok: true }),
|
|
106
|
+
text: async () => '{"ok":true}',
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
let container;
|
|
111
|
+
let connection;
|
|
112
|
+
try {
|
|
113
|
+
__reproTestHooks.resetSdkBackgroundQueuesForTest();
|
|
114
|
+
container = await startMongo();
|
|
115
|
+
connection = await mongoose.createConnection(container.uri).asPromise();
|
|
116
|
+
|
|
117
|
+
const cfg = {
|
|
118
|
+
tenantId: 'TENANT_test',
|
|
119
|
+
appId: 'APP_test',
|
|
120
|
+
appSecret: 'secret',
|
|
121
|
+
captureHeaders: false,
|
|
122
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
123
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const schema = new mongoose.Schema({ subjectNumber: String, status: String });
|
|
127
|
+
reproMongoosePlugin(cfg)(schema);
|
|
128
|
+
const Subject = connection.model('Subject', schema, 'subjects');
|
|
129
|
+
|
|
130
|
+
await runCapturedRequest(cfg, 'S_mongoose_native_suppression', 'A_mongoose_native_suppression', async () => {
|
|
131
|
+
await Subject.create({ subjectNumber: '10013038', status: 'screened' });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await waitFor(() => dbChanges(flattenEvents(capturedBodies)).length === 1);
|
|
135
|
+
await __reproTestHooks.drainSdkBackgroundQueueForTest();
|
|
136
|
+
await flushIngestQueue();
|
|
137
|
+
|
|
138
|
+
const changes = dbChanges(flattenEvents(capturedBodies));
|
|
139
|
+
assert.strictEqual(changes.length, 1);
|
|
140
|
+
assert.strictEqual(changes[0].type, 'mongo');
|
|
141
|
+
assert.strictEqual(changes[0].collection, 'subjects');
|
|
142
|
+
assert.strictEqual(changes[0].op, 'insert');
|
|
143
|
+
assert.strictEqual(changes[0].after.subjectNumber, '10013038');
|
|
144
|
+
|
|
145
|
+
console.log('mongodb mongoose native suppression testcontainers OK');
|
|
146
|
+
} finally {
|
|
147
|
+
await __reproTestHooks.drainSdkBackgroundQueueForTest().catch(() => {});
|
|
148
|
+
await flushIngestQueue().catch(() => {});
|
|
149
|
+
global.fetch = originalFetch;
|
|
150
|
+
if (connection) await connection.close().catch(() => {});
|
|
151
|
+
if (container) await container.stop().catch(() => {});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
main().catch((error) => {
|
|
156
|
+
console.error(error);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const mongoose = require('mongoose');
|
|
3
|
+
const { reproMongoosePlugin, __reproTestHooks } = require('../dist');
|
|
4
|
+
|
|
5
|
+
function execPre(hooks, name, context) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
hooks.execPre(name, context, [], (error) => (error ? reject(error) : resolve()));
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function execPost(hooks, name, context, args = []) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
hooks.execPost(name, context, args, {}, (error) => (error ? reject(error) : resolve()));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function testPostSaveEarlyReturnReleasesSuppression() {
|
|
18
|
+
const schema = new mongoose.Schema({ name: String });
|
|
19
|
+
reproMongoosePlugin({
|
|
20
|
+
tenantId: 'TENANT_test',
|
|
21
|
+
appId: 'APP_test',
|
|
22
|
+
appSecret: 'secret',
|
|
23
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
24
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
25
|
+
})(schema);
|
|
26
|
+
const Model = mongoose.model(`ReproSuppressionCleanup${Date.now()}`, schema, 'subjects');
|
|
27
|
+
const doc = new Model({ name: 'subject' });
|
|
28
|
+
|
|
29
|
+
await __reproTestHooks.runWithRequestContextForTest({ sid: 'S_cleanup', aid: 'A_cleanup' }, async () => {
|
|
30
|
+
await execPre(schema.s.hooks, 'save', doc);
|
|
31
|
+
assert.equal(__reproTestHooks.getNativeMongoSuppressionCountForTest(), 1);
|
|
32
|
+
|
|
33
|
+
// No rid/span is present, so the post hook exits through shouldCaptureDbSpan(false).
|
|
34
|
+
await execPost(schema.s.hooks, 'save', doc, [doc]);
|
|
35
|
+
assert.equal(__reproTestHooks.getNativeMongoSuppressionCountForTest(), 0);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function testPostSaveOutsideOriginalAlsReleasesOwnerStoreSuppression() {
|
|
40
|
+
const schema = new mongoose.Schema({ name: String });
|
|
41
|
+
reproMongoosePlugin({
|
|
42
|
+
tenantId: 'TENANT_test',
|
|
43
|
+
appId: 'APP_test',
|
|
44
|
+
appSecret: 'secret',
|
|
45
|
+
privacy: { environment: 'dev', captureRaw: true },
|
|
46
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
47
|
+
})(schema);
|
|
48
|
+
const Model = mongoose.model(`ReproSuppressionOwnerStore${Date.now()}`, schema, 'subjects');
|
|
49
|
+
const doc = new Model({ name: 'subject' });
|
|
50
|
+
|
|
51
|
+
let token;
|
|
52
|
+
await __reproTestHooks.runWithRequestContextForTest({ sid: 'S_owner', aid: 'A_owner', rid: 'R_owner' }, async () => {
|
|
53
|
+
await execPre(schema.s.hooks, 'save', doc);
|
|
54
|
+
assert.equal(__reproTestHooks.getNativeMongoSuppressionCountForTest(), 1);
|
|
55
|
+
token = doc.__repro_native_mongo_suppression;
|
|
56
|
+
assert.equal(token.ownerStore.nativeMongoSuppressions.length, 1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await execPost(schema.s.hooks, 'save', doc, [doc]);
|
|
60
|
+
assert.equal(token.ownerStore, undefined);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
await testPostSaveEarlyReturnReleasesSuppression();
|
|
65
|
+
await testPostSaveOutsideOriginalAlsReleasesOwnerStoreSuppression();
|
|
66
|
+
console.log('mongodb suppression cleanup OK');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch((error) => {
|
|
70
|
+
console.error(error);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|