@terreno/api 0.21.0 → 0.22.0

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.
Files changed (51) hide show
  1. package/bunfig.toml +1 -1
  2. package/dist/auth.test.js +408 -33
  3. package/dist/models/consentForm.js +2 -1
  4. package/dist/models/consentResponse.js +2 -1
  5. package/dist/models/versionConfig.js +2 -1
  6. package/dist/openApiBuilder.d.ts +18 -0
  7. package/dist/openApiBuilder.js +21 -0
  8. package/dist/openApiBuilder.test.js +16 -0
  9. package/dist/permissions.test.js +10 -43
  10. package/dist/populate.test.js +10 -42
  11. package/dist/syncConsents.test.js +2 -2
  12. package/dist/tests/bunSetup.js +33 -283
  13. package/dist/tests/createTestData.d.ts +9 -0
  14. package/dist/tests/createTestData.js +272 -0
  15. package/dist/tests/models.d.ts +71 -0
  16. package/dist/tests/models.js +134 -0
  17. package/dist/tests/mongoTestSetup.d.ts +7 -0
  18. package/dist/tests/mongoTestSetup.js +150 -0
  19. package/dist/tests/testEnv.d.ts +0 -0
  20. package/dist/tests/testEnv.js +6 -0
  21. package/dist/tests/testHelper.d.ts +22 -0
  22. package/dist/tests/testHelper.js +115 -0
  23. package/dist/tests/types.d.ts +29 -0
  24. package/dist/tests/types.js +2 -0
  25. package/dist/tests.d.ts +10 -78
  26. package/dist/tests.js +24 -264
  27. package/dist/transformers.test.js +14 -50
  28. package/package.json +18 -4
  29. package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
  30. package/src/auth.test.ts +277 -29
  31. package/src/models/consentForm.ts +3 -4
  32. package/src/models/consentResponse.ts +6 -4
  33. package/src/models/versionConfig.ts +3 -4
  34. package/src/openApiBuilder.test.ts +9 -0
  35. package/src/openApiBuilder.ts +24 -0
  36. package/src/permissions.test.ts +8 -23
  37. package/src/populate.test.ts +7 -22
  38. package/src/syncConsents.test.ts +1 -1
  39. package/src/tests/bunSetup.ts +22 -249
  40. package/src/tests/createTestData.ts +176 -0
  41. package/src/tests/models.ts +164 -0
  42. package/src/tests/mongoTestSetup.ts +69 -0
  43. package/src/tests/testEnv.ts +4 -0
  44. package/src/tests/testHelper.ts +57 -0
  45. package/src/tests/types.ts +35 -0
  46. package/src/tests.ts +40 -244
  47. package/src/transformers.test.ts +11 -30
  48. package/tsconfig.typedoc.json +4 -0
  49. package/dist/tests/index.d.ts +0 -1
  50. package/dist/tests/index.js +0 -17
  51. package/src/tests/index.ts +0 -1
package/bunfig.toml CHANGED
@@ -1,5 +1,5 @@
1
1
  [test]
2
- preload = ["./src/tests/bunSetup.ts"]
2
+ preload = ["./src/tests/testEnv.ts", "./src/tests/bunSetup.ts"]
3
3
  root = "./src"
4
4
  coverage = true
5
5
  coveragePathIgnorePatterns = ["dist/**", "**/*.test.ts", "**/tests/**", "**/vendor/**", "../**"]
package/dist/auth.test.js CHANGED
@@ -118,7 +118,6 @@ var decodeTokenPayload = function (token) {
118
118
  var app;
119
119
  var admin;
120
120
  var contextEvents;
121
- var notAdmin;
122
121
  var agent;
123
122
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
124
123
  function addRoutes(router) {
@@ -202,40 +201,18 @@ var decodeTokenPayload = function (token) {
202
201
  }); },
203
202
  }));
204
203
  }
205
- var _a;
206
- return __generator(this, function (_b) {
207
- switch (_b.label) {
204
+ var testData;
205
+ return __generator(this, function (_a) {
206
+ switch (_a.label) {
208
207
  case 0:
209
208
  // Reset to real time - don't freeze time here as passport-local-mongoose
210
209
  // lockout mechanism needs real time to progress
211
210
  (0, bun_test_1.setSystemTime)();
212
- return [4 /*yield*/, (0, tests_1.setupDb)()];
211
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
213
212
  case 1:
214
- _a = __read.apply(void 0, [_b.sent(), 2]), admin = _a[0], notAdmin = _a[1];
213
+ testData = _a.sent();
214
+ admin = testData.users.admin;
215
215
  contextEvents = [];
216
- return [4 /*yield*/, Promise.all([
217
- tests_1.FoodModel.create({
218
- calories: 1,
219
- created: new Date(),
220
- name: "Spinach",
221
- ownerId: notAdmin._id,
222
- }),
223
- tests_1.FoodModel.create({
224
- calories: 100,
225
- created: Date.now() - 10,
226
- hidden: true,
227
- name: "Apple",
228
- ownerId: admin._id,
229
- }),
230
- tests_1.FoodModel.create({
231
- calories: 100,
232
- created: Date.now() - 10,
233
- name: "Carrots",
234
- ownerId: admin._id,
235
- }),
236
- ])];
237
- case 2:
238
- _b.sent();
239
216
  app = new terrenoApp_1.TerrenoApp({
240
217
  configureApp: addRoutes,
241
218
  skipListen: true,
@@ -315,7 +292,7 @@ var decodeTokenPayload = function (token) {
315
292
  return [4 /*yield*/, agent.get("/food").expect(200)];
316
293
  case 7:
317
294
  getRes = _b.sent();
318
- (0, bun_test_1.expect)(getRes.body.data).toHaveLength(3);
295
+ (0, bun_test_1.expect)(getRes.body.data).toHaveLength(4);
319
296
  (0, bun_test_1.expect)(getRes.body.data.find(function (f) { return f.name === "Peas"; })).toBeDefined();
320
297
  return [4 /*yield*/, agent
321
298
  .patch("/food/".concat(food._id))
@@ -547,7 +524,7 @@ var decodeTokenPayload = function (token) {
547
524
  return [4 /*yield*/, agent.get("/food").expect(200)];
548
525
  case 5:
549
526
  getRes = _b.sent();
550
- (0, bun_test_1.expect)(getRes.body.data).toHaveLength(3);
527
+ (0, bun_test_1.expect)(getRes.body.data).toHaveLength(4);
551
528
  food = getRes.body.data.find(function (f) { return f.name === "Apple"; });
552
529
  (0, bun_test_1.expect)(food).toBeDefined();
553
530
  return [4 /*yield*/, agent
@@ -1155,7 +1132,7 @@ var decodeTokenPayload = function (token) {
1155
1132
  switch (_a.label) {
1156
1133
  case 0:
1157
1134
  (0, bun_test_1.setSystemTime)();
1158
- return [4 /*yield*/, (0, tests_1.setupDb)()];
1135
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1159
1136
  case 1:
1160
1137
  _a.sent();
1161
1138
  app = new terrenoApp_1.TerrenoApp({
@@ -1265,7 +1242,7 @@ var decodeTokenPayload = function (token) {
1265
1242
  switch (_a.label) {
1266
1243
  case 0:
1267
1244
  (0, bun_test_1.setSystemTime)();
1268
- return [4 /*yield*/, (0, tests_1.setupDb)()];
1245
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1269
1246
  case 1:
1270
1247
  _a.sent();
1271
1248
  app = new terrenoApp_1.TerrenoApp({
@@ -1326,4 +1303,402 @@ var decodeTokenPayload = function (token) {
1326
1303
  }
1327
1304
  });
1328
1305
  }); });
1306
+ (0, bun_test_1.it)("PATCH /auth/me returns 404 when user is deleted after auth", function () { return __awaiter(void 0, void 0, void 0, function () {
1307
+ var _a, _admin, notAdmin, jwtLib, notAdminId, token, res;
1308
+ return __generator(this, function (_b) {
1309
+ switch (_b.label) {
1310
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1311
+ case 1:
1312
+ _a = __read.apply(void 0, [_b.sent(), 2]), _admin = _a[0], notAdmin = _a[1];
1313
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1314
+ case 2:
1315
+ jwtLib = (_b.sent()).default;
1316
+ notAdminId = notAdmin._id;
1317
+ token = jwtLib.sign({ id: notAdminId.toString() }, process.env.TOKEN_SECRET, {
1318
+ issuer: process.env.TOKEN_ISSUER,
1319
+ });
1320
+ return [4 /*yield*/, tests_1.UserModel.deleteOne({ _id: notAdminId })];
1321
+ case 3:
1322
+ _b.sent();
1323
+ return [4 /*yield*/, agent
1324
+ .patch("/auth/me")
1325
+ .set("authorization", "Bearer ".concat(token))
1326
+ .send({ email: "x@x.com" })];
1327
+ case 4:
1328
+ res = _b.sent();
1329
+ (0, bun_test_1.expect)([401, 404]).toContain(res.status);
1330
+ return [2 /*return*/];
1331
+ }
1332
+ });
1333
+ }); });
1334
+ (0, bun_test_1.it)("PATCH /auth/me returns 403 on validation error", function () { return __awaiter(void 0, void 0, void 0, function () {
1335
+ var _a, admin, jwtLib, adminId, token, res;
1336
+ return __generator(this, function (_b) {
1337
+ switch (_b.label) {
1338
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1339
+ case 1:
1340
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
1341
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1342
+ case 2:
1343
+ jwtLib = (_b.sent()).default;
1344
+ adminId = admin._id;
1345
+ token = jwtLib.sign({ id: adminId.toString() }, process.env.TOKEN_SECRET, {
1346
+ issuer: process.env.TOKEN_ISSUER,
1347
+ });
1348
+ return [4 /*yield*/, agent
1349
+ .patch("/auth/me")
1350
+ .set("authorization", "Bearer ".concat(token))
1351
+ .send({ admin: "not_a_boolean_value_but_will_be_cast" })];
1352
+ case 3:
1353
+ res = _b.sent();
1354
+ (0, bun_test_1.expect)([200, 403]).toContain(res.status);
1355
+ return [2 /*return*/];
1356
+ }
1357
+ });
1358
+ }); });
1359
+ });
1360
+ (0, bun_test_1.describe)("Secret prefix authorization bypass", function () {
1361
+ var app;
1362
+ var agent;
1363
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1364
+ return __generator(this, function (_a) {
1365
+ switch (_a.label) {
1366
+ case 0:
1367
+ (0, bun_test_1.setSystemTime)();
1368
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1369
+ case 1:
1370
+ _a.sent();
1371
+ app = new terrenoApp_1.TerrenoApp({
1372
+ configureApp: function (router) {
1373
+ router.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1374
+ allowAnonymous: true,
1375
+ permissions: {
1376
+ create: [],
1377
+ delete: [],
1378
+ list: [permissions_1.Permissions.IsAny],
1379
+ read: [permissions_1.Permissions.IsAny],
1380
+ update: [],
1381
+ },
1382
+ }));
1383
+ },
1384
+ skipListen: true,
1385
+ userModel: tests_1.UserModel,
1386
+ }).build();
1387
+ agent = supertest_1.default.agent(app);
1388
+ return [2 /*return*/];
1389
+ }
1390
+ });
1391
+ }); });
1392
+ (0, bun_test_1.afterEach)(function () {
1393
+ (0, bun_test_1.setSystemTime)();
1394
+ });
1395
+ (0, bun_test_1.it)("passes through with Secret prefix authorization header without JWT decoding", function () { return __awaiter(void 0, void 0, void 0, function () {
1396
+ var res;
1397
+ return __generator(this, function (_a) {
1398
+ switch (_a.label) {
1399
+ case 0: return [4 /*yield*/, agent.get("/food").set("authorization", "Secret my-secret-token").expect(200)];
1400
+ case 1:
1401
+ res = _a.sent();
1402
+ (0, bun_test_1.expect)(res.body.data).toBeDefined();
1403
+ return [2 /*return*/];
1404
+ }
1405
+ });
1406
+ }); });
1407
+ });
1408
+ (0, bun_test_1.describe)("generateTokens env integration", function () {
1409
+ var OLD_ENV = process.env;
1410
+ (0, bun_test_1.beforeEach)(function () {
1411
+ process.env = __assign({}, OLD_ENV);
1412
+ process.env.TOKEN_SECRET = "secret";
1413
+ process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
1414
+ });
1415
+ (0, bun_test_1.afterEach)(function () {
1416
+ process.env = OLD_ENV;
1417
+ });
1418
+ (0, bun_test_1.it)("includes TOKEN_ISSUER in token when set", function () { return __awaiter(void 0, void 0, void 0, function () {
1419
+ var result, decoded;
1420
+ return __generator(this, function (_a) {
1421
+ switch (_a.label) {
1422
+ case 0:
1423
+ process.env.TOKEN_ISSUER = "test-issuer";
1424
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1425
+ case 1:
1426
+ result = _a.sent();
1427
+ decoded = decodeTokenPayload(result.token);
1428
+ (0, bun_test_1.expect)(decoded.iss).toBe("test-issuer");
1429
+ return [2 /*return*/];
1430
+ }
1431
+ });
1432
+ }); });
1433
+ (0, bun_test_1.it)("generates a unique sessionId when none provided", function () { return __awaiter(void 0, void 0, void 0, function () {
1434
+ var result1, result2;
1435
+ return __generator(this, function (_a) {
1436
+ switch (_a.label) {
1437
+ case 0: return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1438
+ case 1:
1439
+ result1 = _a.sent();
1440
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1441
+ case 2:
1442
+ result2 = _a.sent();
1443
+ (0, bun_test_1.expect)(result1.sessionId).toBeDefined();
1444
+ (0, bun_test_1.expect)(result2.sessionId).toBeDefined();
1445
+ (0, bun_test_1.expect)(result1.sessionId).not.toBe(result2.sessionId);
1446
+ return [2 /*return*/];
1447
+ }
1448
+ });
1449
+ }); });
1450
+ (0, bun_test_1.it)("uses provided sessionId from options", function () { return __awaiter(void 0, void 0, void 0, function () {
1451
+ var result, decoded;
1452
+ return __generator(this, function (_a) {
1453
+ switch (_a.label) {
1454
+ case 0: return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" }, undefined, {
1455
+ sessionId: "custom-session-id",
1456
+ })];
1457
+ case 1:
1458
+ result = _a.sent();
1459
+ decoded = decodeTokenPayload(result.token);
1460
+ (0, bun_test_1.expect)(decoded.sid).toBe("custom-session-id");
1461
+ (0, bun_test_1.expect)(result.sessionId).toBe("custom-session-id");
1462
+ return [2 /*return*/];
1463
+ }
1464
+ });
1465
+ }); });
1466
+ });
1467
+ (0, bun_test_1.describe)("refresh_token without REFRESH_TOKEN_SECRET", function () {
1468
+ var app;
1469
+ var agent;
1470
+ var OLD_ENV = process.env;
1471
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1472
+ return __generator(this, function (_a) {
1473
+ switch (_a.label) {
1474
+ case 0:
1475
+ (0, bun_test_1.setSystemTime)();
1476
+ process.env = __assign({}, OLD_ENV);
1477
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1478
+ case 1:
1479
+ _a.sent();
1480
+ app = new terrenoApp_1.TerrenoApp({
1481
+ configureApp: function () { },
1482
+ skipListen: true,
1483
+ userModel: tests_1.UserModel,
1484
+ }).build();
1485
+ agent = supertest_1.default.agent(app);
1486
+ return [2 /*return*/];
1487
+ }
1488
+ });
1489
+ }); });
1490
+ (0, bun_test_1.afterEach)(function () {
1491
+ (0, bun_test_1.setSystemTime)();
1492
+ process.env = OLD_ENV;
1493
+ });
1494
+ (0, bun_test_1.it)("returns 401 when REFRESH_TOKEN_SECRET is not set", function () { return __awaiter(void 0, void 0, void 0, function () {
1495
+ var res;
1496
+ return __generator(this, function (_a) {
1497
+ switch (_a.label) {
1498
+ case 0:
1499
+ process.env.REFRESH_TOKEN_SECRET = "";
1500
+ return [4 /*yield*/, agent
1501
+ .post("/auth/refresh_token")
1502
+ .send({ refreshToken: "some-token" })
1503
+ .expect(401)];
1504
+ case 1:
1505
+ res = _a.sent();
1506
+ (0, bun_test_1.expect)(res.body.message).toContain("No REFRESH_TOKEN_SECRET set");
1507
+ return [2 /*return*/];
1508
+ }
1509
+ });
1510
+ }); });
1511
+ });
1512
+ (0, bun_test_1.describe)("generateTokens with custom TOKEN_EXPIRES_IN", function () {
1513
+ var OLD_ENV = process.env;
1514
+ (0, bun_test_1.beforeEach)(function () {
1515
+ process.env = __assign({}, OLD_ENV);
1516
+ process.env.TOKEN_SECRET = "secret";
1517
+ process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
1518
+ });
1519
+ (0, bun_test_1.afterEach)(function () {
1520
+ process.env = OLD_ENV;
1521
+ });
1522
+ (0, bun_test_1.it)("uses TOKEN_EXPIRES_IN when set to a valid duration", function () { return __awaiter(void 0, void 0, void 0, function () {
1523
+ var result, decoded, diffSeconds;
1524
+ return __generator(this, function (_a) {
1525
+ switch (_a.label) {
1526
+ case 0:
1527
+ process.env.TOKEN_EXPIRES_IN = "1h";
1528
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1529
+ case 1:
1530
+ result = _a.sent();
1531
+ (0, bun_test_1.expect)(result.token).toBeDefined();
1532
+ decoded = decodeTokenPayload(result.token);
1533
+ diffSeconds = decoded.exp - decoded.iat;
1534
+ // 1h = 3600s
1535
+ (0, bun_test_1.expect)(diffSeconds).toBe(3600);
1536
+ return [2 /*return*/];
1537
+ }
1538
+ });
1539
+ }); });
1540
+ (0, bun_test_1.it)("uses REFRESH_TOKEN_EXPIRES_IN when set to a valid duration", function () { return __awaiter(void 0, void 0, void 0, function () {
1541
+ var result, decoded, diffSeconds;
1542
+ return __generator(this, function (_a) {
1543
+ switch (_a.label) {
1544
+ case 0:
1545
+ process.env.REFRESH_TOKEN_EXPIRES_IN = "7d";
1546
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
1547
+ case 1:
1548
+ result = _a.sent();
1549
+ (0, bun_test_1.expect)(result.refreshToken).toBeDefined();
1550
+ decoded = decodeTokenPayload(result.refreshToken);
1551
+ diffSeconds = decoded.exp - decoded.iat;
1552
+ // 7d = 604800s
1553
+ (0, bun_test_1.expect)(diffSeconds).toBe(604800);
1554
+ return [2 /*return*/];
1555
+ }
1556
+ });
1557
+ }); });
1558
+ });
1559
+ (0, bun_test_1.describe)("JWT cookie extraction and /me routes edge cases", function () {
1560
+ var app;
1561
+ var agent;
1562
+ var OLD_ENV = process.env;
1563
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1564
+ return __generator(this, function (_a) {
1565
+ switch (_a.label) {
1566
+ case 0:
1567
+ (0, bun_test_1.setSystemTime)();
1568
+ process.env = __assign({}, OLD_ENV);
1569
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1570
+ case 1:
1571
+ _a.sent();
1572
+ app = new terrenoApp_1.TerrenoApp({
1573
+ configureApp: function () { },
1574
+ skipListen: true,
1575
+ userModel: tests_1.UserModel,
1576
+ }).build();
1577
+ agent = supertest_1.default.agent(app);
1578
+ return [2 /*return*/];
1579
+ }
1580
+ });
1581
+ }); });
1582
+ (0, bun_test_1.afterEach)(function () {
1583
+ (0, bun_test_1.setSystemTime)();
1584
+ process.env = OLD_ENV;
1585
+ });
1586
+ (0, bun_test_1.it)("returns 401 for /me when no user is authenticated", function () { return __awaiter(void 0, void 0, void 0, function () {
1587
+ var res;
1588
+ return __generator(this, function (_a) {
1589
+ switch (_a.label) {
1590
+ case 0: return [4 /*yield*/, agent.get("/auth/me").expect(401)];
1591
+ case 1:
1592
+ res = _a.sent();
1593
+ (0, bun_test_1.expect)(res.status).toBe(401);
1594
+ return [2 /*return*/];
1595
+ }
1596
+ });
1597
+ }); });
1598
+ (0, bun_test_1.it)("returns 401 for PATCH /me when no user is authenticated", function () { return __awaiter(void 0, void 0, void 0, function () {
1599
+ var res;
1600
+ return __generator(this, function (_a) {
1601
+ switch (_a.label) {
1602
+ case 0: return [4 /*yield*/, agent.patch("/auth/me").send({ name: "Updated" }).expect(401)];
1603
+ case 1:
1604
+ res = _a.sent();
1605
+ (0, bun_test_1.expect)(res.status).toBe(401);
1606
+ return [2 /*return*/];
1607
+ }
1608
+ });
1609
+ }); });
1610
+ (0, bun_test_1.it)("returns 404 for /me when user is deleted from database", function () { return __awaiter(void 0, void 0, void 0, function () {
1611
+ var loginRes, _a, token, userId, freshAgent, res;
1612
+ return __generator(this, function (_b) {
1613
+ switch (_b.label) {
1614
+ case 0: return [4 /*yield*/, agent
1615
+ .post("/auth/login")
1616
+ .send({ email: "notAdmin@example.com", password: "password" })
1617
+ .expect(200)];
1618
+ case 1:
1619
+ loginRes = _b.sent();
1620
+ _a = loginRes.body.data, token = _a.token, userId = _a.userId;
1621
+ // Delete the user from DB
1622
+ return [4 /*yield*/, tests_1.UserModel.deleteOne({ _id: userId })];
1623
+ case 2:
1624
+ // Delete the user from DB
1625
+ _b.sent();
1626
+ freshAgent = supertest_1.default.agent(app);
1627
+ return [4 /*yield*/, freshAgent.get("/auth/me").set("authorization", "Bearer ".concat(token))];
1628
+ case 3:
1629
+ res = _b.sent();
1630
+ // Without the user, the JWT verify succeeds but findById returns null
1631
+ (0, bun_test_1.expect)([401, 404]).toContain(res.status);
1632
+ return [2 /*return*/];
1633
+ }
1634
+ });
1635
+ }); });
1636
+ });
1637
+ (0, bun_test_1.describe)("login error and disabled user paths", function () {
1638
+ var app;
1639
+ var agent;
1640
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1641
+ return __generator(this, function (_a) {
1642
+ switch (_a.label) {
1643
+ case 0:
1644
+ (0, bun_test_1.setSystemTime)();
1645
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
1646
+ case 1:
1647
+ _a.sent();
1648
+ app = new terrenoApp_1.TerrenoApp({
1649
+ configureApp: function () { },
1650
+ skipListen: true,
1651
+ userModel: tests_1.UserModel,
1652
+ }).build();
1653
+ agent = supertest_1.default.agent(app);
1654
+ return [2 /*return*/];
1655
+ }
1656
+ });
1657
+ }); });
1658
+ (0, bun_test_1.afterEach)(function () {
1659
+ (0, bun_test_1.setSystemTime)();
1660
+ });
1661
+ (0, bun_test_1.it)("returns 401 with message for invalid credentials (no user found)", function () { return __awaiter(void 0, void 0, void 0, function () {
1662
+ var res;
1663
+ return __generator(this, function (_a) {
1664
+ switch (_a.label) {
1665
+ case 0: return [4 /*yield*/, agent
1666
+ .post("/auth/login")
1667
+ .send({ email: "nonexistent@example.com", password: "wrong" })
1668
+ .expect(401)];
1669
+ case 1:
1670
+ res = _a.sent();
1671
+ (0, bun_test_1.expect)(res.body.message).toBeDefined();
1672
+ return [2 /*return*/];
1673
+ }
1674
+ });
1675
+ }); });
1676
+ (0, bun_test_1.it)("returns 401 when disabled user tries to access protected route", function () { return __awaiter(void 0, void 0, void 0, function () {
1677
+ var loginRes, _a, token, userId, freshAgent, res;
1678
+ return __generator(this, function (_b) {
1679
+ switch (_b.label) {
1680
+ case 0: return [4 /*yield*/, agent
1681
+ .post("/auth/login")
1682
+ .send({ email: "notAdmin@example.com", password: "password" })
1683
+ .expect(200)];
1684
+ case 1:
1685
+ loginRes = _b.sent();
1686
+ _a = loginRes.body.data, token = _a.token, userId = _a.userId;
1687
+ // Disable the user
1688
+ return [4 /*yield*/, tests_1.UserModel.findByIdAndUpdate(userId, { disabled: true })];
1689
+ case 2:
1690
+ // Disable the user
1691
+ _b.sent();
1692
+ freshAgent = supertest_1.default.agent(app);
1693
+ return [4 /*yield*/, freshAgent
1694
+ .get("/auth/me")
1695
+ .set("authorization", "Bearer ".concat(token))
1696
+ .expect(401)];
1697
+ case 3:
1698
+ res = _b.sent();
1699
+ (0, bun_test_1.expect)(res.body.title).toContain("disabled");
1700
+ return [2 /*return*/];
1701
+ }
1702
+ });
1703
+ }); });
1329
1704
  });
@@ -2,6 +2,7 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.ConsentForm = void 0;
7
8
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -120,4 +121,4 @@ consentFormSchema.plugin(plugins_1.createdUpdatedPlugin);
120
121
  consentFormSchema.plugin(plugins_1.isDeletedPlugin);
121
122
  consentFormSchema.plugin(plugins_1.findOneOrNone);
122
123
  consentFormSchema.plugin(plugins_1.findExactlyOne);
123
- exports.ConsentForm = mongoose_1.default.model("ConsentForm", consentFormSchema);
124
+ exports.ConsentForm = (_a = mongoose_1.default.models.ConsentForm) !== null && _a !== void 0 ? _a : mongoose_1.default.model("ConsentForm", consentFormSchema);
@@ -2,6 +2,7 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.ConsentResponse = void 0;
7
8
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -70,4 +71,4 @@ consentResponseSchema.plugin(plugins_1.createdUpdatedPlugin);
70
71
  consentResponseSchema.plugin(plugins_1.isDeletedPlugin);
71
72
  consentResponseSchema.plugin(plugins_1.findOneOrNone);
72
73
  consentResponseSchema.plugin(plugins_1.findExactlyOne);
73
- exports.ConsentResponse = mongoose_1.default.model("ConsentResponse", consentResponseSchema);
74
+ exports.ConsentResponse = (_a = mongoose_1.default.models.ConsentResponse) !== null && _a !== void 0 ? _a : mongoose_1.default.model("ConsentResponse", consentResponseSchema);
@@ -2,6 +2,7 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.VersionConfig = void 0;
7
8
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -71,4 +72,4 @@ versionConfigSchema.plugin(plugins_1.createdUpdatedPlugin);
71
72
  versionConfigSchema.plugin(plugins_1.isDeletedPlugin);
72
73
  versionConfigSchema.plugin(plugins_1.findOneOrNone);
73
74
  versionConfigSchema.plugin(plugins_1.findExactlyOne);
74
- exports.VersionConfig = mongoose_1.default.model("VersionConfig", versionConfigSchema);
75
+ exports.VersionConfig = (_a = mongoose_1.default.models.VersionConfig) !== null && _a !== void 0 ? _a : mongoose_1.default.model("VersionConfig", versionConfigSchema);
@@ -272,6 +272,24 @@ export declare class OpenApiMiddlewareBuilder {
272
272
  * ```
273
273
  */
274
274
  withSummary(summary: string): this;
275
+ /**
276
+ * Sets an explicit `operationId` for the OpenAPI operation.
277
+ *
278
+ * The `operationId` is a unique string used to identify an operation. Client and SDK
279
+ * generators (e.g. RTK Query codegen) derive generated function and hook names from it,
280
+ * so setting it keeps generated names stable and readable for routes whose URL path would
281
+ * otherwise produce unwieldy names (e.g. deeply nested routes). It must be unique across
282
+ * the whole OpenAPI document.
283
+ *
284
+ * @param operationId - Unique operation identifier (e.g. "getUserStats")
285
+ * @returns The builder instance for chaining
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * builder.withOperationId("getUserStats");
290
+ * ```
291
+ */
292
+ withOperationId(operationId: string): this;
275
293
  /**
276
294
  * Sets the description for the OpenAPI operation.
277
295
  *
@@ -121,6 +121,27 @@ var OpenApiMiddlewareBuilder = /** @class */ (function () {
121
121
  this.config.summary = summary;
122
122
  return this;
123
123
  };
124
+ /**
125
+ * Sets an explicit `operationId` for the OpenAPI operation.
126
+ *
127
+ * The `operationId` is a unique string used to identify an operation. Client and SDK
128
+ * generators (e.g. RTK Query codegen) derive generated function and hook names from it,
129
+ * so setting it keeps generated names stable and readable for routes whose URL path would
130
+ * otherwise produce unwieldy names (e.g. deeply nested routes). It must be unique across
131
+ * the whole OpenAPI document.
132
+ *
133
+ * @param operationId - Unique operation identifier (e.g. "getUserStats")
134
+ * @returns The builder instance for chaining
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * builder.withOperationId("getUserStats");
139
+ * ```
140
+ */
141
+ OpenApiMiddlewareBuilder.prototype.withOperationId = function (operationId) {
142
+ this.config.operationId = operationId;
143
+ return this;
144
+ };
124
145
  /**
125
146
  * Sets the description for the OpenAPI operation.
126
147
  *
@@ -64,6 +64,7 @@ function addRoutesWithBuilder(router, options) {
64
64
  var statsMiddleware = (0, openApiBuilder_1.createOpenApiBuilder)(options !== null && options !== void 0 ? options : {})
65
65
  .withTags(["Stats"])
66
66
  .withSummary("Get food statistics")
67
+ .withOperationId("getFoodStats")
67
68
  .withDescription("Returns aggregated statistics about food items")
68
69
  .withQueryParameter("category", { type: "string" }, {
69
70
  description: "Filter by food category",
@@ -220,6 +221,21 @@ function addRoutesWithBuilder(router, options) {
220
221
  }
221
222
  });
222
223
  }); });
224
+ (0, bun_test_1.it)("includes the explicit operationId in OpenAPI spec", function () { return __awaiter(void 0, void 0, void 0, function () {
225
+ var res, statsPath;
226
+ return __generator(this, function (_a) {
227
+ switch (_a.label) {
228
+ case 0:
229
+ server = (0, supertest_1.default)(app);
230
+ return [4 /*yield*/, server.get("/openapi.json").expect(200)];
231
+ case 1:
232
+ res = _a.sent();
233
+ statsPath = res.body.paths["/food/stats"];
234
+ (0, bun_test_1.expect)(statsPath.get.operationId).toBe("getFoodStats");
235
+ return [2 /*return*/];
236
+ }
237
+ });
238
+ }); });
223
239
  (0, bun_test_1.it)("includes request body schema in OpenAPI spec", function () { return __awaiter(void 0, void 0, void 0, function () {
224
240
  var res, reportsPath, requestBody, schema;
225
241
  return __generator(this, function (_a) {