@terreno/api 0.0.10 → 0.0.11-beta.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.
package/dist/api.test.js CHANGED
@@ -90,7 +90,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
90
90
  Object.defineProperty(exports, "__esModule", { value: true });
91
91
  var bun_test_1 = require("bun:test");
92
92
  var Sentry = __importStar(require("@sentry/node"));
93
- var sortBy_1 = __importDefault(require("lodash/sortBy"));
94
93
  var qs_1 = __importDefault(require("qs"));
95
94
  var supertest_1 = __importDefault(require("supertest"));
96
95
  var api_1 = require("./api");
@@ -99,6 +98,7 @@ var errors_1 = require("./errors");
99
98
  var expressServer_1 = require("./expressServer");
100
99
  var permissions_1 = require("./permissions");
101
100
  var tests_1 = require("./tests");
101
+ var transformers_1 = require("./transformers");
102
102
  (0, bun_test_1.describe)("@terreno/api", function () {
103
103
  var server;
104
104
  var app;
@@ -1879,262 +1879,2147 @@ var tests_1 = require("./tests");
1879
1879
  });
1880
1880
  }); });
1881
1881
  });
1882
- (0, bun_test_1.describe)("discriminator", function () {
1883
- var superUser;
1884
- var staffUser;
1885
- var notAdmin;
1882
+ (0, bun_test_1.describe)("error handling", function () {
1883
+ var admin;
1884
+ var agent;
1885
+ var spinach;
1886
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1887
+ var _a;
1888
+ return __generator(this, function (_b) {
1889
+ switch (_b.label) {
1890
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1891
+ case 1:
1892
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
1893
+ return [4 /*yield*/, tests_1.FoodModel.create({
1894
+ calories: 1,
1895
+ created: new Date("2021-12-03T00:00:20.000Z"),
1896
+ hidden: false,
1897
+ name: "Spinach",
1898
+ ownerId: admin._id,
1899
+ source: {
1900
+ name: "Brand",
1901
+ },
1902
+ })];
1903
+ case 2:
1904
+ spinach = _b.sent();
1905
+ app = (0, tests_1.getBaseServer)();
1906
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
1907
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1908
+ return [2 /*return*/];
1909
+ }
1910
+ });
1911
+ }); });
1912
+ (0, bun_test_1.it)("PUT returns 500 not supported", function () { return __awaiter(void 0, void 0, void 0, function () {
1913
+ var res;
1914
+ return __generator(this, function (_a) {
1915
+ switch (_a.label) {
1916
+ case 0:
1917
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1918
+ allowAnonymous: true,
1919
+ permissions: {
1920
+ create: [permissions_1.Permissions.IsAny],
1921
+ delete: [permissions_1.Permissions.IsAny],
1922
+ list: [permissions_1.Permissions.IsAny],
1923
+ read: [permissions_1.Permissions.IsAny],
1924
+ update: [permissions_1.Permissions.IsAny],
1925
+ },
1926
+ }));
1927
+ server = (0, supertest_1.default)(app);
1928
+ return [4 /*yield*/, server.put("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(500)];
1929
+ case 1:
1930
+ res = _a.sent();
1931
+ (0, bun_test_1.expect)(res.body.title).toBe("PUT is not supported.");
1932
+ return [2 /*return*/];
1933
+ }
1934
+ });
1935
+ }); });
1936
+ (0, bun_test_1.it)("preCreate returning undefined throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
1937
+ var res;
1938
+ return __generator(this, function (_a) {
1939
+ switch (_a.label) {
1940
+ case 0:
1941
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1942
+ allowAnonymous: true,
1943
+ permissions: {
1944
+ create: [permissions_1.Permissions.IsAny],
1945
+ delete: [permissions_1.Permissions.IsAny],
1946
+ list: [permissions_1.Permissions.IsAny],
1947
+ read: [permissions_1.Permissions.IsAny],
1948
+ update: [permissions_1.Permissions.IsAny],
1949
+ },
1950
+ preCreate: function () { return undefined; },
1951
+ }));
1952
+ server = (0, supertest_1.default)(app);
1953
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(403)];
1954
+ case 1:
1955
+ res = _a.sent();
1956
+ (0, bun_test_1.expect)(res.body.title).toBe("Create not allowed");
1957
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preCreate");
1958
+ return [2 /*return*/];
1959
+ }
1960
+ });
1961
+ }); });
1962
+ (0, bun_test_1.it)("preUpdate returning undefined throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
1963
+ var res;
1964
+ return __generator(this, function (_a) {
1965
+ switch (_a.label) {
1966
+ case 0:
1967
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1968
+ allowAnonymous: true,
1969
+ permissions: {
1970
+ create: [permissions_1.Permissions.IsAny],
1971
+ delete: [permissions_1.Permissions.IsAny],
1972
+ list: [permissions_1.Permissions.IsAny],
1973
+ read: [permissions_1.Permissions.IsAny],
1974
+ update: [permissions_1.Permissions.IsAny],
1975
+ },
1976
+ preUpdate: function () { return undefined; },
1977
+ }));
1978
+ server = (0, supertest_1.default)(app);
1979
+ return [4 /*yield*/, server.patch("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(403)];
1980
+ case 1:
1981
+ res = _a.sent();
1982
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
1983
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
1984
+ return [2 /*return*/];
1985
+ }
1986
+ });
1987
+ }); });
1988
+ (0, bun_test_1.it)("preDelete returning undefined throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
1989
+ var res;
1990
+ return __generator(this, function (_a) {
1991
+ switch (_a.label) {
1992
+ case 0:
1993
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
1994
+ allowAnonymous: true,
1995
+ permissions: {
1996
+ create: [permissions_1.Permissions.IsAny],
1997
+ delete: [permissions_1.Permissions.IsAny],
1998
+ list: [permissions_1.Permissions.IsAny],
1999
+ read: [permissions_1.Permissions.IsAny],
2000
+ update: [permissions_1.Permissions.IsAny],
2001
+ },
2002
+ preDelete: function () { return undefined; },
2003
+ }));
2004
+ server = (0, supertest_1.default)(app);
2005
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
2006
+ case 1:
2007
+ agent = _a.sent();
2008
+ return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(403)];
2009
+ case 2:
2010
+ res = _a.sent();
2011
+ (0, bun_test_1.expect)(res.body.title).toBe("Delete not allowed");
2012
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preDelete");
2013
+ return [2 /*return*/];
2014
+ }
2015
+ });
2016
+ }); });
2017
+ (0, bun_test_1.it)("postCreate hook error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2018
+ var res;
2019
+ return __generator(this, function (_a) {
2020
+ switch (_a.label) {
2021
+ case 0:
2022
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2023
+ allowAnonymous: true,
2024
+ permissions: {
2025
+ create: [permissions_1.Permissions.IsAny],
2026
+ delete: [permissions_1.Permissions.IsAny],
2027
+ list: [permissions_1.Permissions.IsAny],
2028
+ read: [permissions_1.Permissions.IsAny],
2029
+ update: [permissions_1.Permissions.IsAny],
2030
+ },
2031
+ postCreate: function () {
2032
+ throw new Error("postCreate failed");
2033
+ },
2034
+ }));
2035
+ server = (0, supertest_1.default)(app);
2036
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(400)];
2037
+ case 1:
2038
+ res = _a.sent();
2039
+ (0, bun_test_1.expect)(res.body.title).toContain("postCreate hook error");
2040
+ return [2 /*return*/];
2041
+ }
2042
+ });
2043
+ }); });
2044
+ (0, bun_test_1.it)("postUpdate hook error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2045
+ var res;
2046
+ return __generator(this, function (_a) {
2047
+ switch (_a.label) {
2048
+ case 0:
2049
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2050
+ allowAnonymous: true,
2051
+ permissions: {
2052
+ create: [permissions_1.Permissions.IsAny],
2053
+ delete: [permissions_1.Permissions.IsAny],
2054
+ list: [permissions_1.Permissions.IsAny],
2055
+ read: [permissions_1.Permissions.IsAny],
2056
+ update: [permissions_1.Permissions.IsAny],
2057
+ },
2058
+ postUpdate: function () {
2059
+ throw new Error("postUpdate failed");
2060
+ },
2061
+ }));
2062
+ server = (0, supertest_1.default)(app);
2063
+ return [4 /*yield*/, server.patch("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(400)];
2064
+ case 1:
2065
+ res = _a.sent();
2066
+ (0, bun_test_1.expect)(res.body.title).toContain("postUpdate hook error");
2067
+ return [2 /*return*/];
2068
+ }
2069
+ });
2070
+ }); });
2071
+ (0, bun_test_1.it)("postDelete hook error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2072
+ var res;
2073
+ return __generator(this, function (_a) {
2074
+ switch (_a.label) {
2075
+ case 0:
2076
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2077
+ allowAnonymous: true,
2078
+ permissions: {
2079
+ create: [permissions_1.Permissions.IsAny],
2080
+ delete: [permissions_1.Permissions.IsAny],
2081
+ list: [permissions_1.Permissions.IsAny],
2082
+ read: [permissions_1.Permissions.IsAny],
2083
+ update: [permissions_1.Permissions.IsAny],
2084
+ },
2085
+ postDelete: function () {
2086
+ throw new Error("postDelete failed");
2087
+ },
2088
+ }));
2089
+ server = (0, supertest_1.default)(app);
2090
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
2091
+ case 1:
2092
+ agent = _a.sent();
2093
+ return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(400)];
2094
+ case 2:
2095
+ res = _a.sent();
2096
+ (0, bun_test_1.expect)(res.body.title).toContain("postDelete hook error");
2097
+ return [2 /*return*/];
2098
+ }
2099
+ });
2100
+ }); });
2101
+ (0, bun_test_1.it)("responseHandler error in read is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2102
+ var res;
2103
+ return __generator(this, function (_a) {
2104
+ switch (_a.label) {
2105
+ case 0:
2106
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2107
+ allowAnonymous: true,
2108
+ permissions: {
2109
+ create: [permissions_1.Permissions.IsAny],
2110
+ delete: [permissions_1.Permissions.IsAny],
2111
+ list: [permissions_1.Permissions.IsAny],
2112
+ read: [permissions_1.Permissions.IsAny],
2113
+ update: [permissions_1.Permissions.IsAny],
2114
+ },
2115
+ responseHandler: function (_data, method) {
2116
+ if (method === "read") {
2117
+ throw new Error("responseHandler read failed");
2118
+ }
2119
+ return {};
2120
+ },
2121
+ }));
2122
+ server = (0, supertest_1.default)(app);
2123
+ return [4 /*yield*/, server.get("/food/".concat(spinach._id)).expect(500)];
2124
+ case 1:
2125
+ res = _a.sent();
2126
+ (0, bun_test_1.expect)(res.body.title).toContain("responseHandler error");
2127
+ return [2 /*return*/];
2128
+ }
2129
+ });
2130
+ }); });
2131
+ (0, bun_test_1.it)("responseHandler error in create is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2132
+ var res;
2133
+ return __generator(this, function (_a) {
2134
+ switch (_a.label) {
2135
+ case 0:
2136
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2137
+ allowAnonymous: true,
2138
+ permissions: {
2139
+ create: [permissions_1.Permissions.IsAny],
2140
+ delete: [permissions_1.Permissions.IsAny],
2141
+ list: [permissions_1.Permissions.IsAny],
2142
+ read: [permissions_1.Permissions.IsAny],
2143
+ update: [permissions_1.Permissions.IsAny],
2144
+ },
2145
+ responseHandler: function (_data, method) {
2146
+ if (method === "create") {
2147
+ throw new Error("responseHandler create failed");
2148
+ }
2149
+ return {};
2150
+ },
2151
+ }));
2152
+ server = (0, supertest_1.default)(app);
2153
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(500)];
2154
+ case 1:
2155
+ res = _a.sent();
2156
+ (0, bun_test_1.expect)(res.body.title).toContain("responseHandler error");
2157
+ return [2 /*return*/];
2158
+ }
2159
+ });
2160
+ }); });
2161
+ (0, bun_test_1.it)("responseHandler error in update is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2162
+ var res;
2163
+ return __generator(this, function (_a) {
2164
+ switch (_a.label) {
2165
+ case 0:
2166
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2167
+ allowAnonymous: true,
2168
+ permissions: {
2169
+ create: [permissions_1.Permissions.IsAny],
2170
+ delete: [permissions_1.Permissions.IsAny],
2171
+ list: [permissions_1.Permissions.IsAny],
2172
+ read: [permissions_1.Permissions.IsAny],
2173
+ update: [permissions_1.Permissions.IsAny],
2174
+ },
2175
+ responseHandler: function (_data, method) {
2176
+ if (method === "update") {
2177
+ throw new Error("responseHandler update failed");
2178
+ }
2179
+ return {};
2180
+ },
2181
+ }));
2182
+ server = (0, supertest_1.default)(app);
2183
+ return [4 /*yield*/, server.patch("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(500)];
2184
+ case 1:
2185
+ res = _a.sent();
2186
+ (0, bun_test_1.expect)(res.body.title).toContain("responseHandler error");
2187
+ return [2 /*return*/];
2188
+ }
2189
+ });
2190
+ }); });
2191
+ (0, bun_test_1.it)("responseHandler error in list is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2192
+ var res;
2193
+ return __generator(this, function (_a) {
2194
+ switch (_a.label) {
2195
+ case 0:
2196
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2197
+ allowAnonymous: true,
2198
+ permissions: {
2199
+ create: [permissions_1.Permissions.IsAny],
2200
+ delete: [permissions_1.Permissions.IsAny],
2201
+ list: [permissions_1.Permissions.IsAny],
2202
+ read: [permissions_1.Permissions.IsAny],
2203
+ update: [permissions_1.Permissions.IsAny],
2204
+ },
2205
+ responseHandler: function (_data, method) {
2206
+ if (method === "list") {
2207
+ throw new Error("responseHandler list failed");
2208
+ }
2209
+ return {};
2210
+ },
2211
+ }));
2212
+ server = (0, supertest_1.default)(app);
2213
+ return [4 /*yield*/, server.get("/food").expect(500)];
2214
+ case 1:
2215
+ res = _a.sent();
2216
+ (0, bun_test_1.expect)(res.body.title).toContain("responseHandler error");
2217
+ return [2 /*return*/];
2218
+ }
2219
+ });
2220
+ }); });
2221
+ (0, bun_test_1.it)("list with non-array responseHandler returns data directly", function () { return __awaiter(void 0, void 0, void 0, function () {
2222
+ var res;
2223
+ return __generator(this, function (_a) {
2224
+ switch (_a.label) {
2225
+ case 0:
2226
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2227
+ allowAnonymous: true,
2228
+ permissions: {
2229
+ create: [permissions_1.Permissions.IsAny],
2230
+ delete: [permissions_1.Permissions.IsAny],
2231
+ list: [permissions_1.Permissions.IsAny],
2232
+ read: [permissions_1.Permissions.IsAny],
2233
+ update: [permissions_1.Permissions.IsAny],
2234
+ },
2235
+ responseHandler: function (_data, method) {
2236
+ if (method === "list") {
2237
+ return { custom: "response" };
2238
+ }
2239
+ return {};
2240
+ },
2241
+ }));
2242
+ server = (0, supertest_1.default)(app);
2243
+ return [4 /*yield*/, server.get("/food").expect(200)];
2244
+ case 1:
2245
+ res = _a.sent();
2246
+ (0, bun_test_1.expect)(res.body.data).toEqual({ custom: "response" });
2247
+ (0, bun_test_1.expect)(res.body.more).toBeUndefined();
2248
+ (0, bun_test_1.expect)(res.body.total).toBeUndefined();
2249
+ return [2 /*return*/];
2250
+ }
2251
+ });
2252
+ }); });
2253
+ (0, bun_test_1.it)("list with query sort param", function () { return __awaiter(void 0, void 0, void 0, function () {
2254
+ var res;
2255
+ return __generator(this, function (_a) {
2256
+ switch (_a.label) {
2257
+ case 0: return [4 /*yield*/, tests_1.FoodModel.create({
2258
+ calories: 200,
2259
+ created: new Date("2021-12-04T00:00:20.000Z"),
2260
+ hidden: false,
2261
+ name: "Apple",
2262
+ ownerId: admin._id,
2263
+ })];
2264
+ case 1:
2265
+ _a.sent();
2266
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2267
+ allowAnonymous: true,
2268
+ permissions: {
2269
+ create: [permissions_1.Permissions.IsAny],
2270
+ delete: [permissions_1.Permissions.IsAny],
2271
+ list: [permissions_1.Permissions.IsAny],
2272
+ read: [permissions_1.Permissions.IsAny],
2273
+ update: [permissions_1.Permissions.IsAny],
2274
+ },
2275
+ queryFields: ["name"],
2276
+ }));
2277
+ server = (0, supertest_1.default)(app);
2278
+ return [4 /*yield*/, server.get("/food?sort=name").expect(200)];
2279
+ case 2:
2280
+ res = _a.sent();
2281
+ (0, bun_test_1.expect)(res.body.data[0].name).toBe("Apple");
2282
+ (0, bun_test_1.expect)(res.body.data[1].name).toBe("Spinach");
2283
+ return [4 /*yield*/, server.get("/food?sort=-name").expect(200)];
2284
+ case 3:
2285
+ // Sort by name descending
2286
+ res = _a.sent();
2287
+ (0, bun_test_1.expect)(res.body.data[0].name).toBe("Spinach");
2288
+ (0, bun_test_1.expect)(res.body.data[1].name).toBe("Apple");
2289
+ return [2 /*return*/];
2290
+ }
2291
+ });
2292
+ }); });
2293
+ (0, bun_test_1.it)("queryFilter error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2294
+ var res;
2295
+ return __generator(this, function (_a) {
2296
+ switch (_a.label) {
2297
+ case 0:
2298
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2299
+ allowAnonymous: true,
2300
+ permissions: {
2301
+ create: [permissions_1.Permissions.IsAny],
2302
+ delete: [permissions_1.Permissions.IsAny],
2303
+ list: [permissions_1.Permissions.IsAny],
2304
+ read: [permissions_1.Permissions.IsAny],
2305
+ update: [permissions_1.Permissions.IsAny],
2306
+ },
2307
+ queryFilter: function () {
2308
+ throw new Error("queryFilter failed");
2309
+ },
2310
+ }));
2311
+ server = (0, supertest_1.default)(app);
2312
+ return [4 /*yield*/, server.get("/food").expect(400)];
2313
+ case 1:
2314
+ res = _a.sent();
2315
+ (0, bun_test_1.expect)(res.body.title).toContain("Query filter error");
2316
+ return [2 /*return*/];
2317
+ }
2318
+ });
2319
+ }); });
2320
+ (0, bun_test_1.it)("custom endpoints take priority", function () { return __awaiter(void 0, void 0, void 0, function () {
2321
+ var res;
2322
+ return __generator(this, function (_a) {
2323
+ switch (_a.label) {
2324
+ case 0:
2325
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2326
+ allowAnonymous: true,
2327
+ endpoints: function (router) {
2328
+ router.get("/custom", function (_req, res) {
2329
+ res.json({ custom: true });
2330
+ });
2331
+ },
2332
+ permissions: {
2333
+ create: [permissions_1.Permissions.IsAny],
2334
+ delete: [permissions_1.Permissions.IsAny],
2335
+ list: [permissions_1.Permissions.IsAny],
2336
+ read: [permissions_1.Permissions.IsAny],
2337
+ update: [permissions_1.Permissions.IsAny],
2338
+ },
2339
+ }));
2340
+ server = (0, supertest_1.default)(app);
2341
+ return [4 /*yield*/, server.get("/food/custom").expect(200)];
2342
+ case 1:
2343
+ res = _a.sent();
2344
+ (0, bun_test_1.expect)(res.body.custom).toBe(true);
2345
+ return [2 /*return*/];
2346
+ }
2347
+ });
2348
+ }); });
2349
+ (0, bun_test_1.it)("disallowed query param returns 400", function () { return __awaiter(void 0, void 0, void 0, function () {
2350
+ var res;
2351
+ return __generator(this, function (_a) {
2352
+ switch (_a.label) {
2353
+ case 0:
2354
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2355
+ allowAnonymous: true,
2356
+ permissions: {
2357
+ create: [permissions_1.Permissions.IsAny],
2358
+ delete: [permissions_1.Permissions.IsAny],
2359
+ list: [permissions_1.Permissions.IsAny],
2360
+ read: [permissions_1.Permissions.IsAny],
2361
+ update: [permissions_1.Permissions.IsAny],
2362
+ },
2363
+ queryFields: ["name"],
2364
+ }));
2365
+ server = (0, supertest_1.default)(app);
2366
+ return [4 /*yield*/, server.get("/food?calories=100").expect(400)];
2367
+ case 1:
2368
+ res = _a.sent();
2369
+ (0, bun_test_1.expect)(res.body.title).toContain("calories is not allowed as a query param");
2370
+ return [2 /*return*/];
2371
+ }
2372
+ });
2373
+ }); });
2374
+ (0, bun_test_1.it)("queryFilter returning null returns empty array", function () { return __awaiter(void 0, void 0, void 0, function () {
2375
+ var res;
2376
+ return __generator(this, function (_a) {
2377
+ switch (_a.label) {
2378
+ case 0:
2379
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2380
+ allowAnonymous: true,
2381
+ permissions: {
2382
+ create: [permissions_1.Permissions.IsAny],
2383
+ delete: [permissions_1.Permissions.IsAny],
2384
+ list: [permissions_1.Permissions.IsAny],
2385
+ read: [permissions_1.Permissions.IsAny],
2386
+ update: [permissions_1.Permissions.IsAny],
2387
+ },
2388
+ queryFilter: function () { return null; },
2389
+ }));
2390
+ server = (0, supertest_1.default)(app);
2391
+ return [4 /*yield*/, server.get("/food").expect(200)];
2392
+ case 1:
2393
+ res = _a.sent();
2394
+ (0, bun_test_1.expect)(res.body.data).toEqual([]);
2395
+ return [2 /*return*/];
2396
+ }
2397
+ });
2398
+ }); });
2399
+ (0, bun_test_1.it)("preUpdate returning null throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
2400
+ var res;
2401
+ return __generator(this, function (_a) {
2402
+ switch (_a.label) {
2403
+ case 0:
2404
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2405
+ allowAnonymous: true,
2406
+ permissions: {
2407
+ create: [permissions_1.Permissions.IsAny],
2408
+ delete: [permissions_1.Permissions.IsAny],
2409
+ list: [permissions_1.Permissions.IsAny],
2410
+ read: [permissions_1.Permissions.IsAny],
2411
+ update: [permissions_1.Permissions.IsAny],
2412
+ },
2413
+ preUpdate: function () { return null; },
2414
+ }));
2415
+ server = (0, supertest_1.default)(app);
2416
+ return [4 /*yield*/, server.patch("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(403)];
2417
+ case 1:
2418
+ res = _a.sent();
2419
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
2420
+ return [2 /*return*/];
2421
+ }
2422
+ });
2423
+ }); });
2424
+ (0, bun_test_1.it)("preDelete returning null throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
2425
+ var res;
2426
+ return __generator(this, function (_a) {
2427
+ switch (_a.label) {
2428
+ case 0:
2429
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2430
+ allowAnonymous: true,
2431
+ permissions: {
2432
+ create: [permissions_1.Permissions.IsAny],
2433
+ delete: [permissions_1.Permissions.IsAny],
2434
+ list: [permissions_1.Permissions.IsAny],
2435
+ read: [permissions_1.Permissions.IsAny],
2436
+ update: [permissions_1.Permissions.IsAny],
2437
+ },
2438
+ preDelete: function () { return null; },
2439
+ }));
2440
+ server = (0, supertest_1.default)(app);
2441
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
2442
+ case 1:
2443
+ agent = _a.sent();
2444
+ return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(403)];
2445
+ case 2:
2446
+ res = _a.sent();
2447
+ (0, bun_test_1.expect)(res.body.title).toBe("Delete not allowed");
2448
+ return [2 /*return*/];
2449
+ }
2450
+ });
2451
+ }); });
2452
+ (0, bun_test_1.it)("preCreate returning null throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
2453
+ var res;
2454
+ return __generator(this, function (_a) {
2455
+ switch (_a.label) {
2456
+ case 0:
2457
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2458
+ allowAnonymous: true,
2459
+ permissions: {
2460
+ create: [permissions_1.Permissions.IsAny],
2461
+ delete: [permissions_1.Permissions.IsAny],
2462
+ list: [permissions_1.Permissions.IsAny],
2463
+ read: [permissions_1.Permissions.IsAny],
2464
+ update: [permissions_1.Permissions.IsAny],
2465
+ },
2466
+ preCreate: function () { return null; },
2467
+ }));
2468
+ server = (0, supertest_1.default)(app);
2469
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(403)];
2470
+ case 1:
2471
+ res = _a.sent();
2472
+ (0, bun_test_1.expect)(res.body.title).toBe("Create not allowed");
2473
+ return [2 /*return*/];
2474
+ }
2475
+ });
2476
+ }); });
2477
+ (0, bun_test_1.it)("preCreate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2478
+ var res;
2479
+ return __generator(this, function (_a) {
2480
+ switch (_a.label) {
2481
+ case 0:
2482
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2483
+ allowAnonymous: true,
2484
+ permissions: {
2485
+ create: [permissions_1.Permissions.IsAny],
2486
+ delete: [permissions_1.Permissions.IsAny],
2487
+ list: [permissions_1.Permissions.IsAny],
2488
+ read: [permissions_1.Permissions.IsAny],
2489
+ update: [permissions_1.Permissions.IsAny],
2490
+ },
2491
+ preCreate: function () {
2492
+ throw new Error("preCreate failed");
2493
+ },
2494
+ }));
2495
+ server = (0, supertest_1.default)(app);
2496
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(400)];
2497
+ case 1:
2498
+ res = _a.sent();
2499
+ (0, bun_test_1.expect)(res.body.title).toContain("preCreate hook error");
2500
+ return [2 /*return*/];
2501
+ }
2502
+ });
2503
+ }); });
2504
+ (0, bun_test_1.it)("preUpdate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2505
+ var res;
2506
+ return __generator(this, function (_a) {
2507
+ switch (_a.label) {
2508
+ case 0:
2509
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2510
+ allowAnonymous: true,
2511
+ permissions: {
2512
+ create: [permissions_1.Permissions.IsAny],
2513
+ delete: [permissions_1.Permissions.IsAny],
2514
+ list: [permissions_1.Permissions.IsAny],
2515
+ read: [permissions_1.Permissions.IsAny],
2516
+ update: [permissions_1.Permissions.IsAny],
2517
+ },
2518
+ preUpdate: function () {
2519
+ throw new Error("preUpdate failed");
2520
+ },
2521
+ }));
2522
+ server = (0, supertest_1.default)(app);
2523
+ return [4 /*yield*/, server.patch("/food/".concat(spinach._id)).send({ name: "Kale" }).expect(400)];
2524
+ case 1:
2525
+ res = _a.sent();
2526
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
2527
+ return [2 /*return*/];
2528
+ }
2529
+ });
2530
+ }); });
2531
+ (0, bun_test_1.it)("invalid array operation type returns 400", function () { return __awaiter(void 0, void 0, void 0, function () {
2532
+ return __generator(this, function (_a) {
2533
+ return [2 /*return*/];
2534
+ });
2535
+ }); });
2536
+ });
2537
+ (0, bun_test_1.describe)("array operation errors", function () {
2538
+ var admin;
2539
+ var apple;
2540
+ var agent;
2541
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
2542
+ var _a;
2543
+ return __generator(this, function (_b) {
2544
+ switch (_b.label) {
2545
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
2546
+ case 1:
2547
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
2548
+ return [4 /*yield*/, tests_1.FoodModel.create({
2549
+ calories: 100,
2550
+ categories: [
2551
+ { name: "Fruit", show: true },
2552
+ { name: "Popular", show: false },
2553
+ ],
2554
+ created: new Date("2021-12-03T00:00:30.000Z"),
2555
+ hidden: false,
2556
+ name: "Apple",
2557
+ ownerId: admin._id,
2558
+ tags: ["healthy", "cheap"],
2559
+ })];
2560
+ case 2:
2561
+ apple = _b.sent();
2562
+ app = (0, tests_1.getBaseServer)();
2563
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
2564
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
2565
+ return [2 /*return*/];
2566
+ }
2567
+ });
2568
+ }); });
2569
+ (0, bun_test_1.it)("array operation preUpdate returning undefined throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
2570
+ var res;
2571
+ return __generator(this, function (_a) {
2572
+ switch (_a.label) {
2573
+ case 0:
2574
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2575
+ allowAnonymous: true,
2576
+ permissions: {
2577
+ create: [permissions_1.Permissions.IsAdmin],
2578
+ delete: [permissions_1.Permissions.IsAdmin],
2579
+ list: [permissions_1.Permissions.IsAdmin],
2580
+ read: [permissions_1.Permissions.IsAdmin],
2581
+ update: [permissions_1.Permissions.IsAdmin],
2582
+ },
2583
+ preUpdate: function () { return undefined; },
2584
+ }));
2585
+ server = (0, supertest_1.default)(app);
2586
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2587
+ case 1:
2588
+ agent = _a.sent();
2589
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
2590
+ case 2:
2591
+ res = _a.sent();
2592
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
2593
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
2594
+ return [2 /*return*/];
2595
+ }
2596
+ });
2597
+ }); });
2598
+ (0, bun_test_1.it)("array operation preUpdate returning null throws error", function () { return __awaiter(void 0, void 0, void 0, function () {
2599
+ var res;
2600
+ return __generator(this, function (_a) {
2601
+ switch (_a.label) {
2602
+ case 0:
2603
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2604
+ allowAnonymous: true,
2605
+ permissions: {
2606
+ create: [permissions_1.Permissions.IsAdmin],
2607
+ delete: [permissions_1.Permissions.IsAdmin],
2608
+ list: [permissions_1.Permissions.IsAdmin],
2609
+ read: [permissions_1.Permissions.IsAdmin],
2610
+ update: [permissions_1.Permissions.IsAdmin],
2611
+ },
2612
+ preUpdate: function () { return null; },
2613
+ }));
2614
+ server = (0, supertest_1.default)(app);
2615
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2616
+ case 1:
2617
+ agent = _a.sent();
2618
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
2619
+ case 2:
2620
+ res = _a.sent();
2621
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
2622
+ return [2 /*return*/];
2623
+ }
2624
+ });
2625
+ }); });
2626
+ (0, bun_test_1.it)("array operation preUpdate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2627
+ var res;
2628
+ return __generator(this, function (_a) {
2629
+ switch (_a.label) {
2630
+ case 0:
2631
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2632
+ allowAnonymous: true,
2633
+ permissions: {
2634
+ create: [permissions_1.Permissions.IsAdmin],
2635
+ delete: [permissions_1.Permissions.IsAdmin],
2636
+ list: [permissions_1.Permissions.IsAdmin],
2637
+ read: [permissions_1.Permissions.IsAdmin],
2638
+ update: [permissions_1.Permissions.IsAdmin],
2639
+ },
2640
+ preUpdate: function () {
2641
+ throw new Error("preUpdate array failed");
2642
+ },
2643
+ }));
2644
+ server = (0, supertest_1.default)(app);
2645
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2646
+ case 1:
2647
+ agent = _a.sent();
2648
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(400)];
2649
+ case 2:
2650
+ res = _a.sent();
2651
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
2652
+ return [2 /*return*/];
2653
+ }
2654
+ });
2655
+ }); });
2656
+ (0, bun_test_1.it)("array operation postUpdate error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2657
+ var res;
2658
+ return __generator(this, function (_a) {
2659
+ switch (_a.label) {
2660
+ case 0:
2661
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2662
+ allowAnonymous: true,
2663
+ permissions: {
2664
+ create: [permissions_1.Permissions.IsAdmin],
2665
+ delete: [permissions_1.Permissions.IsAdmin],
2666
+ list: [permissions_1.Permissions.IsAdmin],
2667
+ read: [permissions_1.Permissions.IsAdmin],
2668
+ update: [permissions_1.Permissions.IsAdmin],
2669
+ },
2670
+ postUpdate: function () {
2671
+ throw new Error("postUpdate array failed");
2672
+ },
2673
+ }));
2674
+ server = (0, supertest_1.default)(app);
2675
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2676
+ case 1:
2677
+ agent = _a.sent();
2678
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(400)];
2679
+ case 2:
2680
+ res = _a.sent();
2681
+ (0, bun_test_1.expect)(res.body.title).toContain("PATCH Post Update error");
2682
+ return [2 /*return*/];
2683
+ }
2684
+ });
2685
+ }); });
2686
+ (0, bun_test_1.it)("array operation denied without update permission", function () { return __awaiter(void 0, void 0, void 0, function () {
2687
+ var res;
2688
+ return __generator(this, function (_a) {
2689
+ switch (_a.label) {
2690
+ case 0:
2691
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2692
+ allowAnonymous: true,
2693
+ permissions: {
2694
+ create: [permissions_1.Permissions.IsAdmin],
2695
+ delete: [permissions_1.Permissions.IsAdmin],
2696
+ list: [permissions_1.Permissions.IsAny],
2697
+ read: [permissions_1.Permissions.IsAny],
2698
+ update: [permissions_1.Permissions.IsAdmin],
2699
+ },
2700
+ }));
2701
+ server = (0, supertest_1.default)(app);
2702
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
2703
+ case 1:
2704
+ agent = _a.sent();
2705
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(405)];
2706
+ case 2:
2707
+ res = _a.sent();
2708
+ (0, bun_test_1.expect)(res.body.title).toContain("Access to PATCH");
2709
+ return [2 /*return*/];
2710
+ }
2711
+ });
2712
+ }); });
2713
+ (0, bun_test_1.it)("array operation on non-existent document returns 404", function () { return __awaiter(void 0, void 0, void 0, function () {
2714
+ var fakeId, res;
2715
+ return __generator(this, function (_a) {
2716
+ switch (_a.label) {
2717
+ case 0:
2718
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2719
+ allowAnonymous: true,
2720
+ permissions: {
2721
+ create: [permissions_1.Permissions.IsAdmin],
2722
+ delete: [permissions_1.Permissions.IsAdmin],
2723
+ list: [permissions_1.Permissions.IsAdmin],
2724
+ read: [permissions_1.Permissions.IsAdmin],
2725
+ update: [permissions_1.Permissions.IsAdmin],
2726
+ },
2727
+ }));
2728
+ server = (0, supertest_1.default)(app);
2729
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2730
+ case 1:
2731
+ agent = _a.sent();
2732
+ fakeId = "000000000000000000000000";
2733
+ return [4 /*yield*/, agent.post("/food/".concat(fakeId, "/tags")).send({ tags: "organic" }).expect(404)];
2734
+ case 2:
2735
+ res = _a.sent();
2736
+ (0, bun_test_1.expect)(res.body.title).toContain("Could not find document to PATCH");
2737
+ return [2 /*return*/];
2738
+ }
2739
+ });
2740
+ }); });
2741
+ (0, bun_test_1.it)("array operation denied when user cannot update specific doc", function () { return __awaiter(void 0, void 0, void 0, function () {
2742
+ var res;
2743
+ return __generator(this, function (_a) {
2744
+ switch (_a.label) {
2745
+ case 0:
2746
+ // Create food owned by admin, then try to update as notAdmin
2747
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2748
+ allowAnonymous: true,
2749
+ permissions: {
2750
+ create: [permissions_1.Permissions.IsAuthenticated],
2751
+ delete: [permissions_1.Permissions.IsAuthenticated],
2752
+ list: [permissions_1.Permissions.IsAuthenticated],
2753
+ read: [permissions_1.Permissions.IsAuthenticated],
2754
+ update: [permissions_1.Permissions.IsOwner],
2755
+ },
2756
+ }));
2757
+ server = (0, supertest_1.default)(app);
2758
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
2759
+ case 1:
2760
+ // Login as notAdmin and try to update admin's food (apple)
2761
+ agent = _a.sent();
2762
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
2763
+ case 2:
2764
+ res = _a.sent();
2765
+ (0, bun_test_1.expect)(res.body.title).toContain("Patch not allowed");
2766
+ return [2 /*return*/];
2767
+ }
2768
+ });
2769
+ }); });
2770
+ (0, bun_test_1.it)("array operation transform error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2771
+ var res;
2772
+ return __generator(this, function (_a) {
2773
+ switch (_a.label) {
2774
+ case 0:
2775
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2776
+ allowAnonymous: true,
2777
+ permissions: {
2778
+ create: [permissions_1.Permissions.IsAdmin],
2779
+ delete: [permissions_1.Permissions.IsAdmin],
2780
+ list: [permissions_1.Permissions.IsAdmin],
2781
+ read: [permissions_1.Permissions.IsAdmin],
2782
+ update: [permissions_1.Permissions.IsAdmin],
2783
+ },
2784
+ transformer: (0, transformers_1.AdminOwnerTransformer)({
2785
+ adminWriteFields: ["name"],
2786
+ }),
2787
+ }));
2788
+ server = (0, supertest_1.default)(app);
2789
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
2790
+ case 1:
2791
+ agent = _a.sent();
2792
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
2793
+ case 2:
2794
+ res = _a.sent();
2795
+ (0, bun_test_1.expect)(res.body.title).toContain("cannot write fields");
2796
+ return [2 /*return*/];
2797
+ }
2798
+ });
2799
+ }); });
2800
+ });
2801
+ (0, bun_test_1.describe)("transformer errors", function () {
2802
+ var admin;
2803
+ var spinach;
2804
+ var agent;
2805
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
2806
+ var _a;
2807
+ return __generator(this, function (_b) {
2808
+ switch (_b.label) {
2809
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
2810
+ case 1:
2811
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
2812
+ return [4 /*yield*/, tests_1.FoodModel.create({
2813
+ calories: 1,
2814
+ created: new Date("2021-12-03T00:00:20.000Z"),
2815
+ hidden: false,
2816
+ name: "Spinach",
2817
+ ownerId: admin._id,
2818
+ source: {
2819
+ name: "Brand",
2820
+ },
2821
+ })];
2822
+ case 2:
2823
+ spinach = _b.sent();
2824
+ app = (0, tests_1.getBaseServer)();
2825
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
2826
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
2827
+ return [2 /*return*/];
2828
+ }
2829
+ });
2830
+ }); });
2831
+ (0, bun_test_1.it)("transform error in create is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2832
+ var res;
2833
+ return __generator(this, function (_a) {
2834
+ switch (_a.label) {
2835
+ case 0:
2836
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2837
+ allowAnonymous: true,
2838
+ permissions: {
2839
+ create: [permissions_1.Permissions.IsAny],
2840
+ delete: [permissions_1.Permissions.IsAny],
2841
+ list: [permissions_1.Permissions.IsAny],
2842
+ read: [permissions_1.Permissions.IsAny],
2843
+ update: [permissions_1.Permissions.IsAny],
2844
+ },
2845
+ transformer: (0, transformers_1.AdminOwnerTransformer)({
2846
+ // Only allow 'name' to be written, so 'calories' will throw
2847
+ anonWriteFields: ["name"],
2848
+ }),
2849
+ }));
2850
+ server = (0, supertest_1.default)(app);
2851
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(400)];
2852
+ case 1:
2853
+ res = _a.sent();
2854
+ (0, bun_test_1.expect)(res.body.title).toContain("cannot write fields");
2855
+ return [2 /*return*/];
2856
+ }
2857
+ });
2858
+ }); });
2859
+ (0, bun_test_1.it)("transform error in patch is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2860
+ var res;
2861
+ return __generator(this, function (_a) {
2862
+ switch (_a.label) {
2863
+ case 0:
2864
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2865
+ allowAnonymous: true,
2866
+ permissions: {
2867
+ create: [permissions_1.Permissions.IsAny],
2868
+ delete: [permissions_1.Permissions.IsAny],
2869
+ list: [permissions_1.Permissions.IsAny],
2870
+ read: [permissions_1.Permissions.IsAny],
2871
+ update: [permissions_1.Permissions.IsAny],
2872
+ },
2873
+ transformer: (0, transformers_1.AdminOwnerTransformer)({
2874
+ // Only allow 'name' to be written, so 'calories' will throw
2875
+ anonWriteFields: ["name"],
2876
+ }),
2877
+ }));
2878
+ server = (0, supertest_1.default)(app);
2879
+ return [4 /*yield*/, server.patch("/food/".concat(spinach._id)).send({ calories: 100 }).expect(403)];
2880
+ case 1:
2881
+ res = _a.sent();
2882
+ (0, bun_test_1.expect)(res.body.title).toContain("cannot write fields");
2883
+ return [2 /*return*/];
2884
+ }
2885
+ });
2886
+ }); });
2887
+ (0, bun_test_1.it)("model.create validation error is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
2888
+ var RequiredModel, res;
2889
+ return __generator(this, function (_a) {
2890
+ switch (_a.label) {
2891
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./tests")); })];
2892
+ case 1:
2893
+ RequiredModel = (_a.sent()).RequiredModel;
2894
+ app.use("/required", (0, api_1.modelRouter)(RequiredModel, {
2895
+ allowAnonymous: true,
2896
+ permissions: {
2897
+ create: [permissions_1.Permissions.IsAny],
2898
+ delete: [permissions_1.Permissions.IsAny],
2899
+ list: [permissions_1.Permissions.IsAny],
2900
+ read: [permissions_1.Permissions.IsAny],
2901
+ update: [permissions_1.Permissions.IsAny],
2902
+ },
2903
+ }));
2904
+ server = (0, supertest_1.default)(app);
2905
+ return [4 /*yield*/, server.post("/required").send({ about: "test" }).expect(400)];
2906
+ case 2:
2907
+ res = _a.sent();
2908
+ (0, bun_test_1.expect)(res.body.title).toContain("Required");
2909
+ return [2 /*return*/];
2910
+ }
2911
+ });
2912
+ }); });
2913
+ (0, bun_test_1.it)("preDelete hook throwing APIError is re-thrown", function () { return __awaiter(void 0, void 0, void 0, function () {
2914
+ var res;
2915
+ return __generator(this, function (_a) {
2916
+ switch (_a.label) {
2917
+ case 0:
2918
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2919
+ allowAnonymous: true,
2920
+ permissions: {
2921
+ create: [permissions_1.Permissions.IsAny],
2922
+ delete: [permissions_1.Permissions.IsAny],
2923
+ list: [permissions_1.Permissions.IsAny],
2924
+ read: [permissions_1.Permissions.IsAny],
2925
+ update: [permissions_1.Permissions.IsAny],
2926
+ },
2927
+ preDelete: function () {
2928
+ throw new errors_1.APIError({
2929
+ disableExternalErrorTracking: true,
2930
+ status: 400,
2931
+ title: "Custom preDelete APIError",
2932
+ });
2933
+ },
2934
+ }));
2935
+ server = (0, supertest_1.default)(app);
2936
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
2937
+ case 1:
2938
+ agent = _a.sent();
2939
+ return [4 /*yield*/, agent.delete("/food/".concat(spinach._id)).expect(400)];
2940
+ case 2:
2941
+ res = _a.sent();
2942
+ (0, bun_test_1.expect)(res.body.title).toBe("Custom preDelete APIError");
2943
+ (0, bun_test_1.expect)(res.body.disableExternalErrorTracking).toBe(true);
2944
+ return [2 /*return*/];
2945
+ }
2946
+ });
2947
+ }); });
2948
+ });
2949
+ (0, bun_test_1.describe)("special query params", function () {
2950
+ var admin;
2951
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
2952
+ var _a;
2953
+ return __generator(this, function (_b) {
2954
+ switch (_b.label) {
2955
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
2956
+ case 1:
2957
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
2958
+ return [4 /*yield*/, tests_1.FoodModel.create({
2959
+ calories: 1,
2960
+ created: new Date("2021-12-03T00:00:20.000Z"),
2961
+ hidden: false,
2962
+ name: "Spinach",
2963
+ ownerId: admin._id,
2964
+ })];
2965
+ case 2:
2966
+ _b.sent();
2967
+ app = (0, tests_1.getBaseServer)();
2968
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
2969
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
2970
+ return [2 /*return*/];
2971
+ }
2972
+ });
2973
+ }); });
2974
+ (0, bun_test_1.it)("period query param is stripped from query", function () { return __awaiter(void 0, void 0, void 0, function () {
2975
+ var res;
2976
+ return __generator(this, function (_a) {
2977
+ switch (_a.label) {
2978
+ case 0:
2979
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
2980
+ allowAnonymous: true,
2981
+ permissions: {
2982
+ create: [permissions_1.Permissions.IsAny],
2983
+ delete: [permissions_1.Permissions.IsAny],
2984
+ list: [permissions_1.Permissions.IsAny],
2985
+ read: [permissions_1.Permissions.IsAny],
2986
+ update: [permissions_1.Permissions.IsAny],
2987
+ },
2988
+ queryFields: ["name", "period"],
2989
+ queryFilter: function (_user, query) {
2990
+ // Simulate a queryFilter that accepts and processes period
2991
+ if (query === null || query === void 0 ? void 0 : query.period) {
2992
+ // Period is processed but shouldn't be passed to mongo
2993
+ return query;
2994
+ }
2995
+ return query !== null && query !== void 0 ? query : {};
2996
+ },
2997
+ }));
2998
+ server = (0, supertest_1.default)(app);
2999
+ return [4 /*yield*/, server.get("/food?period=weekly").expect(200)];
3000
+ case 1:
3001
+ res = _a.sent();
3002
+ (0, bun_test_1.expect)(res.body.data).toBeDefined();
3003
+ return [2 /*return*/];
3004
+ }
3005
+ });
3006
+ }); });
3007
+ (0, bun_test_1.it)("query with false value", function () { return __awaiter(void 0, void 0, void 0, function () {
3008
+ var res;
3009
+ return __generator(this, function (_a) {
3010
+ switch (_a.label) {
3011
+ case 0:
3012
+ // Create a food that is hidden
3013
+ return [4 /*yield*/, tests_1.FoodModel.create({
3014
+ calories: 50,
3015
+ created: new Date("2021-12-04T00:00:20.000Z"),
3016
+ hidden: true,
3017
+ name: "HiddenFood",
3018
+ ownerId: admin._id,
3019
+ })];
3020
+ case 1:
3021
+ // Create a food that is hidden
3022
+ _a.sent();
3023
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3024
+ allowAnonymous: true,
3025
+ permissions: {
3026
+ create: [permissions_1.Permissions.IsAny],
3027
+ delete: [permissions_1.Permissions.IsAny],
3028
+ list: [permissions_1.Permissions.IsAny],
3029
+ read: [permissions_1.Permissions.IsAny],
3030
+ update: [permissions_1.Permissions.IsAny],
3031
+ },
3032
+ queryFields: ["name", "hidden"],
3033
+ }));
3034
+ server = (0, supertest_1.default)(app);
3035
+ return [4 /*yield*/, server.get("/food?hidden=false").expect(200)];
3036
+ case 2:
3037
+ res = _a.sent();
3038
+ (0, bun_test_1.expect)(res.body.data.every(function (f) { return f.hidden === false; })).toBe(true);
3039
+ return [2 /*return*/];
3040
+ }
3041
+ });
3042
+ }); });
3043
+ (0, bun_test_1.it)("$search query triggers special handling code path", function () { return __awaiter(void 0, void 0, void 0, function () {
3044
+ var res;
3045
+ return __generator(this, function (_a) {
3046
+ switch (_a.label) {
3047
+ case 0:
3048
+ // The $search code path just accesses the collection but doesn't do anything with it
3049
+ // This test verifies the code path is exercised
3050
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3051
+ allowAnonymous: true,
3052
+ permissions: {
3053
+ create: [permissions_1.Permissions.IsAny],
3054
+ delete: [permissions_1.Permissions.IsAny],
3055
+ list: [permissions_1.Permissions.IsAny],
3056
+ read: [permissions_1.Permissions.IsAny],
3057
+ update: [permissions_1.Permissions.IsAny],
3058
+ },
3059
+ // Need to include $search in queryFields for it to pass validation
3060
+ queryFields: ["name", "$search"],
3061
+ }));
3062
+ server = (0, supertest_1.default)(app);
3063
+ return [4 /*yield*/, server.get("/food?$search=test")];
3064
+ case 1:
3065
+ res = _a.sent();
3066
+ // May return 500 because $search is passed to Mongo which doesn't support it without Atlas
3067
+ // The important thing is we've exercised the code path
3068
+ (0, bun_test_1.expect)(res.status === 200 || res.status === 500).toBe(true);
3069
+ return [2 /*return*/];
3070
+ }
3071
+ });
3072
+ }); });
3073
+ (0, bun_test_1.it)("$autocomplete query triggers special handling code path", function () { return __awaiter(void 0, void 0, void 0, function () {
3074
+ var res;
3075
+ return __generator(this, function (_a) {
3076
+ switch (_a.label) {
3077
+ case 0:
3078
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3079
+ allowAnonymous: true,
3080
+ permissions: {
3081
+ create: [permissions_1.Permissions.IsAny],
3082
+ delete: [permissions_1.Permissions.IsAny],
3083
+ list: [permissions_1.Permissions.IsAny],
3084
+ read: [permissions_1.Permissions.IsAny],
3085
+ update: [permissions_1.Permissions.IsAny],
3086
+ },
3087
+ queryFields: ["name", "$autocomplete"],
3088
+ }));
3089
+ server = (0, supertest_1.default)(app);
3090
+ return [4 /*yield*/, server.get("/food?$autocomplete=test")];
3091
+ case 1:
3092
+ res = _a.sent();
3093
+ (0, bun_test_1.expect)(res.status === 200 || res.status === 500).toBe(true);
3094
+ return [2 /*return*/];
3095
+ }
3096
+ });
3097
+ }); });
3098
+ });
3099
+ (0, bun_test_1.describe)("addPopulateToQuery", function () {
3100
+ (0, bun_test_1.it)("returns query unchanged with no populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
3101
+ var query, result;
3102
+ return __generator(this, function (_a) {
3103
+ switch (_a.label) {
3104
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3105
+ case 1:
3106
+ _a.sent();
3107
+ query = tests_1.FoodModel.find({});
3108
+ result = (0, api_1.addPopulateToQuery)(query, undefined);
3109
+ (0, bun_test_1.expect)(result).toBe(query);
3110
+ return [2 /*return*/];
3111
+ }
3112
+ });
3113
+ }); });
3114
+ (0, bun_test_1.it)("returns query unchanged with empty populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
3115
+ var query, result;
3116
+ return __generator(this, function (_a) {
3117
+ switch (_a.label) {
3118
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3119
+ case 1:
3120
+ _a.sent();
3121
+ query = tests_1.FoodModel.find({});
3122
+ result = (0, api_1.addPopulateToQuery)(query, []);
3123
+ (0, bun_test_1.expect)(result).toBe(query);
3124
+ return [2 /*return*/];
3125
+ }
3126
+ });
3127
+ }); });
3128
+ (0, bun_test_1.it)("applies multiple populate paths", function () { return __awaiter(void 0, void 0, void 0, function () {
3129
+ var query, result;
3130
+ return __generator(this, function (_a) {
3131
+ switch (_a.label) {
3132
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3133
+ case 1:
3134
+ _a.sent();
3135
+ query = tests_1.FoodModel.find({});
3136
+ result = (0, api_1.addPopulateToQuery)(query, [
3137
+ { fields: ["email"], path: "ownerId" },
3138
+ { fields: ["name"], path: "eatenBy" },
3139
+ ]);
3140
+ // The result should be a query with populate applied
3141
+ (0, bun_test_1.expect)(result).toBeDefined();
3142
+ return [2 /*return*/];
3143
+ }
3144
+ });
3145
+ }); });
3146
+ });
3147
+ (0, bun_test_1.describe)("soft delete with isDeleted plugin", function () {
3148
+ var admin;
3149
+ var agent;
3150
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
3151
+ var _a;
3152
+ return __generator(this, function (_b) {
3153
+ switch (_b.label) {
3154
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3155
+ case 1:
3156
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
3157
+ app = (0, tests_1.getBaseServer)();
3158
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
3159
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
3160
+ return [2 /*return*/];
3161
+ }
3162
+ });
3163
+ }); });
3164
+ (0, bun_test_1.it)("soft deletes user with deleted field", function () { return __awaiter(void 0, void 0, void 0, function () {
3165
+ var res, deletedUser;
3166
+ return __generator(this, function (_a) {
3167
+ switch (_a.label) {
3168
+ case 0:
3169
+ // UserModel has the isDisabledPlugin which adds a 'disabled' field,
3170
+ // but we need to test the 'deleted' field check.
3171
+ // Let's use a model that has the deleted field.
3172
+ app.use("/users", (0, api_1.modelRouter)(tests_1.UserModel, {
3173
+ allowAnonymous: true,
3174
+ permissions: {
3175
+ create: [permissions_1.Permissions.IsAny],
3176
+ delete: [permissions_1.Permissions.IsAny],
3177
+ list: [permissions_1.Permissions.IsAny],
3178
+ read: [permissions_1.Permissions.IsAny],
3179
+ update: [permissions_1.Permissions.IsAny],
3180
+ },
3181
+ }));
3182
+ server = (0, supertest_1.default)(app);
3183
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
3184
+ case 1:
3185
+ agent = _a.sent();
3186
+ return [4 /*yield*/, agent.delete("/users/".concat(admin._id)).expect(204)];
3187
+ case 2:
3188
+ res = _a.sent();
3189
+ (0, bun_test_1.expect)(res.body).toEqual({});
3190
+ return [4 /*yield*/, tests_1.UserModel.findById(admin._id)];
3191
+ case 3:
3192
+ deletedUser = _a.sent();
3193
+ (0, bun_test_1.expect)(deletedUser).toBeNull();
3194
+ return [2 /*return*/];
3195
+ }
3196
+ });
3197
+ }); });
3198
+ });
3199
+ (0, bun_test_1.describe)("populate in create", function () {
3200
+ var admin;
3201
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
3202
+ var _a;
3203
+ return __generator(this, function (_b) {
3204
+ switch (_b.label) {
3205
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3206
+ case 1:
3207
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
3208
+ return [4 /*yield*/, tests_1.FoodModel.create({
3209
+ calories: 1,
3210
+ created: new Date("2021-12-03T00:00:20.000Z"),
3211
+ hidden: false,
3212
+ name: "Spinach",
3213
+ ownerId: admin._id,
3214
+ })];
3215
+ case 2:
3216
+ _b.sent();
3217
+ app = (0, tests_1.getBaseServer)();
3218
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
3219
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
3220
+ return [2 /*return*/];
3221
+ }
3222
+ });
3223
+ }); });
3224
+ (0, bun_test_1.it)("handles populate with valid path in create", function () { return __awaiter(void 0, void 0, void 0, function () {
3225
+ var res;
3226
+ return __generator(this, function (_a) {
3227
+ switch (_a.label) {
3228
+ case 0:
3229
+ // Test that valid populate works in create flow
3230
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3231
+ allowAnonymous: true,
3232
+ permissions: {
3233
+ create: [permissions_1.Permissions.IsAny],
3234
+ delete: [permissions_1.Permissions.IsAny],
3235
+ list: [permissions_1.Permissions.IsAny],
3236
+ read: [permissions_1.Permissions.IsAny],
3237
+ update: [permissions_1.Permissions.IsAny],
3238
+ },
3239
+ populatePaths: [{ fields: ["email"], path: "ownerId" }],
3240
+ }));
3241
+ server = (0, supertest_1.default)(app);
3242
+ return [4 /*yield*/, server
3243
+ .post("/food")
3244
+ .send({ calories: 15, name: "Broccoli", ownerId: admin._id })
3245
+ .expect(201)];
3246
+ case 1:
3247
+ res = _a.sent();
3248
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Broccoli");
3249
+ // Verify populate worked - ownerId should be an object with email
3250
+ (0, bun_test_1.expect)(res.body.data.ownerId.email).toBe(admin.email);
3251
+ return [2 /*return*/];
3252
+ }
3253
+ });
3254
+ }); });
3255
+ });
3256
+ (0, bun_test_1.describe)("save error handling", function () {
3257
+ var admin;
3258
+ var spinach;
3259
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
3260
+ var _a;
3261
+ return __generator(this, function (_b) {
3262
+ switch (_b.label) {
3263
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3264
+ case 1:
3265
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
3266
+ return [4 /*yield*/, tests_1.FoodModel.create({
3267
+ calories: 1,
3268
+ created: new Date("2021-12-03T00:00:20.000Z"),
3269
+ hidden: false,
3270
+ name: "Spinach",
3271
+ ownerId: admin._id,
3272
+ source: {
3273
+ name: "Brand",
3274
+ },
3275
+ })];
3276
+ case 2:
3277
+ spinach = _b.sent();
3278
+ app = (0, tests_1.getBaseServer)();
3279
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
3280
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
3281
+ return [2 /*return*/];
3282
+ }
3283
+ });
3284
+ }); });
3285
+ (0, bun_test_1.it)("handles patch save error with validation failure", function () { return __awaiter(void 0, void 0, void 0, function () {
3286
+ var res;
3287
+ return __generator(this, function (_a) {
3288
+ switch (_a.label) {
3289
+ case 0:
3290
+ // The FoodModel has strict: "throw" which will cause validation errors for unknown fields
3291
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3292
+ allowAnonymous: true,
3293
+ permissions: {
3294
+ create: [permissions_1.Permissions.IsAny],
3295
+ delete: [permissions_1.Permissions.IsAny],
3296
+ list: [permissions_1.Permissions.IsAny],
3297
+ read: [permissions_1.Permissions.IsAny],
3298
+ update: [permissions_1.Permissions.IsAny],
3299
+ },
3300
+ }));
3301
+ server = (0, supertest_1.default)(app);
3302
+ return [4 /*yield*/, server
3303
+ .patch("/food/".concat(spinach._id))
3304
+ .send({ invalidField: "value" })
3305
+ .expect(400)];
3306
+ case 1:
3307
+ res = _a.sent();
3308
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook save error");
3309
+ return [2 /*return*/];
3310
+ }
3311
+ });
3312
+ }); });
3313
+ });
3314
+ (0, bun_test_1.describe)("body undefined after transform without preCreate", function () {
3315
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
3316
+ return __generator(this, function (_a) {
3317
+ switch (_a.label) {
3318
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3319
+ case 1:
3320
+ _a.sent();
3321
+ app = (0, tests_1.getBaseServer)();
3322
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
3323
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
3324
+ return [2 /*return*/];
3325
+ }
3326
+ });
3327
+ }); });
3328
+ (0, bun_test_1.it)("handles undefined body after transform when no preCreate", function () { return __awaiter(void 0, void 0, void 0, function () {
3329
+ var res;
3330
+ return __generator(this, function (_a) {
3331
+ switch (_a.label) {
3332
+ case 0:
3333
+ // Create a transformer that returns undefined
3334
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3335
+ allowAnonymous: true,
3336
+ permissions: {
3337
+ create: [permissions_1.Permissions.IsAny],
3338
+ delete: [permissions_1.Permissions.IsAny],
3339
+ list: [permissions_1.Permissions.IsAny],
3340
+ read: [permissions_1.Permissions.IsAny],
3341
+ update: [permissions_1.Permissions.IsAny],
3342
+ },
3343
+ transformer: {
3344
+ transform: function () { return undefined; },
3345
+ },
3346
+ }));
3347
+ server = (0, supertest_1.default)(app);
3348
+ return [4 /*yield*/, server.post("/food").send({ calories: 15, name: "Broccoli" }).expect(400)];
3349
+ case 1:
3350
+ res = _a.sent();
3351
+ (0, bun_test_1.expect)(res.body.title).toBe("Invalid request body");
3352
+ (0, bun_test_1.expect)(res.body.detail).toBe("Body is undefined");
3353
+ return [2 /*return*/];
3354
+ }
3355
+ });
3356
+ }); });
3357
+ });
3358
+ (0, bun_test_1.describe)("soft delete with deleted field", function () {
3359
+ var _admin;
1886
3360
  var agent;
1887
3361
  (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) {
3362
+ var _a;
3363
+ return __generator(this, function (_b) {
3364
+ switch (_b.label) {
1892
3365
  case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1893
3366
  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());
3367
+ _a = __read.apply(void 0, [_b.sent(), 1]), _admin = _a[0];
1913
3368
  app = (0, tests_1.getBaseServer)();
1914
3369
  (0, auth_1.setupAuth)(app, tests_1.UserModel);
1915
3370
  (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1916
- app.use("/users", (0, api_1.modelRouter)(tests_1.UserModel, {
3371
+ return [2 /*return*/];
3372
+ }
3373
+ });
3374
+ }); });
3375
+ (0, bun_test_1.it)("soft deletes document with deleted field using isDeletedPlugin", function () { return __awaiter(void 0, void 0, void 0, function () {
3376
+ var mongoose, softDeleteSchema, SoftDeleteModel, testDoc, softDeleted;
3377
+ return __generator(this, function (_a) {
3378
+ switch (_a.label) {
3379
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("mongoose")); })];
3380
+ case 1:
3381
+ mongoose = _a.sent();
3382
+ softDeleteSchema = new mongoose.Schema({
3383
+ deleted: { default: false, type: Boolean },
3384
+ name: String,
3385
+ });
3386
+ try {
3387
+ SoftDeleteModel = mongoose.model("SoftDeleteTest");
3388
+ }
3389
+ catch (_b) {
3390
+ SoftDeleteModel = mongoose.model("SoftDeleteTest", softDeleteSchema);
3391
+ }
3392
+ // Clean up any existing documents
3393
+ return [4 /*yield*/, SoftDeleteModel.deleteMany({})];
3394
+ case 2:
3395
+ // Clean up any existing documents
3396
+ _a.sent();
3397
+ return [4 /*yield*/, SoftDeleteModel.create({ name: "TestItem" })];
3398
+ case 3:
3399
+ testDoc = _a.sent();
3400
+ app.use("/softdelete", (0, api_1.modelRouter)(SoftDeleteModel, {
1917
3401
  allowAnonymous: true,
1918
- discriminatorKey: "__t",
1919
3402
  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],
3403
+ create: [permissions_1.Permissions.IsAny],
3404
+ delete: [permissions_1.Permissions.IsAny],
3405
+ list: [permissions_1.Permissions.IsAny],
3406
+ read: [permissions_1.Permissions.IsAny],
3407
+ update: [permissions_1.Permissions.IsAny],
1925
3408
  },
1926
3409
  }));
1927
3410
  server = (0, supertest_1.default)(app);
1928
3411
  return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
3412
+ case 4:
3413
+ agent = _a.sent();
3414
+ // Delete should soft delete (set deleted: true) instead of hard delete
3415
+ return [4 /*yield*/, agent.delete("/softdelete/".concat(testDoc._id)).expect(204)];
1929
3416
  case 5:
1930
- agent = _c.sent();
3417
+ // Delete should soft delete (set deleted: true) instead of hard delete
3418
+ _a.sent();
3419
+ return [4 /*yield*/, SoftDeleteModel.findById(testDoc._id)];
3420
+ case 6:
3421
+ softDeleted = _a.sent();
3422
+ (0, bun_test_1.expect)(softDeleted).not.toBeNull();
3423
+ (0, bun_test_1.expect)(softDeleted === null || softDeleted === void 0 ? void 0 : softDeleted.deleted).toBe(true);
3424
+ // Clean up
3425
+ return [4 /*yield*/, SoftDeleteModel.deleteMany({})];
3426
+ case 7:
3427
+ // Clean up
3428
+ _a.sent();
3429
+ return [2 /*return*/];
3430
+ }
3431
+ });
3432
+ }); });
3433
+ });
3434
+ (0, bun_test_1.describe)("array operation with undefined preUpdate return", function () {
3435
+ var admin;
3436
+ var apple;
3437
+ var agent;
3438
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
3439
+ var _a;
3440
+ return __generator(this, function (_b) {
3441
+ switch (_b.label) {
3442
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
3443
+ case 1:
3444
+ _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
3445
+ return [4 /*yield*/, tests_1.FoodModel.create({
3446
+ calories: 100,
3447
+ categories: [
3448
+ { name: "Fruit", show: true },
3449
+ { name: "Popular", show: false },
3450
+ ],
3451
+ created: new Date("2021-12-03T00:00:30.000Z"),
3452
+ hidden: false,
3453
+ name: "Apple",
3454
+ ownerId: admin._id,
3455
+ tags: ["healthy", "cheap"],
3456
+ })];
3457
+ case 2:
3458
+ apple = _b.sent();
3459
+ app = (0, tests_1.getBaseServer)();
3460
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
3461
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
1931
3462
  return [2 /*return*/];
1932
3463
  }
1933
3464
  });
1934
3465
  }); });
1935
- (0, bun_test_1.it)("gets all users", function () { return __awaiter(void 0, void 0, void 0, function () {
1936
- var res, data;
3466
+ (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 () {
3467
+ var res;
1937
3468
  return __generator(this, function (_a) {
1938
3469
  switch (_a.label) {
1939
- case 0: return [4 /*yield*/, agent.get("/users").expect(200)];
3470
+ case 0:
3471
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3472
+ allowAnonymous: true,
3473
+ permissions: {
3474
+ create: [permissions_1.Permissions.IsAdmin],
3475
+ delete: [permissions_1.Permissions.IsAdmin],
3476
+ list: [permissions_1.Permissions.IsAdmin],
3477
+ read: [permissions_1.Permissions.IsAdmin],
3478
+ update: [permissions_1.Permissions.IsAdmin],
3479
+ },
3480
+ preUpdate: function () { return undefined; },
3481
+ }));
3482
+ server = (0, supertest_1.default)(app);
3483
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
1940
3484
  case 1:
3485
+ agent = _a.sent();
3486
+ return [4 /*yield*/, agent.post("/food/".concat(apple._id, "/tags")).send({ tags: "organic" }).expect(403)];
3487
+ case 2:
1941
3488
  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");
3489
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
3490
+ (0, bun_test_1.expect)(res.body.detail).toBe("A body must be returned from preUpdate");
1964
3491
  return [2 /*return*/];
1965
3492
  }
1966
3493
  });
1967
3494
  }); });
1968
- (0, bun_test_1.it)("gets a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
3495
+ (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 () {
1969
3496
  var res;
1970
3497
  return __generator(this, function (_a) {
1971
3498
  switch (_a.label) {
1972
- case 0: return [4 /*yield*/, agent.get("/users/".concat(superUser._id)).expect(200)];
3499
+ case 0:
3500
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3501
+ allowAnonymous: true,
3502
+ permissions: {
3503
+ create: [permissions_1.Permissions.IsAdmin],
3504
+ delete: [permissions_1.Permissions.IsAdmin],
3505
+ list: [permissions_1.Permissions.IsAdmin],
3506
+ read: [permissions_1.Permissions.IsAdmin],
3507
+ update: [permissions_1.Permissions.IsAdmin],
3508
+ },
3509
+ preUpdate: function () { return null; },
3510
+ }));
3511
+ server = (0, supertest_1.default)(app);
3512
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
1973
3513
  case 1:
3514
+ agent = _a.sent();
3515
+ return [4 /*yield*/, agent
3516
+ .patch("/food/".concat(apple._id, "/tags/healthy"))
3517
+ .send({ tags: "unhealthy" })
3518
+ .expect(403)];
3519
+ case 2:
1974
3520
  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");
3521
+ (0, bun_test_1.expect)(res.body.title).toBe("Update not allowed");
1978
3522
  return [2 /*return*/];
1979
3523
  }
1980
3524
  });
1981
3525
  }); });
1982
- (0, bun_test_1.it)("updates a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
1983
- var res, user;
3526
+ (0, bun_test_1.it)("array operation preUpdate error for array DELETE is handled", function () { return __awaiter(void 0, void 0, void 0, function () {
3527
+ var res;
1984
3528
  return __generator(this, function (_a) {
1985
3529
  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)];
3530
+ case 0:
3531
+ app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
3532
+ allowAnonymous: true,
3533
+ permissions: {
3534
+ create: [permissions_1.Permissions.IsAdmin],
3535
+ delete: [permissions_1.Permissions.IsAdmin],
3536
+ list: [permissions_1.Permissions.IsAdmin],
3537
+ read: [permissions_1.Permissions.IsAdmin],
3538
+ update: [permissions_1.Permissions.IsAdmin],
3539
+ },
3540
+ preUpdate: function () {
3541
+ throw new Error("preUpdate error during delete");
3542
+ },
3543
+ }));
3544
+ server = (0, supertest_1.default)(app);
3545
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
1989
3546
  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)];
3547
+ agent = _a.sent();
3548
+ return [4 /*yield*/, agent.delete("/food/".concat(apple._id, "/tags/healthy")).expect(400)];
1996
3549
  case 2:
1997
3550
  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");
3551
+ (0, bun_test_1.expect)(res.body.title).toContain("preUpdate hook error");
2005
3552
  return [2 /*return*/];
2006
3553
  }
2007
3554
  });
2008
3555
  }); });
2009
- (0, bun_test_1.it)("updates a base user", function () { return __awaiter(void 0, void 0, void 0, function () {
2010
- var res, user;
3556
+ });
3557
+ });
3558
+ (0, bun_test_1.describe)("errors module", function () {
3559
+ (0, bun_test_1.describe)("APIError", function () {
3560
+ (0, bun_test_1.it)("sets default status to 500 when not provided", function () {
3561
+ var error = new errors_1.APIError({ title: "Test error" });
3562
+ (0, bun_test_1.expect)(error.status).toBe(500);
3563
+ });
3564
+ (0, bun_test_1.it)("sets status to 500 for invalid status codes below 400", function () {
3565
+ var error = new errors_1.APIError({ status: 200, title: "Test error" });
3566
+ (0, bun_test_1.expect)(error.status).toBe(500);
3567
+ });
3568
+ (0, bun_test_1.it)("sets status to 500 for invalid status codes above 599", function () {
3569
+ var error = new errors_1.APIError({ status: 600, title: "Test error" });
3570
+ (0, bun_test_1.expect)(error.status).toBe(500);
3571
+ });
3572
+ (0, bun_test_1.it)("includes error stack in message when error is provided", function () {
3573
+ var originalError = new Error("Original error");
3574
+ var apiError = new errors_1.APIError({
3575
+ error: originalError,
3576
+ title: "Wrapped error",
3577
+ });
3578
+ (0, bun_test_1.expect)(apiError.message).toContain("Wrapped error");
3579
+ (0, bun_test_1.expect)(originalError.stack).toBeDefined();
3580
+ (0, bun_test_1.expect)(apiError.message).toContain(originalError.stack);
3581
+ });
3582
+ (0, bun_test_1.it)("includes detail in message when provided", function () {
3583
+ var error = new errors_1.APIError({
3584
+ detail: "More details here",
3585
+ title: "Test error",
3586
+ });
3587
+ (0, bun_test_1.expect)(error.message).toContain("Test error");
3588
+ (0, bun_test_1.expect)(error.message).toContain("More details here");
3589
+ });
3590
+ (0, bun_test_1.it)("sets fields in meta when provided", function () {
3591
+ var _a;
3592
+ var error = new errors_1.APIError({
3593
+ fields: { email: "Invalid email format" },
3594
+ title: "Validation error",
3595
+ });
3596
+ (0, bun_test_1.expect)((_a = error.meta) === null || _a === void 0 ? void 0 : _a.fields).toEqual({ email: "Invalid email format" });
3597
+ });
3598
+ });
3599
+ (0, bun_test_1.describe)("errorsPlugin", function () {
3600
+ (0, bun_test_1.it)("adds apiErrors field to schema", function () { return __awaiter(void 0, void 0, void 0, function () {
3601
+ var mongoose, errorsPlugin, testSchema;
2011
3602
  return __generator(this, function (_a) {
2012
3603
  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)];
3604
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("mongoose")); })];
2017
3605
  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)];
3606
+ mongoose = _a.sent();
3607
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./errors")); })];
2022
3608
  case 2:
2023
- user = _a.sent();
2024
- (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.superTitle).toBeUndefined();
3609
+ errorsPlugin = (_a.sent()).errorsPlugin;
3610
+ testSchema = new mongoose.Schema({ name: String });
3611
+ errorsPlugin(testSchema);
3612
+ (0, bun_test_1.expect)(testSchema.path("apiErrors")).toBeDefined();
2025
3613
  return [2 /*return*/];
2026
3614
  }
2027
3615
  });
2028
3616
  }); });
2029
- (0, bun_test_1.it)("cannot update discriminator key", function () { return __awaiter(void 0, void 0, void 0, function () {
3617
+ });
3618
+ (0, bun_test_1.describe)("isAPIError", function () {
3619
+ (0, bun_test_1.it)("returns true for APIError instances", function () {
3620
+ var isAPIError = require("./errors").isAPIError;
3621
+ var error = new errors_1.APIError({ title: "Test" });
3622
+ (0, bun_test_1.expect)(isAPIError(error)).toBe(true);
3623
+ });
3624
+ (0, bun_test_1.it)("returns false for regular Error instances", function () {
3625
+ var isAPIError = require("./errors").isAPIError;
3626
+ var error = new Error("Test");
3627
+ (0, bun_test_1.expect)(isAPIError(error)).toBe(false);
3628
+ });
3629
+ });
3630
+ (0, bun_test_1.describe)("getDisableExternalErrorTracking", function () {
3631
+ (0, bun_test_1.it)("returns undefined for non-objects", function () {
3632
+ var getDisableExternalErrorTracking = require("./errors").getDisableExternalErrorTracking;
3633
+ (0, bun_test_1.expect)(getDisableExternalErrorTracking(null)).toBeUndefined();
3634
+ (0, bun_test_1.expect)(getDisableExternalErrorTracking("string")).toBeUndefined();
3635
+ });
3636
+ (0, bun_test_1.it)("returns value from APIError", function () {
3637
+ var getDisableExternalErrorTracking = require("./errors").getDisableExternalErrorTracking;
3638
+ var error = new errors_1.APIError({ disableExternalErrorTracking: true, title: "Test" });
3639
+ (0, bun_test_1.expect)(getDisableExternalErrorTracking(error)).toBe(true);
3640
+ });
3641
+ (0, bun_test_1.it)("returns value from plain object with property", function () {
3642
+ var getDisableExternalErrorTracking = require("./errors").getDisableExternalErrorTracking;
3643
+ var obj = { disableExternalErrorTracking: true };
3644
+ (0, bun_test_1.expect)(getDisableExternalErrorTracking(obj)).toBe(true);
3645
+ });
3646
+ });
3647
+ (0, bun_test_1.describe)("getAPIErrorBody", function () {
3648
+ (0, bun_test_1.it)("includes all non-undefined fields", function () {
3649
+ var getAPIErrorBody = require("./errors").getAPIErrorBody;
3650
+ var error = new errors_1.APIError({
3651
+ code: "TEST_CODE",
3652
+ detail: "Test detail",
3653
+ id: "error-123",
3654
+ links: { about: "http://example.com" },
3655
+ meta: { extra: "data" },
3656
+ source: { parameter: "id" },
3657
+ status: 400,
3658
+ title: "Test error",
3659
+ });
3660
+ var body = getAPIErrorBody(error);
3661
+ (0, bun_test_1.expect)(body.title).toBe("Test error");
3662
+ (0, bun_test_1.expect)(body.status).toBe(400);
3663
+ (0, bun_test_1.expect)(body.code).toBe("TEST_CODE");
3664
+ (0, bun_test_1.expect)(body.detail).toBe("Test detail");
3665
+ (0, bun_test_1.expect)(body.id).toBe("error-123");
3666
+ (0, bun_test_1.expect)(body.links).toEqual({ about: "http://example.com" });
3667
+ (0, bun_test_1.expect)(body.source).toEqual({ parameter: "id" });
3668
+ (0, bun_test_1.expect)(body.meta).toEqual({ extra: "data" });
3669
+ });
3670
+ });
3671
+ (0, bun_test_1.describe)("apiUnauthorizedMiddleware", function () {
3672
+ (0, bun_test_1.it)("returns 401 for Unauthorized errors", function () {
3673
+ var apiUnauthorizedMiddleware = require("./errors").apiUnauthorizedMiddleware;
3674
+ var err = new Error("Unauthorized");
3675
+ var res = {
3676
+ json: function (data) {
3677
+ this.body = data;
3678
+ return this;
3679
+ },
3680
+ send: function () {
3681
+ return this;
3682
+ },
3683
+ status: function (code) {
3684
+ this.statusCode = code;
3685
+ return this;
3686
+ },
3687
+ };
3688
+ var next = function () { };
3689
+ apiUnauthorizedMiddleware(err, {}, res, next);
3690
+ (0, bun_test_1.expect)(res.statusCode).toBe(401);
3691
+ (0, bun_test_1.expect)(res.body.title).toBe("Unauthorized");
3692
+ });
3693
+ (0, bun_test_1.it)("calls next for non-Unauthorized errors", function () {
3694
+ var apiUnauthorizedMiddleware = require("./errors").apiUnauthorizedMiddleware;
3695
+ var err = new Error("Some other error");
3696
+ var nextCalled = false;
3697
+ var next = function () {
3698
+ nextCalled = true;
3699
+ };
3700
+ apiUnauthorizedMiddleware(err, {}, {}, next);
3701
+ (0, bun_test_1.expect)(nextCalled).toBe(true);
3702
+ });
3703
+ });
3704
+ });
3705
+ (0, bun_test_1.describe)("permissions module", function () {
3706
+ (0, bun_test_1.describe)("OwnerQueryFilter", function () {
3707
+ (0, bun_test_1.it)("returns ownerId filter when user is provided", function () {
3708
+ var OwnerQueryFilter = require("./permissions").OwnerQueryFilter;
3709
+ var user = { id: "user-123" };
3710
+ var filter = OwnerQueryFilter(user);
3711
+ (0, bun_test_1.expect)(filter).toEqual({ ownerId: "user-123" });
3712
+ });
3713
+ (0, bun_test_1.it)("returns null when user is undefined", function () {
3714
+ var OwnerQueryFilter = require("./permissions").OwnerQueryFilter;
3715
+ var filter = OwnerQueryFilter(undefined);
3716
+ (0, bun_test_1.expect)(filter).toBeNull();
3717
+ });
3718
+ });
3719
+ (0, bun_test_1.describe)("Permissions.IsAuthenticatedOrReadOnly", function () {
3720
+ (0, bun_test_1.it)("returns true for authenticated non-anonymous users", function () {
3721
+ var Permissions = require("./permissions").Permissions;
3722
+ var user = { id: "user-123", isAnonymous: false };
3723
+ (0, bun_test_1.expect)(Permissions.IsAuthenticatedOrReadOnly("create", user)).toBe(true);
3724
+ });
3725
+ (0, bun_test_1.it)("returns true for read methods when user is anonymous", function () {
3726
+ var Permissions = require("./permissions").Permissions;
3727
+ var user = { id: "user-123", isAnonymous: true };
3728
+ (0, bun_test_1.expect)(Permissions.IsAuthenticatedOrReadOnly("list", user)).toBe(true);
3729
+ (0, bun_test_1.expect)(Permissions.IsAuthenticatedOrReadOnly("read", user)).toBe(true);
3730
+ });
3731
+ (0, bun_test_1.it)("returns false for write methods when user is anonymous", function () {
3732
+ var Permissions = require("./permissions").Permissions;
3733
+ var user = { id: "user-123", isAnonymous: true };
3734
+ (0, bun_test_1.expect)(Permissions.IsAuthenticatedOrReadOnly("create", user)).toBe(false);
3735
+ (0, bun_test_1.expect)(Permissions.IsAuthenticatedOrReadOnly("update", user)).toBe(false);
3736
+ (0, bun_test_1.expect)(Permissions.IsAuthenticatedOrReadOnly("delete", user)).toBe(false);
3737
+ });
3738
+ });
3739
+ (0, bun_test_1.describe)("Permissions.IsOwnerOrReadOnly", function () {
3740
+ (0, bun_test_1.it)("returns true when no object is provided", function () {
3741
+ var Permissions = require("./permissions").Permissions;
3742
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("update", { id: "user-123" }, undefined)).toBe(true);
3743
+ });
3744
+ (0, bun_test_1.it)("returns true for admin users", function () {
3745
+ var Permissions = require("./permissions").Permissions;
3746
+ var user = { admin: true, id: "admin-123" };
3747
+ var obj = { ownerId: "other-user" };
3748
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("update", user, obj)).toBe(true);
3749
+ });
3750
+ (0, bun_test_1.it)("returns true when user is owner", function () {
3751
+ var Permissions = require("./permissions").Permissions;
3752
+ var user = { id: "user-123" };
3753
+ var obj = { ownerId: "user-123" };
3754
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("update", user, obj)).toBe(true);
3755
+ });
3756
+ (0, bun_test_1.it)("returns true for read methods when not owner", function () {
3757
+ var Permissions = require("./permissions").Permissions;
3758
+ var user = { id: "user-123" };
3759
+ var obj = { ownerId: "other-user" };
3760
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("list", user, obj)).toBe(true);
3761
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("read", user, obj)).toBe(true);
3762
+ });
3763
+ (0, bun_test_1.it)("returns false for write methods when not owner", function () {
3764
+ var Permissions = require("./permissions").Permissions;
3765
+ var user = { id: "user-123" };
3766
+ var obj = { ownerId: "other-user" };
3767
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("update", user, obj)).toBe(false);
3768
+ (0, bun_test_1.expect)(Permissions.IsOwnerOrReadOnly("delete", user, obj)).toBe(false);
3769
+ });
3770
+ });
3771
+ });
3772
+ (0, bun_test_1.describe)("utils module", function () {
3773
+ (0, bun_test_1.describe)("isValidObjectId", function () {
3774
+ (0, bun_test_1.it)("returns true for valid ObjectId strings", function () {
3775
+ var isValidObjectId = require("./utils").isValidObjectId;
3776
+ (0, bun_test_1.expect)(isValidObjectId("507f1f77bcf86cd799439011")).toBe(true);
3777
+ });
3778
+ (0, bun_test_1.it)("returns false for invalid ObjectId strings", function () {
3779
+ var isValidObjectId = require("./utils").isValidObjectId;
3780
+ (0, bun_test_1.expect)(isValidObjectId("invalid-id")).toBe(false);
3781
+ (0, bun_test_1.expect)(isValidObjectId("12345")).toBe(false);
3782
+ (0, bun_test_1.expect)(isValidObjectId("")).toBe(false);
3783
+ });
3784
+ (0, bun_test_1.it)("returns false for 12-character strings that are not valid ObjectIds", function () {
3785
+ var isValidObjectId = require("./utils").isValidObjectId;
3786
+ // mongoose's native isValid returns true for any 12-char string
3787
+ // but our implementation should return false since toString won't match
3788
+ (0, bun_test_1.expect)(isValidObjectId("123456789012")).toBe(false);
3789
+ });
3790
+ });
3791
+ (0, bun_test_1.describe)("timeout", function () {
3792
+ (0, bun_test_1.it)("resolves after specified time", function () { return __awaiter(void 0, void 0, void 0, function () {
3793
+ var timeout, start, elapsed;
2030
3794
  return __generator(this, function (_a) {
2031
3795
  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)];
3796
+ case 0:
3797
+ timeout = require("./utils").timeout;
3798
+ start = Date.now();
3799
+ return [4 /*yield*/, timeout(50)];
2036
3800
  case 1:
2037
3801
  _a.sent();
2038
- return [4 /*yield*/, agent
2039
- .patch("/users/".concat(staffUser._id))
2040
- .send({ __t: "SuperUser", superTitle: "Batman" })
2041
- .expect(404)];
3802
+ elapsed = Date.now() - start;
3803
+ (0, bun_test_1.expect)(elapsed).toBeGreaterThanOrEqual(40);
3804
+ return [2 /*return*/];
3805
+ }
3806
+ });
3807
+ }); });
3808
+ });
3809
+ // Note: Comprehensive checkModelsStrict tests are in utils.test.ts with mocked mongoose
3810
+ });
3811
+ (0, bun_test_1.describe)("populate module", function () {
3812
+ (0, bun_test_1.describe)("unpopulate", function () {
3813
+ (0, bun_test_1.it)("throws error when path is empty", function () { return __awaiter(void 0, void 0, void 0, function () {
3814
+ var unpopulate, doc;
3815
+ return __generator(this, function (_a) {
3816
+ switch (_a.label) {
3817
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./populate")); })];
3818
+ case 1:
3819
+ unpopulate = (_a.sent()).unpopulate;
3820
+ doc = { name: "test" };
3821
+ (0, bun_test_1.expect)(function () { return unpopulate(doc, ""); }).toThrow("path is required");
3822
+ return [2 /*return*/];
3823
+ }
3824
+ });
3825
+ }); });
3826
+ (0, bun_test_1.it)("unpopulates single populated field", function () { return __awaiter(void 0, void 0, void 0, function () {
3827
+ var unpopulate, doc, result;
3828
+ return __generator(this, function (_a) {
3829
+ switch (_a.label) {
3830
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./populate")); })];
3831
+ case 1:
3832
+ unpopulate = (_a.sent()).unpopulate;
3833
+ doc = {
3834
+ name: "test",
3835
+ ownerId: { _id: "owner-123", email: "owner@test.com" },
3836
+ };
3837
+ result = unpopulate(doc, "ownerId");
3838
+ (0, bun_test_1.expect)(result.ownerId).toBe("owner-123");
3839
+ return [2 /*return*/];
3840
+ }
3841
+ });
3842
+ }); });
3843
+ (0, bun_test_1.it)("unpopulates array of populated fields", function () { return __awaiter(void 0, void 0, void 0, function () {
3844
+ var unpopulate, doc, result;
3845
+ return __generator(this, function (_a) {
3846
+ switch (_a.label) {
3847
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./populate")); })];
3848
+ case 1:
3849
+ unpopulate = (_a.sent()).unpopulate;
3850
+ doc = {
3851
+ items: [{ _id: "item-1", name: "Item 1" }, { _id: "item-2", name: "Item 2" }, "item-3"],
3852
+ name: "test",
3853
+ };
3854
+ result = unpopulate(doc, "items");
3855
+ (0, bun_test_1.expect)(result.items).toEqual(["item-1", "item-2", "item-3"]);
3856
+ return [2 /*return*/];
3857
+ }
3858
+ });
3859
+ }); });
3860
+ (0, bun_test_1.it)("handles nested paths", function () { return __awaiter(void 0, void 0, void 0, function () {
3861
+ var unpopulate, doc, result;
3862
+ return __generator(this, function (_a) {
3863
+ switch (_a.label) {
3864
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./populate")); })];
3865
+ case 1:
3866
+ unpopulate = (_a.sent()).unpopulate;
3867
+ doc = {
3868
+ name: "test",
3869
+ nested: {
3870
+ items: [
3871
+ { _id: "item-1", name: "Item 1" },
3872
+ { _id: "item-2", name: "Item 2" },
3873
+ ],
3874
+ },
3875
+ };
3876
+ result = unpopulate(doc, "nested.items");
3877
+ (0, bun_test_1.expect)(result.nested.items).toEqual(["item-1", "item-2"]);
3878
+ return [2 /*return*/];
3879
+ }
3880
+ });
3881
+ }); });
3882
+ (0, bun_test_1.it)("returns original doc when path does not exist", function () { return __awaiter(void 0, void 0, void 0, function () {
3883
+ var unpopulate, doc, result;
3884
+ return __generator(this, function (_a) {
3885
+ switch (_a.label) {
3886
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./populate")); })];
3887
+ case 1:
3888
+ unpopulate = (_a.sent()).unpopulate;
3889
+ doc = { name: "test" };
3890
+ result = unpopulate(doc, "nonexistent");
3891
+ (0, bun_test_1.expect)(result).toEqual(doc);
3892
+ return [2 /*return*/];
3893
+ }
3894
+ });
3895
+ }); });
3896
+ (0, bun_test_1.it)("handles nested array paths", function () { return __awaiter(void 0, void 0, void 0, function () {
3897
+ var unpopulate, doc, result;
3898
+ return __generator(this, function (_a) {
3899
+ switch (_a.label) {
3900
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./populate")); })];
3901
+ case 1:
3902
+ unpopulate = (_a.sent()).unpopulate;
3903
+ doc = {
3904
+ containers: [
3905
+ { items: [{ _id: "item-1" }, { _id: "item-2" }] },
3906
+ { items: [{ _id: "item-3" }, { _id: "item-4" }] },
3907
+ ],
3908
+ name: "test",
3909
+ };
3910
+ result = unpopulate(doc, "containers.items");
3911
+ (0, bun_test_1.expect)(result.containers[0].items).toEqual(["item-1", "item-2"]);
3912
+ (0, bun_test_1.expect)(result.containers[1].items).toEqual(["item-3", "item-4"]);
3913
+ return [2 /*return*/];
3914
+ }
3915
+ });
3916
+ }); });
3917
+ });
3918
+ });
3919
+ (0, bun_test_1.describe)("auth module edge cases", function () {
3920
+ (0, bun_test_1.describe)("generateTokens", function () {
3921
+ (0, bun_test_1.it)("returns null tokens when user is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
3922
+ var generateTokens, result;
3923
+ return __generator(this, function (_a) {
3924
+ switch (_a.label) {
3925
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./auth")); })];
3926
+ case 1:
3927
+ generateTokens = (_a.sent()).generateTokens;
3928
+ return [4 /*yield*/, generateTokens(null)];
2042
3929
  case 2:
2043
- _a.sent();
3930
+ result = _a.sent();
3931
+ (0, bun_test_1.expect)(result.token).toBeNull();
3932
+ (0, bun_test_1.expect)(result.refreshToken).toBeNull();
2044
3933
  return [2 /*return*/];
2045
3934
  }
2046
3935
  });
2047
3936
  }); });
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;
3937
+ (0, bun_test_1.it)("returns null tokens when user has no _id", function () { return __awaiter(void 0, void 0, void 0, function () {
3938
+ var generateTokens, result;
2050
3939
  return __generator(this, function (_a) {
2051
3940
  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)];
3941
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./auth")); })];
2056
3942
  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)];
3943
+ generateTokens = (_a.sent()).generateTokens;
3944
+ return [4 /*yield*/, generateTokens({ email: "test@test.com" })];
2060
3945
  case 2:
2061
- user = _a.sent();
2062
- (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.department).toBeUndefined();
3946
+ result = _a.sent();
3947
+ (0, bun_test_1.expect)(result.token).toBeNull();
3948
+ (0, bun_test_1.expect)(result.refreshToken).toBeNull();
2063
3949
  return [2 /*return*/];
2064
3950
  }
2065
3951
  });
2066
3952
  }); });
2067
- (0, bun_test_1.it)("creates a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
2068
- var res, user;
3953
+ (0, bun_test_1.it)("includes custom payload from generateJWTPayload option", function () { return __awaiter(void 0, void 0, void 0, function () {
3954
+ var generateTokens, jwt, user, result, decoded;
2069
3955
  return __generator(this, function (_a) {
2070
3956
  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)];
3957
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./auth")); })];
2080
3958
  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)];
3959
+ generateTokens = (_a.sent()).generateTokens;
3960
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
2088
3961
  case 2:
2089
- user = _a.sent();
2090
- (0, bun_test_1.expect)(user === null || user === void 0 ? void 0 : user.superTitle).toBe("Batman");
3962
+ jwt = _a.sent();
3963
+ user = { _id: "user-123" };
3964
+ return [4 /*yield*/, generateTokens(user, {
3965
+ generateJWTPayload: function (u) { return ({ customField: "customValue", userId: u._id }); },
3966
+ })];
3967
+ case 3:
3968
+ result = _a.sent();
3969
+ (0, bun_test_1.expect)(result.token).toBeDefined();
3970
+ decoded = jwt.decode(result.token);
3971
+ (0, bun_test_1.expect)(decoded.customField).toBe("customValue");
3972
+ (0, bun_test_1.expect)(decoded.id).toBe("user-123");
2091
3973
  return [2 /*return*/];
2092
3974
  }
2093
3975
  });
2094
3976
  }); });
2095
- (0, bun_test_1.it)("deletes a discriminated user", function () { return __awaiter(void 0, void 0, void 0, function () {
2096
- var user;
3977
+ (0, bun_test_1.it)("uses custom token expiration from generateTokenExpiration option", function () { return __awaiter(void 0, void 0, void 0, function () {
3978
+ var generateTokens, jwt, user, result, decoded, expectedExp;
2097
3979
  return __generator(this, function (_a) {
2098
3980
  switch (_a.label) {
2099
- case 0:
2100
- // Fails without __t.
2101
- return [4 /*yield*/, agent.delete("/users/".concat(superUser._id)).expect(404)];
3981
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./auth")); })];
2102
3982
  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)];
3983
+ generateTokens = (_a.sent()).generateTokens;
3984
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
2111
3985
  case 2:
2112
- _a.sent();
2113
- return [4 /*yield*/, tests_1.SuperUserModel.findById(superUser._id)];
3986
+ jwt = _a.sent();
3987
+ user = { _id: "user-123" };
3988
+ return [4 /*yield*/, generateTokens(user, {
3989
+ generateTokenExpiration: function () { return "1h"; },
3990
+ })];
2114
3991
  case 3:
2115
- user = _a.sent();
2116
- (0, bun_test_1.expect)(user).toBeNull();
3992
+ result = _a.sent();
3993
+ (0, bun_test_1.expect)(result.token).toBeDefined();
3994
+ decoded = jwt.decode(result.token);
3995
+ expectedExp = Math.floor(Date.now() / 1000) + 3600;
3996
+ (0, bun_test_1.expect)(decoded.exp).toBeGreaterThan(expectedExp - 5);
3997
+ (0, bun_test_1.expect)(decoded.exp).toBeLessThan(expectedExp + 5);
2117
3998
  return [2 /*return*/];
2118
3999
  }
2119
4000
  });
2120
4001
  }); });
2121
- (0, bun_test_1.it)("deletes a base user", function () { return __awaiter(void 0, void 0, void 0, function () {
2122
- var user;
4002
+ (0, bun_test_1.it)("uses custom refresh token expiration from generateRefreshTokenExpiration option", function () { return __awaiter(void 0, void 0, void 0, function () {
4003
+ var generateTokens, jwt, user, result, decoded, expectedExp;
2123
4004
  return __generator(this, function (_a) {
2124
4005
  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)];
4006
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("./auth")); })];
2128
4007
  case 1:
2129
- // Fails for base user with __t
2130
- _a.sent();
2131
- return [4 /*yield*/, agent.delete("/users/".concat(notAdmin._id)).expect(204)];
4008
+ generateTokens = (_a.sent()).generateTokens;
4009
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
2132
4010
  case 2:
2133
- _a.sent();
2134
- return [4 /*yield*/, tests_1.SuperUserModel.findById(notAdmin._id)];
4011
+ jwt = _a.sent();
4012
+ user = { _id: "user-123" };
4013
+ return [4 /*yield*/, generateTokens(user, {
4014
+ generateRefreshTokenExpiration: function () { return "7d"; },
4015
+ })];
2135
4016
  case 3:
2136
- user = _a.sent();
2137
- (0, bun_test_1.expect)(user).toBeNull();
4017
+ result = _a.sent();
4018
+ (0, bun_test_1.expect)(result.refreshToken).toBeDefined();
4019
+ decoded = jwt.decode(result.refreshToken);
4020
+ expectedExp = Math.floor(Date.now() / 1000) + 7 * 24 * 3600;
4021
+ (0, bun_test_1.expect)(decoded.exp).toBeGreaterThan(expectedExp - 10);
4022
+ (0, bun_test_1.expect)(decoded.exp).toBeLessThan(expectedExp + 10);
2138
4023
  return [2 /*return*/];
2139
4024
  }
2140
4025
  });