@terreno/api 0.0.11 → 0.0.13

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.
@@ -0,0 +1,868 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ 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);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ 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;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __read = (this && this.__read) || function (o, n) {
39
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
40
+ if (!m) return o;
41
+ var i = m.call(o), r, ar = [], e;
42
+ try {
43
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
44
+ }
45
+ catch (error) { e = { error: error }; }
46
+ finally {
47
+ try {
48
+ if (r && !r.done && (m = i["return"])) m.call(i);
49
+ }
50
+ finally { if (e) throw e.error; }
51
+ }
52
+ return ar;
53
+ };
54
+ var __importDefault = (this && this.__importDefault) || function (mod) {
55
+ return (mod && mod.__esModule) ? mod : { "default": mod };
56
+ };
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ var bun_test_1 = require("bun:test");
59
+ var supertest_1 = __importDefault(require("supertest"));
60
+ var api_1 = require("./api");
61
+ var auth_1 = require("./auth");
62
+ var permissions_1 = require("./permissions");
63
+ var tests_1 = require("./tests");
64
+ var transformers_1 = require("./transformers");
65
+ (0, bun_test_1.describe)("model array operations", function () {
66
+ var _server;
67
+ var app;
68
+ var admin;
69
+ var spinach;
70
+ var apple;
71
+ var agent;
72
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
73
+ var _a, _b;
74
+ return __generator(this, function (_c) {
75
+ switch (_c.label) {
76
+ case 0:
77
+ process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
78
+ return [4 /*yield*/, (0, tests_1.setupDb)()];
79
+ case 1:
80
+ _a = __read.apply(void 0, [_c.sent(), 1]), admin = _a[0];
81
+ return [4 /*yield*/, Promise.all([
82
+ tests_1.FoodModel.create({
83
+ calories: 1,
84
+ created: new Date("2021-12-03T00:00:20.000Z"),
85
+ hidden: false,
86
+ name: "Spinach",
87
+ ownerId: admin._id,
88
+ source: {
89
+ name: "Brand",
90
+ },
91
+ }),
92
+ tests_1.FoodModel.create({
93
+ calories: 100,
94
+ categories: [
95
+ {
96
+ name: "Fruit",
97
+ show: true,
98
+ },
99
+ {
100
+ name: "Popular",
101
+ show: false,
102
+ },
103
+ ],
104
+ created: new Date("2021-12-03T00:00:30.000Z"),
105
+ hidden: false,
106
+ name: "Apple",
107
+ ownerId: admin._id,
108
+ tags: ["healthy", "cheap"],
109
+ }),
110
+ ])];
111
+ case 2:
112
+ _b = __read.apply(void 0, [_c.sent(), 2]), spinach = _b[0], apple = _b[1];
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
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
117
+ allowAnonymous: true,
118
+ permissions: {
119
+ create: [permissions_1.Permissions.IsAdmin],
120
+ delete: [permissions_1.Permissions.IsAdmin],
121
+ list: [permissions_1.Permissions.IsAdmin],
122
+ read: [permissions_1.Permissions.IsAdmin],
123
+ update: [permissions_1.Permissions.IsAdmin],
124
+ },
125
+ queryFields: ["hidden", "calories", "created", "source.name"],
126
+ sort: { created: "descending" },
127
+ }));
128
+ _server = (0, supertest_1.default)(app);
129
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
130
+ case 3:
131
+ agent = _c.sent();
132
+ return [2 /*return*/];
133
+ }
134
+ });
135
+ }); });
136
+ (0, bun_test_1.it)("add array sub-schema item", function () { return __awaiter(void 0, void 0, void 0, function () {
137
+ var res;
138
+ return __generator(this, function (_a) {
139
+ switch (_a.label) {
140
+ case 0: return [4 /*yield*/, agent
141
+ .post("/food/".concat(apple._id, "/categories"))
142
+ .send({ name: "Good Seller", show: false })
143
+ .expect(400)];
144
+ case 1:
145
+ res = _a.sent();
146
+ (0, bun_test_1.expect)(res.body.title).toBe("Malformed body, array operations should have a single, top level key, got: name,show");
147
+ return [4 /*yield*/, agent
148
+ .post("/food/".concat(apple._id, "/categories"))
149
+ .send({ categories: { name: "Good Seller", show: false } })
150
+ .expect(200)];
151
+ case 2:
152
+ res = _a.sent();
153
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(3);
154
+ (0, bun_test_1.expect)(res.body.data.categories[2].name).toBe("Good Seller");
155
+ return [4 /*yield*/, agent
156
+ .post("/food/".concat(spinach._id, "/categories"))
157
+ .send({ categories: { name: "Good Seller", show: false } })
158
+ .expect(200)];
159
+ case 3:
160
+ res = _a.sent();
161
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(1);
162
+ return [2 /*return*/];
163
+ }
164
+ });
165
+ }); });
166
+ (0, bun_test_1.it)("update array sub-schema item", function () { return __awaiter(void 0, void 0, void 0, function () {
167
+ var res;
168
+ return __generator(this, function (_a) {
169
+ switch (_a.label) {
170
+ case 0: return [4 /*yield*/, agent
171
+ .patch("/food/".concat(apple._id, "/categories/xyz"))
172
+ .send({ categories: { name: "Good Seller", show: false } })
173
+ .expect(404)];
174
+ case 1:
175
+ res = _a.sent();
176
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find categories/xyz");
177
+ return [4 /*yield*/, agent
178
+ .patch("/food/".concat(apple._id, "/categories/").concat(apple.categories[1]._id))
179
+ .send({ categories: { name: "Good Seller", show: false } })
180
+ .expect(200)];
181
+ case 2:
182
+ res = _a.sent();
183
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(2);
184
+ (0, bun_test_1.expect)(res.body.data.categories[1].name).toBe("Good Seller");
185
+ return [2 /*return*/];
186
+ }
187
+ });
188
+ }); });
189
+ (0, bun_test_1.it)("delete array sub-schema item", function () { return __awaiter(void 0, void 0, void 0, function () {
190
+ var res;
191
+ return __generator(this, function (_a) {
192
+ switch (_a.label) {
193
+ case 0: return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/categories/xyz")).expect(404)];
194
+ case 1:
195
+ res = _a.sent();
196
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find categories/xyz");
197
+ return [4 /*yield*/, agent
198
+ .delete("/food/".concat(apple._id, "/categories/").concat(apple.categories[0]._id))
199
+ .expect(200)];
200
+ case 2:
201
+ res = _a.sent();
202
+ (0, bun_test_1.expect)(res.body.data.categories).toHaveLength(1);
203
+ (0, bun_test_1.expect)(res.body.data.categories[0].name).toBe("Popular");
204
+ return [2 /*return*/];
205
+ }
206
+ });
207
+ }); });
208
+ (0, bun_test_1.it)("add array item", function () { return __awaiter(void 0, void 0, void 0, function () {
209
+ var res;
210
+ return __generator(this, function (_a) {
211
+ switch (_a.label) {
212
+ case 0: return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "popular" }).expect(200)];
213
+ case 1:
214
+ res = _a.sent();
215
+ (0, bun_test_1.expect)(res.body.data.tags).toHaveLength(3);
216
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["healthy", "cheap", "popular"]);
217
+ return [4 /*yield*/, agent.post("/food/".concat(spinach._id, "/tags")).send({ tags: "popular" }).expect(200)];
218
+ case 2:
219
+ res = _a.sent();
220
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["popular"]);
221
+ return [2 /*return*/];
222
+ }
223
+ });
224
+ }); });
225
+ (0, bun_test_1.it)("update array item", function () { return __awaiter(void 0, void 0, void 0, function () {
226
+ var res;
227
+ return __generator(this, function (_a) {
228
+ switch (_a.label) {
229
+ case 0: return [4 /*yield*/, agent
230
+ .patch("/food/".concat(apple._id, "/tags/xyz"))
231
+ .send({ tags: "unhealthy" })
232
+ .expect(404)];
233
+ case 1:
234
+ res = _a.sent();
235
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find tags/xyz");
236
+ return [4 /*yield*/, agent
237
+ .patch("/food/".concat(apple._id, "/tags/healthy"))
238
+ .send({ tags: "unhealthy" })
239
+ .expect(200)];
240
+ case 2:
241
+ res = _a.sent();
242
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["unhealthy", "cheap"]);
243
+ return [2 /*return*/];
244
+ }
245
+ });
246
+ }); });
247
+ (0, bun_test_1.it)("delete array item", function () { return __awaiter(void 0, void 0, void 0, function () {
248
+ var res;
249
+ return __generator(this, function (_a) {
250
+ switch (_a.label) {
251
+ case 0: return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/xyz")).expect(404)];
252
+ case 1:
253
+ res = _a.sent();
254
+ (0, bun_test_1.expect)(res.body.title).toBe("Could not find tags/xyz");
255
+ return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/healthy")).expect(200)];
256
+ case 2:
257
+ res = _a.sent();
258
+ (0, bun_test_1.expect)(res.body.data.tags).toEqual(["cheap"]);
259
+ return [2 /*return*/];
260
+ }
261
+ });
262
+ }); });
263
+ (0, bun_test_1.it)("updates timestamps on array subdocuments", function () { return __awaiter(void 0, void 0, void 0, function () {
264
+ var foodWithTimestamps, firstCategoryId, secondCategoryId, res, updatedCategory, unchangedCategory;
265
+ var _a, _b, _c, _d, _e, _f;
266
+ return __generator(this, function (_g) {
267
+ switch (_g.label) {
268
+ case 0: return [4 /*yield*/, tests_1.FoodModel.create({
269
+ calories: 100,
270
+ categories: [
271
+ {
272
+ name: "Category 1",
273
+ show: true,
274
+ updated: new Date("2024-01-01T00:00:00.000Z"),
275
+ },
276
+ {
277
+ name: "Category 2",
278
+ show: true,
279
+ updated: new Date("2024-01-01T00:00:00.000Z"),
280
+ },
281
+ ],
282
+ created: new Date(),
283
+ name: "Food with Timestamps",
284
+ ownerId: admin._id,
285
+ })];
286
+ case 1:
287
+ foodWithTimestamps = _g.sent();
288
+ 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();
289
+ 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();
290
+ if (!firstCategoryId || !secondCategoryId) {
291
+ throw new Error("Failed to create food with categories");
292
+ }
293
+ // Wait a moment to ensure timestamp difference
294
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
295
+ case 2:
296
+ // Wait a moment to ensure timestamp difference
297
+ _g.sent();
298
+ return [4 /*yield*/, agent
299
+ .patch("/food/".concat(foodWithTimestamps._id, "/categories/").concat(firstCategoryId))
300
+ .send({ categories: { name: "Updated Category" } })
301
+ .expect(200)];
302
+ case 3:
303
+ res = _g.sent();
304
+ updatedCategory = res.body.data.categories.find(function (c) { return c._id === firstCategoryId; });
305
+ unchangedCategory = res.body.data.categories.find(function (c) { return c._id === secondCategoryId; });
306
+ if (!updatedCategory || !unchangedCategory) {
307
+ throw new Error("Failed to find categories in response");
308
+ }
309
+ (0, bun_test_1.expect)(updatedCategory.updated).not.toBe(updatedCategory.created);
310
+ (0, bun_test_1.expect)(unchangedCategory.updated).toBe(unchangedCategory.created);
311
+ (0, bun_test_1.expect)(updatedCategory.name).toBe("Updated Category");
312
+ // Unchanged.
313
+ (0, bun_test_1.expect)(updatedCategory.show).toBe(true);
314
+ (0, bun_test_1.expect)(unchangedCategory.show).toBe(true);
315
+ return [2 /*return*/];
316
+ }
317
+ });
318
+ }); });
319
+ (0, bun_test_1.it)("array operations call postUpdate with different copy of document", function () { return __awaiter(void 0, void 0, void 0, function () {
320
+ var postUpdateDoc, postUpdatePrevDoc, postUpdateCalled, categoryId, updatedCategory, prevCategory, remainingCategories, prevCategories;
321
+ return __generator(this, function (_a) {
322
+ switch (_a.label) {
323
+ case 0:
324
+ postUpdateCalled = false;
325
+ app = (0, tests_1.getBaseServer)();
326
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
327
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
328
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
329
+ allowAnonymous: true,
330
+ permissions: {
331
+ create: [permissions_1.Permissions.IsAdmin],
332
+ delete: [permissions_1.Permissions.IsAdmin],
333
+ list: [permissions_1.Permissions.IsAdmin],
334
+ read: [permissions_1.Permissions.IsAdmin],
335
+ update: [permissions_1.Permissions.IsAdmin],
336
+ },
337
+ postUpdate: function (doc, _cleanedBody, _request, prevValue) { return __awaiter(void 0, void 0, void 0, function () {
338
+ return __generator(this, function (_a) {
339
+ postUpdateDoc = doc;
340
+ postUpdatePrevDoc = prevValue;
341
+ postUpdateCalled = true;
342
+ return [2 /*return*/];
343
+ });
344
+ }); },
345
+ }));
346
+ _server = (0, supertest_1.default)(app);
347
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
348
+ case 1:
349
+ agent = _a.sent();
350
+ // Test POST operation (add to array)
351
+ return [4 /*yield*/, agent
352
+ .post("/food/".concat(apple._id, "/categories"))
353
+ .send({ categories: { name: "New Category", show: true } })
354
+ .expect(200)];
355
+ case 2:
356
+ // Test POST operation (add to array)
357
+ _a.sent();
358
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
359
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
360
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
361
+ // Verify they are different object references
362
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
363
+ // Verify the content is different (new category added)
364
+ (0, bun_test_1.expect)(postUpdateDoc.categories).toHaveLength(3);
365
+ (0, bun_test_1.expect)(postUpdatePrevDoc.categories).toHaveLength(2);
366
+ // Reset for next test
367
+ postUpdateCalled = false;
368
+ postUpdateDoc = undefined;
369
+ postUpdatePrevDoc = undefined;
370
+ categoryId = apple.categories[0]._id;
371
+ if (!categoryId) {
372
+ throw new Error("Category ID is undefined");
373
+ }
374
+ return [4 /*yield*/, agent
375
+ .patch("/food/".concat(apple._id, "/categories/").concat(categoryId))
376
+ .send({ categories: { name: "Updated Category", show: false } })
377
+ .expect(200)];
378
+ case 3:
379
+ _a.sent();
380
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
381
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
382
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
383
+ // Verify they are different object references
384
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
385
+ updatedCategory = postUpdateDoc.categories.find(function (c) { return c._id.toString() === categoryId.toString(); });
386
+ prevCategory = postUpdatePrevDoc.categories.find(function (c) { return c._id.toString() === categoryId.toString(); });
387
+ (0, bun_test_1.expect)(updatedCategory.name).toBe("Updated Category");
388
+ (0, bun_test_1.expect)(prevCategory.name).toBe("Fruit");
389
+ // Reset for next test
390
+ postUpdateCalled = false;
391
+ postUpdateDoc = undefined;
392
+ postUpdatePrevDoc = undefined;
393
+ // Test DELETE operation (remove from array)
394
+ return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/categories/").concat(categoryId)).expect(200)];
395
+ case 4:
396
+ // Test DELETE operation (remove from array)
397
+ _a.sent();
398
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
399
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
400
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
401
+ // Verify they are different object references
402
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
403
+ remainingCategories = postUpdateDoc.categories.filter(function (c) { return c._id.toString() === categoryId.toString(); });
404
+ prevCategories = postUpdatePrevDoc.categories.filter(function (c) { return c._id.toString() === categoryId.toString(); });
405
+ (0, bun_test_1.expect)(remainingCategories).toHaveLength(0);
406
+ (0, bun_test_1.expect)(prevCategories).toHaveLength(1);
407
+ return [2 /*return*/];
408
+ }
409
+ });
410
+ }); });
411
+ (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 () {
412
+ var postUpdateDoc, postUpdatePrevDoc, postUpdateCalled;
413
+ return __generator(this, function (_a) {
414
+ switch (_a.label) {
415
+ case 0:
416
+ postUpdateCalled = false;
417
+ app = (0, tests_1.getBaseServer)();
418
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
419
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
420
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
421
+ allowAnonymous: true,
422
+ permissions: {
423
+ create: [permissions_1.Permissions.IsAdmin],
424
+ delete: [permissions_1.Permissions.IsAdmin],
425
+ list: [permissions_1.Permissions.IsAdmin],
426
+ read: [permissions_1.Permissions.IsAdmin],
427
+ update: [permissions_1.Permissions.IsAdmin],
428
+ },
429
+ postUpdate: function (doc, _cleanedBody, _request, prevValue) { return __awaiter(void 0, void 0, void 0, function () {
430
+ return __generator(this, function (_a) {
431
+ postUpdateDoc = doc;
432
+ postUpdatePrevDoc = prevValue;
433
+ postUpdateCalled = true;
434
+ return [2 /*return*/];
435
+ });
436
+ }); },
437
+ }));
438
+ _server = (0, supertest_1.default)(app);
439
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
440
+ case 1:
441
+ agent = _a.sent();
442
+ // Test POST operation with string array (add tag)
443
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(200)];
444
+ case 2:
445
+ // Test POST operation with string array (add tag)
446
+ _a.sent();
447
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
448
+ (0, bun_test_1.expect)(postUpdateDoc).toBeDefined();
449
+ (0, bun_test_1.expect)(postUpdatePrevDoc).toBeDefined();
450
+ // Verify they are different object references
451
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
452
+ // Verify the content is different (new tag added)
453
+ (0, bun_test_1.expect)(postUpdateDoc.tags).toHaveLength(3);
454
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).toHaveLength(2);
455
+ (0, bun_test_1.expect)(postUpdateDoc.tags).toContain("organic");
456
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).not.toContain("organic");
457
+ // Reset for next test
458
+ postUpdateCalled = false;
459
+ postUpdateDoc = undefined;
460
+ postUpdatePrevDoc = undefined;
461
+ // Test PATCH operation with string array (update tag)
462
+ return [4 /*yield*/, agent.patch("/food/".concat(apple._id, "/tags/healthy")).send({ tags: "super-healthy" }).expect(200)];
463
+ case 3:
464
+ // Test PATCH operation with string array (update tag)
465
+ _a.sent();
466
+ (0, bun_test_1.expect)(postUpdateCalled).toBe(true);
467
+ (0, bun_test_1.expect)(postUpdateDoc).not.toBe(postUpdatePrevDoc);
468
+ // Verify the content is different (tag updated)
469
+ (0, bun_test_1.expect)(postUpdateDoc.tags).toContain("super-healthy");
470
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).toContain("healthy");
471
+ (0, bun_test_1.expect)(postUpdateDoc.tags).not.toContain("healthy");
472
+ (0, bun_test_1.expect)(postUpdatePrevDoc.tags).not.toContain("super-healthy");
473
+ return [2 /*return*/];
474
+ }
475
+ });
476
+ }); });
477
+ });
478
+ (0, bun_test_1.describe)("array operation errors", function () {
479
+ var _server;
480
+ var app;
481
+ var admin;
482
+ var apple;
483
+ var agent;
484
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
485
+ var _a;
486
+ return __generator(this, function (_b) {
487
+ switch (_b.label) {
488
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
489
+ case 1:
490
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
491
+ return [4 /*yield*/, tests_1.FoodModel.create({
492
+ calories: 100,
493
+ categories: [
494
+ { name: "Fruit", show: true },
495
+ { name: "Popular", show: false },
496
+ ],
497
+ created: new Date("2021-12-03T00:00:30.000Z"),
498
+ hidden: false,
499
+ name: "Apple",
500
+ ownerId: admin._id,
501
+ tags: ["healthy", "cheap"],
502
+ })];
503
+ case 2:
504
+ apple = _b.sent();
505
+ app = (0, tests_1.getBaseServer)();
506
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
507
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
508
+ return [2 /*return*/];
509
+ }
510
+ });
511
+ }); });
512
+ (0, bun_test_1.it)("array operation preUpdate returning undefined throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
513
+ var res;
514
+ return __generator(this, function (_a) {
515
+ switch (_a.label) {
516
+ case 0:
517
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
518
+ allowAnonymous: true,
519
+ permissions: {
520
+ create: [permissions_1.Permissions.IsAdmin],
521
+ delete: [permissions_1.Permissions.IsAdmin],
522
+ list: [permissions_1.Permissions.IsAdmin],
523
+ read: [permissions_1.Permissions.IsAdmin],
524
+ update: [permissions_1.Permissions.IsAdmin],
525
+ },
526
+ preUpdate: function () { return undefined; },
527
+ }));
528
+ _server = (0, supertest_1.default)(app);
529
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
530
+ case 1:
531
+ agent = _a.sent();
532
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
533
+ case 2:
534
+ res = _a.sent();
535
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
536
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
537
+ return [2 /*return*/];
538
+ }
539
+ });
540
+ }); });
541
+ (0, bun_test_1.it)("array operation preUpdate returning null throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
542
+ var res;
543
+ return __generator(this, function (_a) {
544
+ switch (_a.label) {
545
+ case 0:
546
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
547
+ allowAnonymous: true,
548
+ permissions: {
549
+ create: [permissions_1.Permissions.IsAdmin],
550
+ delete: [permissions_1.Permissions.IsAdmin],
551
+ list: [permissions_1.Permissions.IsAdmin],
552
+ read: [permissions_1.Permissions.IsAdmin],
553
+ update: [permissions_1.Permissions.IsAdmin],
554
+ },
555
+ preUpdate: function () { return null; },
556
+ }));
557
+ _server = (0, supertest_1.default)(app);
558
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
559
+ case 1:
560
+ agent = _a.sent();
561
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
562
+ case 2:
563
+ res = _a.sent();
564
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
565
+ return [2 /*return*/];
566
+ }
567
+ });
568
+ }); });
569
+ (0, bun_test_1.it)("array operation preUpdate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
570
+ var res;
571
+ return __generator(this, function (_a) {
572
+ switch (_a.label) {
573
+ case 0:
574
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
575
+ allowAnonymous: true,
576
+ permissions: {
577
+ create: [permissions_1.Permissions.IsAdmin],
578
+ delete: [permissions_1.Permissions.IsAdmin],
579
+ list: [permissions_1.Permissions.IsAdmin],
580
+ read: [permissions_1.Permissions.IsAdmin],
581
+ update: [permissions_1.Permissions.IsAdmin],
582
+ },
583
+ preUpdate: function () {
584
+ throw new Error("preUpdate array failed");
585
+ },
586
+ }));
587
+ _server = (0, supertest_1.default)(app);
588
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
589
+ case 1:
590
+ agent = _a.sent();
591
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(400)];
592
+ case 2:
593
+ res = _a.sent();
594
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
595
+ return [2 /*return*/];
596
+ }
597
+ });
598
+ }); });
599
+ (0, bun_test_1.it)("array operation postUpdate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
600
+ var res;
601
+ return __generator(this, function (_a) {
602
+ switch (_a.label) {
603
+ case 0:
604
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
605
+ allowAnonymous: true,
606
+ permissions: {
607
+ create: [permissions_1.Permissions.IsAdmin],
608
+ delete: [permissions_1.Permissions.IsAdmin],
609
+ list: [permissions_1.Permissions.IsAdmin],
610
+ read: [permissions_1.Permissions.IsAdmin],
611
+ update: [permissions_1.Permissions.IsAdmin],
612
+ },
613
+ postUpdate: function () {
614
+ throw new Error("postUpdate array failed");
615
+ },
616
+ }));
617
+ _server = (0, supertest_1.default)(app);
618
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
619
+ case 1:
620
+ agent = _a.sent();
621
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(400)];
622
+ case 2:
623
+ res = _a.sent();
624
+ (0, bun_test_1.expect)(res.body.title).toContain("PATCH Post Update error");
625
+ return [2 /*return*/];
626
+ }
627
+ });
628
+ }); });
629
+ (0, bun_test_1.it)("array operation denied without update permission", function () { return __awaiter(void 0, void 0, void 0, function () {
630
+ var res;
631
+ return __generator(this, function (_a) {
632
+ switch (_a.label) {
633
+ case 0:
634
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
635
+ allowAnonymous: true,
636
+ permissions: {
637
+ create: [permissions_1.Permissions.IsAdmin],
638
+ delete: [permissions_1.Permissions.IsAdmin],
639
+ list: [permissions_1.Permissions.IsAny],
640
+ read: [permissions_1.Permissions.IsAny],
641
+ update: [permissions_1.Permissions.IsAdmin],
642
+ },
643
+ }));
644
+ _server = (0, supertest_1.default)(app);
645
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
646
+ case 1:
647
+ agent = _a.sent();
648
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(405)];
649
+ case 2:
650
+ res = _a.sent();
651
+ (0, bun_test_1.expect)(res.body.title).toContain("Access to PATCH");
652
+ return [2 /*return*/];
653
+ }
654
+ });
655
+ }); });
656
+ (0, bun_test_1.it)("array operation on non-existent document returns 404", function () { return __awaiter(void 0, void 0, void 0, function () {
657
+ var fakeId, res;
658
+ return __generator(this, function (_a) {
659
+ switch (_a.label) {
660
+ case 0:
661
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
662
+ allowAnonymous: true,
663
+ permissions: {
664
+ create: [permissions_1.Permissions.IsAdmin],
665
+ delete: [permissions_1.Permissions.IsAdmin],
666
+ list: [permissions_1.Permissions.IsAdmin],
667
+ read: [permissions_1.Permissions.IsAdmin],
668
+ update: [permissions_1.Permissions.IsAdmin],
669
+ },
670
+ }));
671
+ _server = (0, supertest_1.default)(app);
672
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
673
+ case 1:
674
+ agent = _a.sent();
675
+ fakeId = "000000000000000000000000";
676
+ return [4 /*yield*/, agent.post("/food/".concat(fakeId, "/tags")).send({ tags: "organic" }).expect(404)];
677
+ case 2:
678
+ res = _a.sent();
679
+ (0, bun_test_1.expect)(res.body.title).toContain("Could not find document to PATCH");
680
+ return [2 /*return*/];
681
+ }
682
+ });
683
+ }); });
684
+ (0, bun_test_1.it)("array operation denied when user cannot update specific doc", function () { return __awaiter(void 0, void 0, void 0, function () {
685
+ var res;
686
+ return __generator(this, function (_a) {
687
+ switch (_a.label) {
688
+ case 0:
689
+ // Create food owned by admin, then try to update as notAdmin
690
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
691
+ allowAnonymous: true,
692
+ permissions: {
693
+ create: [permissions_1.Permissions.IsAuthenticated],
694
+ delete: [permissions_1.Permissions.IsAuthenticated],
695
+ list: [permissions_1.Permissions.IsAuthenticated],
696
+ read: [permissions_1.Permissions.IsAuthenticated],
697
+ update: [permissions_1.Permissions.IsOwner],
698
+ },
699
+ }));
700
+ _server = (0, supertest_1.default)(app);
701
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
702
+ case 1:
703
+ // Login as notAdmin and try to update admin's food (apple)
704
+ agent = _a.sent();
705
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
706
+ case 2:
707
+ res = _a.sent();
708
+ (0, bun_test_1.expect)(res.body.title).toContain("Patch not allowed");
709
+ return [2 /*return*/];
710
+ }
711
+ });
712
+ }); });
713
+ (0, bun_test_1.it)("array operation transform error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
714
+ var res;
715
+ return __generator(this, function (_a) {
716
+ switch (_a.label) {
717
+ case 0:
718
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
719
+ allowAnonymous: true,
720
+ permissions: {
721
+ create: [permissions_1.Permissions.IsAdmin],
722
+ delete: [permissions_1.Permissions.IsAdmin],
723
+ list: [permissions_1.Permissions.IsAdmin],
724
+ read: [permissions_1.Permissions.IsAdmin],
725
+ update: [permissions_1.Permissions.IsAdmin],
726
+ },
727
+ transformer: (0, transformers_1.AdminOwnerTransformer)({
728
+ adminWriteFields: ["name"],
729
+ }),
730
+ }));
731
+ _server = (0, supertest_1.default)(app);
732
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
733
+ case 1:
734
+ agent = _a.sent();
735
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
736
+ case 2:
737
+ res = _a.sent();
738
+ (0, bun_test_1.expect)(res.body.title).toContain("cannot write fields");
739
+ return [2 /*return*/];
740
+ }
741
+ });
742
+ }); });
743
+ });
744
+ (0, bun_test_1.describe)("array operation with undefined preUpdate return", function () {
745
+ var _server;
746
+ var app;
747
+ var admin;
748
+ var apple;
749
+ var agent;
750
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
751
+ var _a;
752
+ return __generator(this, function (_b) {
753
+ switch (_b.label) {
754
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
755
+ case 1:
756
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
757
+ return [4 /*yield*/, tests_1.FoodModel.create({
758
+ calories: 100,
759
+ categories: [
760
+ { name: "Fruit", show: true },
761
+ { name: "Popular", show: false },
762
+ ],
763
+ created: new Date("2021-12-03T00:00:30.000Z"),
764
+ hidden: false,
765
+ name: "Apple",
766
+ ownerId: admin._id,
767
+ tags: ["healthy", "cheap"],
768
+ })];
769
+ case 2:
770
+ apple = _b.sent();
771
+ app = (0, tests_1.getBaseServer)();
772
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
773
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
774
+ return [2 /*return*/];
775
+ }
776
+ });
777
+ }); });
778
+ (0, bun_test_1.it)("array operation preUpdate returning undefined for array POST throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
779
+ var res;
780
+ return __generator(this, function (_a) {
781
+ switch (_a.label) {
782
+ case 0:
783
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
784
+ allowAnonymous: true,
785
+ permissions: {
786
+ create: [permissions_1.Permissions.IsAdmin],
787
+ delete: [permissions_1.Permissions.IsAdmin],
788
+ list: [permissions_1.Permissions.IsAdmin],
789
+ read: [permissions_1.Permissions.IsAdmin],
790
+ update: [permissions_1.Permissions.IsAdmin],
791
+ },
792
+ preUpdate: function () { return undefined; },
793
+ }));
794
+ _server = (0, supertest_1.default)(app);
795
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
796
+ case 1:
797
+ agent = _a.sent();
798
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
799
+ case 2:
800
+ res = _a.sent();
801
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
802
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
803
+ return [2 /*return*/];
804
+ }
805
+ });
806
+ }); });
807
+ (0, bun_test_1.it)("array operation preUpdate returning null for array PATCH throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
808
+ var res;
809
+ return __generator(this, function (_a) {
810
+ switch (_a.label) {
811
+ case 0:
812
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
813
+ allowAnonymous: true,
814
+ permissions: {
815
+ create: [permissions_1.Permissions.IsAdmin],
816
+ delete: [permissions_1.Permissions.IsAdmin],
817
+ list: [permissions_1.Permissions.IsAdmin],
818
+ read: [permissions_1.Permissions.IsAdmin],
819
+ update: [permissions_1.Permissions.IsAdmin],
820
+ },
821
+ preUpdate: function () { return null; },
822
+ }));
823
+ _server = (0, supertest_1.default)(app);
824
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
825
+ case 1:
826
+ agent = _a.sent();
827
+ return [4 /*yield*/, agent
828
+ .patch("/food/".concat(apple._id, "/tags/healthy"))
829
+ .send({ tags: "unhealthy" })
830
+ .expect(403)];
831
+ case 2:
832
+ res = _a.sent();
833
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
834
+ return [2 /*return*/];
835
+ }
836
+ });
837
+ }); });
838
+ (0, bun_test_1.it)("array operation preUpdate error for array DELETE is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
839
+ var res;
840
+ return __generator(this, function (_a) {
841
+ switch (_a.label) {
842
+ case 0:
843
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
844
+ allowAnonymous: true,
845
+ permissions: {
846
+ create: [permissions_1.Permissions.IsAdmin],
847
+ delete: [permissions_1.Permissions.IsAdmin],
848
+ list: [permissions_1.Permissions.IsAdmin],
849
+ read: [permissions_1.Permissions.IsAdmin],
850
+ update: [permissions_1.Permissions.IsAdmin],
851
+ },
852
+ preUpdate: function () {
853
+ throw new Error("preUpdate error during delete");
854
+ },
855
+ }));
856
+ _server = (0, supertest_1.default)(app);
857
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
858
+ case 1:
859
+ agent = _a.sent();
860
+ return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/healthy")).expect(400)];
861
+ case 2:
862
+ res = _a.sent();
863
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
864
+ return [2 /*return*/];
865
+ }
866
+ });
867
+ }); });
868
+ });