@terreno/api 0.0.1

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 (119) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +170 -0
  3. package/biome.jsonc +22 -0
  4. package/bunfig.toml +4 -0
  5. package/dist/api.d.ts +227 -0
  6. package/dist/api.js +1024 -0
  7. package/dist/api.test.d.ts +1 -0
  8. package/dist/api.test.js +2143 -0
  9. package/dist/auth.d.ts +50 -0
  10. package/dist/auth.js +512 -0
  11. package/dist/auth.test.d.ts +1 -0
  12. package/dist/auth.test.js +778 -0
  13. package/dist/errors.d.ts +75 -0
  14. package/dist/errors.js +216 -0
  15. package/dist/example.d.ts +1 -0
  16. package/dist/example.js +118 -0
  17. package/dist/expressServer.d.ts +35 -0
  18. package/dist/expressServer.js +436 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +30 -0
  21. package/dist/logger.d.ts +23 -0
  22. package/dist/logger.js +249 -0
  23. package/dist/middleware.d.ts +10 -0
  24. package/dist/middleware.js +52 -0
  25. package/dist/notifiers/googleChatNotifier.d.ts +5 -0
  26. package/dist/notifiers/googleChatNotifier.js +130 -0
  27. package/dist/notifiers/googleChatNotifier.test.d.ts +1 -0
  28. package/dist/notifiers/googleChatNotifier.test.js +260 -0
  29. package/dist/notifiers/index.d.ts +3 -0
  30. package/dist/notifiers/index.js +19 -0
  31. package/dist/notifiers/slackNotifier.d.ts +5 -0
  32. package/dist/notifiers/slackNotifier.js +130 -0
  33. package/dist/notifiers/slackNotifier.test.d.ts +1 -0
  34. package/dist/notifiers/slackNotifier.test.js +259 -0
  35. package/dist/notifiers/zoomNotifier.d.ts +34 -0
  36. package/dist/notifiers/zoomNotifier.js +181 -0
  37. package/dist/notifiers/zoomNotifier.test.d.ts +1 -0
  38. package/dist/notifiers/zoomNotifier.test.js +370 -0
  39. package/dist/openApi.d.ts +60 -0
  40. package/dist/openApi.js +441 -0
  41. package/dist/openApi.test.d.ts +1 -0
  42. package/dist/openApi.test.js +445 -0
  43. package/dist/openApiBuilder.d.ts +419 -0
  44. package/dist/openApiBuilder.js +424 -0
  45. package/dist/openApiBuilder.test.d.ts +1 -0
  46. package/dist/openApiBuilder.test.js +509 -0
  47. package/dist/openApiEtag.d.ts +7 -0
  48. package/dist/openApiEtag.js +38 -0
  49. package/dist/permissions.d.ts +26 -0
  50. package/dist/permissions.js +331 -0
  51. package/dist/permissions.test.d.ts +1 -0
  52. package/dist/permissions.test.js +413 -0
  53. package/dist/plugins.d.ts +67 -0
  54. package/dist/plugins.js +315 -0
  55. package/dist/plugins.test.d.ts +1 -0
  56. package/dist/plugins.test.js +639 -0
  57. package/dist/populate.d.ts +14 -0
  58. package/dist/populate.js +315 -0
  59. package/dist/populate.test.d.ts +1 -0
  60. package/dist/populate.test.js +133 -0
  61. package/dist/response.d.ts +0 -0
  62. package/dist/response.js +1 -0
  63. package/dist/tests/bunSetup.d.ts +1 -0
  64. package/dist/tests/bunSetup.js +297 -0
  65. package/dist/tests/index.d.ts +1 -0
  66. package/dist/tests/index.js +17 -0
  67. package/dist/tests.d.ts +99 -0
  68. package/dist/tests.js +273 -0
  69. package/dist/transformers.d.ts +25 -0
  70. package/dist/transformers.js +217 -0
  71. package/dist/transformers.test.d.ts +1 -0
  72. package/dist/transformers.test.js +370 -0
  73. package/dist/utils.d.ts +11 -0
  74. package/dist/utils.js +143 -0
  75. package/dist/utils.test.d.ts +1 -0
  76. package/dist/utils.test.js +14 -0
  77. package/index.ts +1 -0
  78. package/package.json +88 -0
  79. package/src/__snapshots__/openApi.test.ts.snap +4814 -0
  80. package/src/__snapshots__/openApiBuilder.test.ts.snap +1485 -0
  81. package/src/api.test.ts +1661 -0
  82. package/src/api.ts +1036 -0
  83. package/src/auth.test.ts +550 -0
  84. package/src/auth.ts +408 -0
  85. package/src/errors.ts +225 -0
  86. package/src/example.ts +99 -0
  87. package/src/express.d.ts +5 -0
  88. package/src/expressServer.ts +387 -0
  89. package/src/index.ts +14 -0
  90. package/src/logger.ts +190 -0
  91. package/src/middleware.ts +18 -0
  92. package/src/notifiers/googleChatNotifier.test.ts +114 -0
  93. package/src/notifiers/googleChatNotifier.ts +47 -0
  94. package/src/notifiers/index.ts +3 -0
  95. package/src/notifiers/slackNotifier.test.ts +113 -0
  96. package/src/notifiers/slackNotifier.ts +55 -0
  97. package/src/notifiers/zoomNotifier.test.ts +207 -0
  98. package/src/notifiers/zoomNotifier.ts +111 -0
  99. package/src/openApi.test.ts +331 -0
  100. package/src/openApi.ts +494 -0
  101. package/src/openApiBuilder.test.ts +442 -0
  102. package/src/openApiBuilder.ts +636 -0
  103. package/src/openApiEtag.ts +40 -0
  104. package/src/permissions.test.ts +219 -0
  105. package/src/permissions.ts +228 -0
  106. package/src/plugins.test.ts +390 -0
  107. package/src/plugins.ts +289 -0
  108. package/src/populate.test.ts +65 -0
  109. package/src/populate.ts +258 -0
  110. package/src/response.ts +0 -0
  111. package/src/tests/bunSetup.ts +234 -0
  112. package/src/tests/index.ts +1 -0
  113. package/src/tests.ts +218 -0
  114. package/src/transformers.test.ts +202 -0
  115. package/src/transformers.ts +170 -0
  116. package/src/utils.test.ts +14 -0
  117. package/src/utils.ts +47 -0
  118. package/tsconfig.json +60 -0
  119. package/types.d.ts +17 -0
@@ -0,0 +1,2143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __generator = (this && this.__generator) || function (thisArg, body) {
45
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
46
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
47
+ function verb(n) { return function (v) { return step([n, v]); }; }
48
+ function step(op) {
49
+ if (f) throw new TypeError("Generator is already executing.");
50
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
51
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
52
+ if (y = 0, t) op = [op[0] & 2, t.value];
53
+ switch (op[0]) {
54
+ case 0: case 1: t = op; break;
55
+ case 4: _.label++; return { value: op[1], done: false };
56
+ case 5: _.label++; y = op[1]; op = [0]; continue;
57
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
58
+ default:
59
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
60
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
61
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
62
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
63
+ if (t[2]) _.ops.pop();
64
+ _.trys.pop(); continue;
65
+ }
66
+ op = body.call(thisArg, _);
67
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
68
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
+ }
70
+ };
71
+ var __read = (this && this.__read) || function (o, n) {
72
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
73
+ if (!m) return o;
74
+ var i = m.call(o), r, ar = [], e;
75
+ try {
76
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
77
+ }
78
+ catch (error) { e = { error: error }; }
79
+ finally {
80
+ try {
81
+ if (r && !r.done && (m = i["return"])) m.call(i);
82
+ }
83
+ finally { if (e) throw e.error; }
84
+ }
85
+ return ar;
86
+ };
87
+ var __importDefault = (this && this.__importDefault) || function (mod) {
88
+ return (mod && mod.__esModule) ? mod : { "default": mod };
89
+ };
90
+ Object.defineProperty(exports, "__esModule", { value: true });
91
+ var bun_test_1 = require("bun:test");
92
+ var Sentry = __importStar(require("@sentry/node"));
93
+ var sortBy_1 = __importDefault(require("lodash/sortBy"));
94
+ var qs_1 = __importDefault(require("qs"));
95
+ var supertest_1 = __importDefault(require("supertest"));
96
+ var api_1 = require("./api");
97
+ var auth_1 = require("./auth");
98
+ var errors_1 = require("./errors");
99
+ var expressServer_1 = require("./expressServer");
100
+ var permissions_1 = require("./permissions");
101
+ var tests_1 = require("./tests");
102
+ (0, bun_test_1.describe)("@terreno/api", function () {
103
+ var server;
104
+ var app;
105
+ (0, bun_test_1.describe)("pre and post hooks", function () {
106
+ var agent;
107
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
108
+ return __generator(this, function (_a) {
109
+ switch (_a.label) {
110
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
111
+ case 1:
112
+ _a.sent();
113
+ app = (0, tests_1.getBaseServer)();
114
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
115
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
116
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
117
+ case 2:
118
+ agent = _a.sent();
119
+ return [2 /*return*/];
120
+ }
121
+ });
122
+ }); });
123
+ (0, bun_test_1.it)("pre hooks change data", function () { return __awaiter(void 0, void 0, void 0, function () {
124
+ var deleteCalled, res, broccoli;
125
+ return __generator(this, function (_a) {
126
+ switch (_a.label) {
127
+ case 0:
128
+ deleteCalled = false;
129
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
130
+ allowAnonymous: true,
131
+ permissions: {
132
+ create: [permissions_1.Permissions.IsAny],
133
+ delete: [permissions_1.Permissions.IsAny],
134
+ list: [permissions_1.Permissions.IsAny],
135
+ read: [permissions_1.Permissions.IsAny],
136
+ update: [permissions_1.Permissions.IsAny],
137
+ },
138
+ preCreate: function (data) {
139
+ data.calories = 14;
140
+ return data;
141
+ },
142
+ preDelete: function (data) {
143
+ deleteCalled = true;
144
+ return data;
145
+ },
146
+ preUpdate: function (data) {
147
+ data.calories = 15;
148
+ return data;
149
+ },
150
+ }));
151
+ server = (0, supertest_1.default)(app);
152
+ return [4 /*yield*/, server
153
+ .post("/food")
154
+ .send({
155
+ calories: 15,
156
+ name: "Broccoli",
157
+ })
158
+ .expect(201)];
159
+ case 1:
160
+ res = _a.sent();
161
+ return [4 /*yield*/, tests_1.FoodModel.findById(res.body.data._id)];
162
+ case 2:
163
+ broccoli = _a.sent();
164
+ if (!broccoli) {
165
+ throw new Error("Broccoli was not created");
166
+ }
167
+ (0, bun_test_1.expect)(broccoli.name).toBe("Broccoli");
168
+ // Overwritten by the pre create hook
169
+ (0, bun_test_1.expect)(broccoli.calories).toBe(14);
170
+ return [4 /*yield*/, server
171
+ .patch("/food/".concat(broccoli._id))
172
+ .send({
173
+ name: "Broccoli2",
174
+ })
175
+ .expect(200)];
176
+ case 3:
177
+ res = _a.sent();
178
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Broccoli2");
179
+ // Updated by the pre update hook
180
+ (0, bun_test_1.expect)(res.body.data.calories).toBe(15);
181
+ return [4 /*yield*/, agent.delete("/food/".concat(broccoli._id)).expect(204)];
182
+ case 4:
183
+ _a.sent();
184
+ (0, bun_test_1.expect)(deleteCalled).toBe(true);
185
+ return [2 /*return*/];
186
+ }
187
+ });
188
+ }); });
189
+ (0, bun_test_1.it)("pre hooks return null", function () { return __awaiter(void 0, void 0, void 0, function () {
190
+ var notAdmin, spinach, res, broccoli;
191
+ return __generator(this, function (_a) {
192
+ switch (_a.label) {
193
+ case 0: return [4 /*yield*/, tests_1.UserModel.findOne({
194
+ email: "notAdmin@example.com",
195
+ })];
196
+ case 1:
197
+ notAdmin = _a.sent();
198
+ return [4 /*yield*/, tests_1.FoodModel.create({
199
+ calories: 1,
200
+ created: new Date("2021-12-03T00:00:20.000Z"),
201
+ hidden: false,
202
+ name: "Spinach",
203
+ ownerId: notAdmin._id,
204
+ source: {
205
+ name: "Brand",
206
+ },
207
+ })];
208
+ case 2:
209
+ spinach = _a.sent();
210
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
211
+ allowAnonymous: true,
212
+ permissions: {
213
+ create: [permissions_1.Permissions.IsAny],
214
+ delete: [permissions_1.Permissions.IsAny],
215
+ list: [permissions_1.Permissions.IsAny],
216
+ read: [permissions_1.Permissions.IsAny],
217
+ update: [permissions_1.Permissions.IsAny],
218
+ },
219
+ preCreate: function () { return null; },
220
+ preDelete: function () { return null; },
221
+ preUpdate: function () { return null; },
222
+ }));
223
+ server = (0, supertest_1.default)(app);
224
+ return [4 /*yield*/, server
225
+ .post("/food")
226
+ .send({
227
+ calories: 15,
228
+ name: "Broccoli",
229
+ })
230
+ .expect(403)];
231
+ case 3:
232
+ res = _a.sent();
233
+ return [4 /*yield*/, tests_1.FoodModel.findById(res.body._id)];
234
+ case 4:
235
+ broccoli = _a.sent();
236
+ (0, bun_test_1.expect)(broccoli).toBeNull();
237
+ return [4 /*yield*/, server
238
+ .patch("/food/".concat(spinach._id))
239
+ .send({
240
+ name: "Broccoli",
241
+ })
242
+ .expect(403)];
243
+ case 5:
244
+ _a.sent();
245
+ return [4 /*yield*/, server.delete("/food/".concat(spinach._id)).expect(403)];
246
+ case 6:
247
+ _a.sent();
248
+ return [2 /*return*/];
249
+ }
250
+ });
251
+ }); });
252
+ (0, bun_test_1.it)("post hooks succeed", function () { return __awaiter(void 0, void 0, void 0, function () {
253
+ var deleteCalled, res, broccoli;
254
+ return __generator(this, function (_a) {
255
+ switch (_a.label) {
256
+ case 0:
257
+ deleteCalled = false;
258
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
259
+ allowAnonymous: true,
260
+ permissions: {
261
+ create: [permissions_1.Permissions.IsAny],
262
+ delete: [permissions_1.Permissions.IsAny],
263
+ list: [permissions_1.Permissions.IsAny],
264
+ read: [permissions_1.Permissions.IsAny],
265
+ update: [permissions_1.Permissions.IsAny],
266
+ },
267
+ postCreate: function (data) { return __awaiter(void 0, void 0, void 0, function () {
268
+ return __generator(this, function (_a) {
269
+ switch (_a.label) {
270
+ case 0:
271
+ data.calories = 14;
272
+ return [4 /*yield*/, data.save()];
273
+ case 1:
274
+ _a.sent();
275
+ return [2 /*return*/, data];
276
+ }
277
+ });
278
+ }); },
279
+ postDelete: function (data) {
280
+ deleteCalled = true;
281
+ return data;
282
+ },
283
+ postUpdate: function (data) { return __awaiter(void 0, void 0, void 0, function () {
284
+ return __generator(this, function (_a) {
285
+ switch (_a.label) {
286
+ case 0:
287
+ data.calories = 15;
288
+ return [4 /*yield*/, data.save()];
289
+ case 1:
290
+ _a.sent();
291
+ return [2 /*return*/, data];
292
+ }
293
+ });
294
+ }); },
295
+ }));
296
+ server = (0, supertest_1.default)(app);
297
+ return [4 /*yield*/, server
298
+ .post("/food")
299
+ .send({
300
+ calories: 15,
301
+ name: "Broccoli",
302
+ })
303
+ .expect(201)];
304
+ case 1:
305
+ res = _a.sent();
306
+ return [4 /*yield*/, tests_1.FoodModel.findById(res.body.data._id)];
307
+ case 2:
308
+ broccoli = _a.sent();
309
+ if (!broccoli) {
310
+ throw new Error("Broccoli was not created");
311
+ }
312
+ (0, bun_test_1.expect)(broccoli.name).toBe("Broccoli");
313
+ // Overwritten by the pre create hook
314
+ (0, bun_test_1.expect)(broccoli.calories).toBe(14);
315
+ return [4 /*yield*/, server
316
+ .patch("/food/".concat(broccoli._id))
317
+ .send({
318
+ name: "Broccoli2",
319
+ })
320
+ .expect(200)];
321
+ case 3:
322
+ res = _a.sent();
323
+ return [4 /*yield*/, tests_1.FoodModel.findById(res.body.data._id)];
324
+ case 4:
325
+ broccoli = _a.sent();
326
+ if (!broccoli) {
327
+ throw new Error("Broccoli was not update");
328
+ }
329
+ (0, bun_test_1.expect)(broccoli.name).toBe("Broccoli2");
330
+ // Updated by the post update hook
331
+ (0, bun_test_1.expect)(broccoli.calories).toBe(15);
332
+ return [4 /*yield*/, agent.delete("/food/".concat(broccoli._id)).expect(204)];
333
+ case 5:
334
+ _a.sent();
335
+ (0, bun_test_1.expect)(deleteCalled).toBe(true);
336
+ return [2 /*return*/];
337
+ }
338
+ });
339
+ }); });
340
+ (0, bun_test_1.it)("preCreate hook preserves disableExternalErrorTracking on APIError", function () { return __awaiter(void 0, void 0, void 0, function () {
341
+ var res;
342
+ return __generator(this, function (_a) {
343
+ switch (_a.label) {
344
+ case 0:
345
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
346
+ allowAnonymous: true,
347
+ permissions: {
348
+ create: [permissions_1.Permissions.IsAny],
349
+ delete: [permissions_1.Permissions.IsAny],
350
+ list: [permissions_1.Permissions.IsAny],
351
+ read: [permissions_1.Permissions.IsAny],
352
+ update: [permissions_1.Permissions.IsAny],
353
+ },
354
+ preCreate: function () {
355
+ throw new errors_1.APIError({
356
+ disableExternalErrorTracking: true,
357
+ status: 400,
358
+ title: "Custom preCreate error",
359
+ });
360
+ },
361
+ }));
362
+ server = (0, supertest_1.default)(app);
363
+ return [4 /*yield*/, server
364
+ .post("/food")
365
+ .send({
366
+ calories: 15,
367
+ name: "Broccoli",
368
+ })
369
+ .expect(400)];
370
+ case 1:
371
+ res = _a.sent();
372
+ (0, bun_test_1.expect)(res.body.title).toBe("Custom preCreate error");
373
+ (0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
374
+ return [2 /*return*/];
375
+ }
376
+ });
377
+ }); });
378
+ (0, bun_test_1.it)("preCreate hook preserves disableExternalErrorTracking on non-APIError", function () { return __awaiter(void 0, void 0, void 0, function () {
379
+ var res;
380
+ return __generator(this, function (_a) {
381
+ switch (_a.label) {
382
+ case 0:
383
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
384
+ allowAnonymous: true,
385
+ permissions: {
386
+ create: [permissions_1.Permissions.IsAny],
387
+ delete: [permissions_1.Permissions.IsAny],
388
+ list: [permissions_1.Permissions.IsAny],
389
+ read: [permissions_1.Permissions.IsAny],
390
+ update: [permissions_1.Permissions.IsAny],
391
+ },
392
+ preCreate: function () {
393
+ var error = new Error("Some custom error");
394
+ error.disableExternalErrorTracking = true;
395
+ throw error;
396
+ },
397
+ }));
398
+ server = (0, supertest_1.default)(app);
399
+ return [4 /*yield*/, server
400
+ .post("/food")
401
+ .send({
402
+ calories: 15,
403
+ name: "Broccoli",
404
+ })
405
+ .expect(400)];
406
+ case 1:
407
+ res = _a.sent();
408
+ (0, bun_test_1.expect)(res.body.title).toContain("preCreate hook error");
409
+ (0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
410
+ return [2 /*return*/];
411
+ }
412
+ });
413
+ }); });
414
+ (0, bun_test_1.it)("preUpdate hook preserves disableExternalErrorTracking on APIError", function () { return __awaiter(void 0, void 0, void 0, function () {
415
+ var notAdmin, spinach, res;
416
+ return __generator(this, function (_a) {
417
+ switch (_a.label) {
418
+ case 0: return [4 /*yield*/, tests_1.UserModel.findOne({
419
+ email: "notAdmin@example.com",
420
+ })];
421
+ case 1:
422
+ notAdmin = _a.sent();
423
+ return [4 /*yield*/, tests_1.FoodModel.create({
424
+ calories: 1,
425
+ created: new Date("2021-12-03T00:00:20.000Z"),
426
+ hidden: false,
427
+ name: "Spinach",
428
+ ownerId: notAdmin._id,
429
+ source: {
430
+ name: "Brand",
431
+ },
432
+ })];
433
+ case 2:
434
+ spinach = _a.sent();
435
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
436
+ allowAnonymous: true,
437
+ permissions: {
438
+ create: [permissions_1.Permissions.IsAny],
439
+ delete: [permissions_1.Permissions.IsAny],
440
+ list: [permissions_1.Permissions.IsAny],
441
+ read: [permissions_1.Permissions.IsAny],
442
+ update: [permissions_1.Permissions.IsAny],
443
+ },
444
+ preUpdate: function () {
445
+ throw new errors_1.APIError({
446
+ disableExternalErrorTracking: true,
447
+ status: 400,
448
+ title: "Custom preUpdate error",
449
+ });
450
+ },
451
+ }));
452
+ server = (0, supertest_1.default)(app);
453
+ return [4 /*yield*/, server
454
+ .patch("/food/".concat(spinach._id))
455
+ .send({
456
+ name: "Broccoli",
457
+ })
458
+ .expect(400)];
459
+ case 3:
460
+ res = _a.sent();
461
+ (0, bun_test_1.expect)(res.body.title).toBe("Custom preUpdate error");
462
+ (0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
463
+ return [2 /*return*/];
464
+ }
465
+ });
466
+ }); });
467
+ (0, bun_test_1.it)("preUpdate hook preserves disableExternalErrorTracking on non-APIError", function () { return __awaiter(void 0, void 0, void 0, function () {
468
+ var notAdmin, spinach, res;
469
+ return __generator(this, function (_a) {
470
+ switch (_a.label) {
471
+ case 0: return [4 /*yield*/, tests_1.UserModel.findOne({
472
+ email: "notAdmin@example.com",
473
+ })];
474
+ case 1:
475
+ notAdmin = _a.sent();
476
+ return [4 /*yield*/, tests_1.FoodModel.create({
477
+ calories: 1,
478
+ created: new Date("2021-12-03T00:00:20.000Z"),
479
+ hidden: false,
480
+ name: "Spinach",
481
+ ownerId: notAdmin._id,
482
+ source: {
483
+ name: "Brand",
484
+ },
485
+ })];
486
+ case 2:
487
+ spinach = _a.sent();
488
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
489
+ allowAnonymous: true,
490
+ permissions: {
491
+ create: [permissions_1.Permissions.IsAny],
492
+ delete: [permissions_1.Permissions.IsAny],
493
+ list: [permissions_1.Permissions.IsAny],
494
+ read: [permissions_1.Permissions.IsAny],
495
+ update: [permissions_1.Permissions.IsAny],
496
+ },
497
+ preUpdate: function () {
498
+ var error = new Error("Some custom error");
499
+ error.disableExternalErrorTracking = true;
500
+ throw error;
501
+ },
502
+ }));
503
+ server = (0, supertest_1.default)(app);
504
+ return [4 /*yield*/, server
505
+ .patch("/food/".concat(spinach._id))
506
+ .send({
507
+ name: "Broccoli",
508
+ })
509
+ .expect(400)];
510
+ case 3:
511
+ res = _a.sent();
512
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
513
+ (0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
514
+ return [2 /*return*/];
515
+ }
516
+ });
517
+ }); });
518
+ (0, bun_test_1.it)("preDelete hook preserves disableExternalErrorTracking on non-APIError", function () { return __awaiter(void 0, void 0, void 0, function () {
519
+ var notAdmin, spinach, res;
520
+ return __generator(this, function (_a) {
521
+ switch (_a.label) {
522
+ case 0: return [4 /*yield*/, tests_1.UserModel.findOne({
523
+ email: "notAdmin@example.com",
524
+ })];
525
+ case 1:
526
+ notAdmin = _a.sent();
527
+ return [4 /*yield*/, tests_1.FoodModel.create({
528
+ calories: 1,
529
+ created: new Date("2021-12-03T00:00:20.000Z"),
530
+ hidden: false,
531
+ name: "Spinach",
532
+ ownerId: notAdmin._id,
533
+ source: {
534
+ name: "Brand",
535
+ },
536
+ })];
537
+ case 2:
538
+ spinach = _a.sent();
539
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
540
+ allowAnonymous: true,
541
+ permissions: {
542
+ create: [permissions_1.Permissions.IsAny],
543
+ delete: [permissions_1.Permissions.IsAny],
544
+ list: [permissions_1.Permissions.IsAny],
545
+ read: [permissions_1.Permissions.IsAny],
546
+ update: [permissions_1.Permissions.IsAny],
547
+ },
548
+ preDelete: function () {
549
+ var error = new Error("Some custom error");
550
+ error.disableExternalErrorTracking = true;
551
+ throw error;
552
+ },
553
+ }));
554
+ server = (0, supertest_1.default)(app);
555
+ return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(403)];
556
+ case 3:
557
+ res = _a.sent();
558
+ (0, bun_test_1.expect)(res.body.title).toContain("preDelete hook error");
559
+ (0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
560
+ return [2 /*return*/];
561
+ }
562
+ });
563
+ }); });
564
+ });
565
+ (0, bun_test_1.describe)("model array operations", function () {
566
+ var admin;
567
+ var spinach;
568
+ var apple;
569
+ var agent;
570
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
571
+ var _a, _b;
572
+ return __generator(this, function (_c) {
573
+ switch (_c.label) {
574
+ case 0:
575
+ process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
576
+ return [4 /*yield*/, (0, tests_1.setupDb)()];
577
+ case 1:
578
+ _a = __read.apply(void 0, [_c.sent(), 1]), admin = _a[0];
579
+ return [4 /*yield*/, Promise.all([
580
+ tests_1.FoodModel.create({
581
+ calories: 1,
582
+ created: new Date("2021-12-03T00:00:20.000Z"),
583
+ hidden: false,
584
+ name: "Spinach",
585
+ ownerId: admin._id,
586
+ source: {
587
+ name: "Brand",
588
+ },
589
+ }),
590
+ tests_1.FoodModel.create({
591
+ calories: 100,
592
+ categories: [
593
+ {
594
+ name: "Fruit",
595
+ show: true,
596
+ },
597
+ {
598
+ name: "Popular",
599
+ show: false,
600
+ },
601
+ ],
602
+ created: new Date("2021-12-03T00:00:30.000Z"),
603
+ hidden: false,
604
+ name: "Apple",
605
+ ownerId: admin._id,
606
+ tags: ["healthy", "cheap"],
607
+ }),
608
+ ])];
609
+ case 2:
610
+ _b = __read.apply(void 0, [_c.sent(), 2]), spinach = _b[0], apple = _b[1];
611
+ app = (0, tests_1.getBaseServer)();
612
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
613
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
614
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
615
+ allowAnonymous: true,
616
+ permissions: {
617
+ create: [permissions_1.Permissions.IsAdmin],
618
+ delete: [permissions_1.Permissions.IsAdmin],
619
+ list: [permissions_1.Permissions.IsAdmin],
620
+ read: [permissions_1.Permissions.IsAdmin],
621
+ update: [permissions_1.Permissions.IsAdmin],
622
+ },
623
+ queryFields: ["hidden", "calories", "created", "source.name"],
624
+ sort: { created: "descending" },
625
+ }));
626
+ server = (0, supertest_1.default)(app);
627
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
628
+ case 3:
629
+ agent = _c.sent();
630
+ return [2 /*return*/];
631
+ }
632
+ });
633
+ }); });
634
+ (0, bun_test_1.it)("add array sub-schema item", function () { return __awaiter(void 0, void 0, void 0, function () {
635
+ var res;
636
+ return __generator(this, function (_a) {
637
+ switch (_a.label) {
638
+ case 0: return [4 /*yield*/, agent
639
+ .post("/food/".concat(apple._id, "/categories"))
640
+ .send({ name: "Good Seller", show: false })
641
+ .expect(400)];
642
+ case 1:
643
+ res = _a.sent();
644
+ (0, bun_test_1.expect)(res.body.title).toBe("Malformed body, array operations should have a single, top level key, got: name,show");
645
+ return [4 /*yield*/, agent
646
+ .post("/food/".concat(apple._id, "/categories"))
647
+ .send({ categories: { name: "Good Seller", show: false } })
648
+ .expect(200)];
649
+ case 2:
650
+ res = _a.sent();
651
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(3);
652
+ (0, bun_test_1.expect)(res.body.data.categories[2].name).toBe("Good Seller");
653
+ return [4 /*yield*/, agent
654
+ .post("/food/".concat(spinach._id, "/categories"))
655
+ .send({ categories: { name: "Good Seller", show: false } })
656
+ .expect(200)];
657
+ case 3:
658
+ res = _a.sent();
659
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(1);
660
+ return [2 /*return*/];
661
+ }
662
+ });
663
+ }); });
664
+ (0, bun_test_1.it)("update array sub-schema item", function () { return __awaiter(void 0, void 0, void 0, function () {
665
+ var res;
666
+ return __generator(this, function (_a) {
667
+ switch (_a.label) {
668
+ case 0: return [4 /*yield*/, agent
669
+ .patch("/food/".concat(apple._id, "/categories/xyz"))
670
+ .send({ categories: { name: "Good Seller", show: false } })
671
+ .expect(404)];
672
+ case 1:
673
+ res = _a.sent();
674
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find categories/xyz");
675
+ return [4 /*yield*/, agent
676
+ .patch("/food/".concat(apple._id, "/categories/").concat(apple.categories[1]._id))
677
+ .send({ categories: { name: "Good Seller", show: false } })
678
+ .expect(200)];
679
+ case 2:
680
+ res = _a.sent();
681
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(2);
682
+ (0, bun_test_1.expect)(res.body.data.categories[1].name).toBe("Good Seller");
683
+ return [2 /*return*/];
684
+ }
685
+ });
686
+ }); });
687
+ (0, bun_test_1.it)("delete array sub-schema item", function () { return __awaiter(void 0, void 0, void 0, function () {
688
+ var res;
689
+ return __generator(this, function (_a) {
690
+ switch (_a.label) {
691
+ case 0: return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/categories/xyz")).expect(404)];
692
+ case 1:
693
+ res = _a.sent();
694
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find categories/xyz");
695
+ return [4 /*yield*/, agent
696
+ .delete("/food/".concat(apple._id, "/categories/").concat(apple.categories[0]._id))
697
+ .expect(200)];
698
+ case 2:
699
+ res = _a.sent();
700
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(1);
701
+ (0, bun_test_1.expect)(res.body.data.categories[0].name).toBe("Popular");
702
+ return [2 /*return*/];
703
+ }
704
+ });
705
+ }); });
706
+ (0, bun_test_1.it)("add array item", function () { return __awaiter(void 0, void 0, void 0, function () {
707
+ var res;
708
+ return __generator(this, function (_a) {
709
+ switch (_a.label) {
710
+ case 0: return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "popular" }).expect(200)];
711
+ case 1:
712
+ res = _a.sent();
713
+ (0, bun_test_1.expect)(res.body.data.tags).toHaveLength(3);
714
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["healthy", "cheap", "popular"]);
715
+ return [4 /*yield*/, agent.post("/food/".concat(spinach._id, "/tags")).send({ tags: "popular" }).expect(200)];
716
+ case 2:
717
+ res = _a.sent();
718
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["popular"]);
719
+ return [2 /*return*/];
720
+ }
721
+ });
722
+ }); });
723
+ (0, bun_test_1.it)("update array item", function () { return __awaiter(void 0, void 0, void 0, function () {
724
+ var res;
725
+ return __generator(this, function (_a) {
726
+ switch (_a.label) {
727
+ case 0: return [4 /*yield*/, agent
728
+ .patch("/food/".concat(apple._id, "/tags/xyz"))
729
+ .send({ tags: "unhealthy" })
730
+ .expect(404)];
731
+ case 1:
732
+ res = _a.sent();
733
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find tags/xyz");
734
+ return [4 /*yield*/, agent
735
+ .patch("/food/".concat(apple._id, "/tags/healthy"))
736
+ .send({ tags: "unhealthy" })
737
+ .expect(200)];
738
+ case 2:
739
+ res = _a.sent();
740
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["unhealthy", "cheap"]);
741
+ return [2 /*return*/];
742
+ }
743
+ });
744
+ }); });
745
+ (0, bun_test_1.it)("delete array item", function () { return __awaiter(void 0, void 0, void 0, function () {
746
+ var res;
747
+ return __generator(this, function (_a) {
748
+ switch (_a.label) {
749
+ case 0: return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/xyz")).expect(404)];
750
+ case 1:
751
+ res = _a.sent();
752
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find tags/xyz");
753
+ return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/healthy")).expect(200)];
754
+ case 2:
755
+ res = _a.sent();
756
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["cheap"]);
757
+ return [2 /*return*/];
758
+ }
759
+ });
760
+ }); });
761
+ (0, bun_test_1.it)("updates timestamps on array subdocuments", function () { return __awaiter(void 0, void 0, void 0, function () {
762
+ var foodWithTimestamps, firstCategoryId, secondCategoryId, res, updatedCategory, unchangedCategory;
763
+ var _a, _b, _c, _d, _e, _f;
764
+ return __generator(this, function (_g) {
765
+ switch (_g.label) {
766
+ case 0: return [4 /*yield*/, tests_1.FoodModel.create({
767
+ calories: 100,
768
+ categories: [
769
+ {
770
+ name: "Category 1",
771
+ show: true,
772
+ updated: new Date("2024-01-01T00:00:00.000Z"),
773
+ },
774
+ {
775
+ name: "Category 2",
776
+ show: true,
777
+ updated: new Date("2024-01-01T00:00:00.000Z"),
778
+ },
779
+ ],
780
+ created: new Date(),
781
+ name: "Food with Timestamps",
782
+ ownerId: admin._id,
783
+ })];
784
+ case 1:
785
+ foodWithTimestamps = _g.sent();
786
+ firstCategoryId = (_c = (_b = (_a = foodWithTimestamps.categories) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b._id) === null || _c === void 0 ? void 0 : _c.toString();
787
+ secondCategoryId = (_f = (_e = (_d = foodWithTimestamps.categories) === null || _d === void 0 ? void 0 : _d[1]) === null || _e === void 0 ? void 0 : _e._id) === null || _f === void 0 ? void 0 : _f.toString();
788
+ if (!firstCategoryId || !secondCategoryId) {
789
+ throw new Error("Failed to create food with categories");
790
+ }
791
+ // Wait a moment to ensure timestamp difference
792
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
793
+ case 2:
794
+ // Wait a moment to ensure timestamp difference
795
+ _g.sent();
796
+ return [4 /*yield*/, agent
797
+ .patch("/food/".concat(foodWithTimestamps._id, "/categories/").concat(firstCategoryId))
798
+ .send({ categories: { name: "Updated Category" } })
799
+ .expect(200)];
800
+ case 3:
801
+ res = _g.sent();
802
+ updatedCategory = res.body.data.categories.find(function (c) { return c._id === firstCategoryId; });
803
+ unchangedCategory = res.body.data.categories.find(function (c) { return c._id === secondCategoryId; });
804
+ if (!updatedCategory || !unchangedCategory) {
805
+ throw new Error("Failed to find categories in response");
806
+ }
807
+ (0, bun_test_1.expect)(updatedCategory.updated).not.toBe(updatedCategory.created);
808
+ (0, bun_test_1.expect)(unchangedCategory.updated).toBe(unchangedCategory.created);
809
+ (0, bun_test_1.expect)(updatedCategory.name).toBe("Updated Category");
810
+ // Unchanged.
811
+ (0, bun_test_1.expect)(updatedCategory.show).toBe(true);
812
+ (0, bun_test_1.expect)(unchangedCategory.show).toBe(true);
813
+ return [2 /*return*/];
814
+ }
815
+ });
816
+ }); });
817
+ (0, bun_test_1.it)("array operations call postUpdate with different copy of document", function () { return __awaiter(void 0, void 0, void 0, function () {
818
+ var postUpdateDoc, postUpdatePrevDoc, postUpdateCalled, categoryId, updatedCategory, prevCategory, remainingCategories, prevCategories;
819
+ return __generator(this, function (_a) {
820
+ switch (_a.label) {
821
+ case 0:
822
+ postUpdateCalled = false;
823
+ app = (0, tests_1.getBaseServer)();
824
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
825
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
826
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
827
+ allowAnonymous: true,
828
+ permissions: {
829
+ create: [permissions_1.Permissions.IsAdmin],
830
+ delete: [permissions_1.Permissions.IsAdmin],
831
+ list: [permissions_1.Permissions.IsAdmin],
832
+ read: [permissions_1.Permissions.IsAdmin],
833
+ update: [permissions_1.Permissions.IsAdmin],
834
+ },
835
+ postUpdate: function (doc, _cleanedBody, _request, prevValue) { return __awaiter(void 0, void 0, void 0, function () {
836
+ return __generator(this, function (_a) {
837
+ postUpdateDoc = doc;
838
+ postUpdatePrevDoc = prevValue;
839
+ postUpdateCalled = true;
840
+ return [2 /*return*/];
841
+ });
842
+ }); },
843
+ }));
844
+ server = (0, supertest_1.default)(app);
845
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
846
+ case 1:
847
+ agent = _a.sent();
848
+ // Test POST operation (add to array)
849
+ return [4 /*yield*/, agent
850
+ .post("/food/".concat(apple._id, "/categories"))
851
+ .send({ categories: { name: "New Category", show: true } })
852
+ .expect(200)];
853
+ case 2:
854
+ // Test POST operation (add to array)
855
+ _a.sent();
856
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
857
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
858
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
859
+ // Verify they are different object references
860
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
861
+ // Verify the content is different (new category added)
862
+ (0, bun_test_1.expect)(postUpdateDoc.categories).toHaveLength(3);
863
+ (0, bun_test_1.expect)(postUpdatePrevDoc.categories).toHaveLength(2);
864
+ // Reset for next test
865
+ postUpdateCalled = false;
866
+ postUpdateDoc = undefined;
867
+ postUpdatePrevDoc = undefined;
868
+ categoryId = apple.categories[0]._id;
869
+ if (!categoryId) {
870
+ throw new Error("Category ID is undefined");
871
+ }
872
+ return [4 /*yield*/, agent
873
+ .patch("/food/".concat(apple._id, "/categories/").concat(categoryId))
874
+ .send({ categories: { name: "Updated Category", show: false } })
875
+ .expect(200)];
876
+ case 3:
877
+ _a.sent();
878
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
879
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
880
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
881
+ // Verify they are different object references
882
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
883
+ updatedCategory = postUpdateDoc.categories.find(function (c) { return c._id.toString() === categoryId.toString(); });
884
+ prevCategory = postUpdatePrevDoc.categories.find(function (c) { return c._id.toString() === categoryId.toString(); });
885
+ (0, bun_test_1.expect)(updatedCategory.name).toBe("Updated Category");
886
+ (0, bun_test_1.expect)(prevCategory.name).toBe("Fruit");
887
+ // Reset for next test
888
+ postUpdateCalled = false;
889
+ postUpdateDoc = undefined;
890
+ postUpdatePrevDoc = undefined;
891
+ // Test DELETE operation (remove from array)
892
+ return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/categories/").concat(categoryId)).expect(200)];
893
+ case 4:
894
+ // Test DELETE operation (remove from array)
895
+ _a.sent();
896
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
897
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
898
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
899
+ // Verify they are different object references
900
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
901
+ remainingCategories = postUpdateDoc.categories.filter(function (c) { return c._id.toString() === categoryId.toString(); });
902
+ prevCategories = postUpdatePrevDoc.categories.filter(function (c) { return c._id.toString() === categoryId.toString(); });
903
+ (0, bun_test_1.expect)(remainingCategories).toHaveLength(0);
904
+ (0, bun_test_1.expect)(prevCategories).toHaveLength(1);
905
+ return [2 /*return*/];
906
+ }
907
+ });
908
+ }); });
909
+ (0, bun_test_1.it)("array operations with string arrays call postUpdate with different copy", function () { return __awaiter(void 0, void 0, void 0, function () {
910
+ var postUpdateDoc, postUpdatePrevDoc, postUpdateCalled;
911
+ return __generator(this, function (_a) {
912
+ switch (_a.label) {
913
+ case 0:
914
+ postUpdateCalled = false;
915
+ app = (0, tests_1.getBaseServer)();
916
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
917
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
918
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
919
+ allowAnonymous: true,
920
+ permissions: {
921
+ create: [permissions_1.Permissions.IsAdmin],
922
+ delete: [permissions_1.Permissions.IsAdmin],
923
+ list: [permissions_1.Permissions.IsAdmin],
924
+ read: [permissions_1.Permissions.IsAdmin],
925
+ update: [permissions_1.Permissions.IsAdmin],
926
+ },
927
+ postUpdate: function (doc, _cleanedBody, _request, prevValue) { return __awaiter(void 0, void 0, void 0, function () {
928
+ return __generator(this, function (_a) {
929
+ postUpdateDoc = doc;
930
+ postUpdatePrevDoc = prevValue;
931
+ postUpdateCalled = true;
932
+ return [2 /*return*/];
933
+ });
934
+ }); },
935
+ }));
936
+ server = (0, supertest_1.default)(app);
937
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
938
+ case 1:
939
+ agent = _a.sent();
940
+ // Test POST operation with string array (add tag)
941
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(200)];
942
+ case 2:
943
+ // Test POST operation with string array (add tag)
944
+ _a.sent();
945
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
946
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
947
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
948
+ // Verify they are different object references
949
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
950
+ // Verify the content is different (new tag added)
951
+ (0, bun_test_1.expect)(postUpdateDoc.tags).toHaveLength(3);
952
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).toHaveLength(2);
953
+ (0, bun_test_1.expect)(postUpdateDoc.tags).toContain("organic");
954
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).not.toContain("organic");
955
+ // Reset for next test
956
+ postUpdateCalled = false;
957
+ postUpdateDoc = undefined;
958
+ postUpdatePrevDoc = undefined;
959
+ // Test PATCH operation with string array (update tag)
960
+ return [4 /*yield*/, agent
961
+ .patch("/food/".concat(apple._id, "/tags/healthy"))
962
+ .send({ tags: "super-healthy" })
963
+ .expect(200)];
964
+ case 3:
965
+ // Test PATCH operation with string array (update tag)
966
+ _a.sent();
967
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
968
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
969
+ // Verify the content is different (tag updated)
970
+ (0, bun_test_1.expect)(postUpdateDoc.tags).toContain("super-healthy");
971
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).toContain("healthy");
972
+ (0, bun_test_1.expect)(postUpdateDoc.tags).not.toContain("healthy");
973
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).not.toContain("super-healthy");
974
+ return [2 /*return*/];
975
+ }
976
+ });
977
+ }); });
978
+ });
979
+ (0, bun_test_1.describe)("standard methods", function () {
980
+ var notAdmin;
981
+ var admin;
982
+ var adminOther;
983
+ var agent;
984
+ var spinach;
985
+ var apple;
986
+ var carrots;
987
+ var pizza;
988
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
989
+ var results;
990
+ var _a, _b;
991
+ return __generator(this, function (_c) {
992
+ switch (_c.label) {
993
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
994
+ case 1:
995
+ _a = __read.apply(void 0, [_c.sent(), 3]), admin = _a[0], notAdmin = _a[1], adminOther = _a[2];
996
+ return [4 /*yield*/, Promise.all([
997
+ tests_1.FoodModel.create({
998
+ calories: 1,
999
+ created: new Date("2021-12-03T00:00:20.000Z"),
1000
+ eatenBy: [admin._id],
1001
+ hidden: false,
1002
+ lastEatenWith: {
1003
+ dressing: new Date("2021-12-03T19:00:30.000Z"),
1004
+ },
1005
+ name: "Spinach",
1006
+ ownerId: notAdmin._id,
1007
+ source: {
1008
+ dateAdded: "2023-12-13T12:30:00.000Z",
1009
+ href: "https://www.google.com",
1010
+ name: "Brand",
1011
+ },
1012
+ }),
1013
+ tests_1.FoodModel.create({
1014
+ calories: 100,
1015
+ created: new Date("2021-12-03T00:00:30.000Z"),
1016
+ hidden: true,
1017
+ name: "Apple",
1018
+ ownerId: admin._id,
1019
+ tags: ["healthy"],
1020
+ }),
1021
+ tests_1.FoodModel.create({
1022
+ calories: 100,
1023
+ created: new Date("2021-12-03T00:00:00.000Z"),
1024
+ eatenBy: [admin._id, notAdmin._id],
1025
+ hidden: false,
1026
+ name: "Carrots",
1027
+ ownerId: admin._id,
1028
+ source: {
1029
+ name: "USDA",
1030
+ },
1031
+ tags: ["healthy", "cheap"],
1032
+ }),
1033
+ tests_1.FoodModel.create({
1034
+ calories: 400,
1035
+ created: new Date("2021-12-03T00:00:10.000Z"),
1036
+ eatenBy: [adminOther._id],
1037
+ hidden: false,
1038
+ name: "Pizza",
1039
+ ownerId: admin._id,
1040
+ tags: ["cheap"],
1041
+ }),
1042
+ ])];
1043
+ case 2:
1044
+ results = (_c.sent());
1045
+ _b = __read(results, 4), spinach = _b[0], apple = _b[1], carrots = _b[2], pizza = _b[3];
1046
+ app = (0, tests_1.getBaseServer)();
1047
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
1048
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1049
+ app.use(expressServer_1.logRequests);
1050
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1051
+ allowAnonymous: true,
1052
+ defaultLimit: 2,
1053
+ defaultQueryParams: { hidden: false },
1054
+ maxLimit: 3,
1055
+ permissions: {
1056
+ create: [permissions_1.Permissions.IsAuthenticated],
1057
+ delete: [permissions_1.Permissions.IsAdmin],
1058
+ list: [permissions_1.Permissions.IsAny],
1059
+ read: [permissions_1.Permissions.IsAny],
1060
+ update: [permissions_1.Permissions.IsOwner],
1061
+ },
1062
+ populatePaths: [{ path: "ownerId" }],
1063
+ queryFields: ["hidden", "name", "calories", "created", "source.name", "tags", "eatenBy"],
1064
+ sort: { created: "descending" },
1065
+ }));
1066
+ server = (0, supertest_1.default)(app);
1067
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
1068
+ case 3:
1069
+ agent = _c.sent();
1070
+ return [2 /*return*/];
1071
+ }
1072
+ });
1073
+ }); });
1074
+ (0, bun_test_1.it)("read default", function () { return __awaiter(void 0, void 0, void 0, function () {
1075
+ var res;
1076
+ return __generator(this, function (_a) {
1077
+ switch (_a.label) {
1078
+ case 0: return [4 /*yield*/, agent.get("/food/".concat(spinach._id)).expect(200)];
1079
+ case 1:
1080
+ res = _a.sent();
1081
+ (0, bun_test_1.expect)(res.body.data._id).toBe(spinach._id.toString());
1082
+ // Ensure populate works
1083
+ (0, bun_test_1.expect)(res.body.data.ownerId._id).toBe(notAdmin.id);
1084
+ // Ensure maps are properly transformed
1085
+ (0, bun_test_1.expect)(res.body.data.lastEatenWith).toEqual({
1086
+ dressing: "2021-12-03T19:00:30.000Z",
1087
+ });
1088
+ return [2 /*return*/];
1089
+ }
1090
+ });
1091
+ }); });
1092
+ (0, bun_test_1.it)("list default", function () { return __awaiter(void 0, void 0, void 0, function () {
1093
+ var res;
1094
+ return __generator(this, function (_a) {
1095
+ switch (_a.label) {
1096
+ case 0: return [4 /*yield*/, agent.get("/food").expect(200)];
1097
+ case 1:
1098
+ res = _a.sent();
1099
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
1100
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(spinach.id);
1101
+ (0, bun_test_1.expect)(res.body.data[0].ownerId._id).toBe(notAdmin.id);
1102
+ (0, bun_test_1.expect)(res.body.data[1].id).toBe(pizza.id);
1103
+ (0, bun_test_1.expect)(res.body.data[1].ownerId._id).toBe(admin.id);
1104
+ // Check that mongoose Map is handled correctly.
1105
+ (0, bun_test_1.expect)(res.body.data[0].lastEatenWith).toEqual({
1106
+ dressing: "2021-12-03T19:00:30.000Z",
1107
+ });
1108
+ (0, bun_test_1.expect)(res.body.data[1].lastEatenWith).toEqual(undefined);
1109
+ (0, bun_test_1.expect)(res.body.more).toBe(true);
1110
+ (0, bun_test_1.expect)(res.body.total).toBe(3);
1111
+ return [2 /*return*/];
1112
+ }
1113
+ });
1114
+ }); });
1115
+ (0, bun_test_1.it)("list limit", function () { return __awaiter(void 0, void 0, void 0, function () {
1116
+ var res;
1117
+ return __generator(this, function (_a) {
1118
+ switch (_a.label) {
1119
+ case 0: return [4 /*yield*/, agent.get("/food?limit=1").expect(200)];
1120
+ case 1:
1121
+ res = _a.sent();
1122
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1123
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(spinach.id);
1124
+ (0, bun_test_1.expect)(res.body.data[0].ownerId._id).toBe(notAdmin.id);
1125
+ (0, bun_test_1.expect)(res.body.more).toBe(true);
1126
+ (0, bun_test_1.expect)(res.body.total).toBe(3);
1127
+ return [2 /*return*/];
1128
+ }
1129
+ });
1130
+ }); });
1131
+ (0, bun_test_1.it)("list limit over", function () { return __awaiter(void 0, void 0, void 0, function () {
1132
+ var res;
1133
+ return __generator(this, function (_a) {
1134
+ switch (_a.label) {
1135
+ case 0:
1136
+ // This shouldn't be seen, it's the end of the list.
1137
+ return [4 /*yield*/, tests_1.FoodModel.create({
1138
+ calories: 400,
1139
+ created: new Date("2021-12-02T00:00:10.000Z"),
1140
+ hidden: false,
1141
+ name: "Pizza",
1142
+ ownerId: admin._id,
1143
+ })];
1144
+ case 1:
1145
+ // This shouldn't be seen, it's the end of the list.
1146
+ _a.sent();
1147
+ return [4 /*yield*/, agent.get("/food?limit=4").expect(200)];
1148
+ case 2:
1149
+ res = _a.sent();
1150
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(3);
1151
+ (0, bun_test_1.expect)(res.body.more).toBe(true);
1152
+ (0, bun_test_1.expect)(res.body.total).toBe(4);
1153
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(spinach.id);
1154
+ (0, bun_test_1.expect)(res.body.data[1].id).toBe(pizza.id);
1155
+ (0, bun_test_1.expect)(res.body.data[2].id).toBe(carrots.id);
1156
+ (0, bun_test_1.expect)(Sentry.captureMessage).toHaveBeenCalledWith('More than 3 results returned for foods without pagination, data may be silently truncated. req.query: {"limit":"4"}');
1157
+ return [2 /*return*/];
1158
+ }
1159
+ });
1160
+ }); });
1161
+ (0, bun_test_1.it)("list page", function () { return __awaiter(void 0, void 0, void 0, function () {
1162
+ var res;
1163
+ return __generator(this, function (_a) {
1164
+ switch (_a.label) {
1165
+ case 0: return [4 /*yield*/, agent.get("/food?limit=1&page=2").expect(200)];
1166
+ case 1:
1167
+ res = _a.sent();
1168
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1169
+ (0, bun_test_1.expect)(res.body.more).toBe(true);
1170
+ (0, bun_test_1.expect)(res.body.total).toBe(3);
1171
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(pizza.id);
1172
+ return [2 /*return*/];
1173
+ }
1174
+ });
1175
+ }); });
1176
+ (0, bun_test_1.it)("list page 0 ", function () { return __awaiter(void 0, void 0, void 0, function () {
1177
+ var res;
1178
+ return __generator(this, function (_a) {
1179
+ switch (_a.label) {
1180
+ case 0: return [4 /*yield*/, agent.get("/food?limit=1&page=0").expect(400)];
1181
+ case 1:
1182
+ res = _a.sent();
1183
+ (0, bun_test_1.expect)(res.body.title).toBe("Invalid page: 0");
1184
+ return [2 /*return*/];
1185
+ }
1186
+ });
1187
+ }); });
1188
+ (0, bun_test_1.it)("list page with garbage ", function () { return __awaiter(void 0, void 0, void 0, function () {
1189
+ var res;
1190
+ return __generator(this, function (_a) {
1191
+ switch (_a.label) {
1192
+ case 0: return [4 /*yield*/, agent.get("/food?limit=1&page=abc").expect(400)];
1193
+ case 1:
1194
+ res = _a.sent();
1195
+ (0, bun_test_1.expect)(res.body.title).toBe("Invalid page: abc");
1196
+ return [2 /*return*/];
1197
+ }
1198
+ });
1199
+ }); });
1200
+ (0, bun_test_1.it)("list page over", function () { return __awaiter(void 0, void 0, void 0, function () {
1201
+ var res;
1202
+ return __generator(this, function (_a) {
1203
+ switch (_a.label) {
1204
+ case 0: return [4 /*yield*/, agent.get("/food?limit=1&page=5").expect(200)];
1205
+ case 1:
1206
+ res = _a.sent();
1207
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(0);
1208
+ (0, bun_test_1.expect)(res.body.more).toBe(false);
1209
+ (0, bun_test_1.expect)(res.body.total).toBe(3);
1210
+ return [2 /*return*/];
1211
+ }
1212
+ });
1213
+ }); });
1214
+ (0, bun_test_1.it)("list query params", function () { return __awaiter(void 0, void 0, void 0, function () {
1215
+ var res;
1216
+ return __generator(this, function (_a) {
1217
+ switch (_a.label) {
1218
+ case 0: return [4 /*yield*/, agent.get("/food?hidden=true").expect(200)];
1219
+ case 1:
1220
+ res = _a.sent();
1221
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1222
+ (0, bun_test_1.expect)(res.body.more).toBe(false);
1223
+ (0, bun_test_1.expect)(res.body.total).toBe(1);
1224
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(apple.id);
1225
+ return [2 /*return*/];
1226
+ }
1227
+ });
1228
+ }); });
1229
+ (0, bun_test_1.it)("list query params not in list", function () { return __awaiter(void 0, void 0, void 0, function () {
1230
+ var res;
1231
+ return __generator(this, function (_a) {
1232
+ switch (_a.label) {
1233
+ case 0: return [4 /*yield*/, agent.get("/food?ownerId=".concat(admin._id)).expect(400)];
1234
+ case 1:
1235
+ res = _a.sent();
1236
+ (0, bun_test_1.expect)(res.body.title).toBe("ownerId is not allowed as a query param.");
1237
+ return [2 /*return*/];
1238
+ }
1239
+ });
1240
+ }); });
1241
+ (0, bun_test_1.it)("list query by nested param", function () { return __awaiter(void 0, void 0, void 0, function () {
1242
+ var res;
1243
+ return __generator(this, function (_a) {
1244
+ switch (_a.label) {
1245
+ case 0: return [4 /*yield*/, agent.get("/food?source.name=USDA").expect(200)];
1246
+ case 1:
1247
+ res = _a.sent();
1248
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1249
+ (0, bun_test_1.expect)(res.body.total).toBe(1);
1250
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(carrots.id);
1251
+ return [2 /*return*/];
1252
+ }
1253
+ });
1254
+ }); });
1255
+ (0, bun_test_1.it)("query by date", function () { return __awaiter(void 0, void 0, void 0, function () {
1256
+ var authRes, token, res, createdDates;
1257
+ return __generator(this, function (_a) {
1258
+ switch (_a.label) {
1259
+ case 0: return [4 /*yield*/, server
1260
+ .post("/auth/login")
1261
+ .send({ email: "admin@example.com", password: "securePassword" })
1262
+ .expect(200)];
1263
+ case 1:
1264
+ authRes = _a.sent();
1265
+ token = authRes.body.data.token;
1266
+ return [4 /*yield*/, server
1267
+ .get("/food?limit=3&".concat(qs_1.default.stringify({
1268
+ created: {
1269
+ $gte: "2021-12-03T00:00:00.000Z",
1270
+ $lte: "2021-12-03T00:00:20.000Z",
1271
+ },
1272
+ })))
1273
+ .set("authorization", "Bearer ".concat(token))
1274
+ .expect(200)];
1275
+ case 2:
1276
+ res = _a.sent();
1277
+ (0, bun_test_1.expect)(res.body.data.map(function (d) { return d.created; })).toEqual(bun_test_1.expect.arrayContaining([
1278
+ "2021-12-03T00:00:20.000Z",
1279
+ "2021-12-03T00:00:10.000Z",
1280
+ "2021-12-03T00:00:00.000Z",
1281
+ ]));
1282
+ (0, bun_test_1.expect)(res.body.data.map(function (d) { return d.created; })).toHaveLength(3);
1283
+ return [4 /*yield*/, server
1284
+ .get("/food?limit=3&".concat(qs_1.default.stringify({
1285
+ created: {
1286
+ $gte: "2021-12-03T00:00:00.000Z",
1287
+ $lt: "2021-12-03T00:00:20.000Z",
1288
+ },
1289
+ })))
1290
+ .set("authorization", "Bearer ".concat(token))
1291
+ .expect(200)];
1292
+ case 3:
1293
+ // Inclusive one side
1294
+ res = _a.sent();
1295
+ (0, bun_test_1.expect)(res.body.data.map(function (d) { return d.created; })).toEqual(bun_test_1.expect.arrayContaining(["2021-12-03T00:00:10.000Z", "2021-12-03T00:00:00.000Z"]));
1296
+ (0, bun_test_1.expect)(res.body.data.map(function (d) { return d.created; })).toHaveLength(2);
1297
+ return [4 /*yield*/, server
1298
+ .get("/food?limit=3&".concat(qs_1.default.stringify({
1299
+ created: {
1300
+ $gt: "2021-12-03T00:00:00.000Z",
1301
+ $lt: "2021-12-03T00:00:20.000Z",
1302
+ },
1303
+ })))
1304
+ .set("authorization", "Bearer ".concat(token))
1305
+ .expect(200)];
1306
+ case 4:
1307
+ // Inclusive both sides
1308
+ res = _a.sent();
1309
+ createdDates = res.body.data.map(function (d) { return d.created; });
1310
+ (0, bun_test_1.expect)(createdDates).toEqual(bun_test_1.expect.arrayContaining(["2021-12-03T00:00:10.000Z"]));
1311
+ (0, bun_test_1.expect)(createdDates).toHaveLength(1);
1312
+ return [2 /*return*/];
1313
+ }
1314
+ });
1315
+ }); });
1316
+ (0, bun_test_1.it)("query with a space", function () { return __awaiter(void 0, void 0, void 0, function () {
1317
+ var greenBeans, res;
1318
+ return __generator(this, function (_a) {
1319
+ switch (_a.label) {
1320
+ case 0: return [4 /*yield*/, tests_1.FoodModel.create({
1321
+ calories: 102,
1322
+ created: Date.now() - 10,
1323
+ name: "Green Beans",
1324
+ ownerId: admin === null || admin === void 0 ? void 0 : admin._id,
1325
+ })];
1326
+ case 1:
1327
+ greenBeans = _a.sent();
1328
+ return [4 /*yield*/, agent.get("/food?".concat(qs_1.default.stringify({ name: "Green Beans" }))).expect(200)];
1329
+ case 2:
1330
+ res = _a.sent();
1331
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1332
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(greenBeans === null || greenBeans === void 0 ? void 0 : greenBeans.id);
1333
+ (0, bun_test_1.expect)(res.body.data[0].name).toBe("Green Beans");
1334
+ return [2 /*return*/];
1335
+ }
1336
+ });
1337
+ }); });
1338
+ (0, bun_test_1.it)("query with a regex", function () { return __awaiter(void 0, void 0, void 0, function () {
1339
+ var greenBeans, res;
1340
+ return __generator(this, function (_a) {
1341
+ switch (_a.label) {
1342
+ case 0: return [4 /*yield*/, tests_1.FoodModel.create({
1343
+ calories: 102,
1344
+ created: Date.now() - 10,
1345
+ name: "Green Beans",
1346
+ ownerId: admin === null || admin === void 0 ? void 0 : admin._id,
1347
+ })];
1348
+ case 1:
1349
+ greenBeans = _a.sent();
1350
+ return [4 /*yield*/, agent.get("/food?".concat(qs_1.default.stringify({ name: { $regex: "Green" } }))).expect(200)];
1351
+ case 2:
1352
+ res = _a.sent();
1353
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1354
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(greenBeans === null || greenBeans === void 0 ? void 0 : greenBeans.id);
1355
+ (0, bun_test_1.expect)(res.body.data[0].name).toBe("Green Beans");
1356
+ return [4 /*yield*/, agent.get("/food?".concat(qs_1.default.stringify({ name: { $regex: "green" } }))).expect(200)];
1357
+ case 3:
1358
+ // Fails with different casing and sensitive
1359
+ res = _a.sent();
1360
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(0);
1361
+ return [4 /*yield*/, agent
1362
+ .get("/food?".concat(qs_1.default.stringify({ name: { $options: "i", $regex: "green" } })))
1363
+ .expect(200)];
1364
+ case 4:
1365
+ // Case insensitive does match different casing
1366
+ res = _a.sent();
1367
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1368
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(greenBeans === null || greenBeans === void 0 ? void 0 : greenBeans.id);
1369
+ return [2 /*return*/];
1370
+ }
1371
+ });
1372
+ }); });
1373
+ (0, bun_test_1.it)("query with an $in operator", function () { return __awaiter(void 0, void 0, void 0, function () {
1374
+ var res, names1, names2;
1375
+ return __generator(this, function (_a) {
1376
+ switch (_a.label) {
1377
+ case 0: return [4 /*yield*/, server
1378
+ .get("/food?".concat(qs_1.default.stringify({
1379
+ name: {
1380
+ $in: ["Apple", "Spinach"],
1381
+ },
1382
+ })))
1383
+ .expect(200)];
1384
+ case 1:
1385
+ res = _a.sent();
1386
+ names1 = res.body.data.map(function (d) { return d.name; });
1387
+ (0, bun_test_1.expect)(names1).toEqual(bun_test_1.expect.arrayContaining(["Spinach"]));
1388
+ (0, bun_test_1.expect)(names1).toHaveLength(1);
1389
+ return [4 /*yield*/, server
1390
+ .get("/food?".concat(qs_1.default.stringify({
1391
+ name: {
1392
+ $in: ["Carrots", "Spinach"],
1393
+ },
1394
+ })))
1395
+ .expect(200)];
1396
+ case 2:
1397
+ // Query without hidden food.
1398
+ res = _a.sent();
1399
+ names2 = res.body.data.map(function (d) { return d.name; });
1400
+ (0, bun_test_1.expect)(names2).toEqual(bun_test_1.expect.arrayContaining(["Spinach", "Carrots"]));
1401
+ (0, bun_test_1.expect)(names2).toHaveLength(2);
1402
+ return [2 /*return*/];
1403
+ }
1404
+ });
1405
+ }); });
1406
+ (0, bun_test_1.it)("query with an $in for _ids in nested object", function () { return __awaiter(void 0, void 0, void 0, function () {
1407
+ var res, names3;
1408
+ return __generator(this, function (_a) {
1409
+ switch (_a.label) {
1410
+ case 0: return [4 /*yield*/, server
1411
+ .get("/food?".concat(qs_1.default.stringify({
1412
+ eatenBy: {
1413
+ $in: [notAdmin._id.toString(), adminOther._id.toString()],
1414
+ },
1415
+ })))
1416
+ .expect(200)];
1417
+ case 1:
1418
+ res = _a.sent();
1419
+ (0, bun_test_1.expect)(res.body.more).toBe(false);
1420
+ (0, bun_test_1.expect)(res.body.total).toBe(2);
1421
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
1422
+ names3 = res.body.data.map(function (d) { return d.name; });
1423
+ (0, bun_test_1.expect)(names3).toEqual(bun_test_1.expect.arrayContaining(["Carrots", "Pizza"]));
1424
+ (0, bun_test_1.expect)(names3).toHaveLength(2);
1425
+ return [2 /*return*/];
1426
+ }
1427
+ });
1428
+ }); });
1429
+ (0, bun_test_1.it)("query $and operator on same field", function () { return __awaiter(void 0, void 0, void 0, function () {
1430
+ var res;
1431
+ return __generator(this, function (_a) {
1432
+ switch (_a.label) {
1433
+ case 0: return [4 /*yield*/, agent
1434
+ .get("/food?".concat(qs_1.default.stringify({ $and: [{ tags: "healthy" }, { tags: "cheap" }] })))
1435
+ .expect(200)];
1436
+ case 1:
1437
+ res = _a.sent();
1438
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1439
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(carrots === null || carrots === void 0 ? void 0 : carrots._id.toString());
1440
+ return [2 /*return*/];
1441
+ }
1442
+ });
1443
+ }); });
1444
+ (0, bun_test_1.it)("query $and operator on same field, nested objects", function () { return __awaiter(void 0, void 0, void 0, function () {
1445
+ var res;
1446
+ return __generator(this, function (_a) {
1447
+ switch (_a.label) {
1448
+ case 0: return [4 /*yield*/, agent
1449
+ .get("/food?".concat(qs_1.default.stringify({
1450
+ $and: [{ eatenBy: admin.id }, { eatenBy: notAdmin.id }],
1451
+ })))
1452
+ .expect(200)];
1453
+ case 1:
1454
+ res = _a.sent();
1455
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1456
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(carrots === null || carrots === void 0 ? void 0 : carrots._id.toString());
1457
+ return [2 /*return*/];
1458
+ }
1459
+ });
1460
+ }); });
1461
+ (0, bun_test_1.it)("query $or operator on same field", function () { return __awaiter(void 0, void 0, void 0, function () {
1462
+ var res, ids1;
1463
+ return __generator(this, function (_a) {
1464
+ switch (_a.label) {
1465
+ case 0: return [4 /*yield*/, agent
1466
+ .get("/food?".concat(qs_1.default.stringify({ $or: [{ name: "Carrots" }, { name: "Pizza" }] })))
1467
+ .expect(200)];
1468
+ case 1:
1469
+ res = _a.sent();
1470
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
1471
+ ids1 = res.body.data.map(function (d) { return d.id; });
1472
+ (0, bun_test_1.expect)(ids1).toEqual(bun_test_1.expect.arrayContaining([carrots === null || carrots === void 0 ? void 0 : carrots._id.toString(), pizza === null || pizza === void 0 ? void 0 : pizza._id.toString()]));
1473
+ (0, bun_test_1.expect)(ids1).toHaveLength(2);
1474
+ return [2 /*return*/];
1475
+ }
1476
+ });
1477
+ }); });
1478
+ (0, bun_test_1.it)("query $and operator on same field, nested objects", function () { return __awaiter(void 0, void 0, void 0, function () {
1479
+ var res, ids2;
1480
+ return __generator(this, function (_a) {
1481
+ switch (_a.label) {
1482
+ case 0: return [4 /*yield*/, agent
1483
+ .get("/food?".concat(qs_1.default.stringify({
1484
+ $or: [{ eatenBy: admin.id }, { eatenBy: notAdmin.id }],
1485
+ limit: 3,
1486
+ })))
1487
+ .expect(200)];
1488
+ case 1:
1489
+ res = _a.sent();
1490
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
1491
+ ids2 = res.body.data.map(function (d) { return d.id; });
1492
+ (0, bun_test_1.expect)(ids2).toEqual(bun_test_1.expect.arrayContaining([carrots === null || carrots === void 0 ? void 0 : carrots._id.toString(), spinach === null || spinach === void 0 ? void 0 : spinach._id.toString()]));
1493
+ (0, bun_test_1.expect)(ids2).toHaveLength(2);
1494
+ return [2 /*return*/];
1495
+ }
1496
+ });
1497
+ }); });
1498
+ (0, bun_test_1.it)("query $and and $or are rejected if field is not in queryFields", function () { return __awaiter(void 0, void 0, void 0, function () {
1499
+ var res;
1500
+ return __generator(this, function (_a) {
1501
+ switch (_a.label) {
1502
+ case 0: return [4 /*yield*/, agent
1503
+ .get("/food?".concat(qs_1.default.stringify({ $and: [{ ownerId: "healthy" }, { tags: "cheap" }] })))
1504
+ .expect(400)];
1505
+ case 1:
1506
+ res = _a.sent();
1507
+ (0, bun_test_1.expect)(res.body.title).toBe("ownerId is not allowed as a query param.");
1508
+ return [4 /*yield*/, agent
1509
+ .get("/food?".concat(qs_1.default.stringify({ $and: [{ tags: "cheap" }, { ownerId: "healthy" }] })))
1510
+ .expect(400)];
1511
+ case 2:
1512
+ // Check in the other order
1513
+ res = _a.sent();
1514
+ (0, bun_test_1.expect)(res.body.title).toBe("ownerId is not allowed as a query param.");
1515
+ return [4 /*yield*/, agent
1516
+ .get("/food?".concat(qs_1.default.stringify({ $or: [{ tags: "cheap" }, { ownerId: "healthy" }] })))
1517
+ .expect(400)];
1518
+ case 3:
1519
+ res = _a.sent();
1520
+ (0, bun_test_1.expect)(res.body.title).toBe("ownerId is not allowed as a query param.");
1521
+ return [2 /*return*/];
1522
+ }
1523
+ });
1524
+ }); });
1525
+ (0, bun_test_1.it)("query with a number", function () { return __awaiter(void 0, void 0, void 0, function () {
1526
+ var res;
1527
+ return __generator(this, function (_a) {
1528
+ switch (_a.label) {
1529
+ case 0: return [4 /*yield*/, agent.get("/food?calories=100").expect(200)];
1530
+ case 1:
1531
+ res = _a.sent();
1532
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
1533
+ (0, bun_test_1.expect)(res.body.data[0].id).toBe(carrots === null || carrots === void 0 ? void 0 : carrots._id.toString());
1534
+ return [2 /*return*/];
1535
+ }
1536
+ });
1537
+ }); });
1538
+ (0, bun_test_1.it)("update", function () { return __awaiter(void 0, void 0, void 0, function () {
1539
+ var res;
1540
+ return __generator(this, function (_a) {
1541
+ switch (_a.label) {
1542
+ case 0: return [4 /*yield*/, agent.patch("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(200)];
1543
+ case 1:
1544
+ res = _a.sent();
1545
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Kale");
1546
+ (0, bun_test_1.expect)(res.body.data.calories).toBe(1);
1547
+ (0, bun_test_1.expect)(res.body.data.hidden).toBe(false);
1548
+ return [4 /*yield*/, agent
1549
+ .patch("/food/".concat(spinach._id))
1550
+ .send({ lastEatenWith: { dressing: "2023-12-03T00:00:20.000Z" } })
1551
+ .expect(200)];
1552
+ case 2:
1553
+ // Update a Map field.
1554
+ res = _a.sent();
1555
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Kale");
1556
+ (0, bun_test_1.expect)(res.body.data.calories).toBe(1);
1557
+ (0, bun_test_1.expect)(res.body.data.hidden).toBe(false);
1558
+ (0, bun_test_1.expect)(res.body.data.lastEatenWith).toEqual({
1559
+ dressing: "2023-12-03T00:00:20.000Z",
1560
+ });
1561
+ return [4 /*yield*/, agent
1562
+ .patch("/food/".concat(spinach._id))
1563
+ .send({
1564
+ lastEatenWith: {
1565
+ cucumber: "2023-12-04T12:00:20.000Z",
1566
+ dressing: "2023-12-03T00:00:20.000Z",
1567
+ },
1568
+ })
1569
+ .expect(200)];
1570
+ case 3:
1571
+ // Update a Map field.
1572
+ res = _a.sent();
1573
+ (0, bun_test_1.expect)(res.body.data.lastEatenWith).toEqual({
1574
+ cucumber: "2023-12-04T12:00:20.000Z",
1575
+ dressing: "2023-12-03T00:00:20.000Z",
1576
+ });
1577
+ return [2 /*return*/];
1578
+ }
1579
+ });
1580
+ }); });
1581
+ (0, bun_test_1.it)("update using dot notation", function () { return __awaiter(void 0, void 0, void 0, function () {
1582
+ var res, dbSpinach;
1583
+ return __generator(this, function (_a) {
1584
+ switch (_a.label) {
1585
+ case 0: return [4 /*yield*/, agent
1586
+ .patch("/food/".concat(spinach._id))
1587
+ .send({ "source.href": "https://food.com" })
1588
+ .expect(200)];
1589
+ case 1:
1590
+ res = _a.sent();
1591
+ // Assert the field was updated with dot notation.
1592
+ (0, bun_test_1.expect)(res.body.data.source.href).toBe("https://food.com");
1593
+ // Assert these fields haven't changed.
1594
+ (0, bun_test_1.expect)(res.body.data.source.name).toBe("Brand");
1595
+ (0, bun_test_1.expect)(res.body.data.source.dateAdded).toBe("2023-12-13T12:30:00.000Z");
1596
+ return [4 /*yield*/, tests_1.FoodModel.findById(spinach._id)];
1597
+ case 2:
1598
+ dbSpinach = _a.sent();
1599
+ (0, bun_test_1.expect)(dbSpinach === null || dbSpinach === void 0 ? void 0 : dbSpinach.source.href).toBe("https://food.com");
1600
+ (0, bun_test_1.expect)(dbSpinach === null || dbSpinach === void 0 ? void 0 : dbSpinach.source.name).toBe("Brand");
1601
+ (0, bun_test_1.expect)(dbSpinach === null || dbSpinach === void 0 ? void 0 : dbSpinach.source.dateAdded).toBe("2023-12-13T12:30:00.000Z");
1602
+ return [2 /*return*/];
1603
+ }
1604
+ });
1605
+ }); });
1606
+ });
1607
+ (0, bun_test_1.describe)("populate", function () {
1608
+ var admin;
1609
+ var notAdmin;
1610
+ var agent;
1611
+ var spinach;
1612
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1613
+ var _a, _b;
1614
+ return __generator(this, function (_c) {
1615
+ switch (_c.label) {
1616
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1617
+ case 1:
1618
+ _a = __read.apply(void 0, [_c.sent(), 2]), admin = _a[0], notAdmin = _a[1];
1619
+ return [4 /*yield*/, Promise.all([
1620
+ tests_1.FoodModel.create({
1621
+ calories: 1,
1622
+ created: new Date("2021-12-03T00:00:20.000Z"),
1623
+ hidden: false,
1624
+ name: "Spinach",
1625
+ ownerId: admin._id,
1626
+ source: {
1627
+ name: "Brand",
1628
+ },
1629
+ }),
1630
+ tests_1.FoodModel.create({
1631
+ calories: 1,
1632
+ created: new Date("2022-12-03T00:00:20.000Z"),
1633
+ hidden: false,
1634
+ name: "Carrots",
1635
+ ownerId: notAdmin._id,
1636
+ source: {
1637
+ name: "User",
1638
+ },
1639
+ }),
1640
+ ])];
1641
+ case 2:
1642
+ _b = __read.apply(void 0, [_c.sent(), 1]), spinach = _b[0];
1643
+ app = (0, tests_1.getBaseServer)();
1644
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
1645
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1646
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1647
+ allowAnonymous: true,
1648
+ permissions: {
1649
+ create: [permissions_1.Permissions.IsAny],
1650
+ delete: [permissions_1.Permissions.IsAny],
1651
+ list: [permissions_1.Permissions.IsAny],
1652
+ read: [permissions_1.Permissions.IsAny],
1653
+ update: [permissions_1.Permissions.IsAny],
1654
+ },
1655
+ populatePaths: [{ fields: ["email"], path: "ownerId" }],
1656
+ sort: "-created",
1657
+ }));
1658
+ server = (0, supertest_1.default)(app);
1659
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
1660
+ case 3:
1661
+ agent = _c.sent();
1662
+ return [2 /*return*/];
1663
+ }
1664
+ });
1665
+ }); });
1666
+ (0, bun_test_1.it)("lists with populate", function () { return __awaiter(void 0, void 0, void 0, function () {
1667
+ var res, _a, carrots, spin;
1668
+ return __generator(this, function (_b) {
1669
+ switch (_b.label) {
1670
+ case 0: return [4 /*yield*/, agent.get("/food").expect(200)];
1671
+ case 1:
1672
+ res = _b.sent();
1673
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
1674
+ _a = __read(res.body.data, 2), carrots = _a[0], spin = _a[1];
1675
+ (0, bun_test_1.expect)(carrots.ownerId._id).toBe(notAdmin._id.toString());
1676
+ (0, bun_test_1.expect)(carrots.ownerId.email).toBe(notAdmin.email);
1677
+ (0, bun_test_1.expect)(carrots.ownerId.name).toBeUndefined();
1678
+ (0, bun_test_1.expect)(spin.ownerId._id).toBe(admin._id.toString());
1679
+ (0, bun_test_1.expect)(spin.ownerId.email).toBe(admin.email);
1680
+ (0, bun_test_1.expect)(spin.ownerId.name).toBeUndefined();
1681
+ return [2 /*return*/];
1682
+ }
1683
+ });
1684
+ }); });
1685
+ (0, bun_test_1.it)("reads with populate", function () { return __awaiter(void 0, void 0, void 0, function () {
1686
+ var res;
1687
+ return __generator(this, function (_a) {
1688
+ switch (_a.label) {
1689
+ case 0: return [4 /*yield*/, agent.get("/food/".concat(spinach._id)).expect(200)];
1690
+ case 1:
1691
+ res = _a.sent();
1692
+ (0, bun_test_1.expect)(res.body.data.ownerId._id).toBe(admin._id.toString());
1693
+ (0, bun_test_1.expect)(res.body.data.ownerId.email).toBe(admin.email);
1694
+ (0, bun_test_1.expect)(res.body.data.ownerId.name).toBeUndefined();
1695
+ return [2 /*return*/];
1696
+ }
1697
+ });
1698
+ }); });
1699
+ (0, bun_test_1.it)("creates with populate", function () { return __awaiter(void 0, void 0, void 0, function () {
1700
+ var res;
1701
+ return __generator(this, function (_a) {
1702
+ switch (_a.label) {
1703
+ case 0: return [4 /*yield*/, server
1704
+ .post("/food")
1705
+ .send({
1706
+ calories: 15,
1707
+ name: "Broccoli",
1708
+ ownerId: admin._id,
1709
+ })
1710
+ .expect(201)];
1711
+ case 1:
1712
+ res = _a.sent();
1713
+ (0, bun_test_1.expect)(res.body.data.ownerId._id).toBe(admin._id.toString());
1714
+ (0, bun_test_1.expect)(res.body.data.ownerId.email).toBe(admin.email);
1715
+ (0, bun_test_1.expect)(res.body.data.ownerId.name).toBeUndefined();
1716
+ return [2 /*return*/];
1717
+ }
1718
+ });
1719
+ }); });
1720
+ (0, bun_test_1.it)("updates with populate", function () { return __awaiter(void 0, void 0, void 0, function () {
1721
+ var res;
1722
+ return __generator(this, function (_a) {
1723
+ switch (_a.label) {
1724
+ case 0: return [4 /*yield*/, server
1725
+ .patch("/food/".concat(spinach._id))
1726
+ .send({
1727
+ name: "NotSpinach",
1728
+ })
1729
+ .expect(200)];
1730
+ case 1:
1731
+ res = _a.sent();
1732
+ (0, bun_test_1.expect)(res.body.data.ownerId._id).toBe(admin._id.toString());
1733
+ (0, bun_test_1.expect)(res.body.data.ownerId.email).toBe(admin.email);
1734
+ (0, bun_test_1.expect)(res.body.data.ownerId.name).toBeUndefined();
1735
+ return [2 /*return*/];
1736
+ }
1737
+ });
1738
+ }); });
1739
+ });
1740
+ (0, bun_test_1.describe)("responseHandler", function () {
1741
+ var admin;
1742
+ var agent;
1743
+ var spinach;
1744
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1745
+ var _a, _b;
1746
+ return __generator(this, function (_c) {
1747
+ switch (_c.label) {
1748
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1749
+ case 1:
1750
+ _a = __read.apply(void 0, [_c.sent(), 1]), admin = _a[0];
1751
+ return [4 /*yield*/, Promise.all([
1752
+ tests_1.FoodModel.create({
1753
+ calories: 1,
1754
+ created: new Date("2021-12-03T00:00:20.000Z"),
1755
+ hidden: false,
1756
+ name: "Spinach",
1757
+ ownerId: admin._id,
1758
+ source: {
1759
+ name: "Brand",
1760
+ },
1761
+ }),
1762
+ tests_1.FoodModel.create({
1763
+ calories: 100,
1764
+ created: Date.now() - 10,
1765
+ hidden: true,
1766
+ name: "Apple",
1767
+ ownerId: admin === null || admin === void 0 ? void 0 : admin._id,
1768
+ }),
1769
+ ])];
1770
+ case 2:
1771
+ _b = __read.apply(void 0, [_c.sent(), 1]), spinach = _b[0];
1772
+ app = (0, tests_1.getBaseServer)();
1773
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
1774
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1775
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1776
+ allowAnonymous: true,
1777
+ permissions: {
1778
+ create: [permissions_1.Permissions.IsAny],
1779
+ delete: [permissions_1.Permissions.IsAny],
1780
+ list: [permissions_1.Permissions.IsAny],
1781
+ read: [permissions_1.Permissions.IsAny],
1782
+ update: [permissions_1.Permissions.IsAny],
1783
+ },
1784
+ responseHandler: function (data, method) {
1785
+ if (method === "list") {
1786
+ return data.map(function (d) { return ({
1787
+ foo: "bar",
1788
+ id: d._id,
1789
+ }); });
1790
+ }
1791
+ return {
1792
+ foo: "bar",
1793
+ id: data._id,
1794
+ };
1795
+ },
1796
+ }));
1797
+ server = (0, supertest_1.default)(app);
1798
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
1799
+ case 3:
1800
+ agent = _c.sent();
1801
+ return [2 /*return*/];
1802
+ }
1803
+ });
1804
+ }); });
1805
+ (0, bun_test_1.it)("reads with serialize", function () { return __awaiter(void 0, void 0, void 0, function () {
1806
+ var res;
1807
+ return __generator(this, function (_a) {
1808
+ switch (_a.label) {
1809
+ case 0: return [4 /*yield*/, agent.get("/food/".concat(spinach._id)).expect(200)];
1810
+ case 1:
1811
+ res = _a.sent();
1812
+ (0, bun_test_1.expect)(res.body.data.ownerId).toBeUndefined();
1813
+ (0, bun_test_1.expect)(res.body.data.id).toBe(spinach._id.toString());
1814
+ (0, bun_test_1.expect)(res.body.data.foo).toBe("bar");
1815
+ return [2 /*return*/];
1816
+ }
1817
+ });
1818
+ }); });
1819
+ (0, bun_test_1.it)("list with serialize", function () { return __awaiter(void 0, void 0, void 0, function () {
1820
+ var res;
1821
+ return __generator(this, function (_a) {
1822
+ switch (_a.label) {
1823
+ case 0: return [4 /*yield*/, agent.get("/food").expect(200)];
1824
+ case 1:
1825
+ res = _a.sent();
1826
+ (0, bun_test_1.expect)(res.body.data[0].ownerId).toBeUndefined();
1827
+ (0, bun_test_1.expect)(res.body.data[1].ownerId).toBeUndefined();
1828
+ (0, bun_test_1.expect)(res.body.data[0].id).toBeDefined();
1829
+ (0, bun_test_1.expect)(res.body.data[0].foo).toBe("bar");
1830
+ (0, bun_test_1.expect)(res.body.data[1].id).toBeDefined();
1831
+ (0, bun_test_1.expect)(res.body.data[1].foo).toBe("bar");
1832
+ return [2 /*return*/];
1833
+ }
1834
+ });
1835
+ }); });
1836
+ });
1837
+ (0, bun_test_1.describe)("plugins", function () {
1838
+ var agent;
1839
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1840
+ return __generator(this, function (_a) {
1841
+ switch (_a.label) {
1842
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1843
+ case 1:
1844
+ _a.sent();
1845
+ app = (0, tests_1.getBaseServer)();
1846
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
1847
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1848
+ app.use("/users", (0, api_1.modelRouter)(tests_1.UserModel, {
1849
+ allowAnonymous: true,
1850
+ permissions: {
1851
+ create: [permissions_1.Permissions.IsAny],
1852
+ delete: [permissions_1.Permissions.IsAny],
1853
+ list: [permissions_1.Permissions.IsAny],
1854
+ read: [permissions_1.Permissions.IsAny],
1855
+ update: [permissions_1.Permissions.IsAny],
1856
+ },
1857
+ }));
1858
+ server = (0, supertest_1.default)(app);
1859
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
1860
+ case 2:
1861
+ agent = _a.sent();
1862
+ return [2 /*return*/];
1863
+ }
1864
+ });
1865
+ }); });
1866
+ (0, bun_test_1.it)("check that security fields are filtered", function () { return __awaiter(void 0, void 0, void 0, function () {
1867
+ var res;
1868
+ return __generator(this, function (_a) {
1869
+ switch (_a.label) {
1870
+ case 0: return [4 /*yield*/, agent.get("/users").expect(200)];
1871
+ case 1:
1872
+ res = _a.sent();
1873
+ (0, bun_test_1.expect)(res.body.data[0].email).toBeDefined();
1874
+ (0, bun_test_1.expect)(res.body.data[0].token).toBeUndefined();
1875
+ (0, bun_test_1.expect)(res.body.data[0].hash).toBeUndefined();
1876
+ (0, bun_test_1.expect)(res.body.data[0].salt).toBeUndefined();
1877
+ return [2 /*return*/];
1878
+ }
1879
+ });
1880
+ }); });
1881
+ });
1882
+ (0, bun_test_1.describe)("discriminator", function () {
1883
+ var superUser;
1884
+ var staffUser;
1885
+ var notAdmin;
1886
+ var agent;
1887
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1888
+ var _a, staffUserId, superUserId;
1889
+ var _b;
1890
+ return __generator(this, function (_c) {
1891
+ switch (_c.label) {
1892
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1893
+ case 1:
1894
+ _b = __read.apply(void 0, [_c.sent(), 1]), notAdmin = _b[0];
1895
+ return [4 /*yield*/, Promise.all([
1896
+ tests_1.StaffUserModel.create({
1897
+ department: "Accounting",
1898
+ email: "staff@example.com",
1899
+ }),
1900
+ tests_1.SuperUserModel.create({
1901
+ email: "superuser@example.com",
1902
+ superTitle: "Super Man",
1903
+ }),
1904
+ ])];
1905
+ case 2:
1906
+ _a = __read.apply(void 0, [_c.sent(), 2]), staffUserId = _a[0], superUserId = _a[1];
1907
+ return [4 /*yield*/, tests_1.UserModel.findById(staffUserId)];
1908
+ case 3:
1909
+ staffUser = (_c.sent());
1910
+ return [4 /*yield*/, tests_1.UserModel.findById(superUserId)];
1911
+ case 4:
1912
+ superUser = (_c.sent());
1913
+ app = (0, tests_1.getBaseServer)();
1914
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
1915
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1916
+ app.use("/users", (0, api_1.modelRouter)(tests_1.UserModel, {
1917
+ allowAnonymous: true,
1918
+ discriminatorKey: "__t",
1919
+ permissions: {
1920
+ create: [permissions_1.Permissions.IsAuthenticated],
1921
+ delete: [permissions_1.Permissions.IsAuthenticated],
1922
+ list: [permissions_1.Permissions.IsAuthenticated],
1923
+ read: [permissions_1.Permissions.IsAuthenticated],
1924
+ update: [permissions_1.Permissions.IsAuthenticated],
1925
+ },
1926
+ }));
1927
+ server = (0, supertest_1.default)(app);
1928
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
1929
+ case 5:
1930
+ agent = _c.sent();
1931
+ return [2 /*return*/];
1932
+ }
1933
+ });
1934
+ }); });
1935
+ (0, bun_test_1.it)("gets all users", function () { return __awaiter(void 0, void 0, void 0, function () {
1936
+ var res, data;
1937
+ return __generator(this, function (_a) {
1938
+ switch (_a.label) {
1939
+ case 0: return [4 /*yield*/, agent.get("/users").expect(200)];
1940
+ case 1:
1941
+ res = _a.sent();
1942
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(5);
1943
+ data = (0, sortBy_1.default)(res.body.data, ["email"]);
1944
+ (0, bun_test_1.expect)(data[0].email).toBe("admin+other@example.com");
1945
+ (0, bun_test_1.expect)(data[0].department).toBeUndefined();
1946
+ (0, bun_test_1.expect)(data[0].supertitle).toBeUndefined();
1947
+ (0, bun_test_1.expect)(data[0].__t).toBeUndefined();
1948
+ (0, bun_test_1.expect)(data[1].email).toBe("admin@example.com");
1949
+ (0, bun_test_1.expect)(data[1].department).toBeUndefined();
1950
+ (0, bun_test_1.expect)(data[1].supertitle).toBeUndefined();
1951
+ (0, bun_test_1.expect)(data[1].__t).toBeUndefined();
1952
+ (0, bun_test_1.expect)(data[2].email).toBe("notAdmin@example.com");
1953
+ (0, bun_test_1.expect)(data[2].department).toBeUndefined();
1954
+ (0, bun_test_1.expect)(data[2].supertitle).toBeUndefined();
1955
+ (0, bun_test_1.expect)(data[2].__t).toBeUndefined();
1956
+ (0, bun_test_1.expect)(data[3].email).toBe("staff@example.com");
1957
+ (0, bun_test_1.expect)(data[3].department).toBe("Accounting");
1958
+ (0, bun_test_1.expect)(data[3].supertitle).toBeUndefined();
1959
+ (0, bun_test_1.expect)(data[3].__t).toBe("Staff");
1960
+ (0, bun_test_1.expect)(data[4].email).toBe("superuser@example.com");
1961
+ (0, bun_test_1.expect)(data[4].department).toBeUndefined();
1962
+ (0, bun_test_1.expect)(data[4].superTitle).toBe("Super Man");
1963
+ (0, bun_test_1.expect)(data[4].__t).toBe("SuperUser");
1964
+ return [2 /*return*/];
1965
+ }
1966
+ });
1967
+ }); });
1968
+ (0, bun_test_1.it)("gets a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
1969
+ var res;
1970
+ return __generator(this, function (_a) {
1971
+ switch (_a.label) {
1972
+ case 0: return [4 /*yield*/, agent.get("/users/".concat(superUser._id)).expect(200)];
1973
+ case 1:
1974
+ res = _a.sent();
1975
+ (0, bun_test_1.expect)(res.body.data.email).toBe("superuser@example.com");
1976
+ (0, bun_test_1.expect)(res.body.data.department).toBeUndefined();
1977
+ (0, bun_test_1.expect)(res.body.data.superTitle).toBe("Super Man");
1978
+ return [2 /*return*/];
1979
+ }
1980
+ });
1981
+ }); });
1982
+ (0, bun_test_1.it)("updates a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
1983
+ var res, user;
1984
+ return __generator(this, function (_a) {
1985
+ switch (_a.label) {
1986
+ case 0:
1987
+ // Fails without __t.
1988
+ return [4 /*yield*/, agent.patch("/users/".concat(superUser._id)).send({ superTitle: "Batman" }).expect(404)];
1989
+ case 1:
1990
+ // Fails without __t.
1991
+ _a.sent();
1992
+ return [4 /*yield*/, agent
1993
+ .patch("/users/".concat(superUser._id))
1994
+ .send({ __t: "SuperUser", superTitle: "Batman" })
1995
+ .expect(200)];
1996
+ case 2:
1997
+ res = _a.sent();
1998
+ (0, bun_test_1.expect)(res.body.data.email).toBe("superuser@example.com");
1999
+ (0, bun_test_1.expect)(res.body.data.department).toBeUndefined();
2000
+ (0, bun_test_1.expect)(res.body.data.superTitle).toBe("Batman");
2001
+ return [4 /*yield*/, tests_1.SuperUserModel.findById(superUser._id)];
2002
+ case 3:
2003
+ user = _a.sent();
2004
+ (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.superTitle).toBe("Batman");
2005
+ return [2 /*return*/];
2006
+ }
2007
+ });
2008
+ }); });
2009
+ (0, bun_test_1.it)("updates a base user", function () { return __awaiter(void 0, void 0, void 0, function () {
2010
+ var res, user;
2011
+ return __generator(this, function (_a) {
2012
+ switch (_a.label) {
2013
+ case 0: return [4 /*yield*/, agent
2014
+ .patch("/users/".concat(notAdmin._id))
2015
+ .send({ email: "newemail@example.com", superTitle: "The Boss" })
2016
+ .expect(200)];
2017
+ case 1:
2018
+ res = _a.sent();
2019
+ (0, bun_test_1.expect)(res.body.data.email).toBe("newemail@example.com");
2020
+ (0, bun_test_1.expect)(res.body.data.superTitle).toBeUndefined();
2021
+ return [4 /*yield*/, tests_1.SuperUserModel.findById(notAdmin._id)];
2022
+ case 2:
2023
+ user = _a.sent();
2024
+ (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.superTitle).toBeUndefined();
2025
+ return [2 /*return*/];
2026
+ }
2027
+ });
2028
+ }); });
2029
+ (0, bun_test_1.it)("cannot update discriminator key", function () { return __awaiter(void 0, void 0, void 0, function () {
2030
+ return __generator(this, function (_a) {
2031
+ switch (_a.label) {
2032
+ case 0: return [4 /*yield*/, agent
2033
+ .patch("/users/".concat(notAdmin._id))
2034
+ .send({ __t: "Staff", superTitle: "Batman" })
2035
+ .expect(404)];
2036
+ case 1:
2037
+ _a.sent();
2038
+ return [4 /*yield*/, agent
2039
+ .patch("/users/".concat(staffUser._id))
2040
+ .send({ __t: "SuperUser", superTitle: "Batman" })
2041
+ .expect(404)];
2042
+ case 2:
2043
+ _a.sent();
2044
+ return [2 /*return*/];
2045
+ }
2046
+ });
2047
+ }); });
2048
+ (0, bun_test_1.it)("updating a field on another discriminated model does nothing", function () { return __awaiter(void 0, void 0, void 0, function () {
2049
+ var res, user;
2050
+ return __generator(this, function (_a) {
2051
+ switch (_a.label) {
2052
+ case 0: return [4 /*yield*/, agent
2053
+ .patch("/users/".concat(superUser._id))
2054
+ .send({ __t: "SuperUser", department: "Journalism" })
2055
+ .expect(200)];
2056
+ case 1:
2057
+ res = _a.sent();
2058
+ (0, bun_test_1.expect)(res.body.data.department).toBeUndefined();
2059
+ return [4 /*yield*/, tests_1.SuperUserModel.findById(superUser._id)];
2060
+ case 2:
2061
+ user = _a.sent();
2062
+ (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.department).toBeUndefined();
2063
+ return [2 /*return*/];
2064
+ }
2065
+ });
2066
+ }); });
2067
+ (0, bun_test_1.it)("creates a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
2068
+ var res, user;
2069
+ return __generator(this, function (_a) {
2070
+ switch (_a.label) {
2071
+ case 0: return [4 /*yield*/, agent
2072
+ .post("/users")
2073
+ .send({
2074
+ __t: "SuperUser",
2075
+ department: "R&D",
2076
+ email: "brucewayne@example.com",
2077
+ superTitle: "Batman",
2078
+ })
2079
+ .expect(201)];
2080
+ case 1:
2081
+ res = _a.sent();
2082
+ (0, bun_test_1.expect)(res.body.data.email).toBe("brucewayne@example.com");
2083
+ // Because we pass __t, this should create a SuperUser which has no department, so this is
2084
+ // dropped.
2085
+ (0, bun_test_1.expect)(res.body.data.department).toBeUndefined();
2086
+ (0, bun_test_1.expect)(res.body.data.superTitle).toBe("Batman");
2087
+ return [4 /*yield*/, tests_1.SuperUserModel.findById(res.body.data._id)];
2088
+ case 2:
2089
+ user = _a.sent();
2090
+ (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.superTitle).toBe("Batman");
2091
+ return [2 /*return*/];
2092
+ }
2093
+ });
2094
+ }); });
2095
+ (0, bun_test_1.it)("deletes a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
2096
+ var user;
2097
+ return __generator(this, function (_a) {
2098
+ switch (_a.label) {
2099
+ case 0:
2100
+ // Fails without __t.
2101
+ return [4 /*yield*/, agent.delete("/users/".concat(superUser._id)).expect(404)];
2102
+ case 1:
2103
+ // Fails without __t.
2104
+ _a.sent();
2105
+ return [4 /*yield*/, agent
2106
+ .delete("/users/".concat(superUser._id))
2107
+ .send({
2108
+ __t: "SuperUser",
2109
+ })
2110
+ .expect(204)];
2111
+ case 2:
2112
+ _a.sent();
2113
+ return [4 /*yield*/, tests_1.SuperUserModel.findById(superUser._id)];
2114
+ case 3:
2115
+ user = _a.sent();
2116
+ (0, bun_test_1.expect)(user).toBeNull();
2117
+ return [2 /*return*/];
2118
+ }
2119
+ });
2120
+ }); });
2121
+ (0, bun_test_1.it)("deletes a base user", function () { return __awaiter(void 0, void 0, void 0, function () {
2122
+ var user;
2123
+ return __generator(this, function (_a) {
2124
+ switch (_a.label) {
2125
+ case 0:
2126
+ // Fails for base user with __t
2127
+ return [4 /*yield*/, agent.delete("/users/".concat(notAdmin._id)).send({ __t: "SuperUser" }).expect(404)];
2128
+ case 1:
2129
+ // Fails for base user with __t
2130
+ _a.sent();
2131
+ return [4 /*yield*/, agent.delete("/users/".concat(notAdmin._id)).expect(204)];
2132
+ case 2:
2133
+ _a.sent();
2134
+ return [4 /*yield*/, tests_1.SuperUserModel.findById(notAdmin._id)];
2135
+ case 3:
2136
+ user = _a.sent();
2137
+ (0, bun_test_1.expect)(user).toBeNull();
2138
+ return [2 /*return*/];
2139
+ }
2140
+ });
2141
+ }); });
2142
+ });
2143
+ });