@terreno/api 0.14.0 → 0.14.2
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/__tests__/versionCheckPlugin.test.js +119 -0
- package/dist/betterAuthSetup.js +13 -3
- package/dist/config.js +12 -3
- package/dist/errors.js +5 -2
- package/dist/example.js +3 -0
- package/dist/expressServer.test.js +231 -0
- package/dist/realtime/changeStreamWatcher.js +6 -2
- package/dist/realtime/realtime.test.js +1785 -0
- package/dist/requestContext.test.js +143 -0
- package/package.json +1 -1
- package/src/__tests__/versionCheckPlugin.test.ts +81 -0
- package/src/betterAuthSetup.ts +13 -3
- package/src/config.ts +13 -3
- package/src/errors.ts +24 -18
- package/src/example.ts +3 -0
- package/src/expressServer.test.ts +176 -0
- package/src/realtime/changeStreamWatcher.ts +6 -2
- package/src/realtime/realtime.test.ts +1415 -1
- package/src/requestContext.test.ts +125 -0
|
@@ -125,12 +125,24 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
125
125
|
}
|
|
126
126
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
127
127
|
};
|
|
128
|
+
var __values = (this && this.__values) || function(o) {
|
|
129
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
130
|
+
if (m) return m.call(o);
|
|
131
|
+
if (o && typeof o.length === "number") return {
|
|
132
|
+
next: function () {
|
|
133
|
+
if (o && i >= o.length) o = void 0;
|
|
134
|
+
return { value: o && o[i++], done: !o };
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
138
|
+
};
|
|
128
139
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
129
140
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
130
141
|
};
|
|
131
142
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
132
143
|
var bun_test_1 = require("bun:test");
|
|
133
144
|
var express_1 = __importDefault(require("express"));
|
|
145
|
+
var mongoose_1 = __importDefault(require("mongoose"));
|
|
134
146
|
var changeStreamWatcher_1 = require("./changeStreamWatcher");
|
|
135
147
|
var queryMatcher_1 = require("./queryMatcher");
|
|
136
148
|
var queryStore_1 = require("./queryStore");
|
|
@@ -2140,6 +2152,1779 @@ var createMockSocket = function (decodedToken) {
|
|
|
2140
2152
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2141
2153
|
// redactCredentials — Redis URL logging
|
|
2142
2154
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2156
|
+
// startChangeStreamWatcher / stopChangeStreamWatcher — MongoDB change stream
|
|
2157
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2158
|
+
(0, bun_test_1.describe)("startChangeStreamWatcher", function () {
|
|
2159
|
+
var originalDb;
|
|
2160
|
+
(0, bun_test_1.beforeEach)(function () {
|
|
2161
|
+
originalDb = mongoose_1.default.connection.db;
|
|
2162
|
+
(0, registry_1.clearRealtimeRegistry)();
|
|
2163
|
+
});
|
|
2164
|
+
(0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2165
|
+
var stop;
|
|
2166
|
+
return __generator(this, function (_a) {
|
|
2167
|
+
switch (_a.label) {
|
|
2168
|
+
case 0:
|
|
2169
|
+
mongoose_1.default.connection.db = originalDb;
|
|
2170
|
+
(0, registry_1.clearRealtimeRegistry)();
|
|
2171
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2172
|
+
case 1:
|
|
2173
|
+
stop = (_a.sent()).stopChangeStreamWatcher;
|
|
2174
|
+
return [4 /*yield*/, stop()];
|
|
2175
|
+
case 2:
|
|
2176
|
+
_a.sent();
|
|
2177
|
+
return [2 /*return*/];
|
|
2178
|
+
}
|
|
2179
|
+
});
|
|
2180
|
+
}); });
|
|
2181
|
+
var createMockChangeStream = function () {
|
|
2182
|
+
var listeners = new Map();
|
|
2183
|
+
return {
|
|
2184
|
+
close: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
2185
|
+
return [2 /*return*/];
|
|
2186
|
+
}); }); }),
|
|
2187
|
+
listeners: listeners,
|
|
2188
|
+
on: function (event, handler) {
|
|
2189
|
+
listeners.set(event, handler);
|
|
2190
|
+
return this;
|
|
2191
|
+
},
|
|
2192
|
+
trigger: function (event) {
|
|
2193
|
+
var args = [];
|
|
2194
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
2195
|
+
args[_i - 1] = arguments[_i];
|
|
2196
|
+
}
|
|
2197
|
+
var handler = listeners.get(event);
|
|
2198
|
+
if (handler) {
|
|
2199
|
+
handler.apply(void 0, __spreadArray([], __read(args), false));
|
|
2200
|
+
}
|
|
2201
|
+
},
|
|
2202
|
+
};
|
|
2203
|
+
};
|
|
2204
|
+
var createMockIo = function () {
|
|
2205
|
+
var rooms = new Map();
|
|
2206
|
+
var sockets = new Map();
|
|
2207
|
+
return {
|
|
2208
|
+
sockets: {
|
|
2209
|
+
adapter: { rooms: rooms },
|
|
2210
|
+
sockets: sockets,
|
|
2211
|
+
},
|
|
2212
|
+
to: function (_room) { return ({
|
|
2213
|
+
emit: function (_event, _data) { },
|
|
2214
|
+
}); },
|
|
2215
|
+
};
|
|
2216
|
+
};
|
|
2217
|
+
(0, bun_test_1.it)("initializes and registers change/error/close/end listeners", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2218
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2219
|
+
return __generator(this, function (_a) {
|
|
2220
|
+
switch (_a.label) {
|
|
2221
|
+
case 0:
|
|
2222
|
+
mockStream = createMockChangeStream();
|
|
2223
|
+
mockDb = {
|
|
2224
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2225
|
+
};
|
|
2226
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2227
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2228
|
+
case 1:
|
|
2229
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2230
|
+
io = createMockIo();
|
|
2231
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2232
|
+
(0, bun_test_1.expect)(mockDb.watch).toHaveBeenCalled();
|
|
2233
|
+
(0, bun_test_1.expect)(mockStream.listeners.has("change")).toBe(true);
|
|
2234
|
+
(0, bun_test_1.expect)(mockStream.listeners.has("error")).toBe(true);
|
|
2235
|
+
(0, bun_test_1.expect)(mockStream.listeners.has("close")).toBe(true);
|
|
2236
|
+
(0, bun_test_1.expect)(mockStream.listeners.has("end")).toBe(true);
|
|
2237
|
+
return [2 /*return*/];
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
}); });
|
|
2241
|
+
(0, bun_test_1.it)("throws when mongoose connection db is unavailable", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2242
|
+
var startChangeStreamWatcher, io;
|
|
2243
|
+
return __generator(this, function (_a) {
|
|
2244
|
+
switch (_a.label) {
|
|
2245
|
+
case 0:
|
|
2246
|
+
mongoose_1.default.connection.db = undefined;
|
|
2247
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2248
|
+
case 1:
|
|
2249
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2250
|
+
io = createMockIo();
|
|
2251
|
+
(0, bun_test_1.expect)(function () { return startChangeStreamWatcher(io); }).toThrow("MongoDB connection not available for change stream");
|
|
2252
|
+
return [2 /*return*/];
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2255
|
+
}); });
|
|
2256
|
+
(0, bun_test_1.it)("throws when watch returns null", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2257
|
+
var mockDb, startChangeStreamWatcher, io;
|
|
2258
|
+
return __generator(this, function (_a) {
|
|
2259
|
+
switch (_a.label) {
|
|
2260
|
+
case 0:
|
|
2261
|
+
mockDb = {
|
|
2262
|
+
watch: (0, bun_test_1.mock)(function () { return null; }),
|
|
2263
|
+
};
|
|
2264
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2265
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2266
|
+
case 1:
|
|
2267
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2268
|
+
io = createMockIo();
|
|
2269
|
+
(0, bun_test_1.expect)(function () { return startChangeStreamWatcher(io); }).toThrow("Failed to create change stream watcher");
|
|
2270
|
+
return [2 /*return*/];
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
}); });
|
|
2274
|
+
(0, bun_test_1.it)("handles change events for registered models", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2275
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2276
|
+
return __generator(this, function (_a) {
|
|
2277
|
+
switch (_a.label) {
|
|
2278
|
+
case 0:
|
|
2279
|
+
mockStream = createMockChangeStream();
|
|
2280
|
+
mockDb = {
|
|
2281
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2282
|
+
};
|
|
2283
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2284
|
+
(0, registry_1.registerRealtime)({
|
|
2285
|
+
collectionName: "todos",
|
|
2286
|
+
config: {
|
|
2287
|
+
methods: ["create", "update", "delete"],
|
|
2288
|
+
roomStrategy: "model",
|
|
2289
|
+
},
|
|
2290
|
+
modelName: "Todo",
|
|
2291
|
+
options: {
|
|
2292
|
+
permissions: {
|
|
2293
|
+
create: [function () { return true; }],
|
|
2294
|
+
delete: [function () { return true; }],
|
|
2295
|
+
list: [function () { return true; }],
|
|
2296
|
+
read: [function () { return true; }],
|
|
2297
|
+
update: [function () { return true; }],
|
|
2298
|
+
},
|
|
2299
|
+
},
|
|
2300
|
+
routePath: "/todos",
|
|
2301
|
+
});
|
|
2302
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2303
|
+
case 1:
|
|
2304
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2305
|
+
io = createMockIo();
|
|
2306
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2307
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2308
|
+
(0, bun_test_1.expect)(changeHandler).toBeDefined();
|
|
2309
|
+
return [4 /*yield*/, changeHandler({
|
|
2310
|
+
documentKey: { _id: "doc-1" },
|
|
2311
|
+
fullDocument: { _id: "doc-1", name: "Test Todo" },
|
|
2312
|
+
ns: { coll: "todos" },
|
|
2313
|
+
operationType: "insert",
|
|
2314
|
+
})];
|
|
2315
|
+
case 2:
|
|
2316
|
+
_a.sent();
|
|
2317
|
+
return [2 /*return*/];
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
}); });
|
|
2321
|
+
(0, bun_test_1.it)("skips events for unregistered collections", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2322
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2323
|
+
return __generator(this, function (_a) {
|
|
2324
|
+
switch (_a.label) {
|
|
2325
|
+
case 0:
|
|
2326
|
+
mockStream = createMockChangeStream();
|
|
2327
|
+
mockDb = {
|
|
2328
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2329
|
+
};
|
|
2330
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2331
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2332
|
+
case 1:
|
|
2333
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2334
|
+
io = createMockIo();
|
|
2335
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2336
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2337
|
+
// Trigger for an unregistered collection — should not throw
|
|
2338
|
+
return [4 /*yield*/, changeHandler({
|
|
2339
|
+
documentKey: { _id: "doc-1" },
|
|
2340
|
+
fullDocument: { _id: "doc-1" },
|
|
2341
|
+
ns: { coll: "unknown_collection" },
|
|
2342
|
+
operationType: "insert",
|
|
2343
|
+
})];
|
|
2344
|
+
case 2:
|
|
2345
|
+
// Trigger for an unregistered collection — should not throw
|
|
2346
|
+
_a.sent();
|
|
2347
|
+
return [2 /*return*/];
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
}); });
|
|
2351
|
+
(0, bun_test_1.it)("skips events when method is not enabled for the model", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2352
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2353
|
+
return __generator(this, function (_a) {
|
|
2354
|
+
switch (_a.label) {
|
|
2355
|
+
case 0:
|
|
2356
|
+
mockStream = createMockChangeStream();
|
|
2357
|
+
mockDb = {
|
|
2358
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2359
|
+
};
|
|
2360
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2361
|
+
(0, registry_1.registerRealtime)({
|
|
2362
|
+
collectionName: "todos",
|
|
2363
|
+
config: {
|
|
2364
|
+
methods: ["create"], // only create enabled
|
|
2365
|
+
roomStrategy: "model",
|
|
2366
|
+
},
|
|
2367
|
+
modelName: "Todo",
|
|
2368
|
+
options: {
|
|
2369
|
+
permissions: {
|
|
2370
|
+
create: [function () { return true; }],
|
|
2371
|
+
delete: [],
|
|
2372
|
+
list: [function () { return true; }],
|
|
2373
|
+
read: [function () { return true; }],
|
|
2374
|
+
update: [],
|
|
2375
|
+
},
|
|
2376
|
+
},
|
|
2377
|
+
routePath: "/todos",
|
|
2378
|
+
});
|
|
2379
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2380
|
+
case 1:
|
|
2381
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2382
|
+
io = createMockIo();
|
|
2383
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2384
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2385
|
+
// Update event should be skipped because "update" not in methods
|
|
2386
|
+
return [4 /*yield*/, changeHandler({
|
|
2387
|
+
documentKey: { _id: "doc-1" },
|
|
2388
|
+
fullDocument: { _id: "doc-1", name: "Updated" },
|
|
2389
|
+
ns: { coll: "todos" },
|
|
2390
|
+
operationType: "update",
|
|
2391
|
+
updateDescription: { updatedFields: { name: "Updated" } },
|
|
2392
|
+
})];
|
|
2393
|
+
case 2:
|
|
2394
|
+
// Update event should be skipped because "update" not in methods
|
|
2395
|
+
_a.sent();
|
|
2396
|
+
return [2 /*return*/];
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
}); });
|
|
2400
|
+
(0, bun_test_1.it)("handles delete events for owner-strategy models", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2401
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2402
|
+
return __generator(this, function (_a) {
|
|
2403
|
+
switch (_a.label) {
|
|
2404
|
+
case 0:
|
|
2405
|
+
mockStream = createMockChangeStream();
|
|
2406
|
+
mockDb = {
|
|
2407
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2408
|
+
};
|
|
2409
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2410
|
+
(0, registry_1.registerRealtime)({
|
|
2411
|
+
collectionName: "todos",
|
|
2412
|
+
config: {
|
|
2413
|
+
methods: ["create", "update", "delete"],
|
|
2414
|
+
roomStrategy: "owner",
|
|
2415
|
+
},
|
|
2416
|
+
modelName: "Todo",
|
|
2417
|
+
options: {
|
|
2418
|
+
permissions: {
|
|
2419
|
+
create: [function () { return true; }],
|
|
2420
|
+
delete: [function () { return true; }],
|
|
2421
|
+
list: [function () { return true; }],
|
|
2422
|
+
read: [function () { return true; }],
|
|
2423
|
+
update: [function () { return true; }],
|
|
2424
|
+
},
|
|
2425
|
+
},
|
|
2426
|
+
routePath: "/todos",
|
|
2427
|
+
});
|
|
2428
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2429
|
+
case 1:
|
|
2430
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2431
|
+
io = createMockIo();
|
|
2432
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2433
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2434
|
+
// Hard delete (no fullDocument)
|
|
2435
|
+
return [4 /*yield*/, changeHandler({
|
|
2436
|
+
documentKey: { _id: "doc-1" },
|
|
2437
|
+
ns: { coll: "todos" },
|
|
2438
|
+
operationType: "delete",
|
|
2439
|
+
})];
|
|
2440
|
+
case 2:
|
|
2441
|
+
// Hard delete (no fullDocument)
|
|
2442
|
+
_a.sent();
|
|
2443
|
+
return [2 /*return*/];
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
}); });
|
|
2447
|
+
(0, bun_test_1.it)("handles delete events for broadcast-strategy models", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2448
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2449
|
+
return __generator(this, function (_a) {
|
|
2450
|
+
switch (_a.label) {
|
|
2451
|
+
case 0:
|
|
2452
|
+
mockStream = createMockChangeStream();
|
|
2453
|
+
mockDb = {
|
|
2454
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2455
|
+
};
|
|
2456
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2457
|
+
(0, registry_1.registerRealtime)({
|
|
2458
|
+
collectionName: "broadcasts",
|
|
2459
|
+
config: {
|
|
2460
|
+
methods: ["create", "update", "delete"],
|
|
2461
|
+
roomStrategy: "broadcast",
|
|
2462
|
+
},
|
|
2463
|
+
modelName: "Broadcast",
|
|
2464
|
+
options: {
|
|
2465
|
+
permissions: {
|
|
2466
|
+
create: [function () { return true; }],
|
|
2467
|
+
delete: [function () { return true; }],
|
|
2468
|
+
list: [function () { return true; }],
|
|
2469
|
+
read: [function () { return true; }],
|
|
2470
|
+
update: [function () { return true; }],
|
|
2471
|
+
},
|
|
2472
|
+
},
|
|
2473
|
+
routePath: "/broadcasts",
|
|
2474
|
+
});
|
|
2475
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2476
|
+
case 1:
|
|
2477
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2478
|
+
io = createMockIo();
|
|
2479
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2480
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2481
|
+
// Hard delete for broadcast strategy
|
|
2482
|
+
return [4 /*yield*/, changeHandler({
|
|
2483
|
+
documentKey: { _id: "doc-1" },
|
|
2484
|
+
ns: { coll: "broadcasts" },
|
|
2485
|
+
operationType: "delete",
|
|
2486
|
+
})];
|
|
2487
|
+
case 2:
|
|
2488
|
+
// Hard delete for broadcast strategy
|
|
2489
|
+
_a.sent();
|
|
2490
|
+
return [2 /*return*/];
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
}); });
|
|
2494
|
+
(0, bun_test_1.it)("includes updatedFields in event for update operations", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2495
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2496
|
+
return __generator(this, function (_a) {
|
|
2497
|
+
switch (_a.label) {
|
|
2498
|
+
case 0:
|
|
2499
|
+
mockStream = createMockChangeStream();
|
|
2500
|
+
mockDb = {
|
|
2501
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2502
|
+
};
|
|
2503
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2504
|
+
(0, registry_1.registerRealtime)({
|
|
2505
|
+
collectionName: "todos",
|
|
2506
|
+
config: {
|
|
2507
|
+
methods: ["create", "update", "delete"],
|
|
2508
|
+
roomStrategy: "model",
|
|
2509
|
+
},
|
|
2510
|
+
modelName: "Todo",
|
|
2511
|
+
options: {
|
|
2512
|
+
permissions: {
|
|
2513
|
+
create: [function () { return true; }],
|
|
2514
|
+
delete: [function () { return true; }],
|
|
2515
|
+
list: [function () { return true; }],
|
|
2516
|
+
read: [function () { return true; }],
|
|
2517
|
+
update: [function () { return true; }],
|
|
2518
|
+
},
|
|
2519
|
+
},
|
|
2520
|
+
routePath: "/todos",
|
|
2521
|
+
});
|
|
2522
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2523
|
+
case 1:
|
|
2524
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2525
|
+
io = createMockIo();
|
|
2526
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2527
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2528
|
+
return [4 /*yield*/, changeHandler({
|
|
2529
|
+
documentKey: { _id: "doc-1" },
|
|
2530
|
+
fullDocument: { _id: "doc-1", name: "Updated", status: "done" },
|
|
2531
|
+
ns: { coll: "todos" },
|
|
2532
|
+
operationType: "update",
|
|
2533
|
+
updateDescription: { updatedFields: { name: "Updated", status: "done" } },
|
|
2534
|
+
})];
|
|
2535
|
+
case 2:
|
|
2536
|
+
_a.sent();
|
|
2537
|
+
return [2 /*return*/];
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
}); });
|
|
2541
|
+
(0, bun_test_1.it)("respects ignoredCollections config", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2542
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, pipeline, matchStage;
|
|
2543
|
+
return __generator(this, function (_a) {
|
|
2544
|
+
switch (_a.label) {
|
|
2545
|
+
case 0:
|
|
2546
|
+
mockStream = createMockChangeStream();
|
|
2547
|
+
mockDb = {
|
|
2548
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2549
|
+
};
|
|
2550
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2551
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2552
|
+
case 1:
|
|
2553
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2554
|
+
io = createMockIo();
|
|
2555
|
+
startChangeStreamWatcher(io, { ignoredCollections: ["audit_logs"] }, true);
|
|
2556
|
+
pipeline = mockDb.watch.mock.calls[0][0];
|
|
2557
|
+
matchStage = pipeline[0].$match;
|
|
2558
|
+
(0, bun_test_1.expect)(matchStage["ns.coll"].$nin).toContain("audit_logs");
|
|
2559
|
+
(0, bun_test_1.expect)(matchStage["ns.coll"].$nin).toContain("socketio");
|
|
2560
|
+
(0, bun_test_1.expect)(matchStage["ns.coll"].$nin).toContain("sessions");
|
|
2561
|
+
return [2 /*return*/];
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
}); });
|
|
2565
|
+
(0, bun_test_1.it)("respects ignoredOperations config", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2566
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2567
|
+
return __generator(this, function (_a) {
|
|
2568
|
+
switch (_a.label) {
|
|
2569
|
+
case 0:
|
|
2570
|
+
mockStream = createMockChangeStream();
|
|
2571
|
+
mockDb = {
|
|
2572
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2573
|
+
};
|
|
2574
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2575
|
+
(0, registry_1.registerRealtime)({
|
|
2576
|
+
collectionName: "todos",
|
|
2577
|
+
config: {
|
|
2578
|
+
methods: ["create", "update", "delete"],
|
|
2579
|
+
roomStrategy: "model",
|
|
2580
|
+
},
|
|
2581
|
+
modelName: "Todo",
|
|
2582
|
+
options: {
|
|
2583
|
+
permissions: {
|
|
2584
|
+
create: [function () { return true; }],
|
|
2585
|
+
delete: [function () { return true; }],
|
|
2586
|
+
list: [function () { return true; }],
|
|
2587
|
+
read: [function () { return true; }],
|
|
2588
|
+
update: [function () { return true; }],
|
|
2589
|
+
},
|
|
2590
|
+
},
|
|
2591
|
+
routePath: "/todos",
|
|
2592
|
+
});
|
|
2593
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2594
|
+
case 1:
|
|
2595
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2596
|
+
io = createMockIo();
|
|
2597
|
+
startChangeStreamWatcher(io, { ignoredOperations: ["insert"] }, true);
|
|
2598
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2599
|
+
// This insert should be skipped because "insert" is ignored
|
|
2600
|
+
return [4 /*yield*/, changeHandler({
|
|
2601
|
+
documentKey: { _id: "doc-1" },
|
|
2602
|
+
fullDocument: { _id: "doc-1" },
|
|
2603
|
+
ns: { coll: "todos" },
|
|
2604
|
+
operationType: "insert",
|
|
2605
|
+
})];
|
|
2606
|
+
case 2:
|
|
2607
|
+
// This insert should be skipped because "insert" is ignored
|
|
2608
|
+
_a.sent();
|
|
2609
|
+
return [2 /*return*/];
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
}); });
|
|
2613
|
+
(0, bun_test_1.it)("skips events with no collectionName or docId", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2614
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2615
|
+
return __generator(this, function (_a) {
|
|
2616
|
+
switch (_a.label) {
|
|
2617
|
+
case 0:
|
|
2618
|
+
mockStream = createMockChangeStream();
|
|
2619
|
+
mockDb = {
|
|
2620
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2621
|
+
};
|
|
2622
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2623
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2624
|
+
case 1:
|
|
2625
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2626
|
+
io = createMockIo();
|
|
2627
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2628
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2629
|
+
// Missing ns.coll
|
|
2630
|
+
return [4 /*yield*/, changeHandler({
|
|
2631
|
+
documentKey: { _id: "doc-1" },
|
|
2632
|
+
ns: {},
|
|
2633
|
+
operationType: "insert",
|
|
2634
|
+
})];
|
|
2635
|
+
case 2:
|
|
2636
|
+
// Missing ns.coll
|
|
2637
|
+
_a.sent();
|
|
2638
|
+
// Missing documentKey
|
|
2639
|
+
return [4 /*yield*/, changeHandler({
|
|
2640
|
+
documentKey: {},
|
|
2641
|
+
ns: { coll: "todos" },
|
|
2642
|
+
operationType: "insert",
|
|
2643
|
+
})];
|
|
2644
|
+
case 3:
|
|
2645
|
+
// Missing documentKey
|
|
2646
|
+
_a.sent();
|
|
2647
|
+
return [2 /*return*/];
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2650
|
+
}); });
|
|
2651
|
+
(0, bun_test_1.it)("skips non-CRUD operation types", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2652
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, changeHandler;
|
|
2653
|
+
return __generator(this, function (_a) {
|
|
2654
|
+
switch (_a.label) {
|
|
2655
|
+
case 0:
|
|
2656
|
+
mockStream = createMockChangeStream();
|
|
2657
|
+
mockDb = {
|
|
2658
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2659
|
+
};
|
|
2660
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2661
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2662
|
+
case 1:
|
|
2663
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2664
|
+
io = createMockIo();
|
|
2665
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2666
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2667
|
+
// "drop" is not in our pipeline filter, should be skipped
|
|
2668
|
+
return [4 /*yield*/, changeHandler({
|
|
2669
|
+
operationType: "drop",
|
|
2670
|
+
})];
|
|
2671
|
+
case 2:
|
|
2672
|
+
// "drop" is not in our pipeline filter, should be skipped
|
|
2673
|
+
_a.sent();
|
|
2674
|
+
return [2 /*return*/];
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
}); });
|
|
2678
|
+
(0, bun_test_1.it)("handles error/close/end events gracefully", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2679
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2680
|
+
return __generator(this, function (_a) {
|
|
2681
|
+
switch (_a.label) {
|
|
2682
|
+
case 0:
|
|
2683
|
+
mockStream = createMockChangeStream();
|
|
2684
|
+
mockDb = {
|
|
2685
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2686
|
+
};
|
|
2687
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2688
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2689
|
+
case 1:
|
|
2690
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2691
|
+
io = createMockIo();
|
|
2692
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2693
|
+
// Trigger error, close, end — should not throw
|
|
2694
|
+
mockStream.trigger("error", new Error("test error"));
|
|
2695
|
+
mockStream.trigger("close");
|
|
2696
|
+
mockStream.trigger("end");
|
|
2697
|
+
return [2 /*return*/];
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2700
|
+
}); });
|
|
2701
|
+
(0, bun_test_1.it)("uses custom batchSize and fullDocument config", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2702
|
+
var mockStream, mockDb, startChangeStreamWatcher, io, options;
|
|
2703
|
+
return __generator(this, function (_a) {
|
|
2704
|
+
switch (_a.label) {
|
|
2705
|
+
case 0:
|
|
2706
|
+
mockStream = createMockChangeStream();
|
|
2707
|
+
mockDb = {
|
|
2708
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2709
|
+
};
|
|
2710
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2711
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2712
|
+
case 1:
|
|
2713
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2714
|
+
io = createMockIo();
|
|
2715
|
+
startChangeStreamWatcher(io, { batchSize: 100, fullDocument: "whenAvailable" }, true);
|
|
2716
|
+
options = mockDb.watch.mock.calls[0][1];
|
|
2717
|
+
(0, bun_test_1.expect)(options.batchSize).toBe(100);
|
|
2718
|
+
(0, bun_test_1.expect)(options.fullDocument).toBe("whenAvailable");
|
|
2719
|
+
return [2 /*return*/];
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
}); });
|
|
2723
|
+
(0, bun_test_1.it)("catches errors thrown in the change handler", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2724
|
+
var mockStream, mockDb, startChangeStreamWatcher, emissions, mockSocket, rooms, sockets, io, changeHandler;
|
|
2725
|
+
return __generator(this, function (_a) {
|
|
2726
|
+
switch (_a.label) {
|
|
2727
|
+
case 0:
|
|
2728
|
+
mockStream = createMockChangeStream();
|
|
2729
|
+
mockDb = {
|
|
2730
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2731
|
+
};
|
|
2732
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2733
|
+
// Register with a model that will throw during permission check
|
|
2734
|
+
(0, registry_1.registerRealtime)({
|
|
2735
|
+
collectionName: "todos",
|
|
2736
|
+
config: {
|
|
2737
|
+
methods: ["create", "update", "delete"],
|
|
2738
|
+
roomStrategy: "model",
|
|
2739
|
+
},
|
|
2740
|
+
modelName: "Todo",
|
|
2741
|
+
options: {
|
|
2742
|
+
permissions: {
|
|
2743
|
+
create: [function () { return true; }],
|
|
2744
|
+
delete: [function () { return true; }],
|
|
2745
|
+
list: [function () { return true; }],
|
|
2746
|
+
read: [
|
|
2747
|
+
function () {
|
|
2748
|
+
throw new Error("permission check error");
|
|
2749
|
+
},
|
|
2750
|
+
],
|
|
2751
|
+
update: [function () { return true; }],
|
|
2752
|
+
},
|
|
2753
|
+
},
|
|
2754
|
+
routePath: "/todos",
|
|
2755
|
+
});
|
|
2756
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2757
|
+
case 1:
|
|
2758
|
+
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2759
|
+
emissions = [];
|
|
2760
|
+
mockSocket = {
|
|
2761
|
+
decodedToken: { id: "user-1" },
|
|
2762
|
+
emit: function (_event, _data) {
|
|
2763
|
+
emissions.push({ _data: _data, _event: _event });
|
|
2764
|
+
},
|
|
2765
|
+
id: "sock-1",
|
|
2766
|
+
};
|
|
2767
|
+
rooms = new Map();
|
|
2768
|
+
rooms.set("model:todos", new Set(["sock-1"]));
|
|
2769
|
+
sockets = new Map();
|
|
2770
|
+
sockets.set("sock-1", mockSocket);
|
|
2771
|
+
io = {
|
|
2772
|
+
sockets: {
|
|
2773
|
+
adapter: { rooms: rooms },
|
|
2774
|
+
sockets: sockets,
|
|
2775
|
+
},
|
|
2776
|
+
to: function () { return ({ emit: function () { } }); },
|
|
2777
|
+
};
|
|
2778
|
+
startChangeStreamWatcher(io, {}, true);
|
|
2779
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2780
|
+
// Should not throw even though permission check throws
|
|
2781
|
+
return [4 /*yield*/, changeHandler({
|
|
2782
|
+
documentKey: { _id: "doc-1" },
|
|
2783
|
+
fullDocument: { _id: "doc-1", name: "Test" },
|
|
2784
|
+
ns: { coll: "todos" },
|
|
2785
|
+
operationType: "insert",
|
|
2786
|
+
})];
|
|
2787
|
+
case 2:
|
|
2788
|
+
// Should not throw even though permission check throws
|
|
2789
|
+
_a.sent();
|
|
2790
|
+
return [2 /*return*/];
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
}); });
|
|
2794
|
+
});
|
|
2795
|
+
(0, bun_test_1.describe)("stopChangeStreamWatcher", function () {
|
|
2796
|
+
(0, bun_test_1.it)("closes and nullifies the active watcher", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2797
|
+
var mockStream, mockDb, originalDb, _a, startChangeStreamWatcher, stopChangeStreamWatcher, io;
|
|
2798
|
+
return __generator(this, function (_b) {
|
|
2799
|
+
switch (_b.label) {
|
|
2800
|
+
case 0:
|
|
2801
|
+
mockStream = {
|
|
2802
|
+
close: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
2803
|
+
return [2 /*return*/];
|
|
2804
|
+
}); }); }),
|
|
2805
|
+
on: function () { },
|
|
2806
|
+
};
|
|
2807
|
+
mockDb = {
|
|
2808
|
+
watch: (0, bun_test_1.mock)(function () { return mockStream; }),
|
|
2809
|
+
};
|
|
2810
|
+
originalDb = mongoose_1.default.connection.db;
|
|
2811
|
+
mongoose_1.default.connection.db = mockDb;
|
|
2812
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./changeStreamWatcher")); })];
|
|
2813
|
+
case 1:
|
|
2814
|
+
_a = _b.sent(), startChangeStreamWatcher = _a.startChangeStreamWatcher, stopChangeStreamWatcher = _a.stopChangeStreamWatcher;
|
|
2815
|
+
io = {
|
|
2816
|
+
sockets: {
|
|
2817
|
+
adapter: { rooms: new Map() },
|
|
2818
|
+
sockets: new Map(),
|
|
2819
|
+
},
|
|
2820
|
+
to: function () { return ({ emit: function () { } }); },
|
|
2821
|
+
};
|
|
2822
|
+
startChangeStreamWatcher(io);
|
|
2823
|
+
return [4 /*yield*/, stopChangeStreamWatcher()];
|
|
2824
|
+
case 2:
|
|
2825
|
+
_b.sent();
|
|
2826
|
+
(0, bun_test_1.expect)(mockStream.close).toHaveBeenCalled();
|
|
2827
|
+
// Calling again should be a no-op
|
|
2828
|
+
return [4 /*yield*/, stopChangeStreamWatcher()];
|
|
2829
|
+
case 3:
|
|
2830
|
+
// Calling again should be a no-op
|
|
2831
|
+
_b.sent();
|
|
2832
|
+
mongoose_1.default.connection.db = originalDb;
|
|
2833
|
+
return [2 /*return*/];
|
|
2834
|
+
}
|
|
2835
|
+
});
|
|
2836
|
+
}); });
|
|
2837
|
+
});
|
|
2838
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2839
|
+
// RealtimeApp.onServerCreated / setupAdapter
|
|
2840
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2841
|
+
(0, bun_test_1.describe)("RealtimeApp.onServerCreated", function () {
|
|
2842
|
+
var servers = [];
|
|
2843
|
+
(0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2844
|
+
var servers_1, servers_1_1, s;
|
|
2845
|
+
var e_1, _a;
|
|
2846
|
+
return __generator(this, function (_b) {
|
|
2847
|
+
try {
|
|
2848
|
+
for (servers_1 = __values(servers), servers_1_1 = servers_1.next(); !servers_1_1.done; servers_1_1 = servers_1.next()) {
|
|
2849
|
+
s = servers_1_1.value;
|
|
2850
|
+
s.close();
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
2854
|
+
finally {
|
|
2855
|
+
try {
|
|
2856
|
+
if (servers_1_1 && !servers_1_1.done && (_a = servers_1.return)) _a.call(servers_1);
|
|
2857
|
+
}
|
|
2858
|
+
finally { if (e_1) throw e_1.error; }
|
|
2859
|
+
}
|
|
2860
|
+
servers.length = 0;
|
|
2861
|
+
return [2 /*return*/];
|
|
2862
|
+
});
|
|
2863
|
+
}); });
|
|
2864
|
+
var makeServer = function () {
|
|
2865
|
+
var http = require("http");
|
|
2866
|
+
var server = http.createServer();
|
|
2867
|
+
servers.push(server);
|
|
2868
|
+
return server;
|
|
2869
|
+
};
|
|
2870
|
+
(0, bun_test_1.it)("throws when TOKEN_SECRET is missing", function () {
|
|
2871
|
+
var originalSecret = process.env.TOKEN_SECRET;
|
|
2872
|
+
process.env.TOKEN_SECRET = "";
|
|
2873
|
+
var app = new realtimeApp_1.RealtimeApp({ tokenSecret: undefined });
|
|
2874
|
+
var server = makeServer();
|
|
2875
|
+
(0, bun_test_1.expect)(function () { return app.onServerCreated(server); }).toThrow("TOKEN_SECRET is required");
|
|
2876
|
+
process.env.TOKEN_SECRET = originalSecret;
|
|
2877
|
+
});
|
|
2878
|
+
(0, bun_test_1.it)("sets up Socket.io with valid config", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2879
|
+
var app, server, originalDb, mockStream;
|
|
2880
|
+
return __generator(this, function (_a) {
|
|
2881
|
+
switch (_a.label) {
|
|
2882
|
+
case 0:
|
|
2883
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
2884
|
+
adapter: "none",
|
|
2885
|
+
tokenSecret: "test-secret",
|
|
2886
|
+
});
|
|
2887
|
+
server = makeServer();
|
|
2888
|
+
originalDb = mongoose_1.default.connection.db;
|
|
2889
|
+
mockStream = { close: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
2890
|
+
return [2 /*return*/];
|
|
2891
|
+
}); }); }, on: function () { return mockStream; } };
|
|
2892
|
+
mongoose_1.default.connection.db = { watch: function () { return mockStream; } };
|
|
2893
|
+
app.onServerCreated(server);
|
|
2894
|
+
(0, bun_test_1.expect)(app.getIo()).toBeDefined();
|
|
2895
|
+
return [4 /*yield*/, app.close()];
|
|
2896
|
+
case 1:
|
|
2897
|
+
_a.sent();
|
|
2898
|
+
mongoose_1.default.connection.db = originalDb;
|
|
2899
|
+
return [2 /*return*/];
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
}); });
|
|
2903
|
+
});
|
|
2904
|
+
(0, bun_test_1.describe)("RealtimeApp.setupAdapter (private, via onServerCreated config)", function () {
|
|
2905
|
+
var servers = [];
|
|
2906
|
+
(0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2907
|
+
var servers_2, servers_2_1, s;
|
|
2908
|
+
var e_2, _a;
|
|
2909
|
+
return __generator(this, function (_b) {
|
|
2910
|
+
try {
|
|
2911
|
+
for (servers_2 = __values(servers), servers_2_1 = servers_2.next(); !servers_2_1.done; servers_2_1 = servers_2.next()) {
|
|
2912
|
+
s = servers_2_1.value;
|
|
2913
|
+
s.close();
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
2917
|
+
finally {
|
|
2918
|
+
try {
|
|
2919
|
+
if (servers_2_1 && !servers_2_1.done && (_a = servers_2.return)) _a.call(servers_2);
|
|
2920
|
+
}
|
|
2921
|
+
finally { if (e_2) throw e_2.error; }
|
|
2922
|
+
}
|
|
2923
|
+
servers.length = 0;
|
|
2924
|
+
return [2 /*return*/];
|
|
2925
|
+
});
|
|
2926
|
+
}); });
|
|
2927
|
+
var makeServer = function () {
|
|
2928
|
+
var http = require("http");
|
|
2929
|
+
var server = http.createServer();
|
|
2930
|
+
servers.push(server);
|
|
2931
|
+
return server;
|
|
2932
|
+
};
|
|
2933
|
+
(0, bun_test_1.it)("logs warning when redis adapter requested but no URL found", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2934
|
+
var originalValkey, originalRedis, originalDb, mockStream, app, server;
|
|
2935
|
+
return __generator(this, function (_a) {
|
|
2936
|
+
switch (_a.label) {
|
|
2937
|
+
case 0:
|
|
2938
|
+
originalValkey = process.env.VALKEY_URL;
|
|
2939
|
+
originalRedis = process.env.REDIS_URL;
|
|
2940
|
+
originalDb = mongoose_1.default.connection.db;
|
|
2941
|
+
process.env.VALKEY_URL = "";
|
|
2942
|
+
process.env.REDIS_URL = "";
|
|
2943
|
+
mockStream = { close: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
2944
|
+
return [2 /*return*/];
|
|
2945
|
+
}); }); }, on: function () { return mockStream; } };
|
|
2946
|
+
mongoose_1.default.connection.db = { watch: function () { return mockStream; } };
|
|
2947
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
2948
|
+
adapter: "redis",
|
|
2949
|
+
tokenSecret: "test-secret",
|
|
2950
|
+
});
|
|
2951
|
+
server = makeServer();
|
|
2952
|
+
app.onServerCreated(server);
|
|
2953
|
+
return [4 /*yield*/, app.close()];
|
|
2954
|
+
case 1:
|
|
2955
|
+
_a.sent();
|
|
2956
|
+
process.env.VALKEY_URL = originalValkey;
|
|
2957
|
+
process.env.REDIS_URL = originalRedis;
|
|
2958
|
+
mongoose_1.default.connection.db = originalDb;
|
|
2959
|
+
return [2 /*return*/];
|
|
2960
|
+
}
|
|
2961
|
+
});
|
|
2962
|
+
}); });
|
|
2963
|
+
(0, bun_test_1.it)("logs info when redis adapter has a URL", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2964
|
+
var originalValkey, originalDb, mockStream, app, server;
|
|
2965
|
+
return __generator(this, function (_a) {
|
|
2966
|
+
switch (_a.label) {
|
|
2967
|
+
case 0:
|
|
2968
|
+
originalValkey = process.env.VALKEY_URL;
|
|
2969
|
+
originalDb = mongoose_1.default.connection.db;
|
|
2970
|
+
process.env.VALKEY_URL = "redis://user:pass@localhost:6379/0";
|
|
2971
|
+
mockStream = { close: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
2972
|
+
return [2 /*return*/];
|
|
2973
|
+
}); }); }, on: function () { return mockStream; } };
|
|
2974
|
+
mongoose_1.default.connection.db = { watch: function () { return mockStream; } };
|
|
2975
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
2976
|
+
adapter: "redis",
|
|
2977
|
+
debug: true,
|
|
2978
|
+
tokenSecret: "test-secret",
|
|
2979
|
+
});
|
|
2980
|
+
server = makeServer();
|
|
2981
|
+
app.onServerCreated(server);
|
|
2982
|
+
return [4 /*yield*/, app.close()];
|
|
2983
|
+
case 1:
|
|
2984
|
+
_a.sent();
|
|
2985
|
+
process.env.VALKEY_URL = originalValkey;
|
|
2986
|
+
mongoose_1.default.connection.db = originalDb;
|
|
2987
|
+
return [2 /*return*/];
|
|
2988
|
+
}
|
|
2989
|
+
});
|
|
2990
|
+
}); });
|
|
2991
|
+
(0, bun_test_1.it)("no-op adapter mode 'none'", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2992
|
+
var originalDb, mockStream, app, server;
|
|
2993
|
+
return __generator(this, function (_a) {
|
|
2994
|
+
switch (_a.label) {
|
|
2995
|
+
case 0:
|
|
2996
|
+
originalDb = mongoose_1.default.connection.db;
|
|
2997
|
+
mockStream = { close: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
2998
|
+
return [2 /*return*/];
|
|
2999
|
+
}); }); }, on: function () { return mockStream; } };
|
|
3000
|
+
mongoose_1.default.connection.db = { watch: function () { return mockStream; } };
|
|
3001
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
3002
|
+
adapter: "none",
|
|
3003
|
+
tokenSecret: "test-secret",
|
|
3004
|
+
});
|
|
3005
|
+
server = makeServer();
|
|
3006
|
+
app.onServerCreated(server);
|
|
3007
|
+
(0, bun_test_1.expect)(app.getIo()).toBeDefined();
|
|
3008
|
+
return [4 /*yield*/, app.close()];
|
|
3009
|
+
case 1:
|
|
3010
|
+
_a.sent();
|
|
3011
|
+
mongoose_1.default.connection.db = originalDb;
|
|
3012
|
+
return [2 /*return*/];
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
}); });
|
|
3016
|
+
});
|
|
3017
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3018
|
+
// ensureApiId
|
|
3019
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3020
|
+
(0, bun_test_1.describe)("ensureApiId", function () {
|
|
3021
|
+
(0, bun_test_1.it)("returns null as-is", function () {
|
|
3022
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)(null)).toBeNull();
|
|
3023
|
+
});
|
|
3024
|
+
(0, bun_test_1.it)("returns undefined as-is", function () {
|
|
3025
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)(undefined)).toBeUndefined();
|
|
3026
|
+
});
|
|
3027
|
+
(0, bun_test_1.it)("returns arrays as-is", function () {
|
|
3028
|
+
var arr = [1, 2, 3];
|
|
3029
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)(arr)).toBe(arr);
|
|
3030
|
+
});
|
|
3031
|
+
(0, bun_test_1.it)("returns primitive values as-is (non-object)", function () {
|
|
3032
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)("string")).toBe("string");
|
|
3033
|
+
});
|
|
3034
|
+
(0, bun_test_1.it)("adds id from _id when id is missing", function () {
|
|
3035
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)({ _id: "abc" })).toEqual({ _id: "abc", id: "abc" });
|
|
3036
|
+
});
|
|
3037
|
+
(0, bun_test_1.it)("does not overwrite existing id", function () {
|
|
3038
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)({ _id: "abc", id: "existing" })).toEqual({ _id: "abc", id: "existing" });
|
|
3039
|
+
});
|
|
3040
|
+
(0, bun_test_1.it)("returns object without _id unchanged", function () {
|
|
3041
|
+
var obj = { name: "test" };
|
|
3042
|
+
(0, bun_test_1.expect)((0, changeStreamWatcher_1.ensureApiId)(obj)).toBe(obj);
|
|
3043
|
+
});
|
|
3044
|
+
});
|
|
3045
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3046
|
+
// startChangeStreamWatcher & stopChangeStreamWatcher
|
|
3047
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3048
|
+
(0, bun_test_1.describe)("startChangeStreamWatcher & stopChangeStreamWatcher", function () {
|
|
3049
|
+
var makeMockIo = function () {
|
|
3050
|
+
var emissions = [];
|
|
3051
|
+
var rooms = new Map();
|
|
3052
|
+
var sockets = new Map();
|
|
3053
|
+
return {
|
|
3054
|
+
emissions: emissions,
|
|
3055
|
+
sockets: {
|
|
3056
|
+
adapter: { rooms: rooms },
|
|
3057
|
+
sockets: sockets,
|
|
3058
|
+
},
|
|
3059
|
+
to: function (_room) { return ({
|
|
3060
|
+
emit: function () { },
|
|
3061
|
+
}); },
|
|
3062
|
+
};
|
|
3063
|
+
};
|
|
3064
|
+
(0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3065
|
+
return __generator(this, function (_a) {
|
|
3066
|
+
switch (_a.label) {
|
|
3067
|
+
case 0: return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3068
|
+
case 1:
|
|
3069
|
+
_a.sent();
|
|
3070
|
+
(0, registry_1.clearRealtimeRegistry)();
|
|
3071
|
+
return [2 /*return*/];
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
}); });
|
|
3075
|
+
(0, bun_test_1.it)("starts and stops without error when MongoDB is connected", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3076
|
+
var io;
|
|
3077
|
+
return __generator(this, function (_a) {
|
|
3078
|
+
switch (_a.label) {
|
|
3079
|
+
case 0:
|
|
3080
|
+
io = makeMockIo();
|
|
3081
|
+
(0, bun_test_1.expect)(function () { return (0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, false); }).not.toThrow();
|
|
3082
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3083
|
+
case 1:
|
|
3084
|
+
_a.sent();
|
|
3085
|
+
return [2 /*return*/];
|
|
3086
|
+
}
|
|
3087
|
+
});
|
|
3088
|
+
}); });
|
|
3089
|
+
(0, bun_test_1.it)("starts with debug mode enabled", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3090
|
+
var io;
|
|
3091
|
+
return __generator(this, function (_a) {
|
|
3092
|
+
switch (_a.label) {
|
|
3093
|
+
case 0:
|
|
3094
|
+
io = makeMockIo();
|
|
3095
|
+
(0, bun_test_1.expect)(function () { return (0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, true); }).not.toThrow();
|
|
3096
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3097
|
+
case 1:
|
|
3098
|
+
_a.sent();
|
|
3099
|
+
return [2 /*return*/];
|
|
3100
|
+
}
|
|
3101
|
+
});
|
|
3102
|
+
}); });
|
|
3103
|
+
(0, bun_test_1.it)("starts with custom config options", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3104
|
+
var io;
|
|
3105
|
+
return __generator(this, function (_a) {
|
|
3106
|
+
switch (_a.label) {
|
|
3107
|
+
case 0:
|
|
3108
|
+
io = makeMockIo();
|
|
3109
|
+
(0, bun_test_1.expect)(function () {
|
|
3110
|
+
return (0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {
|
|
3111
|
+
batchSize: 10,
|
|
3112
|
+
fullDocument: "whenAvailable",
|
|
3113
|
+
ignoredCollections: ["logs"],
|
|
3114
|
+
ignoredOperations: ["delete"],
|
|
3115
|
+
}, false);
|
|
3116
|
+
}).not.toThrow();
|
|
3117
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3118
|
+
case 1:
|
|
3119
|
+
_a.sent();
|
|
3120
|
+
return [2 /*return*/];
|
|
3121
|
+
}
|
|
3122
|
+
});
|
|
3123
|
+
}); });
|
|
3124
|
+
(0, bun_test_1.it)("stopChangeStreamWatcher is safe to call when no watcher is active", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3125
|
+
return __generator(this, function (_a) {
|
|
3126
|
+
switch (_a.label) {
|
|
3127
|
+
case 0: return [4 /*yield*/, (0, bun_test_1.expect)((0, changeStreamWatcher_1.stopChangeStreamWatcher)()).resolves.toBeUndefined()];
|
|
3128
|
+
case 1:
|
|
3129
|
+
_a.sent();
|
|
3130
|
+
return [2 /*return*/];
|
|
3131
|
+
}
|
|
3132
|
+
});
|
|
3133
|
+
}); });
|
|
3134
|
+
(0, bun_test_1.it)("stopChangeStreamWatcher can be called multiple times", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3135
|
+
var io;
|
|
3136
|
+
return __generator(this, function (_a) {
|
|
3137
|
+
switch (_a.label) {
|
|
3138
|
+
case 0:
|
|
3139
|
+
io = makeMockIo();
|
|
3140
|
+
(0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, false);
|
|
3141
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3142
|
+
case 1:
|
|
3143
|
+
_a.sent();
|
|
3144
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3145
|
+
case 2:
|
|
3146
|
+
_a.sent();
|
|
3147
|
+
return [2 /*return*/];
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
}); });
|
|
3151
|
+
});
|
|
3152
|
+
// Change streams require a MongoDB replica set. CI (api-ci.yml) runs standalone MongoDB,
|
|
3153
|
+
// so these tests are skipped when replica sets are not available.
|
|
3154
|
+
var hasReplicaSet = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3155
|
+
var mongoose_2, admin, status_1, _a;
|
|
3156
|
+
return __generator(this, function (_b) {
|
|
3157
|
+
switch (_b.label) {
|
|
3158
|
+
case 0:
|
|
3159
|
+
_b.trys.push([0, 2, , 3]);
|
|
3160
|
+
mongoose_2 = require("mongoose");
|
|
3161
|
+
admin = mongoose_2.connection.db.admin();
|
|
3162
|
+
return [4 /*yield*/, admin.command({ replSetGetStatus: 1 })];
|
|
3163
|
+
case 1:
|
|
3164
|
+
status_1 = _b.sent();
|
|
3165
|
+
return [2 /*return*/, !!status_1.ok];
|
|
3166
|
+
case 2:
|
|
3167
|
+
_a = _b.sent();
|
|
3168
|
+
return [2 /*return*/, false];
|
|
3169
|
+
case 3: return [2 /*return*/];
|
|
3170
|
+
}
|
|
3171
|
+
});
|
|
3172
|
+
}); };
|
|
3173
|
+
(0, bun_test_1.describe)("startChangeStreamWatcher — change event integration", function () {
|
|
3174
|
+
var mongoose = require("mongoose");
|
|
3175
|
+
var replicaSetAvailable = false;
|
|
3176
|
+
var realtimeTestSchema = new mongoose.Schema({
|
|
3177
|
+
deleted: { default: false, type: Boolean },
|
|
3178
|
+
name: { type: String },
|
|
3179
|
+
ownerId: { type: String },
|
|
3180
|
+
}, { collection: "realtimetests", strict: "throw" });
|
|
3181
|
+
var RealtimeTestModel;
|
|
3182
|
+
try {
|
|
3183
|
+
RealtimeTestModel = mongoose.model("RealtimeTest");
|
|
3184
|
+
}
|
|
3185
|
+
catch (_a) {
|
|
3186
|
+
RealtimeTestModel = mongoose.model("RealtimeTest", realtimeTestSchema);
|
|
3187
|
+
}
|
|
3188
|
+
var makeTrackedIo = function () {
|
|
3189
|
+
var emissions = [];
|
|
3190
|
+
var rooms = new Map();
|
|
3191
|
+
var sockets = new Map();
|
|
3192
|
+
var addSocketToRoom = function (room, decodedToken) {
|
|
3193
|
+
var _a;
|
|
3194
|
+
if (decodedToken === void 0) { decodedToken = { admin: true, id: "admin" }; }
|
|
3195
|
+
var socketId = "socket-".concat(Math.random().toString(36).slice(2, 9));
|
|
3196
|
+
if (!rooms.has(room)) {
|
|
3197
|
+
rooms.set(room, new Set());
|
|
3198
|
+
}
|
|
3199
|
+
(_a = rooms.get(room)) === null || _a === void 0 ? void 0 : _a.add(socketId);
|
|
3200
|
+
sockets.set(socketId, {
|
|
3201
|
+
decodedToken: decodedToken,
|
|
3202
|
+
emit: function (event, payload) {
|
|
3203
|
+
emissions.push({ event: event, payload: payload, room: room, socketId: socketId });
|
|
3204
|
+
},
|
|
3205
|
+
id: socketId,
|
|
3206
|
+
});
|
|
3207
|
+
};
|
|
3208
|
+
return {
|
|
3209
|
+
addSocketToRoom: addSocketToRoom,
|
|
3210
|
+
emissions: emissions,
|
|
3211
|
+
sockets: {
|
|
3212
|
+
adapter: { rooms: rooms },
|
|
3213
|
+
sockets: sockets,
|
|
3214
|
+
},
|
|
3215
|
+
to: function (room) { return ({
|
|
3216
|
+
emit: function (event, payload) {
|
|
3217
|
+
emissions.push({ event: event, payload: payload, room: room });
|
|
3218
|
+
},
|
|
3219
|
+
}); },
|
|
3220
|
+
};
|
|
3221
|
+
};
|
|
3222
|
+
(0, bun_test_1.beforeAll)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3223
|
+
return __generator(this, function (_a) {
|
|
3224
|
+
switch (_a.label) {
|
|
3225
|
+
case 0: return [4 /*yield*/, hasReplicaSet()];
|
|
3226
|
+
case 1:
|
|
3227
|
+
replicaSetAvailable = _a.sent();
|
|
3228
|
+
return [2 /*return*/];
|
|
3229
|
+
}
|
|
3230
|
+
});
|
|
3231
|
+
}); });
|
|
3232
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3233
|
+
return __generator(this, function (_a) {
|
|
3234
|
+
switch (_a.label) {
|
|
3235
|
+
case 0:
|
|
3236
|
+
(0, registry_1.clearRealtimeRegistry)();
|
|
3237
|
+
(0, queryStore_1.clearQueryStore)();
|
|
3238
|
+
return [4 /*yield*/, RealtimeTestModel.deleteMany({})];
|
|
3239
|
+
case 1:
|
|
3240
|
+
_a.sent();
|
|
3241
|
+
return [2 /*return*/];
|
|
3242
|
+
}
|
|
3243
|
+
});
|
|
3244
|
+
}); });
|
|
3245
|
+
(0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3246
|
+
return __generator(this, function (_a) {
|
|
3247
|
+
switch (_a.label) {
|
|
3248
|
+
case 0: return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3249
|
+
case 1:
|
|
3250
|
+
_a.sent();
|
|
3251
|
+
(0, registry_1.clearRealtimeRegistry)();
|
|
3252
|
+
(0, queryStore_1.clearQueryStore)();
|
|
3253
|
+
return [4 /*yield*/, RealtimeTestModel.deleteMany({})];
|
|
3254
|
+
case 2:
|
|
3255
|
+
_a.sent();
|
|
3256
|
+
return [2 /*return*/];
|
|
3257
|
+
}
|
|
3258
|
+
});
|
|
3259
|
+
}); });
|
|
3260
|
+
(0, bun_test_1.it)("processes insert events from MongoDB change stream", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3261
|
+
var io, createEmissions;
|
|
3262
|
+
return __generator(this, function (_a) {
|
|
3263
|
+
switch (_a.label) {
|
|
3264
|
+
case 0:
|
|
3265
|
+
if (!replicaSetAvailable) {
|
|
3266
|
+
return [2 /*return*/];
|
|
3267
|
+
}
|
|
3268
|
+
(0, registry_1.registerRealtime)({
|
|
3269
|
+
collectionName: "realtimetests",
|
|
3270
|
+
config: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
3271
|
+
modelName: "RealtimeTest",
|
|
3272
|
+
options: {
|
|
3273
|
+
permissions: {
|
|
3274
|
+
create: [function () { return true; }],
|
|
3275
|
+
delete: [function () { return true; }],
|
|
3276
|
+
list: [function () { return true; }],
|
|
3277
|
+
read: [function () { return true; }],
|
|
3278
|
+
update: [function () { return true; }],
|
|
3279
|
+
},
|
|
3280
|
+
},
|
|
3281
|
+
routePath: "/realtimetests",
|
|
3282
|
+
});
|
|
3283
|
+
io = makeTrackedIo();
|
|
3284
|
+
io.addSocketToRoom("model:realtimetests");
|
|
3285
|
+
(0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, true);
|
|
3286
|
+
return [4 /*yield*/, RealtimeTestModel.create({ name: "test-item", ownerId: "user-1" })];
|
|
3287
|
+
case 1:
|
|
3288
|
+
_a.sent();
|
|
3289
|
+
// Wait for the change stream event to be processed
|
|
3290
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1500); })];
|
|
3291
|
+
case 2:
|
|
3292
|
+
// Wait for the change stream event to be processed
|
|
3293
|
+
_a.sent();
|
|
3294
|
+
createEmissions = io.emissions.filter(function (e) { var _a; return e.event === "sync" && ((_a = e.payload) === null || _a === void 0 ? void 0 : _a.method) === "create"; });
|
|
3295
|
+
(0, bun_test_1.expect)(createEmissions.length).toBeGreaterThanOrEqual(1);
|
|
3296
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3297
|
+
case 3:
|
|
3298
|
+
_a.sent();
|
|
3299
|
+
return [2 /*return*/];
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
}); });
|
|
3303
|
+
(0, bun_test_1.it)("processes update events from MongoDB change stream", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3304
|
+
var doc, io, updateEmissions;
|
|
3305
|
+
return __generator(this, function (_a) {
|
|
3306
|
+
switch (_a.label) {
|
|
3307
|
+
case 0:
|
|
3308
|
+
if (!replicaSetAvailable) {
|
|
3309
|
+
return [2 /*return*/];
|
|
3310
|
+
}
|
|
3311
|
+
(0, registry_1.registerRealtime)({
|
|
3312
|
+
collectionName: "realtimetests",
|
|
3313
|
+
config: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
3314
|
+
modelName: "RealtimeTest",
|
|
3315
|
+
options: {
|
|
3316
|
+
permissions: {
|
|
3317
|
+
create: [function () { return true; }],
|
|
3318
|
+
delete: [function () { return true; }],
|
|
3319
|
+
list: [function () { return true; }],
|
|
3320
|
+
read: [function () { return true; }],
|
|
3321
|
+
update: [function () { return true; }],
|
|
3322
|
+
},
|
|
3323
|
+
},
|
|
3324
|
+
routePath: "/realtimetests",
|
|
3325
|
+
});
|
|
3326
|
+
return [4 /*yield*/, RealtimeTestModel.create({ name: "item-to-update", ownerId: "user-1" })];
|
|
3327
|
+
case 1:
|
|
3328
|
+
doc = _a.sent();
|
|
3329
|
+
io = makeTrackedIo();
|
|
3330
|
+
io.addSocketToRoom("model:realtimetests");
|
|
3331
|
+
(0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, true);
|
|
3332
|
+
return [4 /*yield*/, RealtimeTestModel.updateOne({ _id: doc._id }, { $set: { name: "updated-item" } })];
|
|
3333
|
+
case 2:
|
|
3334
|
+
_a.sent();
|
|
3335
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1500); })];
|
|
3336
|
+
case 3:
|
|
3337
|
+
_a.sent();
|
|
3338
|
+
updateEmissions = io.emissions.filter(function (e) { var _a; return e.event === "sync" && ((_a = e.payload) === null || _a === void 0 ? void 0 : _a.method) === "update"; });
|
|
3339
|
+
(0, bun_test_1.expect)(updateEmissions.length).toBeGreaterThanOrEqual(1);
|
|
3340
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3341
|
+
case 4:
|
|
3342
|
+
_a.sent();
|
|
3343
|
+
return [2 /*return*/];
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
}); });
|
|
3347
|
+
(0, bun_test_1.it)("processes hard delete events from MongoDB change stream", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3348
|
+
var doc, io, deleteEmissions;
|
|
3349
|
+
return __generator(this, function (_a) {
|
|
3350
|
+
switch (_a.label) {
|
|
3351
|
+
case 0:
|
|
3352
|
+
if (!replicaSetAvailable) {
|
|
3353
|
+
return [2 /*return*/];
|
|
3354
|
+
}
|
|
3355
|
+
(0, registry_1.registerRealtime)({
|
|
3356
|
+
collectionName: "realtimetests",
|
|
3357
|
+
config: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
3358
|
+
modelName: "RealtimeTest",
|
|
3359
|
+
options: {
|
|
3360
|
+
permissions: {
|
|
3361
|
+
create: [function () { return true; }],
|
|
3362
|
+
delete: [function () { return true; }],
|
|
3363
|
+
list: [function () { return true; }],
|
|
3364
|
+
read: [function () { return true; }],
|
|
3365
|
+
update: [function () { return true; }],
|
|
3366
|
+
},
|
|
3367
|
+
},
|
|
3368
|
+
routePath: "/realtimetests",
|
|
3369
|
+
});
|
|
3370
|
+
return [4 /*yield*/, RealtimeTestModel.create({ name: "item-to-delete" })];
|
|
3371
|
+
case 1:
|
|
3372
|
+
doc = _a.sent();
|
|
3373
|
+
io = makeTrackedIo();
|
|
3374
|
+
io.addSocketToRoom("model:realtimetests");
|
|
3375
|
+
(0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, true);
|
|
3376
|
+
return [4 /*yield*/, RealtimeTestModel.deleteOne({ _id: doc._id })];
|
|
3377
|
+
case 2:
|
|
3378
|
+
_a.sent();
|
|
3379
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1500); })];
|
|
3380
|
+
case 3:
|
|
3381
|
+
_a.sent();
|
|
3382
|
+
deleteEmissions = io.emissions.filter(function (e) { var _a; return e.event === "sync" && ((_a = e.payload) === null || _a === void 0 ? void 0 : _a.method) === "delete"; });
|
|
3383
|
+
(0, bun_test_1.expect)(deleteEmissions.length).toBeGreaterThanOrEqual(1);
|
|
3384
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3385
|
+
case 4:
|
|
3386
|
+
_a.sent();
|
|
3387
|
+
return [2 /*return*/];
|
|
3388
|
+
}
|
|
3389
|
+
});
|
|
3390
|
+
}); });
|
|
3391
|
+
(0, bun_test_1.it)("processes soft delete events from MongoDB change stream", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3392
|
+
var doc, io, deleteEmissions;
|
|
3393
|
+
return __generator(this, function (_a) {
|
|
3394
|
+
switch (_a.label) {
|
|
3395
|
+
case 0:
|
|
3396
|
+
if (!replicaSetAvailable) {
|
|
3397
|
+
return [2 /*return*/];
|
|
3398
|
+
}
|
|
3399
|
+
(0, registry_1.registerRealtime)({
|
|
3400
|
+
collectionName: "realtimetests",
|
|
3401
|
+
config: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
3402
|
+
modelName: "RealtimeTest",
|
|
3403
|
+
options: {
|
|
3404
|
+
permissions: {
|
|
3405
|
+
create: [function () { return true; }],
|
|
3406
|
+
delete: [function () { return true; }],
|
|
3407
|
+
list: [function () { return true; }],
|
|
3408
|
+
read: [function () { return true; }],
|
|
3409
|
+
update: [function () { return true; }],
|
|
3410
|
+
},
|
|
3411
|
+
},
|
|
3412
|
+
routePath: "/realtimetests",
|
|
3413
|
+
});
|
|
3414
|
+
return [4 /*yield*/, RealtimeTestModel.create({ name: "item-to-soft-delete" })];
|
|
3415
|
+
case 1:
|
|
3416
|
+
doc = _a.sent();
|
|
3417
|
+
io = makeTrackedIo();
|
|
3418
|
+
io.addSocketToRoom("model:realtimetests");
|
|
3419
|
+
(0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, true);
|
|
3420
|
+
return [4 /*yield*/, RealtimeTestModel.updateOne({ _id: doc._id }, { $set: { deleted: true } })];
|
|
3421
|
+
case 2:
|
|
3422
|
+
_a.sent();
|
|
3423
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1500); })];
|
|
3424
|
+
case 3:
|
|
3425
|
+
_a.sent();
|
|
3426
|
+
deleteEmissions = io.emissions.filter(function (e) { var _a; return e.event === "sync" && ((_a = e.payload) === null || _a === void 0 ? void 0 : _a.method) === "delete"; });
|
|
3427
|
+
(0, bun_test_1.expect)(deleteEmissions.length).toBeGreaterThanOrEqual(1);
|
|
3428
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3429
|
+
case 4:
|
|
3430
|
+
_a.sent();
|
|
3431
|
+
return [2 /*return*/];
|
|
3432
|
+
}
|
|
3433
|
+
});
|
|
3434
|
+
}); });
|
|
3435
|
+
(0, bun_test_1.it)("includes updatedFields and emits to document rooms", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3436
|
+
var doc, docId, io, updateEmissions;
|
|
3437
|
+
return __generator(this, function (_a) {
|
|
3438
|
+
switch (_a.label) {
|
|
3439
|
+
case 0:
|
|
3440
|
+
if (!replicaSetAvailable) {
|
|
3441
|
+
return [2 /*return*/];
|
|
3442
|
+
}
|
|
3443
|
+
(0, registry_1.registerRealtime)({
|
|
3444
|
+
collectionName: "realtimetests",
|
|
3445
|
+
config: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
3446
|
+
modelName: "RealtimeTest",
|
|
3447
|
+
options: {
|
|
3448
|
+
permissions: {
|
|
3449
|
+
create: [function () { return true; }],
|
|
3450
|
+
delete: [function () { return true; }],
|
|
3451
|
+
list: [function () { return true; }],
|
|
3452
|
+
read: [function () { return true; }],
|
|
3453
|
+
update: [function () { return true; }],
|
|
3454
|
+
},
|
|
3455
|
+
},
|
|
3456
|
+
routePath: "/realtimetests",
|
|
3457
|
+
});
|
|
3458
|
+
return [4 /*yield*/, RealtimeTestModel.create({ name: "fields-test" })];
|
|
3459
|
+
case 1:
|
|
3460
|
+
doc = _a.sent();
|
|
3461
|
+
docId = doc._id.toString();
|
|
3462
|
+
io = makeTrackedIo();
|
|
3463
|
+
io.addSocketToRoom("model:realtimetests");
|
|
3464
|
+
io.addSocketToRoom("document:realtimetests:".concat(docId));
|
|
3465
|
+
(0, changeStreamWatcher_1.startChangeStreamWatcher)(io, {}, true);
|
|
3466
|
+
return [4 /*yield*/, RealtimeTestModel.updateOne({ _id: doc._id }, { $set: { name: "fields-updated" } })];
|
|
3467
|
+
case 2:
|
|
3468
|
+
_a.sent();
|
|
3469
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 1500); })];
|
|
3470
|
+
case 3:
|
|
3471
|
+
_a.sent();
|
|
3472
|
+
updateEmissions = io.emissions.filter(function (e) { var _a; return e.event === "sync" && ((_a = e.payload) === null || _a === void 0 ? void 0 : _a.method) === "update"; });
|
|
3473
|
+
(0, bun_test_1.expect)(updateEmissions.length).toBeGreaterThanOrEqual(1);
|
|
3474
|
+
if (updateEmissions.length > 0) {
|
|
3475
|
+
(0, bun_test_1.expect)(updateEmissions[0].payload.updatedFields).toBeDefined();
|
|
3476
|
+
(0, bun_test_1.expect)(updateEmissions[0].payload.updatedFields).toContain("name");
|
|
3477
|
+
}
|
|
3478
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.stopChangeStreamWatcher)()];
|
|
3479
|
+
case 4:
|
|
3480
|
+
_a.sent();
|
|
3481
|
+
return [2 /*return*/];
|
|
3482
|
+
}
|
|
3483
|
+
});
|
|
3484
|
+
}); });
|
|
3485
|
+
});
|
|
3486
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3487
|
+
// emitToDocumentAndQueryRooms — no-entry path
|
|
3488
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3489
|
+
(0, bun_test_1.describe)("emitToDocumentAndQueryRooms — no registry entry", function () {
|
|
3490
|
+
var makeIoSimple = function () {
|
|
3491
|
+
var emissions = [];
|
|
3492
|
+
return {
|
|
3493
|
+
emissions: emissions,
|
|
3494
|
+
sockets: {
|
|
3495
|
+
adapter: { rooms: new Map() },
|
|
3496
|
+
sockets: new Map(),
|
|
3497
|
+
},
|
|
3498
|
+
to: function (room) { return ({
|
|
3499
|
+
emit: function (event, payload) {
|
|
3500
|
+
emissions.push({ event: event, payload: payload, room: room });
|
|
3501
|
+
},
|
|
3502
|
+
}); },
|
|
3503
|
+
};
|
|
3504
|
+
};
|
|
3505
|
+
(0, bun_test_1.beforeEach)(function () {
|
|
3506
|
+
(0, queryStore_1.clearQueryStore)();
|
|
3507
|
+
});
|
|
3508
|
+
(0, bun_test_1.afterEach)(function () {
|
|
3509
|
+
(0, queryStore_1.clearQueryStore)();
|
|
3510
|
+
});
|
|
3511
|
+
(0, bun_test_1.it)("emits to document room via io.to when no entry is provided", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3512
|
+
var io, event;
|
|
3513
|
+
return __generator(this, function (_a) {
|
|
3514
|
+
switch (_a.label) {
|
|
3515
|
+
case 0:
|
|
3516
|
+
io = makeIoSimple();
|
|
3517
|
+
event = {
|
|
3518
|
+
collection: "items",
|
|
3519
|
+
id: "doc-1",
|
|
3520
|
+
method: "update",
|
|
3521
|
+
model: "Item",
|
|
3522
|
+
timestamp: 1,
|
|
3523
|
+
};
|
|
3524
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.emitToDocumentAndQueryRooms)(io, "items", event, {}, function () { })];
|
|
3525
|
+
case 1:
|
|
3526
|
+
_a.sent();
|
|
3527
|
+
(0, bun_test_1.expect)(io.emissions.some(function (e) { return e.room === "document:items:doc-1"; })).toBe(true);
|
|
3528
|
+
return [2 /*return*/];
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
}); });
|
|
3532
|
+
(0, bun_test_1.it)("emits hard deletes to query rooms via io.to when no entry", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3533
|
+
var queryId, io, event;
|
|
3534
|
+
return __generator(this, function (_a) {
|
|
3535
|
+
switch (_a.label) {
|
|
3536
|
+
case 0:
|
|
3537
|
+
queryId = (0, queryStore_1.computeQueryId)("items", { status: "active" });
|
|
3538
|
+
(0, queryStore_1.addQuerySubscription)("socket-a", "items", { status: "active" }, queryId);
|
|
3539
|
+
io = makeIoSimple();
|
|
3540
|
+
event = {
|
|
3541
|
+
collection: "items",
|
|
3542
|
+
id: "doc-1",
|
|
3543
|
+
method: "delete",
|
|
3544
|
+
model: "Item",
|
|
3545
|
+
timestamp: 1,
|
|
3546
|
+
};
|
|
3547
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.emitToDocumentAndQueryRooms)(io, "items", event, undefined, function () { })];
|
|
3548
|
+
case 1:
|
|
3549
|
+
_a.sent();
|
|
3550
|
+
(0, bun_test_1.expect)(io.emissions.some(function (e) { return e.room === "query:".concat(queryId); })).toBe(true);
|
|
3551
|
+
return [2 /*return*/];
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
}); });
|
|
3555
|
+
(0, bun_test_1.it)("emits soft delete to query rooms via io.to when no entry and doc matches", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3556
|
+
var queryId, io, event;
|
|
3557
|
+
return __generator(this, function (_a) {
|
|
3558
|
+
switch (_a.label) {
|
|
3559
|
+
case 0:
|
|
3560
|
+
queryId = (0, queryStore_1.computeQueryId)("items", { status: "active" });
|
|
3561
|
+
(0, queryStore_1.addQuerySubscription)("socket-a", "items", { status: "active" }, queryId);
|
|
3562
|
+
io = makeIoSimple();
|
|
3563
|
+
event = {
|
|
3564
|
+
collection: "items",
|
|
3565
|
+
id: "doc-1",
|
|
3566
|
+
method: "delete",
|
|
3567
|
+
model: "Item",
|
|
3568
|
+
timestamp: 1,
|
|
3569
|
+
};
|
|
3570
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.emitToDocumentAndQueryRooms)(io, "items", event, { status: "active" }, function () { })];
|
|
3571
|
+
case 1:
|
|
3572
|
+
_a.sent();
|
|
3573
|
+
(0, bun_test_1.expect)(io.emissions.some(function (e) { return e.room === "query:".concat(queryId); })).toBe(true);
|
|
3574
|
+
return [2 /*return*/];
|
|
3575
|
+
}
|
|
3576
|
+
});
|
|
3577
|
+
}); });
|
|
3578
|
+
(0, bun_test_1.it)("emits create events to query rooms via io.to when no entry and doc matches", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3579
|
+
var queryId, io, event;
|
|
3580
|
+
return __generator(this, function (_a) {
|
|
3581
|
+
switch (_a.label) {
|
|
3582
|
+
case 0:
|
|
3583
|
+
queryId = (0, queryStore_1.computeQueryId)("items", { status: "active" });
|
|
3584
|
+
(0, queryStore_1.addQuerySubscription)("socket-a", "items", { status: "active" }, queryId);
|
|
3585
|
+
io = makeIoSimple();
|
|
3586
|
+
event = {
|
|
3587
|
+
collection: "items",
|
|
3588
|
+
id: "doc-1",
|
|
3589
|
+
method: "create",
|
|
3590
|
+
model: "Item",
|
|
3591
|
+
timestamp: 1,
|
|
3592
|
+
};
|
|
3593
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.emitToDocumentAndQueryRooms)(io, "items", event, { status: "active" }, function () { })];
|
|
3594
|
+
case 1:
|
|
3595
|
+
_a.sent();
|
|
3596
|
+
(0, bun_test_1.expect)(io.emissions.some(function (e) { return e.room === "query:".concat(queryId); })).toBe(true);
|
|
3597
|
+
return [2 /*return*/];
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
}); });
|
|
3601
|
+
(0, bun_test_1.it)("emits update events to query rooms via io.to when no entry and doc matches", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3602
|
+
var queryId, io, event;
|
|
3603
|
+
return __generator(this, function (_a) {
|
|
3604
|
+
switch (_a.label) {
|
|
3605
|
+
case 0:
|
|
3606
|
+
queryId = (0, queryStore_1.computeQueryId)("items", { status: "active" });
|
|
3607
|
+
(0, queryStore_1.addQuerySubscription)("socket-a", "items", { status: "active" }, queryId);
|
|
3608
|
+
io = makeIoSimple();
|
|
3609
|
+
event = {
|
|
3610
|
+
collection: "items",
|
|
3611
|
+
id: "doc-1",
|
|
3612
|
+
method: "update",
|
|
3613
|
+
model: "Item",
|
|
3614
|
+
timestamp: 1,
|
|
3615
|
+
};
|
|
3616
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.emitToDocumentAndQueryRooms)(io, "items", event, { status: "active" }, function () { })];
|
|
3617
|
+
case 1:
|
|
3618
|
+
_a.sent();
|
|
3619
|
+
(0, bun_test_1.expect)(io.emissions.some(function (e) { return e.room === "query:".concat(queryId); })).toBe(true);
|
|
3620
|
+
return [2 /*return*/];
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
}); });
|
|
3624
|
+
(0, bun_test_1.it)("emits delete to query rooms via io.to when update no longer matches and no entry", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3625
|
+
var queryId, io, event, queryEmissions;
|
|
3626
|
+
return __generator(this, function (_a) {
|
|
3627
|
+
switch (_a.label) {
|
|
3628
|
+
case 0:
|
|
3629
|
+
queryId = (0, queryStore_1.computeQueryId)("items", { status: "active" });
|
|
3630
|
+
(0, queryStore_1.addQuerySubscription)("socket-a", "items", { status: "active" }, queryId);
|
|
3631
|
+
io = makeIoSimple();
|
|
3632
|
+
event = {
|
|
3633
|
+
collection: "items",
|
|
3634
|
+
id: "doc-1",
|
|
3635
|
+
method: "update",
|
|
3636
|
+
model: "Item",
|
|
3637
|
+
timestamp: 1,
|
|
3638
|
+
};
|
|
3639
|
+
return [4 /*yield*/, (0, changeStreamWatcher_1.emitToDocumentAndQueryRooms)(io, "items", event, { status: "inactive" }, function () { })];
|
|
3640
|
+
case 1:
|
|
3641
|
+
_a.sent();
|
|
3642
|
+
queryEmissions = io.emissions.filter(function (e) { return e.room === "query:".concat(queryId); });
|
|
3643
|
+
(0, bun_test_1.expect)(queryEmissions.length).toBe(1);
|
|
3644
|
+
(0, bun_test_1.expect)(queryEmissions[0].payload).toMatchObject({ method: "delete" });
|
|
3645
|
+
return [2 /*return*/];
|
|
3646
|
+
}
|
|
3647
|
+
});
|
|
3648
|
+
}); });
|
|
3649
|
+
});
|
|
3650
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3651
|
+
// RealtimeApp — onServerCreated, setupAdapter, close
|
|
3652
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3653
|
+
(0, bun_test_1.describe)("RealtimeApp — onServerCreated and setupAdapter", function () {
|
|
3654
|
+
var originalEnv = process.env;
|
|
3655
|
+
(0, bun_test_1.beforeEach)(function () {
|
|
3656
|
+
process.env = __assign(__assign({}, originalEnv), { TOKEN_SECRET: "test-secret" });
|
|
3657
|
+
});
|
|
3658
|
+
(0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3659
|
+
return __generator(this, function (_a) {
|
|
3660
|
+
process.env = originalEnv;
|
|
3661
|
+
(0, registry_1.clearRealtimeRegistry)();
|
|
3662
|
+
return [2 /*return*/];
|
|
3663
|
+
});
|
|
3664
|
+
}); });
|
|
3665
|
+
(0, bun_test_1.it)("register adds /realtime/health endpoint with debug flag", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3666
|
+
var expressApp, app, supertest, st, res;
|
|
3667
|
+
return __generator(this, function (_a) {
|
|
3668
|
+
switch (_a.label) {
|
|
3669
|
+
case 0:
|
|
3670
|
+
expressApp = (0, express_1.default)();
|
|
3671
|
+
app = new realtimeApp_1.RealtimeApp({ debug: true });
|
|
3672
|
+
app.register(expressApp);
|
|
3673
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("supertest")); })];
|
|
3674
|
+
case 1:
|
|
3675
|
+
supertest = _a.sent();
|
|
3676
|
+
st = supertest.default(expressApp);
|
|
3677
|
+
return [4 /*yield*/, st.get("/realtime/health").expect(200)];
|
|
3678
|
+
case 2:
|
|
3679
|
+
res = _a.sent();
|
|
3680
|
+
(0, bun_test_1.expect)(res.body.status).toBe("not_started");
|
|
3681
|
+
(0, bun_test_1.expect)(res.body.debug).toBe(true);
|
|
3682
|
+
(0, bun_test_1.expect)(res.body.clients).toBe(0);
|
|
3683
|
+
return [2 /*return*/];
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
}); });
|
|
3687
|
+
(0, bun_test_1.it)("onServerCreated sets up Socket.io with JWT auth", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3688
|
+
var http, app, expressApp, server;
|
|
3689
|
+
return __generator(this, function (_a) {
|
|
3690
|
+
switch (_a.label) {
|
|
3691
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3692
|
+
case 1:
|
|
3693
|
+
http = _a.sent();
|
|
3694
|
+
app = new realtimeApp_1.RealtimeApp({ debug: true, tokenSecret: "test-secret" });
|
|
3695
|
+
expressApp = (0, express_1.default)();
|
|
3696
|
+
app.register(expressApp);
|
|
3697
|
+
server = http.createServer(expressApp);
|
|
3698
|
+
app.onServerCreated(server);
|
|
3699
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3700
|
+
return [4 /*yield*/, app.close()];
|
|
3701
|
+
case 2:
|
|
3702
|
+
_a.sent();
|
|
3703
|
+
server.close();
|
|
3704
|
+
return [2 /*return*/];
|
|
3705
|
+
}
|
|
3706
|
+
});
|
|
3707
|
+
}); });
|
|
3708
|
+
(0, bun_test_1.it)("onServerCreated throws when TOKEN_SECRET is missing", function () {
|
|
3709
|
+
var http = require("node:http");
|
|
3710
|
+
var origSecret = process.env.TOKEN_SECRET;
|
|
3711
|
+
process.env.TOKEN_SECRET = "";
|
|
3712
|
+
var app = new realtimeApp_1.RealtimeApp({});
|
|
3713
|
+
var expressApp = (0, express_1.default)();
|
|
3714
|
+
app.register(expressApp);
|
|
3715
|
+
var server = http.createServer(expressApp);
|
|
3716
|
+
(0, bun_test_1.expect)(function () { return app.onServerCreated(server); }).toThrow("TOKEN_SECRET is required");
|
|
3717
|
+
process.env.TOKEN_SECRET = origSecret;
|
|
3718
|
+
server.close();
|
|
3719
|
+
});
|
|
3720
|
+
(0, bun_test_1.it)("onServerCreated uses default TOKEN_SECRET from env", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3721
|
+
var http, app, expressApp, server;
|
|
3722
|
+
return __generator(this, function (_a) {
|
|
3723
|
+
switch (_a.label) {
|
|
3724
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3725
|
+
case 1:
|
|
3726
|
+
http = _a.sent();
|
|
3727
|
+
process.env.TOKEN_SECRET = "env-secret";
|
|
3728
|
+
app = new realtimeApp_1.RealtimeApp({ debug: false });
|
|
3729
|
+
expressApp = (0, express_1.default)();
|
|
3730
|
+
app.register(expressApp);
|
|
3731
|
+
server = http.createServer(expressApp);
|
|
3732
|
+
app.onServerCreated(server);
|
|
3733
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3734
|
+
return [4 /*yield*/, app.close()];
|
|
3735
|
+
case 2:
|
|
3736
|
+
_a.sent();
|
|
3737
|
+
server.close();
|
|
3738
|
+
return [2 /*return*/];
|
|
3739
|
+
}
|
|
3740
|
+
});
|
|
3741
|
+
}); });
|
|
3742
|
+
(0, bun_test_1.it)("setupAdapter logs info for redis adapter with URL", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3743
|
+
var http, app, expressApp, server;
|
|
3744
|
+
return __generator(this, function (_a) {
|
|
3745
|
+
switch (_a.label) {
|
|
3746
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3747
|
+
case 1:
|
|
3748
|
+
http = _a.sent();
|
|
3749
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
3750
|
+
adapter: "redis",
|
|
3751
|
+
debug: true,
|
|
3752
|
+
redisUrl: "redis://user:pass@localhost:6379",
|
|
3753
|
+
tokenSecret: "test-secret",
|
|
3754
|
+
});
|
|
3755
|
+
expressApp = (0, express_1.default)();
|
|
3756
|
+
app.register(expressApp);
|
|
3757
|
+
server = http.createServer(expressApp);
|
|
3758
|
+
app.onServerCreated(server);
|
|
3759
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3760
|
+
return [4 /*yield*/, app.close()];
|
|
3761
|
+
case 2:
|
|
3762
|
+
_a.sent();
|
|
3763
|
+
server.close();
|
|
3764
|
+
return [2 /*return*/];
|
|
3765
|
+
}
|
|
3766
|
+
});
|
|
3767
|
+
}); });
|
|
3768
|
+
(0, bun_test_1.it)("setupAdapter warns when redis adapter has no URL", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3769
|
+
var http, origValkey, origRedis, app, expressApp, server;
|
|
3770
|
+
return __generator(this, function (_a) {
|
|
3771
|
+
switch (_a.label) {
|
|
3772
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3773
|
+
case 1:
|
|
3774
|
+
http = _a.sent();
|
|
3775
|
+
origValkey = process.env.VALKEY_URL;
|
|
3776
|
+
origRedis = process.env.REDIS_URL;
|
|
3777
|
+
delete process.env.VALKEY_URL;
|
|
3778
|
+
delete process.env.REDIS_URL;
|
|
3779
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
3780
|
+
adapter: "redis",
|
|
3781
|
+
debug: true,
|
|
3782
|
+
tokenSecret: "test-secret",
|
|
3783
|
+
});
|
|
3784
|
+
expressApp = (0, express_1.default)();
|
|
3785
|
+
app.register(expressApp);
|
|
3786
|
+
server = http.createServer(expressApp);
|
|
3787
|
+
app.onServerCreated(server);
|
|
3788
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3789
|
+
return [4 /*yield*/, app.close()];
|
|
3790
|
+
case 2:
|
|
3791
|
+
_a.sent();
|
|
3792
|
+
server.close();
|
|
3793
|
+
process.env.VALKEY_URL = origValkey;
|
|
3794
|
+
process.env.REDIS_URL = origRedis;
|
|
3795
|
+
return [2 /*return*/];
|
|
3796
|
+
}
|
|
3797
|
+
});
|
|
3798
|
+
}); });
|
|
3799
|
+
(0, bun_test_1.it)("setupAdapter with none adapter does nothing extra", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3800
|
+
var http, app, expressApp, server;
|
|
3801
|
+
return __generator(this, function (_a) {
|
|
3802
|
+
switch (_a.label) {
|
|
3803
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3804
|
+
case 1:
|
|
3805
|
+
http = _a.sent();
|
|
3806
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
3807
|
+
adapter: "none",
|
|
3808
|
+
debug: true,
|
|
3809
|
+
tokenSecret: "test-secret",
|
|
3810
|
+
});
|
|
3811
|
+
expressApp = (0, express_1.default)();
|
|
3812
|
+
app.register(expressApp);
|
|
3813
|
+
server = http.createServer(expressApp);
|
|
3814
|
+
app.onServerCreated(server);
|
|
3815
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3816
|
+
return [4 /*yield*/, app.close()];
|
|
3817
|
+
case 2:
|
|
3818
|
+
_a.sent();
|
|
3819
|
+
server.close();
|
|
3820
|
+
return [2 /*return*/];
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
}); });
|
|
3824
|
+
(0, bun_test_1.it)("close is safe after onServerCreated", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3825
|
+
var http, app, expressApp, server;
|
|
3826
|
+
return __generator(this, function (_a) {
|
|
3827
|
+
switch (_a.label) {
|
|
3828
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3829
|
+
case 1:
|
|
3830
|
+
http = _a.sent();
|
|
3831
|
+
app = new realtimeApp_1.RealtimeApp({ tokenSecret: "test-secret" });
|
|
3832
|
+
expressApp = (0, express_1.default)();
|
|
3833
|
+
app.register(expressApp);
|
|
3834
|
+
server = http.createServer(expressApp);
|
|
3835
|
+
app.onServerCreated(server);
|
|
3836
|
+
return [4 /*yield*/, app.close()];
|
|
3837
|
+
case 2:
|
|
3838
|
+
_a.sent();
|
|
3839
|
+
(0, bun_test_1.expect)(app.getIo()).toBeNull();
|
|
3840
|
+
server.close();
|
|
3841
|
+
return [2 /*return*/];
|
|
3842
|
+
}
|
|
3843
|
+
});
|
|
3844
|
+
}); });
|
|
3845
|
+
(0, bun_test_1.it)("health endpoint reports running after onServerCreated", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3846
|
+
var http, app, expressApp, server, supertest, st, res;
|
|
3847
|
+
return __generator(this, function (_a) {
|
|
3848
|
+
switch (_a.label) {
|
|
3849
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3850
|
+
case 1:
|
|
3851
|
+
http = _a.sent();
|
|
3852
|
+
app = new realtimeApp_1.RealtimeApp({ tokenSecret: "test-secret" });
|
|
3853
|
+
expressApp = (0, express_1.default)();
|
|
3854
|
+
app.register(expressApp);
|
|
3855
|
+
server = http.createServer(expressApp);
|
|
3856
|
+
app.onServerCreated(server);
|
|
3857
|
+
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("supertest")); })];
|
|
3858
|
+
case 2:
|
|
3859
|
+
supertest = _a.sent();
|
|
3860
|
+
st = supertest.default(expressApp);
|
|
3861
|
+
return [4 /*yield*/, st.get("/realtime/health").expect(200)];
|
|
3862
|
+
case 3:
|
|
3863
|
+
res = _a.sent();
|
|
3864
|
+
(0, bun_test_1.expect)(res.body.status).toBe("running");
|
|
3865
|
+
return [4 /*yield*/, app.close()];
|
|
3866
|
+
case 4:
|
|
3867
|
+
_a.sent();
|
|
3868
|
+
server.close();
|
|
3869
|
+
return [2 /*return*/];
|
|
3870
|
+
}
|
|
3871
|
+
});
|
|
3872
|
+
}); });
|
|
3873
|
+
(0, bun_test_1.it)("onServerCreated with custom cors option", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3874
|
+
var http, app, expressApp, server;
|
|
3875
|
+
return __generator(this, function (_a) {
|
|
3876
|
+
switch (_a.label) {
|
|
3877
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3878
|
+
case 1:
|
|
3879
|
+
http = _a.sent();
|
|
3880
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
3881
|
+
cors: { methods: ["GET"], origin: "https://example.com" },
|
|
3882
|
+
tokenSecret: "test-secret",
|
|
3883
|
+
});
|
|
3884
|
+
expressApp = (0, express_1.default)();
|
|
3885
|
+
app.register(expressApp);
|
|
3886
|
+
server = http.createServer(expressApp);
|
|
3887
|
+
app.onServerCreated(server);
|
|
3888
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3889
|
+
return [4 /*yield*/, app.close()];
|
|
3890
|
+
case 2:
|
|
3891
|
+
_a.sent();
|
|
3892
|
+
server.close();
|
|
3893
|
+
return [2 /*return*/];
|
|
3894
|
+
}
|
|
3895
|
+
});
|
|
3896
|
+
}); });
|
|
3897
|
+
(0, bun_test_1.it)("setupAdapter uses VALKEY_URL when redisUrl not provided", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
3898
|
+
var http, app, expressApp, server;
|
|
3899
|
+
return __generator(this, function (_a) {
|
|
3900
|
+
switch (_a.label) {
|
|
3901
|
+
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("node:http")); })];
|
|
3902
|
+
case 1:
|
|
3903
|
+
http = _a.sent();
|
|
3904
|
+
process.env.VALKEY_URL = "redis://localhost:6379";
|
|
3905
|
+
app = new realtimeApp_1.RealtimeApp({
|
|
3906
|
+
adapter: "redis",
|
|
3907
|
+
debug: true,
|
|
3908
|
+
tokenSecret: "test-secret",
|
|
3909
|
+
});
|
|
3910
|
+
expressApp = (0, express_1.default)();
|
|
3911
|
+
app.register(expressApp);
|
|
3912
|
+
server = http.createServer(expressApp);
|
|
3913
|
+
app.onServerCreated(server);
|
|
3914
|
+
(0, bun_test_1.expect)(app.getIo()).not.toBeNull();
|
|
3915
|
+
return [4 /*yield*/, app.close()];
|
|
3916
|
+
case 2:
|
|
3917
|
+
_a.sent();
|
|
3918
|
+
server.close();
|
|
3919
|
+
delete process.env.VALKEY_URL;
|
|
3920
|
+
return [2 /*return*/];
|
|
3921
|
+
}
|
|
3922
|
+
});
|
|
3923
|
+
}); });
|
|
3924
|
+
});
|
|
3925
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3926
|
+
// redactCredentials — Redis URL logging
|
|
3927
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2143
3928
|
(0, bun_test_1.describe)("redactCredentials", function () {
|
|
2144
3929
|
(0, bun_test_1.it)("redacts user:password@ in a redis URL", function () {
|
|
2145
3930
|
(0, bun_test_1.expect)((0, realtimeApp_1.redactCredentials)("redis://user:secret@host:6379/0")).toBe("redis://***@host:6379/0");
|