@terreno/api 0.20.2 → 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 (107) hide show
  1. package/.ai/guidelines/core.md +71 -0
  2. package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
  3. package/README.md +54 -1
  4. package/bunfig.toml +1 -1
  5. package/dist/__tests__/versionCheckPlugin.test.js +29 -7
  6. package/dist/actions.openApi.test.js +13 -11
  7. package/dist/api.js +98 -11
  8. package/dist/api.query.test.js +31 -1
  9. package/dist/api.test.js +211 -0
  10. package/dist/auth.test.js +418 -43
  11. package/dist/betterAuth.d.ts +1 -1
  12. package/dist/consentApp.test.js +1 -0
  13. package/dist/example.js +4 -4
  14. package/dist/expressServer.d.ts +0 -22
  15. package/dist/expressServer.js +1 -125
  16. package/dist/expressServer.test.js +90 -91
  17. package/dist/githubAuth.test.js +22 -22
  18. package/dist/logger.d.ts +154 -0
  19. package/dist/logger.js +445 -26
  20. package/dist/logger.test.js +435 -0
  21. package/dist/middleware.d.ts +7 -0
  22. package/dist/middleware.js +58 -1
  23. package/dist/middleware.test.js +159 -0
  24. package/dist/models/consentForm.js +2 -1
  25. package/dist/models/consentResponse.js +2 -1
  26. package/dist/models/versionConfig.js +2 -1
  27. package/dist/openApi.test.js +10 -17
  28. package/dist/openApiBuilder.d.ts +18 -0
  29. package/dist/openApiBuilder.js +21 -0
  30. package/dist/openApiBuilder.test.js +34 -10
  31. package/dist/permissions.test.js +10 -43
  32. package/dist/populate.test.js +10 -42
  33. package/dist/realtime/changeStreamWatcher.d.ts +4 -4
  34. package/dist/realtime/changeStreamWatcher.js +2 -4
  35. package/dist/realtime/queryMatcher.d.ts +1 -1
  36. package/dist/realtime/queryMatcher.js +39 -14
  37. package/dist/realtime/types.d.ts +3 -3
  38. package/dist/requestContext.d.ts +61 -0
  39. package/dist/requestContext.js +74 -0
  40. package/dist/secretProviders.test.js +335 -0
  41. package/dist/syncConsents.test.js +2 -2
  42. package/dist/terrenoApp.d.ts +27 -15
  43. package/dist/terrenoApp.js +24 -14
  44. package/dist/terrenoApp.test.js +52 -0
  45. package/dist/tests/bunSetup.js +66 -262
  46. package/dist/tests/createTestData.d.ts +9 -0
  47. package/dist/tests/createTestData.js +272 -0
  48. package/dist/tests/models.d.ts +71 -0
  49. package/dist/tests/models.js +134 -0
  50. package/dist/tests/mongoTestSetup.d.ts +7 -0
  51. package/dist/tests/mongoTestSetup.js +150 -0
  52. package/dist/tests/testEnv.d.ts +0 -0
  53. package/dist/tests/testEnv.js +6 -0
  54. package/dist/tests/testHelper.d.ts +22 -0
  55. package/dist/tests/testHelper.js +115 -0
  56. package/dist/tests/types.d.ts +29 -0
  57. package/dist/tests/types.js +2 -0
  58. package/dist/tests.d.ts +10 -78
  59. package/dist/tests.js +24 -241
  60. package/dist/transformers.test.js +14 -50
  61. package/package.json +18 -4
  62. package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
  63. package/src/__tests__/versionCheckPlugin.test.ts +43 -15
  64. package/src/actions.openApi.test.ts +12 -10
  65. package/src/api.query.test.ts +24 -1
  66. package/src/api.test.ts +169 -0
  67. package/src/api.ts +71 -0
  68. package/src/auth.test.ts +287 -39
  69. package/src/betterAuth.ts +1 -1
  70. package/src/consentApp.test.ts +1 -0
  71. package/src/example.ts +4 -4
  72. package/src/expressServer.test.ts +82 -85
  73. package/src/expressServer.ts +1 -213
  74. package/src/githubAuth.test.ts +22 -22
  75. package/src/logger.test.ts +466 -1
  76. package/src/logger.ts +477 -14
  77. package/src/middleware.test.ts +74 -2
  78. package/src/middleware.ts +57 -0
  79. package/src/models/consentForm.ts +3 -4
  80. package/src/models/consentResponse.ts +6 -4
  81. package/src/models/versionConfig.ts +3 -4
  82. package/src/openApi.test.ts +10 -17
  83. package/src/openApiBuilder.test.ts +27 -10
  84. package/src/openApiBuilder.ts +24 -0
  85. package/src/permissions.test.ts +8 -23
  86. package/src/populate.test.ts +7 -22
  87. package/src/realtime/changeStreamWatcher.ts +15 -10
  88. package/src/realtime/queryMatcher.ts +54 -27
  89. package/src/realtime/types.ts +4 -4
  90. package/src/requestContext.ts +86 -0
  91. package/src/secretProviders.test.ts +219 -1
  92. package/src/syncConsents.test.ts +1 -1
  93. package/src/terrenoApp.test.ts +38 -0
  94. package/src/terrenoApp.ts +37 -15
  95. package/src/tests/bunSetup.ts +22 -236
  96. package/src/tests/createTestData.ts +176 -0
  97. package/src/tests/models.ts +164 -0
  98. package/src/tests/mongoTestSetup.ts +69 -0
  99. package/src/tests/testEnv.ts +4 -0
  100. package/src/tests/testHelper.ts +57 -0
  101. package/src/tests/types.ts +35 -0
  102. package/src/tests.ts +40 -231
  103. package/src/transformers.test.ts +11 -30
  104. package/tsconfig.typedoc.json +4 -0
  105. package/dist/tests/index.d.ts +0 -1
  106. package/dist/tests/index.js +0 -17
  107. package/src/tests/index.ts +0 -1
package/dist/api.js CHANGED
@@ -173,9 +173,95 @@ exports.addPopulateToQuery = addPopulateToQuery;
173
173
  var PAGINATION_QUERY_PARAMS = ["limit", "page", "sort"];
174
174
  // Add support for more complex queries.
175
175
  var COMPLEX_QUERY_PARAMS = ["$and", "$or"];
176
+ /**
177
+ * Parses a date-range query bound from an ISO-8601 string using Luxon, throwing a 400
178
+ * APIError when the value cannot be parsed. Centralizes date-string parsing so the repo's
179
+ * Luxon convention is honored for admin changelist date-range filters.
180
+ */
181
+ var parseDateRangeBound = function (rawValue, queryKey) {
182
+ var parsed = luxon_1.DateTime.fromISO(String(rawValue), { zone: "utc" });
183
+ if (!parsed.isValid) {
184
+ throw new errors_1.APIError({
185
+ status: 400,
186
+ title: "Invalid date for query parameter ".concat(queryKey),
187
+ });
188
+ }
189
+ return parsed.toJSDate();
190
+ };
191
+ /**
192
+ * Collapses `field_gte` / `field_lte` query pairs into `{ field: { $gte, $lte } }` for Date paths,
193
+ * so admin changelist date-range filters map to valid Mongoose range queries.
194
+ */
195
+ var mergeDateRangeQueryParams = function (model, query) {
196
+ var e_2, _a, e_3, _b;
197
+ var schema = model.schema;
198
+ var result = __assign({}, query);
199
+ var dateRangeBases = new Set();
200
+ try {
201
+ for (var _c = __values(Object.keys(result)), _d = _c.next(); !_d.done; _d = _c.next()) {
202
+ var key = _d.value;
203
+ var match = /^(.+)_(gte|lte)$/.exec(key);
204
+ if (!match) {
205
+ continue;
206
+ }
207
+ var baseField = match[1];
208
+ var path = schema.path(baseField);
209
+ if (!path || path.instance !== "Date") {
210
+ continue;
211
+ }
212
+ dateRangeBases.add(baseField);
213
+ }
214
+ }
215
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
216
+ finally {
217
+ try {
218
+ if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
219
+ }
220
+ finally { if (e_2) throw e_2.error; }
221
+ }
222
+ try {
223
+ for (var dateRangeBases_1 = __values(dateRangeBases), dateRangeBases_1_1 = dateRangeBases_1.next(); !dateRangeBases_1_1.done; dateRangeBases_1_1 = dateRangeBases_1.next()) {
224
+ var baseField = dateRangeBases_1_1.value;
225
+ var gteKey = "".concat(baseField, "_gte");
226
+ var lteKey = "".concat(baseField, "_lte");
227
+ var gteRaw = result[gteKey];
228
+ var lteRaw = result[lteKey];
229
+ var bounds = {};
230
+ if (gteRaw !== undefined && gteRaw !== null && String(gteRaw).trim() !== "") {
231
+ bounds.$gte = parseDateRangeBound(gteRaw, gteKey);
232
+ }
233
+ if (lteRaw !== undefined && lteRaw !== null && String(lteRaw).trim() !== "") {
234
+ bounds.$lte = parseDateRangeBound(lteRaw, lteKey);
235
+ }
236
+ if (Object.keys(bounds).length === 0) {
237
+ continue;
238
+ }
239
+ delete result[gteKey];
240
+ delete result[lteKey];
241
+ var direct = result[baseField];
242
+ if (direct !== undefined && direct !== null && typeof direct !== "object") {
243
+ delete result[baseField];
244
+ }
245
+ if (typeof direct === "object" && direct !== null && !Array.isArray(direct)) {
246
+ result[baseField] = __assign(__assign({}, direct), bounds);
247
+ }
248
+ else {
249
+ result[baseField] = bounds;
250
+ }
251
+ }
252
+ }
253
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
254
+ finally {
255
+ try {
256
+ if (dateRangeBases_1_1 && !dateRangeBases_1_1.done && (_b = dateRangeBases_1.return)) _b.call(dateRangeBases_1);
257
+ }
258
+ finally { if (e_3) throw e_3.error; }
259
+ }
260
+ return result;
261
+ };
176
262
  // Ensures query params are allowed. Also checks nested query params when using $and/$or.
177
263
  var checkQueryParamAllowed = function (queryParam, queryParamValue, queryFields) {
178
- var e_2, _a, e_3, _b;
264
+ var e_4, _a, e_5, _b;
179
265
  if (queryFields === void 0) { queryFields = []; }
180
266
  // Cast for iteration through complex query values
181
267
  var complexValue = queryParamValue;
@@ -187,26 +273,26 @@ var checkQueryParamAllowed = function (queryParam, queryParamValue, queryFields)
187
273
  for (var complexValue_1 = __values(complexValue), complexValue_1_1 = complexValue_1.next(); !complexValue_1_1.done; complexValue_1_1 = complexValue_1.next()) {
188
274
  var subQuery = complexValue_1_1.value;
189
275
  try {
190
- for (var _c = (e_3 = void 0, __values(Object.keys(subQuery))), _d = _c.next(); !_d.done; _d = _c.next()) {
276
+ for (var _c = (e_5 = void 0, __values(Object.keys(subQuery))), _d = _c.next(); !_d.done; _d = _c.next()) {
191
277
  var subKey = _d.value;
192
278
  checkQueryParamAllowed(subKey, subQuery[subKey], queryFields);
193
279
  }
194
280
  }
195
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
281
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
196
282
  finally {
197
283
  try {
198
284
  if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
199
285
  }
200
- finally { if (e_3) throw e_3.error; }
286
+ finally { if (e_5) throw e_5.error; }
201
287
  }
202
288
  }
203
289
  }
204
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
290
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
205
291
  finally {
206
292
  try {
207
293
  if (complexValue_1_1 && !complexValue_1_1.done && (_a = complexValue_1.return)) _a.call(complexValue_1);
208
294
  }
209
- finally { if (e_2) throw e_2.error; }
295
+ finally { if (e_4) throw e_4.error; }
210
296
  }
211
297
  return;
212
298
  }
@@ -489,7 +575,7 @@ function _buildModelRouter(model, options) {
489
575
  queryValidation,
490
576
  ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
491
577
  var query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
492
- var e_4, _e, e_5, _f;
578
+ var e_6, _e, e_7, _f;
493
579
  var _g, _h, _j, _k, _l, _m;
494
580
  return __generator(this, function (_o) {
495
581
  switch (_o.label) {
@@ -501,12 +587,12 @@ function _buildModelRouter(model, options) {
501
587
  query[queryParam] = (_h = options.defaultQueryParams) === null || _h === void 0 ? void 0 : _h[queryParam];
502
588
  }
503
589
  }
504
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
590
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
505
591
  finally {
506
592
  try {
507
593
  if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
508
594
  }
509
- finally { if (e_4) throw e_4.error; }
595
+ finally { if (e_6) throw e_6.error; }
510
596
  }
511
597
  try {
512
598
  for (_c = __values(Object.keys(req.query)), _d = _c.next(); !_d.done; _d = _c.next()) {
@@ -527,13 +613,14 @@ function _buildModelRouter(model, options) {
527
613
  }
528
614
  }
529
615
  }
530
- catch (e_5_1) { e_5 = { error: e_5_1 }; }
616
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
531
617
  finally {
532
618
  try {
533
619
  if (_d && !_d.done && (_f = _c.return)) _f.call(_c);
534
620
  }
535
- finally { if (e_5) throw e_5.error; }
621
+ finally { if (e_7) throw e_7.error; }
536
622
  }
623
+ query = mergeDateRangeQueryParams(model, query);
537
624
  // Special operators. NOTE: these request Mongo Atlas.
538
625
  if (req.query.$search) {
539
626
  (_j = mongoose_1.default.connection.db) === null || _j === void 0 ? void 0 : _j.collection(model.collection.collectionName);
@@ -184,7 +184,17 @@ var tests_1 = require("./tests");
184
184
  update: [permissions_1.Permissions.IsOwner],
185
185
  },
186
186
  populatePaths: [{ path: "ownerId" }],
187
- queryFields: ["hidden", "name", "calories", "created", "source.name", "tags", "eatenBy"],
187
+ queryFields: [
188
+ "hidden",
189
+ "name",
190
+ "calories",
191
+ "created",
192
+ "created_gte",
193
+ "created_lte",
194
+ "source.name",
195
+ "tags",
196
+ "eatenBy",
197
+ ],
188
198
  sort: { created: "descending" },
189
199
  }));
190
200
  server = (0, supertest_1.default)(app);
@@ -344,6 +354,26 @@ var tests_1 = require("./tests");
344
354
  }
345
355
  });
346
356
  }); });
357
+ (0, bun_test_1.it)("list applies created_gte and created_lte as a Date range", function () { return __awaiter(void 0, void 0, void 0, function () {
358
+ var res, names;
359
+ return __generator(this, function (_a) {
360
+ switch (_a.label) {
361
+ case 0: return [4 /*yield*/, agent
362
+ .get("/food")
363
+ .query({
364
+ created_gte: "2021-12-03T00:00:05.000Z",
365
+ created_lte: "2021-12-03T00:00:25.000Z",
366
+ limit: 10,
367
+ })
368
+ .expect(200)];
369
+ case 1:
370
+ res = _a.sent();
371
+ names = res.body.data.map(function (d) { return d.name; }).sort();
372
+ (0, bun_test_1.expect)(names).toEqual(["Pizza", "Spinach"]);
373
+ return [2 /*return*/];
374
+ }
375
+ });
376
+ }); });
347
377
  (0, bun_test_1.it)("list query params not in list", function () { return __awaiter(void 0, void 0, void 0, function () {
348
378
  var res;
349
379
  return __generator(this, function (_a) {
package/dist/api.test.js CHANGED
@@ -2588,5 +2588,216 @@ var transformers_1 = require("./transformers");
2588
2588
  }
2589
2589
  });
2590
2590
  }); });
2591
+ (0, bun_test_1.it)("returns 400 when X-Unmodified-Since-ISO header is an invalid date", function () { return __awaiter(void 0, void 0, void 0, function () {
2592
+ var res;
2593
+ return __generator(this, function (_a) {
2594
+ switch (_a.label) {
2595
+ case 0: return [4 /*yield*/, agent
2596
+ .patch("/food/".concat(spinach._id))
2597
+ .set("X-Unmodified-Since-ISO", "not-a-date")
2598
+ .send({ name: "Bad Date" })
2599
+ .expect(400)];
2600
+ case 1:
2601
+ res = _a.sent();
2602
+ (0, bun_test_1.expect)(res.body.title).toBe("Invalid conflict-detection timestamp");
2603
+ (0, bun_test_1.expect)(res.body.detail).toContain("X-Unmodified-Since-ISO");
2604
+ return [2 /*return*/];
2605
+ }
2606
+ });
2607
+ }); });
2608
+ (0, bun_test_1.it)("returns 400 when If-Unmodified-Since header is an invalid HTTP date", function () { return __awaiter(void 0, void 0, void 0, function () {
2609
+ var res;
2610
+ return __generator(this, function (_a) {
2611
+ switch (_a.label) {
2612
+ case 0: return [4 /*yield*/, agent
2613
+ .patch("/food/".concat(spinach._id))
2614
+ .set("If-Unmodified-Since", "not-a-valid-http-date")
2615
+ .send({ name: "Bad HTTP Date" })
2616
+ .expect(400)];
2617
+ case 1:
2618
+ res = _a.sent();
2619
+ (0, bun_test_1.expect)(res.body.title).toBe("Invalid conflict-detection timestamp");
2620
+ (0, bun_test_1.expect)(res.body.detail).toContain("If-Unmodified-Since");
2621
+ return [2 /*return*/];
2622
+ }
2623
+ });
2624
+ }); });
2625
+ (0, bun_test_1.it)("returns 400 when _updatedAt body field is an invalid date", function () { return __awaiter(void 0, void 0, void 0, function () {
2626
+ var res;
2627
+ return __generator(this, function (_a) {
2628
+ switch (_a.label) {
2629
+ case 0: return [4 /*yield*/, agent
2630
+ .patch("/food/".concat(spinach._id))
2631
+ .send({ _updatedAt: "garbage-date", name: "Bad Body Date" })
2632
+ .expect(400)];
2633
+ case 1:
2634
+ res = _a.sent();
2635
+ (0, bun_test_1.expect)(res.body.title).toBe("Invalid conflict-detection timestamp");
2636
+ (0, bun_test_1.expect)(res.body.detail).toContain("_updatedAt");
2637
+ return [2 /*return*/];
2638
+ }
2639
+ });
2640
+ }); });
2641
+ (0, bun_test_1.it)("handles doc timestamp stored as ISO string", function () { return __awaiter(void 0, void 0, void 0, function () {
2642
+ var res;
2643
+ return __generator(this, function (_a) {
2644
+ switch (_a.label) {
2645
+ case 0: return [4 /*yield*/, tests_1.FoodModel.collection.updateOne({ _id: spinach._id }, { $set: { updated: "2025-06-15T12:00:00.000Z" } })];
2646
+ case 1:
2647
+ _a.sent();
2648
+ return [4 /*yield*/, agent
2649
+ .patch("/food/".concat(spinach._id))
2650
+ .set("If-Unmodified-Since", luxon_1.DateTime.fromISO("2025-06-15T11:00:00.000Z").toHTTP())
2651
+ .send({ name: "String Timestamp" })
2652
+ .expect(409)];
2653
+ case 2:
2654
+ res = _a.sent();
2655
+ (0, bun_test_1.expect)(res.body.error).toBe("Conflict");
2656
+ return [2 /*return*/];
2657
+ }
2658
+ });
2659
+ }); });
2660
+ });
2661
+ (0, bun_test_1.describe)("three-arg modelRouter registration", function () {
2662
+ (0, bun_test_1.it)("returns a ModelRouterRegistration with path and realtime", function () {
2663
+ var registration = (0, api_1.modelRouter)("/food", tests_1.FoodModel, {
2664
+ permissions: {
2665
+ create: [permissions_1.Permissions.IsAny],
2666
+ delete: [permissions_1.Permissions.IsAny],
2667
+ list: [permissions_1.Permissions.IsAny],
2668
+ read: [permissions_1.Permissions.IsAny],
2669
+ update: [permissions_1.Permissions.IsAny],
2670
+ },
2671
+ realtime: { methods: ["create", "update", "delete"], roomStrategy: "model" },
2672
+ });
2673
+ (0, bun_test_1.expect)(registration).toHaveProperty("__type", "modelRouter");
2674
+ (0, bun_test_1.expect)(registration).toHaveProperty("path", "/food");
2675
+ (0, bun_test_1.expect)(registration).toHaveProperty("router");
2676
+ (0, bun_test_1.expect)(registration).toHaveProperty("_buildWithOpenApi");
2677
+ });
2678
+ (0, bun_test_1.it)("logs a warning when realtime config is used without the path form", function () {
2679
+ var router = (0, api_1.modelRouter)(tests_1.FoodModel, {
2680
+ permissions: {
2681
+ create: [permissions_1.Permissions.IsAny],
2682
+ delete: [permissions_1.Permissions.IsAny],
2683
+ list: [permissions_1.Permissions.IsAny],
2684
+ read: [permissions_1.Permissions.IsAny],
2685
+ update: [permissions_1.Permissions.IsAny],
2686
+ },
2687
+ realtime: { methods: ["create", "update", "delete"], roomStrategy: "model" },
2688
+ });
2689
+ // Returns a plain Router, not a registration, because no path was given
2690
+ (0, bun_test_1.expect)(router).toBeDefined();
2691
+ (0, bun_test_1.expect)(router).not.toHaveProperty("__type");
2692
+ });
2693
+ });
2694
+ (0, bun_test_1.describe)("create transform error wrapping", function () {
2695
+ var admin;
2696
+ var agent;
2697
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
2698
+ var _a;
2699
+ return __generator(this, function (_b) {
2700
+ switch (_b.label) {
2701
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
2702
+ case 1:
2703
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
2704
+ app = (0, tests_1.getBaseServer)();
2705
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
2706
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
2707
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2708
+ permissions: {
2709
+ create: [permissions_1.Permissions.IsAny],
2710
+ delete: [permissions_1.Permissions.IsAny],
2711
+ list: [permissions_1.Permissions.IsAny],
2712
+ read: [permissions_1.Permissions.IsAny],
2713
+ update: [permissions_1.Permissions.IsAny],
2714
+ },
2715
+ transformer: {
2716
+ transform: function () {
2717
+ throw new Error("generic transform error");
2718
+ },
2719
+ },
2720
+ }));
2721
+ server = (0, supertest_1.default)(app);
2722
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2723
+ case 2:
2724
+ agent = _b.sent();
2725
+ return [2 /*return*/];
2726
+ }
2727
+ });
2728
+ }); });
2729
+ (0, bun_test_1.it)("wraps non-APIError transform errors in a 400 APIError on create", function () { return __awaiter(void 0, void 0, void 0, function () {
2730
+ var res;
2731
+ return __generator(this, function (_a) {
2732
+ switch (_a.label) {
2733
+ case 0: return [4 /*yield*/, agent
2734
+ .post("/food")
2735
+ .send({ calories: 10, name: "New Food", ownerId: admin._id })
2736
+ .expect(400)];
2737
+ case 1:
2738
+ res = _a.sent();
2739
+ (0, bun_test_1.expect)(res.body.title).toContain("generic transform error");
2740
+ return [2 /*return*/];
2741
+ }
2742
+ });
2743
+ }); });
2744
+ });
2745
+ (0, bun_test_1.describe)("update transform error wrapping", function () {
2746
+ var admin;
2747
+ var agent;
2748
+ var spinach;
2749
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
2750
+ var _a;
2751
+ return __generator(this, function (_b) {
2752
+ switch (_b.label) {
2753
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
2754
+ case 1:
2755
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
2756
+ return [4 /*yield*/, tests_1.FoodModel.create({
2757
+ calories: 10,
2758
+ created: new Date(),
2759
+ hidden: false,
2760
+ name: "Spinach",
2761
+ ownerId: admin._id,
2762
+ })];
2763
+ case 2:
2764
+ spinach = _b.sent();
2765
+ app = (0, tests_1.getBaseServer)();
2766
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
2767
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
2768
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2769
+ permissions: {
2770
+ create: [permissions_1.Permissions.IsAny],
2771
+ delete: [permissions_1.Permissions.IsAny],
2772
+ list: [permissions_1.Permissions.IsAny],
2773
+ read: [permissions_1.Permissions.IsAny],
2774
+ update: [permissions_1.Permissions.IsAny],
2775
+ },
2776
+ transformer: {
2777
+ transform: function () {
2778
+ throw new Error("update transform error");
2779
+ },
2780
+ },
2781
+ }));
2782
+ server = (0, supertest_1.default)(app);
2783
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2784
+ case 3:
2785
+ agent = _b.sent();
2786
+ return [2 /*return*/];
2787
+ }
2788
+ });
2789
+ }); });
2790
+ (0, bun_test_1.it)("wraps non-APIError transform errors in a 403 APIError on update", function () { return __awaiter(void 0, void 0, void 0, function () {
2791
+ var res;
2792
+ return __generator(this, function (_a) {
2793
+ switch (_a.label) {
2794
+ case 0: return [4 /*yield*/, agent.patch("/food/".concat(spinach._id)).send({ name: "Updated" }).expect(403)];
2795
+ case 1:
2796
+ res = _a.sent();
2797
+ (0, bun_test_1.expect)(res.body.title).toContain("update transform error");
2798
+ return [2 /*return*/];
2799
+ }
2800
+ });
2801
+ }); });
2591
2802
  });
2592
2803
  });