@terreno/api 0.0.17 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.local.md +204 -0
- package/.cursor/rules/00-root.mdc +338 -0
- package/.github/copilot-instructions.md +333 -0
- package/AGENTS.md +333 -0
- package/README.md +76 -7
- package/biome.jsonc +1 -1
- package/dist/api.d.ts +68 -1
- package/dist/api.js +140 -5
- package/dist/api.query.test.js +1 -1
- package/dist/api.test.js +222 -484
- package/dist/auth.js +3 -1
- package/dist/errors.js +15 -12
- package/dist/example.js +7 -7
- package/dist/expressServer.d.ts +8 -2
- package/dist/expressServer.js +8 -1
- package/dist/githubAuth.d.ts +64 -0
- package/dist/githubAuth.js +293 -0
- package/dist/githubAuth.test.d.ts +1 -0
- package/dist/githubAuth.test.js +351 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/logger.js +1 -1
- package/dist/middleware.js +1 -1
- package/dist/notifiers/googleChatNotifier.js +1 -1
- package/dist/notifiers/googleChatNotifier.test.js +1 -1
- package/dist/notifiers/slackNotifier.js +1 -1
- package/dist/notifiers/slackNotifier.test.js +1 -1
- package/dist/notifiers/zoomNotifier.js +1 -1
- package/dist/notifiers/zoomNotifier.test.js +1 -1
- package/dist/openApi.test.js +8 -5
- package/dist/openApiBuilder.d.ts +69 -1
- package/dist/openApiBuilder.js +109 -5
- package/dist/openApiValidator.d.ts +296 -0
- package/dist/openApiValidator.js +698 -0
- package/dist/openApiValidator.test.d.ts +1 -0
- package/dist/openApiValidator.test.js +346 -0
- package/dist/permissions.js +1 -1
- package/dist/plugins.test.js +3 -3
- package/dist/terrenoPlugin.d.ts +4 -0
- package/dist/terrenoPlugin.js +2 -0
- package/dist/tests/bunSetup.js +2 -2
- package/dist/tests.js +34 -24
- package/package.json +7 -2
- package/src/__snapshots__/openApi.test.ts.snap +399 -0
- package/src/__snapshots__/openApiBuilder.test.ts.snap +108 -0
- package/src/api.query.test.ts +1 -1
- package/src/api.test.ts +161 -374
- package/src/api.ts +210 -4
- package/src/auth.ts +3 -1
- package/src/errors.ts +15 -12
- package/src/example.ts +7 -7
- package/src/expressServer.ts +18 -2
- package/src/githubAuth.test.ts +223 -0
- package/src/githubAuth.ts +335 -0
- package/src/index.ts +3 -0
- package/src/logger.ts +1 -1
- package/src/middleware.ts +1 -1
- package/src/notifiers/googleChatNotifier.test.ts +1 -1
- package/src/notifiers/googleChatNotifier.ts +1 -1
- package/src/notifiers/slackNotifier.test.ts +1 -1
- package/src/notifiers/slackNotifier.ts +1 -1
- package/src/notifiers/zoomNotifier.test.ts +1 -1
- package/src/notifiers/zoomNotifier.ts +1 -1
- package/src/openApi.test.ts +8 -5
- package/src/openApiBuilder.ts +188 -15
- package/src/openApiValidator.test.ts +241 -0
- package/src/openApiValidator.ts +860 -0
- package/src/permissions.ts +1 -1
- package/src/plugins.test.ts +3 -3
- package/src/terrenoPlugin.ts +5 -0
- package/src/tests/bunSetup.ts +2 -2
- package/src/tests.ts +34 -24
- package/CLAUDE.md +0 -107
- package/dist/response.d.ts +0 -0
- package/dist/response.js +0 -1
- package/index.ts +0 -1
- package/src/response.ts +0 -0
package/dist/api.test.js
CHANGED
|
@@ -728,6 +728,7 @@ var transformers_1 = require("./transformers");
|
|
|
728
728
|
(0, bun_test_1.describe)("transformer errors", function () {
|
|
729
729
|
var admin;
|
|
730
730
|
var spinach;
|
|
731
|
+
var agent;
|
|
731
732
|
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
732
733
|
var _a;
|
|
733
734
|
return __generator(this, function (_b) {
|
|
@@ -832,6 +833,41 @@ var transformers_1 = require("./transformers");
|
|
|
832
833
|
}
|
|
833
834
|
});
|
|
834
835
|
}); });
|
|
836
|
+
(0, bun_test_1.it)("preDelete hook throwing APIError is re-thrown", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
837
|
+
var res;
|
|
838
|
+
return __generator(this, function (_a) {
|
|
839
|
+
switch (_a.label) {
|
|
840
|
+
case 0:
|
|
841
|
+
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
842
|
+
allowAnonymous: true,
|
|
843
|
+
permissions: {
|
|
844
|
+
create: [permissions_1.Permissions.IsAny],
|
|
845
|
+
delete: [permissions_1.Permissions.IsAny],
|
|
846
|
+
list: [permissions_1.Permissions.IsAny],
|
|
847
|
+
read: [permissions_1.Permissions.IsAny],
|
|
848
|
+
update: [permissions_1.Permissions.IsAny],
|
|
849
|
+
},
|
|
850
|
+
preDelete: function () {
|
|
851
|
+
throw new errors_1.APIError({
|
|
852
|
+
disableExternalErrorTracking: true,
|
|
853
|
+
status: 400,
|
|
854
|
+
title: "Custom preDelete APIError",
|
|
855
|
+
});
|
|
856
|
+
},
|
|
857
|
+
}));
|
|
858
|
+
server = (0, supertest_1.default)(app);
|
|
859
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
|
|
860
|
+
case 1:
|
|
861
|
+
agent = _a.sent();
|
|
862
|
+
return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(400)];
|
|
863
|
+
case 2:
|
|
864
|
+
res = _a.sent();
|
|
865
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Custom preDelete APIError");
|
|
866
|
+
(0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
|
|
867
|
+
return [2 /*return*/];
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}); });
|
|
835
871
|
});
|
|
836
872
|
(0, bun_test_1.describe)("addPopulateToQuery", function () {
|
|
837
873
|
(0, bun_test_1.it)("returns query unchanged with no populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
@@ -1109,8 +1145,12 @@ var transformers_1 = require("./transformers");
|
|
|
1109
1145
|
case 1:
|
|
1110
1146
|
mongoose = _a.sent();
|
|
1111
1147
|
softDeleteSchema = new mongoose.Schema({
|
|
1112
|
-
deleted: {
|
|
1113
|
-
|
|
1148
|
+
deleted: {
|
|
1149
|
+
default: false,
|
|
1150
|
+
description: "Whether this item has been soft deleted",
|
|
1151
|
+
type: Boolean,
|
|
1152
|
+
},
|
|
1153
|
+
name: { description: "The name of the item", type: String },
|
|
1114
1154
|
});
|
|
1115
1155
|
try {
|
|
1116
1156
|
SoftDeleteModel = mongoose.model("SoftDeleteTest");
|
|
@@ -1192,10 +1232,8 @@ var transformers_1 = require("./transformers");
|
|
|
1192
1232
|
});
|
|
1193
1233
|
}); });
|
|
1194
1234
|
});
|
|
1195
|
-
(0, bun_test_1.describe)("
|
|
1235
|
+
(0, bun_test_1.describe)("special query params", function () {
|
|
1196
1236
|
var admin;
|
|
1197
|
-
var spinach;
|
|
1198
|
-
var agent;
|
|
1199
1237
|
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1200
1238
|
var _a;
|
|
1201
1239
|
return __generator(this, function (_b) {
|
|
@@ -1209,12 +1247,9 @@ var transformers_1 = require("./transformers");
|
|
|
1209
1247
|
hidden: false,
|
|
1210
1248
|
name: "Spinach",
|
|
1211
1249
|
ownerId: admin._id,
|
|
1212
|
-
source: {
|
|
1213
|
-
name: "Brand",
|
|
1214
|
-
},
|
|
1215
1250
|
})];
|
|
1216
1251
|
case 2:
|
|
1217
|
-
|
|
1252
|
+
_b.sent();
|
|
1218
1253
|
app = (0, tests_1.getBaseServer)();
|
|
1219
1254
|
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1220
1255
|
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
@@ -1222,7 +1257,7 @@ var transformers_1 = require("./transformers");
|
|
|
1222
1257
|
}
|
|
1223
1258
|
});
|
|
1224
1259
|
}); });
|
|
1225
|
-
(0, bun_test_1.it)("
|
|
1260
|
+
(0, bun_test_1.it)("period query param is stripped from query", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1226
1261
|
var res;
|
|
1227
1262
|
return __generator(this, function (_a) {
|
|
1228
1263
|
switch (_a.label) {
|
|
@@ -1236,25 +1271,41 @@ var transformers_1 = require("./transformers");
|
|
|
1236
1271
|
read: [permissions_1.Permissions.IsAny],
|
|
1237
1272
|
update: [permissions_1.Permissions.IsAny],
|
|
1238
1273
|
},
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1274
|
+
queryFields: ["name", "period"],
|
|
1275
|
+
queryFilter: function (_user, query) {
|
|
1276
|
+
// Simulate a queryFilter that accepts and processes period
|
|
1277
|
+
if (query === null || query === void 0 ? void 0 : query.period) {
|
|
1278
|
+
// Period is processed but shouldn't be passed to mongo
|
|
1279
|
+
return query;
|
|
1280
|
+
}
|
|
1281
|
+
return query !== null && query !== void 0 ? query : {};
|
|
1282
|
+
},
|
|
1243
1283
|
}));
|
|
1244
1284
|
server = (0, supertest_1.default)(app);
|
|
1245
|
-
return [4 /*yield*/, server.
|
|
1285
|
+
return [4 /*yield*/, server.get("/food?period=weekly").expect(200)];
|
|
1246
1286
|
case 1:
|
|
1247
1287
|
res = _a.sent();
|
|
1248
|
-
(0, bun_test_1.expect)(res.body.
|
|
1288
|
+
(0, bun_test_1.expect)(res.body.data).toBeDefined();
|
|
1249
1289
|
return [2 /*return*/];
|
|
1250
1290
|
}
|
|
1251
1291
|
});
|
|
1252
1292
|
}); });
|
|
1253
|
-
(0, bun_test_1.it)("
|
|
1293
|
+
(0, bun_test_1.it)("query with false value", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1254
1294
|
var res;
|
|
1255
1295
|
return __generator(this, function (_a) {
|
|
1256
1296
|
switch (_a.label) {
|
|
1257
|
-
case 0:
|
|
1297
|
+
case 0:
|
|
1298
|
+
// Create a food that is hidden
|
|
1299
|
+
return [4 /*yield*/, tests_1.FoodModel.create({
|
|
1300
|
+
calories: 50,
|
|
1301
|
+
created: new Date("2021-12-04T00:00:20.000Z"),
|
|
1302
|
+
hidden: true,
|
|
1303
|
+
name: "HiddenFood",
|
|
1304
|
+
ownerId: admin._id,
|
|
1305
|
+
})];
|
|
1306
|
+
case 1:
|
|
1307
|
+
// Create a food that is hidden
|
|
1308
|
+
_a.sent();
|
|
1258
1309
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1259
1310
|
allowAnonymous: true,
|
|
1260
1311
|
permissions: {
|
|
@@ -1264,28 +1315,25 @@ var transformers_1 = require("./transformers");
|
|
|
1264
1315
|
read: [permissions_1.Permissions.IsAny],
|
|
1265
1316
|
update: [permissions_1.Permissions.IsAny],
|
|
1266
1317
|
},
|
|
1267
|
-
|
|
1268
|
-
// Only allow 'name' to be written, so 'calories' will throw
|
|
1269
|
-
anonWriteFields: ["name"],
|
|
1270
|
-
}),
|
|
1318
|
+
queryFields: ["name", "hidden"],
|
|
1271
1319
|
}));
|
|
1272
1320
|
server = (0, supertest_1.default)(app);
|
|
1273
|
-
return [4 /*yield*/, server.
|
|
1274
|
-
case
|
|
1321
|
+
return [4 /*yield*/, server.get("/food?hidden=false").expect(200)];
|
|
1322
|
+
case 2:
|
|
1275
1323
|
res = _a.sent();
|
|
1276
|
-
(0, bun_test_1.expect)(res.body.
|
|
1324
|
+
(0, bun_test_1.expect)(res.body.data.every(function (f) { return f.hidden === false; })).toBe(true);
|
|
1277
1325
|
return [2 /*return*/];
|
|
1278
1326
|
}
|
|
1279
1327
|
});
|
|
1280
1328
|
}); });
|
|
1281
|
-
(0, bun_test_1.it)("
|
|
1282
|
-
var
|
|
1329
|
+
(0, bun_test_1.it)("$search query triggers special handling code path", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1330
|
+
var res;
|
|
1283
1331
|
return __generator(this, function (_a) {
|
|
1284
1332
|
switch (_a.label) {
|
|
1285
|
-
case 0:
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
app.use("/
|
|
1333
|
+
case 0:
|
|
1334
|
+
// The $search code path just accesses the collection but doesn't do anything with it
|
|
1335
|
+
// This test verifies the code path is exercised
|
|
1336
|
+
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1289
1337
|
allowAnonymous: true,
|
|
1290
1338
|
permissions: {
|
|
1291
1339
|
create: [permissions_1.Permissions.IsAny],
|
|
@@ -1294,17 +1342,21 @@ var transformers_1 = require("./transformers");
|
|
|
1294
1342
|
read: [permissions_1.Permissions.IsAny],
|
|
1295
1343
|
update: [permissions_1.Permissions.IsAny],
|
|
1296
1344
|
},
|
|
1345
|
+
// Need to include $search in queryFields for it to pass validation
|
|
1346
|
+
queryFields: ["name", "$search"],
|
|
1297
1347
|
}));
|
|
1298
1348
|
server = (0, supertest_1.default)(app);
|
|
1299
|
-
return [4 /*yield*/, server.
|
|
1300
|
-
case
|
|
1349
|
+
return [4 /*yield*/, server.get("/food?$search=test")];
|
|
1350
|
+
case 1:
|
|
1301
1351
|
res = _a.sent();
|
|
1302
|
-
|
|
1352
|
+
// May return 500 because $search is passed to Mongo which doesn't support it without Atlas
|
|
1353
|
+
// The important thing is we've exercised the code path
|
|
1354
|
+
(0, bun_test_1.expect)(res.status === 200 || res.status === 500).toBe(true);
|
|
1303
1355
|
return [2 /*return*/];
|
|
1304
1356
|
}
|
|
1305
1357
|
});
|
|
1306
1358
|
}); });
|
|
1307
|
-
(0, bun_test_1.it)("
|
|
1359
|
+
(0, bun_test_1.it)("$autocomplete query triggers special handling code path", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1308
1360
|
var res;
|
|
1309
1361
|
return __generator(this, function (_a) {
|
|
1310
1362
|
switch (_a.label) {
|
|
@@ -1318,30 +1370,22 @@ var transformers_1 = require("./transformers");
|
|
|
1318
1370
|
read: [permissions_1.Permissions.IsAny],
|
|
1319
1371
|
update: [permissions_1.Permissions.IsAny],
|
|
1320
1372
|
},
|
|
1321
|
-
|
|
1322
|
-
throw new errors_1.APIError({
|
|
1323
|
-
disableExternalErrorTracking: true,
|
|
1324
|
-
status: 400,
|
|
1325
|
-
title: "Custom preDelete APIError",
|
|
1326
|
-
});
|
|
1327
|
-
},
|
|
1373
|
+
queryFields: ["name", "$autocomplete"],
|
|
1328
1374
|
}));
|
|
1329
1375
|
server = (0, supertest_1.default)(app);
|
|
1330
|
-
return [4 /*yield*/,
|
|
1376
|
+
return [4 /*yield*/, server.get("/food?$autocomplete=test")];
|
|
1331
1377
|
case 1:
|
|
1332
|
-
agent = _a.sent();
|
|
1333
|
-
return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(400)];
|
|
1334
|
-
case 2:
|
|
1335
1378
|
res = _a.sent();
|
|
1336
|
-
(0, bun_test_1.expect)(res.
|
|
1337
|
-
(0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
|
|
1379
|
+
(0, bun_test_1.expect)(res.status === 200 || res.status === 500).toBe(true);
|
|
1338
1380
|
return [2 /*return*/];
|
|
1339
1381
|
}
|
|
1340
1382
|
});
|
|
1341
1383
|
}); });
|
|
1342
1384
|
});
|
|
1343
|
-
(0, bun_test_1.describe)("
|
|
1385
|
+
(0, bun_test_1.describe)("array operation with undefined preUpdate return", function () {
|
|
1344
1386
|
var admin;
|
|
1387
|
+
var apple;
|
|
1388
|
+
var agent;
|
|
1345
1389
|
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1346
1390
|
var _a;
|
|
1347
1391
|
return __generator(this, function (_b) {
|
|
@@ -1350,14 +1394,19 @@ var transformers_1 = require("./transformers");
|
|
|
1350
1394
|
case 1:
|
|
1351
1395
|
_a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
|
|
1352
1396
|
return [4 /*yield*/, tests_1.FoodModel.create({
|
|
1353
|
-
calories:
|
|
1354
|
-
|
|
1397
|
+
calories: 100,
|
|
1398
|
+
categories: [
|
|
1399
|
+
{ name: "Fruit", show: true },
|
|
1400
|
+
{ name: "Popular", show: false },
|
|
1401
|
+
],
|
|
1402
|
+
created: new Date("2021-12-03T00:00:30.000Z"),
|
|
1355
1403
|
hidden: false,
|
|
1356
|
-
name: "
|
|
1404
|
+
name: "Apple",
|
|
1357
1405
|
ownerId: admin._id,
|
|
1406
|
+
tags: ["healthy", "cheap"],
|
|
1358
1407
|
})];
|
|
1359
1408
|
case 2:
|
|
1360
|
-
_b.sent();
|
|
1409
|
+
apple = _b.sent();
|
|
1361
1410
|
app = (0, tests_1.getBaseServer)();
|
|
1362
1411
|
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1363
1412
|
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
@@ -1365,7 +1414,7 @@ var transformers_1 = require("./transformers");
|
|
|
1365
1414
|
}
|
|
1366
1415
|
});
|
|
1367
1416
|
}); });
|
|
1368
|
-
(0, bun_test_1.it)("
|
|
1417
|
+
(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 () {
|
|
1369
1418
|
var res;
|
|
1370
1419
|
return __generator(this, function (_a) {
|
|
1371
1420
|
switch (_a.label) {
|
|
@@ -1373,98 +1422,89 @@ var transformers_1 = require("./transformers");
|
|
|
1373
1422
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1374
1423
|
allowAnonymous: true,
|
|
1375
1424
|
permissions: {
|
|
1376
|
-
create: [permissions_1.Permissions.
|
|
1377
|
-
delete: [permissions_1.Permissions.
|
|
1378
|
-
list: [permissions_1.Permissions.
|
|
1379
|
-
read: [permissions_1.Permissions.
|
|
1380
|
-
update: [permissions_1.Permissions.
|
|
1381
|
-
},
|
|
1382
|
-
queryFields: ["name", "period"],
|
|
1383
|
-
queryFilter: function (_user, query) {
|
|
1384
|
-
// Simulate a queryFilter that accepts and processes period
|
|
1385
|
-
if (query === null || query === void 0 ? void 0 : query.period) {
|
|
1386
|
-
// Period is processed but shouldn't be passed to mongo
|
|
1387
|
-
return query;
|
|
1388
|
-
}
|
|
1389
|
-
return query !== null && query !== void 0 ? query : {};
|
|
1425
|
+
create: [permissions_1.Permissions.IsAdmin],
|
|
1426
|
+
delete: [permissions_1.Permissions.IsAdmin],
|
|
1427
|
+
list: [permissions_1.Permissions.IsAdmin],
|
|
1428
|
+
read: [permissions_1.Permissions.IsAdmin],
|
|
1429
|
+
update: [permissions_1.Permissions.IsAdmin],
|
|
1390
1430
|
},
|
|
1431
|
+
preUpdate: function () { return undefined; },
|
|
1391
1432
|
}));
|
|
1392
1433
|
server = (0, supertest_1.default)(app);
|
|
1393
|
-
return [4 /*yield*/,
|
|
1434
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
1394
1435
|
case 1:
|
|
1436
|
+
agent = _a.sent();
|
|
1437
|
+
return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
|
|
1438
|
+
case 2:
|
|
1395
1439
|
res = _a.sent();
|
|
1396
|
-
(0, bun_test_1.expect)(res.body.
|
|
1440
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
|
|
1441
|
+
(0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
|
|
1397
1442
|
return [2 /*return*/];
|
|
1398
1443
|
}
|
|
1399
1444
|
});
|
|
1400
1445
|
}); });
|
|
1401
|
-
(0, bun_test_1.it)("
|
|
1446
|
+
(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 () {
|
|
1402
1447
|
var res;
|
|
1403
1448
|
return __generator(this, function (_a) {
|
|
1404
1449
|
switch (_a.label) {
|
|
1405
|
-
case 0:
|
|
1406
|
-
// Create a food that is hidden
|
|
1407
|
-
return [4 /*yield*/, tests_1.FoodModel.create({
|
|
1408
|
-
calories: 50,
|
|
1409
|
-
created: new Date("2021-12-04T00:00:20.000Z"),
|
|
1410
|
-
hidden: true,
|
|
1411
|
-
name: "HiddenFood",
|
|
1412
|
-
ownerId: admin._id,
|
|
1413
|
-
})];
|
|
1414
|
-
case 1:
|
|
1415
|
-
// Create a food that is hidden
|
|
1416
|
-
_a.sent();
|
|
1450
|
+
case 0:
|
|
1417
1451
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1418
1452
|
allowAnonymous: true,
|
|
1419
1453
|
permissions: {
|
|
1420
|
-
create: [permissions_1.Permissions.
|
|
1421
|
-
delete: [permissions_1.Permissions.
|
|
1422
|
-
list: [permissions_1.Permissions.
|
|
1423
|
-
read: [permissions_1.Permissions.
|
|
1424
|
-
update: [permissions_1.Permissions.
|
|
1454
|
+
create: [permissions_1.Permissions.IsAdmin],
|
|
1455
|
+
delete: [permissions_1.Permissions.IsAdmin],
|
|
1456
|
+
list: [permissions_1.Permissions.IsAdmin],
|
|
1457
|
+
read: [permissions_1.Permissions.IsAdmin],
|
|
1458
|
+
update: [permissions_1.Permissions.IsAdmin],
|
|
1425
1459
|
},
|
|
1426
|
-
|
|
1460
|
+
preUpdate: function () { return null; },
|
|
1427
1461
|
}));
|
|
1428
1462
|
server = (0, supertest_1.default)(app);
|
|
1429
|
-
return [4 /*yield*/,
|
|
1463
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
1464
|
+
case 1:
|
|
1465
|
+
agent = _a.sent();
|
|
1466
|
+
return [4 /*yield*/, agent
|
|
1467
|
+
.patch("/food/".concat(apple._id, "/tags/healthy"))
|
|
1468
|
+
.send({ tags: "unhealthy" })
|
|
1469
|
+
.expect(403)];
|
|
1430
1470
|
case 2:
|
|
1431
1471
|
res = _a.sent();
|
|
1432
|
-
(0, bun_test_1.expect)(res.body.
|
|
1472
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
|
|
1433
1473
|
return [2 /*return*/];
|
|
1434
1474
|
}
|
|
1435
1475
|
});
|
|
1436
1476
|
}); });
|
|
1437
|
-
(0, bun_test_1.it)("
|
|
1477
|
+
(0, bun_test_1.it)("array operation preUpdate error for array DELETE is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1438
1478
|
var res;
|
|
1439
1479
|
return __generator(this, function (_a) {
|
|
1440
1480
|
switch (_a.label) {
|
|
1441
1481
|
case 0:
|
|
1442
|
-
// The $search code path just accesses the collection but doesn't do anything with it
|
|
1443
|
-
// This test verifies the code path is exercised
|
|
1444
1482
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1445
1483
|
allowAnonymous: true,
|
|
1446
1484
|
permissions: {
|
|
1447
|
-
create: [permissions_1.Permissions.
|
|
1448
|
-
delete: [permissions_1.Permissions.
|
|
1449
|
-
list: [permissions_1.Permissions.
|
|
1450
|
-
read: [permissions_1.Permissions.
|
|
1451
|
-
update: [permissions_1.Permissions.
|
|
1485
|
+
create: [permissions_1.Permissions.IsAdmin],
|
|
1486
|
+
delete: [permissions_1.Permissions.IsAdmin],
|
|
1487
|
+
list: [permissions_1.Permissions.IsAdmin],
|
|
1488
|
+
read: [permissions_1.Permissions.IsAdmin],
|
|
1489
|
+
update: [permissions_1.Permissions.IsAdmin],
|
|
1490
|
+
},
|
|
1491
|
+
preUpdate: function () {
|
|
1492
|
+
throw new Error("preUpdate error during delete");
|
|
1452
1493
|
},
|
|
1453
|
-
// Need to include $search in queryFields for it to pass validation
|
|
1454
|
-
queryFields: ["name", "$search"],
|
|
1455
1494
|
}));
|
|
1456
1495
|
server = (0, supertest_1.default)(app);
|
|
1457
|
-
return [4 /*yield*/,
|
|
1496
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
1458
1497
|
case 1:
|
|
1498
|
+
agent = _a.sent();
|
|
1499
|
+
return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/healthy")).expect(400)];
|
|
1500
|
+
case 2:
|
|
1459
1501
|
res = _a.sent();
|
|
1460
|
-
|
|
1461
|
-
// The important thing is we've exercised the code path
|
|
1462
|
-
(0, bun_test_1.expect)(res.status === 200 || res.status === 500).toBe(true);
|
|
1502
|
+
(0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
|
|
1463
1503
|
return [2 /*return*/];
|
|
1464
1504
|
}
|
|
1465
1505
|
});
|
|
1466
1506
|
}); });
|
|
1467
|
-
(0, bun_test_1.it)("
|
|
1507
|
+
(0, bun_test_1.it)("array operation postUpdate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1468
1508
|
var res;
|
|
1469
1509
|
return __generator(this, function (_a) {
|
|
1470
1510
|
switch (_a.label) {
|
|
@@ -1472,391 +1512,57 @@ var transformers_1 = require("./transformers");
|
|
|
1472
1512
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1473
1513
|
allowAnonymous: true,
|
|
1474
1514
|
permissions: {
|
|
1475
|
-
create: [permissions_1.Permissions.
|
|
1476
|
-
delete: [permissions_1.Permissions.
|
|
1477
|
-
list: [permissions_1.Permissions.
|
|
1478
|
-
read: [permissions_1.Permissions.
|
|
1479
|
-
update: [permissions_1.Permissions.
|
|
1515
|
+
create: [permissions_1.Permissions.IsAdmin],
|
|
1516
|
+
delete: [permissions_1.Permissions.IsAdmin],
|
|
1517
|
+
list: [permissions_1.Permissions.IsAdmin],
|
|
1518
|
+
read: [permissions_1.Permissions.IsAdmin],
|
|
1519
|
+
update: [permissions_1.Permissions.IsAdmin],
|
|
1480
1520
|
},
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
server = (0, supertest_1.default)(app);
|
|
1484
|
-
return [4 /*yield*/, server.get("/food?$autocomplete=test")];
|
|
1485
|
-
case 1:
|
|
1486
|
-
res = _a.sent();
|
|
1487
|
-
(0, bun_test_1.expect)(res.status === 200 || res.status === 500).toBe(true);
|
|
1488
|
-
return [2 /*return*/];
|
|
1489
|
-
}
|
|
1490
|
-
});
|
|
1491
|
-
}); });
|
|
1492
|
-
});
|
|
1493
|
-
(0, bun_test_1.describe)("addPopulateToQuery", function () {
|
|
1494
|
-
(0, bun_test_1.it)("returns query unchanged with no populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1495
|
-
var query, result;
|
|
1496
|
-
return __generator(this, function (_a) {
|
|
1497
|
-
switch (_a.label) {
|
|
1498
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1499
|
-
case 1:
|
|
1500
|
-
_a.sent();
|
|
1501
|
-
query = tests_1.FoodModel.find({});
|
|
1502
|
-
result = (0, api_1.addPopulateToQuery)(query, undefined);
|
|
1503
|
-
(0, bun_test_1.expect)(result).toBe(query);
|
|
1504
|
-
return [2 /*return*/];
|
|
1505
|
-
}
|
|
1506
|
-
});
|
|
1507
|
-
}); });
|
|
1508
|
-
(0, bun_test_1.it)("returns query unchanged with empty populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1509
|
-
var query, result;
|
|
1510
|
-
return __generator(this, function (_a) {
|
|
1511
|
-
switch (_a.label) {
|
|
1512
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1513
|
-
case 1:
|
|
1514
|
-
_a.sent();
|
|
1515
|
-
query = tests_1.FoodModel.find({});
|
|
1516
|
-
result = (0, api_1.addPopulateToQuery)(query, []);
|
|
1517
|
-
(0, bun_test_1.expect)(result).toBe(query);
|
|
1518
|
-
return [2 /*return*/];
|
|
1519
|
-
}
|
|
1520
|
-
});
|
|
1521
|
-
}); });
|
|
1522
|
-
(0, bun_test_1.it)("applies multiple populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1523
|
-
var query, result;
|
|
1524
|
-
return __generator(this, function (_a) {
|
|
1525
|
-
switch (_a.label) {
|
|
1526
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1527
|
-
case 1:
|
|
1528
|
-
_a.sent();
|
|
1529
|
-
query = tests_1.FoodModel.find({});
|
|
1530
|
-
result = (0, api_1.addPopulateToQuery)(query, [
|
|
1531
|
-
{ fields: ["email"], path: "ownerId" },
|
|
1532
|
-
{ fields: ["name"], path: "eatenBy" },
|
|
1533
|
-
]);
|
|
1534
|
-
// The result should be a query with populate applied
|
|
1535
|
-
(0, bun_test_1.expect)(result).toBeDefined();
|
|
1536
|
-
return [2 /*return*/];
|
|
1537
|
-
}
|
|
1538
|
-
});
|
|
1539
|
-
}); });
|
|
1540
|
-
});
|
|
1541
|
-
(0, bun_test_1.describe)("soft delete with isDeleted plugin", function () {
|
|
1542
|
-
var admin;
|
|
1543
|
-
var agent;
|
|
1544
|
-
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1545
|
-
var _a;
|
|
1546
|
-
return __generator(this, function (_b) {
|
|
1547
|
-
switch (_b.label) {
|
|
1548
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1549
|
-
case 1:
|
|
1550
|
-
_a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
|
|
1551
|
-
app = (0, tests_1.getBaseServer)();
|
|
1552
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1553
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
1554
|
-
return [2 /*return*/];
|
|
1555
|
-
}
|
|
1556
|
-
});
|
|
1557
|
-
}); });
|
|
1558
|
-
(0, bun_test_1.it)("soft deletes user with deleted field", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1559
|
-
var res, deletedUser;
|
|
1560
|
-
return __generator(this, function (_a) {
|
|
1561
|
-
switch (_a.label) {
|
|
1562
|
-
case 0:
|
|
1563
|
-
// UserModel has the isDisabledPlugin which adds a 'disabled' field,
|
|
1564
|
-
// but we need to test the 'deleted' field check.
|
|
1565
|
-
// Let's use a model that has the deleted field.
|
|
1566
|
-
app.use("/users", (0, api_1.modelRouter)(tests_1.UserModel, {
|
|
1567
|
-
allowAnonymous: true,
|
|
1568
|
-
permissions: {
|
|
1569
|
-
create: [permissions_1.Permissions.IsAny],
|
|
1570
|
-
delete: [permissions_1.Permissions.IsAny],
|
|
1571
|
-
list: [permissions_1.Permissions.IsAny],
|
|
1572
|
-
read: [permissions_1.Permissions.IsAny],
|
|
1573
|
-
update: [permissions_1.Permissions.IsAny],
|
|
1521
|
+
postUpdate: function () {
|
|
1522
|
+
throw new Error("postUpdate array failed");
|
|
1574
1523
|
},
|
|
1575
1524
|
}));
|
|
1576
1525
|
server = (0, supertest_1.default)(app);
|
|
1577
|
-
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "
|
|
1526
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
1578
1527
|
case 1:
|
|
1579
1528
|
agent = _a.sent();
|
|
1580
|
-
return [4 /*yield*/, agent.
|
|
1529
|
+
return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(400)];
|
|
1581
1530
|
case 2:
|
|
1582
1531
|
res = _a.sent();
|
|
1583
|
-
(0, bun_test_1.expect)(res.body).
|
|
1584
|
-
return [4 /*yield*/, tests_1.UserModel.findById(admin._id)];
|
|
1585
|
-
case 3:
|
|
1586
|
-
deletedUser = _a.sent();
|
|
1587
|
-
(0, bun_test_1.expect)(deletedUser).toBeNull();
|
|
1588
|
-
return [2 /*return*/];
|
|
1589
|
-
}
|
|
1590
|
-
});
|
|
1591
|
-
}); });
|
|
1592
|
-
});
|
|
1593
|
-
(0, bun_test_1.describe)("populate in create", function () {
|
|
1594
|
-
var admin;
|
|
1595
|
-
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1596
|
-
var _a;
|
|
1597
|
-
return __generator(this, function (_b) {
|
|
1598
|
-
switch (_b.label) {
|
|
1599
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1600
|
-
case 1:
|
|
1601
|
-
_a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
|
|
1602
|
-
return [4 /*yield*/, tests_1.FoodModel.create({
|
|
1603
|
-
calories: 1,
|
|
1604
|
-
created: new Date("2021-12-03T00:00:20.000Z"),
|
|
1605
|
-
hidden: false,
|
|
1606
|
-
name: "Spinach",
|
|
1607
|
-
ownerId: admin._id,
|
|
1608
|
-
})];
|
|
1609
|
-
case 2:
|
|
1610
|
-
_b.sent();
|
|
1611
|
-
app = (0, tests_1.getBaseServer)();
|
|
1612
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1613
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
1614
|
-
return [2 /*return*/];
|
|
1615
|
-
}
|
|
1616
|
-
});
|
|
1617
|
-
}); });
|
|
1618
|
-
(0, bun_test_1.it)("handles populate with valid path in create", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1619
|
-
var res;
|
|
1620
|
-
return __generator(this, function (_a) {
|
|
1621
|
-
switch (_a.label) {
|
|
1622
|
-
case 0:
|
|
1623
|
-
// Test that valid populate works in create flow
|
|
1624
|
-
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1625
|
-
allowAnonymous: true,
|
|
1626
|
-
permissions: {
|
|
1627
|
-
create: [permissions_1.Permissions.IsAny],
|
|
1628
|
-
delete: [permissions_1.Permissions.IsAny],
|
|
1629
|
-
list: [permissions_1.Permissions.IsAny],
|
|
1630
|
-
read: [permissions_1.Permissions.IsAny],
|
|
1631
|
-
update: [permissions_1.Permissions.IsAny],
|
|
1632
|
-
},
|
|
1633
|
-
populatePaths: [{ fields: ["email"], path: "ownerId" }],
|
|
1634
|
-
}));
|
|
1635
|
-
server = (0, supertest_1.default)(app);
|
|
1636
|
-
return [4 /*yield*/, server
|
|
1637
|
-
.post("/food")
|
|
1638
|
-
.send({ calories: 15, name: "Broccoli", ownerId: admin._id })
|
|
1639
|
-
.expect(201)];
|
|
1640
|
-
case 1:
|
|
1641
|
-
res = _a.sent();
|
|
1642
|
-
(0, bun_test_1.expect)(res.body.data.name).toBe("Broccoli");
|
|
1643
|
-
// Verify populate worked - ownerId should be an object with email
|
|
1644
|
-
(0, bun_test_1.expect)(res.body.data.ownerId.email).toBe(admin.email);
|
|
1645
|
-
return [2 /*return*/];
|
|
1646
|
-
}
|
|
1647
|
-
});
|
|
1648
|
-
}); });
|
|
1649
|
-
});
|
|
1650
|
-
(0, bun_test_1.describe)("save error handling", function () {
|
|
1651
|
-
var admin;
|
|
1652
|
-
var spinach;
|
|
1653
|
-
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1654
|
-
var _a;
|
|
1655
|
-
return __generator(this, function (_b) {
|
|
1656
|
-
switch (_b.label) {
|
|
1657
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1658
|
-
case 1:
|
|
1659
|
-
_a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
|
|
1660
|
-
return [4 /*yield*/, tests_1.FoodModel.create({
|
|
1661
|
-
calories: 1,
|
|
1662
|
-
created: new Date("2021-12-03T00:00:20.000Z"),
|
|
1663
|
-
hidden: false,
|
|
1664
|
-
name: "Spinach",
|
|
1665
|
-
ownerId: admin._id,
|
|
1666
|
-
source: {
|
|
1667
|
-
name: "Brand",
|
|
1668
|
-
},
|
|
1669
|
-
})];
|
|
1670
|
-
case 2:
|
|
1671
|
-
spinach = _b.sent();
|
|
1672
|
-
app = (0, tests_1.getBaseServer)();
|
|
1673
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1674
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
1675
|
-
return [2 /*return*/];
|
|
1676
|
-
}
|
|
1677
|
-
});
|
|
1678
|
-
}); });
|
|
1679
|
-
(0, bun_test_1.it)("handles patch save error with validation failure", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1680
|
-
var res;
|
|
1681
|
-
return __generator(this, function (_a) {
|
|
1682
|
-
switch (_a.label) {
|
|
1683
|
-
case 0:
|
|
1684
|
-
// The FoodModel has strict: "throw" which will cause validation errors for unknown fields
|
|
1685
|
-
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1686
|
-
allowAnonymous: true,
|
|
1687
|
-
permissions: {
|
|
1688
|
-
create: [permissions_1.Permissions.IsAny],
|
|
1689
|
-
delete: [permissions_1.Permissions.IsAny],
|
|
1690
|
-
list: [permissions_1.Permissions.IsAny],
|
|
1691
|
-
read: [permissions_1.Permissions.IsAny],
|
|
1692
|
-
update: [permissions_1.Permissions.IsAny],
|
|
1693
|
-
},
|
|
1694
|
-
}));
|
|
1695
|
-
server = (0, supertest_1.default)(app);
|
|
1696
|
-
return [4 /*yield*/, server
|
|
1697
|
-
.patch("/food/".concat(spinach._id))
|
|
1698
|
-
.send({ invalidField: "value" })
|
|
1699
|
-
.expect(400)];
|
|
1700
|
-
case 1:
|
|
1701
|
-
res = _a.sent();
|
|
1702
|
-
(0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook save error");
|
|
1532
|
+
(0, bun_test_1.expect)(res.body.title).toContain("PATCH Post Update error");
|
|
1703
1533
|
return [2 /*return*/];
|
|
1704
1534
|
}
|
|
1705
1535
|
});
|
|
1706
1536
|
}); });
|
|
1707
|
-
|
|
1708
|
-
(0, bun_test_1.describe)("body undefined after transform without preCreate", function () {
|
|
1709
|
-
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1710
|
-
return __generator(this, function (_a) {
|
|
1711
|
-
switch (_a.label) {
|
|
1712
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1713
|
-
case 1:
|
|
1714
|
-
_a.sent();
|
|
1715
|
-
app = (0, tests_1.getBaseServer)();
|
|
1716
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1717
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
1718
|
-
return [2 /*return*/];
|
|
1719
|
-
}
|
|
1720
|
-
});
|
|
1721
|
-
}); });
|
|
1722
|
-
(0, bun_test_1.it)("handles undefined body after transform when no preCreate", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1537
|
+
(0, bun_test_1.it)("array operation denied without update permission", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1723
1538
|
var res;
|
|
1724
1539
|
return __generator(this, function (_a) {
|
|
1725
1540
|
switch (_a.label) {
|
|
1726
1541
|
case 0:
|
|
1727
|
-
// Create a transformer that returns undefined
|
|
1728
1542
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1729
1543
|
allowAnonymous: true,
|
|
1730
1544
|
permissions: {
|
|
1731
|
-
create: [permissions_1.Permissions.
|
|
1732
|
-
delete: [permissions_1.Permissions.
|
|
1733
|
-
list: [permissions_1.Permissions.IsAny],
|
|
1734
|
-
read: [permissions_1.Permissions.IsAny],
|
|
1735
|
-
update: [permissions_1.Permissions.IsAny],
|
|
1736
|
-
},
|
|
1737
|
-
transformer: {
|
|
1738
|
-
transform: function () { return undefined; },
|
|
1739
|
-
},
|
|
1740
|
-
}));
|
|
1741
|
-
server = (0, supertest_1.default)(app);
|
|
1742
|
-
return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(400)];
|
|
1743
|
-
case 1:
|
|
1744
|
-
res = _a.sent();
|
|
1745
|
-
(0, bun_test_1.expect)(res.body.title).toBe("Invalid request body");
|
|
1746
|
-
(0, bun_test_1.expect)(res.body.detail).toBe("Body is undefined");
|
|
1747
|
-
return [2 /*return*/];
|
|
1748
|
-
}
|
|
1749
|
-
});
|
|
1750
|
-
}); });
|
|
1751
|
-
});
|
|
1752
|
-
(0, bun_test_1.describe)("soft delete with deleted field", function () {
|
|
1753
|
-
var agent;
|
|
1754
|
-
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1755
|
-
return __generator(this, function (_a) {
|
|
1756
|
-
switch (_a.label) {
|
|
1757
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1758
|
-
case 1:
|
|
1759
|
-
_a.sent();
|
|
1760
|
-
app = (0, tests_1.getBaseServer)();
|
|
1761
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1762
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
1763
|
-
return [2 /*return*/];
|
|
1764
|
-
}
|
|
1765
|
-
});
|
|
1766
|
-
}); });
|
|
1767
|
-
(0, bun_test_1.it)("soft deletes document with deleted field using isDeletedPlugin", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1768
|
-
var mongoose, softDeleteSchema, SoftDeleteModel, testDoc, softDeleted;
|
|
1769
|
-
return __generator(this, function (_a) {
|
|
1770
|
-
switch (_a.label) {
|
|
1771
|
-
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("mongoose")); })];
|
|
1772
|
-
case 1:
|
|
1773
|
-
mongoose = _a.sent();
|
|
1774
|
-
softDeleteSchema = new mongoose.Schema({
|
|
1775
|
-
deleted: { default: false, type: Boolean },
|
|
1776
|
-
name: String,
|
|
1777
|
-
});
|
|
1778
|
-
try {
|
|
1779
|
-
SoftDeleteModel = mongoose.model("SoftDeleteTest");
|
|
1780
|
-
}
|
|
1781
|
-
catch (_b) {
|
|
1782
|
-
SoftDeleteModel = mongoose.model("SoftDeleteTest", softDeleteSchema);
|
|
1783
|
-
}
|
|
1784
|
-
// Clean up any existing documents
|
|
1785
|
-
return [4 /*yield*/, SoftDeleteModel.deleteMany({})];
|
|
1786
|
-
case 2:
|
|
1787
|
-
// Clean up any existing documents
|
|
1788
|
-
_a.sent();
|
|
1789
|
-
return [4 /*yield*/, SoftDeleteModel.create({ name: "TestItem" })];
|
|
1790
|
-
case 3:
|
|
1791
|
-
testDoc = _a.sent();
|
|
1792
|
-
app.use("/softdelete", (0, api_1.modelRouter)(SoftDeleteModel, {
|
|
1793
|
-
allowAnonymous: true,
|
|
1794
|
-
permissions: {
|
|
1795
|
-
create: [permissions_1.Permissions.IsAny],
|
|
1796
|
-
delete: [permissions_1.Permissions.IsAny],
|
|
1545
|
+
create: [permissions_1.Permissions.IsAdmin],
|
|
1546
|
+
delete: [permissions_1.Permissions.IsAdmin],
|
|
1797
1547
|
list: [permissions_1.Permissions.IsAny],
|
|
1798
1548
|
read: [permissions_1.Permissions.IsAny],
|
|
1799
|
-
update: [permissions_1.Permissions.
|
|
1549
|
+
update: [permissions_1.Permissions.IsAdmin],
|
|
1800
1550
|
},
|
|
1801
1551
|
}));
|
|
1802
1552
|
server = (0, supertest_1.default)(app);
|
|
1803
1553
|
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
|
|
1804
|
-
case 4:
|
|
1805
|
-
agent = _a.sent();
|
|
1806
|
-
// Delete should soft delete (set deleted: true) instead of hard delete
|
|
1807
|
-
return [4 /*yield*/, agent.delete("/softdelete/".concat(testDoc._id)).expect(204)];
|
|
1808
|
-
case 5:
|
|
1809
|
-
// Delete should soft delete (set deleted: true) instead of hard delete
|
|
1810
|
-
_a.sent();
|
|
1811
|
-
return [4 /*yield*/, SoftDeleteModel.findById(testDoc._id)];
|
|
1812
|
-
case 6:
|
|
1813
|
-
softDeleted = _a.sent();
|
|
1814
|
-
(0, bun_test_1.expect)(softDeleted).not.toBeNull();
|
|
1815
|
-
(0, bun_test_1.expect)(softDeleted === null || softDeleted === void 0 ? void 0 : softDeleted.deleted).toBe(true);
|
|
1816
|
-
// Clean up
|
|
1817
|
-
return [4 /*yield*/, SoftDeleteModel.deleteMany({})];
|
|
1818
|
-
case 7:
|
|
1819
|
-
// Clean up
|
|
1820
|
-
_a.sent();
|
|
1821
|
-
return [2 /*return*/];
|
|
1822
|
-
}
|
|
1823
|
-
});
|
|
1824
|
-
}); });
|
|
1825
|
-
});
|
|
1826
|
-
(0, bun_test_1.describe)("array operation with undefined preUpdate return", function () {
|
|
1827
|
-
var admin;
|
|
1828
|
-
var apple;
|
|
1829
|
-
var agent;
|
|
1830
|
-
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1831
|
-
var _a;
|
|
1832
|
-
return __generator(this, function (_b) {
|
|
1833
|
-
switch (_b.label) {
|
|
1834
|
-
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1835
1554
|
case 1:
|
|
1836
|
-
|
|
1837
|
-
return [4 /*yield*/,
|
|
1838
|
-
calories: 100,
|
|
1839
|
-
categories: [
|
|
1840
|
-
{ name: "Fruit", show: true },
|
|
1841
|
-
{ name: "Popular", show: false },
|
|
1842
|
-
],
|
|
1843
|
-
created: new Date("2021-12-03T00:00:30.000Z"),
|
|
1844
|
-
hidden: false,
|
|
1845
|
-
name: "Apple",
|
|
1846
|
-
ownerId: admin._id,
|
|
1847
|
-
tags: ["healthy", "cheap"],
|
|
1848
|
-
})];
|
|
1555
|
+
agent = _a.sent();
|
|
1556
|
+
return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(405)];
|
|
1849
1557
|
case 2:
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
1853
|
-
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
1558
|
+
res = _a.sent();
|
|
1559
|
+
(0, bun_test_1.expect)(res.body.title).toContain("Access to PATCH");
|
|
1854
1560
|
return [2 /*return*/];
|
|
1855
1561
|
}
|
|
1856
1562
|
});
|
|
1857
1563
|
}); });
|
|
1858
|
-
(0, bun_test_1.it)("array operation
|
|
1859
|
-
var res;
|
|
1564
|
+
(0, bun_test_1.it)("array operation on non-existent document returns 404", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1565
|
+
var fakeId, res;
|
|
1860
1566
|
return __generator(this, function (_a) {
|
|
1861
1567
|
switch (_a.label) {
|
|
1862
1568
|
case 0:
|
|
@@ -1869,53 +1575,50 @@ var transformers_1 = require("./transformers");
|
|
|
1869
1575
|
read: [permissions_1.Permissions.IsAdmin],
|
|
1870
1576
|
update: [permissions_1.Permissions.IsAdmin],
|
|
1871
1577
|
},
|
|
1872
|
-
preUpdate: function () { return undefined; },
|
|
1873
1578
|
}));
|
|
1874
1579
|
server = (0, supertest_1.default)(app);
|
|
1875
1580
|
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
1876
1581
|
case 1:
|
|
1877
1582
|
agent = _a.sent();
|
|
1878
|
-
|
|
1583
|
+
fakeId = "000000000000000000000000";
|
|
1584
|
+
return [4 /*yield*/, agent.post("/food/".concat(fakeId, "/tags")).send({ tags: "organic" }).expect(404)];
|
|
1879
1585
|
case 2:
|
|
1880
1586
|
res = _a.sent();
|
|
1881
|
-
(0, bun_test_1.expect)(res.body.title).
|
|
1882
|
-
(0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
|
|
1587
|
+
(0, bun_test_1.expect)(res.body.title).toContain("Could not find document to PATCH");
|
|
1883
1588
|
return [2 /*return*/];
|
|
1884
1589
|
}
|
|
1885
1590
|
});
|
|
1886
1591
|
}); });
|
|
1887
|
-
(0, bun_test_1.it)("array operation
|
|
1592
|
+
(0, bun_test_1.it)("array operation denied when user cannot update specific doc", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1888
1593
|
var res;
|
|
1889
1594
|
return __generator(this, function (_a) {
|
|
1890
1595
|
switch (_a.label) {
|
|
1891
1596
|
case 0:
|
|
1597
|
+
// Create food owned by admin, then try to update as notAdmin
|
|
1892
1598
|
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1893
1599
|
allowAnonymous: true,
|
|
1894
1600
|
permissions: {
|
|
1895
|
-
create: [permissions_1.Permissions.
|
|
1896
|
-
delete: [permissions_1.Permissions.
|
|
1897
|
-
list: [permissions_1.Permissions.
|
|
1898
|
-
read: [permissions_1.Permissions.
|
|
1899
|
-
update: [permissions_1.Permissions.
|
|
1601
|
+
create: [permissions_1.Permissions.IsAuthenticated],
|
|
1602
|
+
delete: [permissions_1.Permissions.IsAuthenticated],
|
|
1603
|
+
list: [permissions_1.Permissions.IsAuthenticated],
|
|
1604
|
+
read: [permissions_1.Permissions.IsAuthenticated],
|
|
1605
|
+
update: [permissions_1.Permissions.IsOwner],
|
|
1900
1606
|
},
|
|
1901
|
-
preUpdate: function () { return null; },
|
|
1902
1607
|
}));
|
|
1903
1608
|
server = (0, supertest_1.default)(app);
|
|
1904
|
-
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "
|
|
1609
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
|
|
1905
1610
|
case 1:
|
|
1611
|
+
// Login as notAdmin and try to update admin's food (apple)
|
|
1906
1612
|
agent = _a.sent();
|
|
1907
|
-
return [4 /*yield*/, agent
|
|
1908
|
-
.patch("/food/".concat(apple._id, "/tags/healthy"))
|
|
1909
|
-
.send({ tags: "unhealthy" })
|
|
1910
|
-
.expect(403)];
|
|
1613
|
+
return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
|
|
1911
1614
|
case 2:
|
|
1912
1615
|
res = _a.sent();
|
|
1913
|
-
(0, bun_test_1.expect)(res.body.title).
|
|
1616
|
+
(0, bun_test_1.expect)(res.body.title).toContain("Patch not allowed");
|
|
1914
1617
|
return [2 /*return*/];
|
|
1915
1618
|
}
|
|
1916
1619
|
});
|
|
1917
1620
|
}); });
|
|
1918
|
-
(0, bun_test_1.it)("array operation
|
|
1621
|
+
(0, bun_test_1.it)("array operation transform error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1919
1622
|
var res;
|
|
1920
1623
|
return __generator(this, function (_a) {
|
|
1921
1624
|
switch (_a.label) {
|
|
@@ -1929,18 +1632,18 @@ var transformers_1 = require("./transformers");
|
|
|
1929
1632
|
read: [permissions_1.Permissions.IsAdmin],
|
|
1930
1633
|
update: [permissions_1.Permissions.IsAdmin],
|
|
1931
1634
|
},
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
},
|
|
1635
|
+
transformer: (0, transformers_1.AdminOwnerTransformer)({
|
|
1636
|
+
adminWriteFields: ["name"],
|
|
1637
|
+
}),
|
|
1935
1638
|
}));
|
|
1936
1639
|
server = (0, supertest_1.default)(app);
|
|
1937
1640
|
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
1938
1641
|
case 1:
|
|
1939
1642
|
agent = _a.sent();
|
|
1940
|
-
return [4 /*yield*/, agent.
|
|
1643
|
+
return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
|
|
1941
1644
|
case 2:
|
|
1942
1645
|
res = _a.sent();
|
|
1943
|
-
(0, bun_test_1.expect)(res.body.title).toContain("
|
|
1646
|
+
(0, bun_test_1.expect)(res.body.title).toContain("cannot write fields");
|
|
1944
1647
|
return [2 /*return*/];
|
|
1945
1648
|
}
|
|
1946
1649
|
});
|
|
@@ -1949,7 +1652,7 @@ var transformers_1 = require("./transformers");
|
|
|
1949
1652
|
(0, bun_test_1.describe)("transformer errors", function () {
|
|
1950
1653
|
var admin;
|
|
1951
1654
|
var spinach;
|
|
1952
|
-
var
|
|
1655
|
+
var agent;
|
|
1953
1656
|
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1954
1657
|
var _a;
|
|
1955
1658
|
return __generator(this, function (_b) {
|
|
@@ -2058,6 +1761,41 @@ var transformers_1 = require("./transformers");
|
|
|
2058
1761
|
}
|
|
2059
1762
|
});
|
|
2060
1763
|
}); });
|
|
1764
|
+
(0, bun_test_1.it)("preDelete hook throwing APIError is re-thrown", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1765
|
+
var res;
|
|
1766
|
+
return __generator(this, function (_a) {
|
|
1767
|
+
switch (_a.label) {
|
|
1768
|
+
case 0:
|
|
1769
|
+
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
1770
|
+
allowAnonymous: true,
|
|
1771
|
+
permissions: {
|
|
1772
|
+
create: [permissions_1.Permissions.IsAny],
|
|
1773
|
+
delete: [permissions_1.Permissions.IsAny],
|
|
1774
|
+
list: [permissions_1.Permissions.IsAny],
|
|
1775
|
+
read: [permissions_1.Permissions.IsAny],
|
|
1776
|
+
update: [permissions_1.Permissions.IsAny],
|
|
1777
|
+
},
|
|
1778
|
+
preDelete: function () {
|
|
1779
|
+
throw new errors_1.APIError({
|
|
1780
|
+
disableExternalErrorTracking: true,
|
|
1781
|
+
status: 400,
|
|
1782
|
+
title: "Custom preDelete APIError",
|
|
1783
|
+
});
|
|
1784
|
+
},
|
|
1785
|
+
}));
|
|
1786
|
+
server = (0, supertest_1.default)(app);
|
|
1787
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
|
|
1788
|
+
case 1:
|
|
1789
|
+
agent = _a.sent();
|
|
1790
|
+
return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(400)];
|
|
1791
|
+
case 2:
|
|
1792
|
+
res = _a.sent();
|
|
1793
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Custom preDelete APIError");
|
|
1794
|
+
(0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
|
|
1795
|
+
return [2 /*return*/];
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
}); });
|
|
2061
1799
|
});
|
|
2062
1800
|
(0, bun_test_1.describe)("special query params", function () {
|
|
2063
1801
|
var admin;
|