@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/CLAUDE.md +107 -0
- package/bunfig.toml +3 -2
- package/dist/api.d.ts +3 -14
- package/dist/api.js +44 -68
- package/dist/api.test.js +2051 -166
- package/dist/permissions.d.ts +1 -1
- package/dist/permissions.js +17 -25
- package/dist/utils.test.js +169 -7
- package/package.json +3 -2
- package/src/api.test.ts +1736 -142
- package/src/api.ts +19 -61
- package/src/permissions.ts +4 -14
- package/src/utils.test.ts +189 -9
package/src/api.ts
CHANGED
|
@@ -241,16 +241,6 @@ export interface ModelRouterOptions<T> {
|
|
|
241
241
|
request: express.Request,
|
|
242
242
|
options: ModelRouterOptions<T>
|
|
243
243
|
) => Promise<JSONValue>;
|
|
244
|
-
/**
|
|
245
|
-
* The discriminatorKey that you passed when creating the Mongoose models. Defaults to __t. See:
|
|
246
|
-
* https://mongoosejs.com/docs/discriminators.html If this key is provided,
|
|
247
|
-
* you must provide the same key as part of the top level of the body when making performing
|
|
248
|
-
* update or delete operations on this model.
|
|
249
|
-
* \{discriminatorKey: "__t"\}
|
|
250
|
-
*
|
|
251
|
-
* PATCH \{__t: "SuperUser", name: "Foo"\} // __t is required or there will be a 404 error.
|
|
252
|
-
*/
|
|
253
|
-
discriminatorKey?: string;
|
|
254
244
|
/**
|
|
255
245
|
* The OpenAPI generator for this server. This is used to generate the OpenAPI documentation.
|
|
256
246
|
*/
|
|
@@ -275,23 +265,6 @@ export interface ModelRouterOptions<T> {
|
|
|
275
265
|
openApiExtraModelProperties?: any;
|
|
276
266
|
}
|
|
277
267
|
|
|
278
|
-
// A function to decide which model to use. If no discriminators are provided,
|
|
279
|
-
// just returns the base model. If
|
|
280
|
-
export function getModel(baseModel: Model<any>, body?: any, options?: ModelRouterOptions<any>) {
|
|
281
|
-
const discriminatorKey = options?.discriminatorKey ?? "__t";
|
|
282
|
-
const modelName = body?.[discriminatorKey];
|
|
283
|
-
if (!modelName) {
|
|
284
|
-
return baseModel;
|
|
285
|
-
}
|
|
286
|
-
const model = baseModel.discriminators?.[modelName];
|
|
287
|
-
if (!model) {
|
|
288
|
-
throw new Error(
|
|
289
|
-
`Could not find discriminator model for key ${modelName}, baseModel: ${baseModel}`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
return model;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
268
|
// Ensures query params are allowed. Also checks nested query params when using $and/$or.
|
|
296
269
|
function checkQueryParamAllowed(
|
|
297
270
|
queryParam: string,
|
|
@@ -337,15 +310,12 @@ function checkQueryParamAllowed(
|
|
|
337
310
|
// }
|
|
338
311
|
|
|
339
312
|
/**
|
|
340
|
-
* Create a set of CRUD routes given a Mongoose model
|
|
313
|
+
* Create a set of CRUD routes given a Mongoose model and configuration options.
|
|
341
314
|
*
|
|
342
|
-
* @param
|
|
315
|
+
* @param model A Mongoose Model
|
|
343
316
|
* @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
|
|
344
317
|
*/
|
|
345
|
-
export function modelRouter<T>(
|
|
346
|
-
baseModel: Model<T>,
|
|
347
|
-
options: ModelRouterOptions<T>
|
|
348
|
-
): express.Router {
|
|
318
|
+
export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>): express.Router {
|
|
349
319
|
const router = express.Router();
|
|
350
320
|
|
|
351
321
|
// Do before the other router options so endpoints take priority.
|
|
@@ -359,12 +329,10 @@ export function modelRouter<T>(
|
|
|
359
329
|
"/",
|
|
360
330
|
[
|
|
361
331
|
authenticateMiddleware(options.allowAnonymous),
|
|
362
|
-
createOpenApiMiddleware(
|
|
363
|
-
permissionMiddleware(
|
|
332
|
+
createOpenApiMiddleware(model, options),
|
|
333
|
+
permissionMiddleware(model, options),
|
|
364
334
|
],
|
|
365
335
|
asyncHandler(async (req: Request, res: Response) => {
|
|
366
|
-
const model = getModel(baseModel, req.body?.__t, options);
|
|
367
|
-
|
|
368
336
|
let body: Partial<T> | (Partial<T> | undefined)[] | null | undefined;
|
|
369
337
|
try {
|
|
370
338
|
body = transform<T>(options, req.body, "create", req.user);
|
|
@@ -426,7 +394,7 @@ export function modelRouter<T>(
|
|
|
426
394
|
|
|
427
395
|
if (options.populatePaths) {
|
|
428
396
|
try {
|
|
429
|
-
let populateQuery = model.findById(data._id);
|
|
397
|
+
let populateQuery: any = model.findById(data._id);
|
|
430
398
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
431
399
|
data = await populateQuery.exec();
|
|
432
400
|
} catch (error: any) {
|
|
@@ -469,13 +437,10 @@ export function modelRouter<T>(
|
|
|
469
437
|
"/",
|
|
470
438
|
[
|
|
471
439
|
authenticateMiddleware(options.allowAnonymous),
|
|
472
|
-
permissionMiddleware(
|
|
473
|
-
listOpenApiMiddleware(
|
|
440
|
+
permissionMiddleware(model, options),
|
|
441
|
+
listOpenApiMiddleware(model, options),
|
|
474
442
|
],
|
|
475
443
|
asyncHandler(async (req: Request, res: Response) => {
|
|
476
|
-
// For pure read queries, Mongoose will return the correct data with just the base model.
|
|
477
|
-
const model = baseModel;
|
|
478
|
-
|
|
479
444
|
let query: any = {};
|
|
480
445
|
for (const queryParam of Object.keys(options.defaultQueryParams ?? [])) {
|
|
481
446
|
query[queryParam] = options.defaultQueryParams?.[queryParam];
|
|
@@ -624,8 +589,8 @@ export function modelRouter<T>(
|
|
|
624
589
|
"/:id",
|
|
625
590
|
[
|
|
626
591
|
authenticateMiddleware(options.allowAnonymous),
|
|
627
|
-
getOpenApiMiddleware(
|
|
628
|
-
permissionMiddleware(
|
|
592
|
+
getOpenApiMiddleware(model, options),
|
|
593
|
+
permissionMiddleware(model, options),
|
|
629
594
|
],
|
|
630
595
|
asyncHandler(async (req: Request, res: Response) => {
|
|
631
596
|
const data: mongoose.Document & T = (req as any).obj;
|
|
@@ -658,12 +623,10 @@ export function modelRouter<T>(
|
|
|
658
623
|
"/:id",
|
|
659
624
|
[
|
|
660
625
|
authenticateMiddleware(options.allowAnonymous),
|
|
661
|
-
patchOpenApiMiddleware(
|
|
662
|
-
permissionMiddleware(
|
|
626
|
+
patchOpenApiMiddleware(model, options),
|
|
627
|
+
permissionMiddleware(model, options),
|
|
663
628
|
],
|
|
664
629
|
asyncHandler(async (req: Request, res: Response) => {
|
|
665
|
-
const model = getModel(baseModel, req.body, options);
|
|
666
|
-
|
|
667
630
|
let doc: mongoose.Document & T = (req as any).obj;
|
|
668
631
|
|
|
669
632
|
let body;
|
|
@@ -731,7 +694,7 @@ export function modelRouter<T>(
|
|
|
731
694
|
}
|
|
732
695
|
|
|
733
696
|
if (options.populatePaths) {
|
|
734
|
-
let populateQuery = model.findById(doc._id);
|
|
697
|
+
let populateQuery: any = model.findById(doc._id);
|
|
735
698
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
736
699
|
doc = await populateQuery.exec();
|
|
737
700
|
}
|
|
@@ -766,12 +729,10 @@ export function modelRouter<T>(
|
|
|
766
729
|
"/:id",
|
|
767
730
|
[
|
|
768
731
|
authenticateMiddleware(options.allowAnonymous),
|
|
769
|
-
deleteOpenApiMiddleware(
|
|
770
|
-
permissionMiddleware(
|
|
732
|
+
deleteOpenApiMiddleware(model, options),
|
|
733
|
+
permissionMiddleware(model, options),
|
|
771
734
|
],
|
|
772
735
|
asyncHandler(async (req: Request, res: Response) => {
|
|
773
|
-
const model = getModel(baseModel, req.body, options);
|
|
774
|
-
|
|
775
736
|
const doc: mongoose.Document & T & {deleted?: boolean} = (req as any).obj;
|
|
776
737
|
|
|
777
738
|
if (options.preDelete) {
|
|
@@ -849,7 +810,6 @@ export function modelRouter<T>(
|
|
|
849
810
|
operation: "POST" | "PATCH" | "DELETE"
|
|
850
811
|
) {
|
|
851
812
|
// TODO Combine array operations and .patch(), as they are very similar.
|
|
852
|
-
const model = getModel(baseModel, req.body, options);
|
|
853
813
|
|
|
854
814
|
if (!(await checkPermissions("update", options.permissions.update, req.user))) {
|
|
855
815
|
throw new APIError({
|
|
@@ -861,9 +821,7 @@ export function modelRouter<T>(
|
|
|
861
821
|
const doc = await model.findById(req.params.id);
|
|
862
822
|
// Make a copy for passing pre-saved values to hooks.
|
|
863
823
|
const prevDoc = cloneDeep(doc);
|
|
864
|
-
|
|
865
|
-
// hooks.
|
|
866
|
-
if (!doc || (doc.__t && !req.body.__t)) {
|
|
824
|
+
if (!doc) {
|
|
867
825
|
throw new APIError({
|
|
868
826
|
status: 404,
|
|
869
827
|
title: `Could not find document to PATCH: ${req.params.id}`,
|
|
@@ -979,7 +937,7 @@ export function modelRouter<T>(
|
|
|
979
937
|
|
|
980
938
|
if (options.postUpdate) {
|
|
981
939
|
try {
|
|
982
|
-
await options.postUpdate(doc, body, req, prevDoc);
|
|
940
|
+
await options.postUpdate(doc as any, body, req, prevDoc as any);
|
|
983
941
|
} catch (error: any) {
|
|
984
942
|
throw new APIError({
|
|
985
943
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
@@ -989,7 +947,7 @@ export function modelRouter<T>(
|
|
|
989
947
|
});
|
|
990
948
|
}
|
|
991
949
|
}
|
|
992
|
-
return res.json({data: serialize<T>(req, options, doc)});
|
|
950
|
+
return res.json({data: serialize<T>(req, options, doc as any)});
|
|
993
951
|
}
|
|
994
952
|
|
|
995
953
|
async function arrayPost(req: Request, res: Response) {
|
|
@@ -1004,7 +962,7 @@ export function modelRouter<T>(
|
|
|
1004
962
|
return arrayOperation(req, res, "DELETE");
|
|
1005
963
|
}
|
|
1006
964
|
// Set up routes for managing array fields. Check if there any array fields to add this for.
|
|
1007
|
-
if (Object.values(
|
|
965
|
+
if (Object.values(model.schema.paths).find((config: any) => config.instance === "Array")) {
|
|
1008
966
|
router.post(
|
|
1009
967
|
"/:id/:field",
|
|
1010
968
|
authenticateMiddleware(options.allowAnonymous),
|
package/src/permissions.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type express from "express";
|
|
|
4
4
|
import type {NextFunction} from "express";
|
|
5
5
|
import mongoose, {type Model} from "mongoose";
|
|
6
6
|
|
|
7
|
-
import {addPopulateToQuery,
|
|
7
|
+
import {addPopulateToQuery, type ModelRouterOptions, type RESTMethod} from "./api";
|
|
8
8
|
import type {User} from "./auth";
|
|
9
9
|
import {APIError} from "./errors";
|
|
10
10
|
import {logger} from "./logger";
|
|
@@ -101,8 +101,8 @@ export async function checkPermissions<T>(
|
|
|
101
101
|
// finds the relevant object, checks the permissions, and attaches the object to the request as
|
|
102
102
|
// req.obj.
|
|
103
103
|
export function permissionMiddleware<T>(
|
|
104
|
-
|
|
105
|
-
options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths"
|
|
104
|
+
model: Model<T>,
|
|
105
|
+
options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths">
|
|
106
106
|
) {
|
|
107
107
|
return async (req: express.Request, _res: express.Response, next: NextFunction) => {
|
|
108
108
|
if (req.method === "OPTIONS") {
|
|
@@ -131,8 +131,6 @@ export function permissionMiddleware<T>(
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const model = getModel(baseModel, req.body, options);
|
|
135
|
-
|
|
136
134
|
// All methods check for permissions.
|
|
137
135
|
if (!(await checkPermissions(method, options.permissions[method], req.user))) {
|
|
138
136
|
throw new APIError({
|
|
@@ -159,15 +157,7 @@ export function permissionMiddleware<T>(
|
|
|
159
157
|
title: `GET failed on ${req.params.id}`,
|
|
160
158
|
});
|
|
161
159
|
}
|
|
162
|
-
if (!data
|
|
163
|
-
// For discriminated models, return 404 without checking hidden state
|
|
164
|
-
if (["update", "delete"].includes(method) && data?.__t && !req.body?.__t) {
|
|
165
|
-
throw new APIError({
|
|
166
|
-
status: 404,
|
|
167
|
-
title: `Document ${req.params.id} not found for model ${model.modelName}`,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
160
|
+
if (!data) {
|
|
171
161
|
// Check if document exists but is hidden. Completely skip plugins.
|
|
172
162
|
const hiddenDoc = await model.collection.findOne({
|
|
173
163
|
_id: new mongoose.Types.ObjectId(req.params.id),
|
package/src/utils.test.ts
CHANGED
|
@@ -1,14 +1,194 @@
|
|
|
1
|
-
import {describe, expect, it} from "bun:test";
|
|
1
|
+
import {describe, expect, it, spyOn} from "bun:test";
|
|
2
|
+
import mongoose from "mongoose";
|
|
2
3
|
|
|
3
|
-
import {isValidObjectId} from "./utils";
|
|
4
|
+
import {checkModelsStrict, isValidObjectId} from "./utils";
|
|
4
5
|
|
|
5
6
|
describe("utils", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
describe("isValidObjectId", () => {
|
|
8
|
+
it("checks valid ObjectIds", () => {
|
|
9
|
+
expect(isValidObjectId("62c44da0003d9f8ee8cc925c")).toBe(true);
|
|
10
|
+
expect(isValidObjectId("620000000000000000000000")).toBe(true);
|
|
11
|
+
// Mongoose's builtin "ObjectId.isValid" will falsely say this is an ObjectId.
|
|
12
|
+
expect(isValidObjectId("1234567890ab")).toBe(false);
|
|
13
|
+
expect(isValidObjectId("microsoft123")).toBe(false);
|
|
14
|
+
expect(isValidObjectId("62c44da0003d9f8ee8cc925x")).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("checkModelsStrict", () => {
|
|
19
|
+
it("throws error when toObject.virtuals is not true", () => {
|
|
20
|
+
// Create a schema without toObject.virtuals
|
|
21
|
+
const testSchema = new mongoose.Schema({name: String});
|
|
22
|
+
testSchema.set("strict", "throw");
|
|
23
|
+
// Not setting toObject.virtuals
|
|
24
|
+
|
|
25
|
+
if (mongoose.models.ToObjectTestModel) {
|
|
26
|
+
delete mongoose.models.ToObjectTestModel;
|
|
27
|
+
}
|
|
28
|
+
mongoose.model("ToObjectTestModel", testSchema);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// This should throw because ToObjectTestModel doesn't have toObject.virtuals
|
|
32
|
+
expect(() => checkModelsStrict()).toThrow("toObject.virtuals not set to true");
|
|
33
|
+
} finally {
|
|
34
|
+
delete mongoose.models.ToObjectTestModel;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("throws error when toJSON.virtuals is not true", () => {
|
|
39
|
+
// Create a schema with toObject.virtuals but without toJSON.virtuals
|
|
40
|
+
const testSchema = new mongoose.Schema({name: String});
|
|
41
|
+
testSchema.set("toObject", {virtuals: true});
|
|
42
|
+
testSchema.set("strict", "throw");
|
|
43
|
+
// Not setting toJSON.virtuals
|
|
44
|
+
|
|
45
|
+
if (mongoose.models.ToJsonTestModel) {
|
|
46
|
+
delete mongoose.models.ToJsonTestModel;
|
|
47
|
+
}
|
|
48
|
+
mongoose.model("ToJsonTestModel", testSchema);
|
|
49
|
+
|
|
50
|
+
// Use spyOn to intercept modelNames and return only our test model
|
|
51
|
+
const spy = spyOn(mongoose, "modelNames").mockReturnValue(["ToJsonTestModel"]);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
expect(() => checkModelsStrict()).toThrow("toJSON.virtuals not set to true");
|
|
55
|
+
} finally {
|
|
56
|
+
spy.mockRestore();
|
|
57
|
+
delete mongoose.models.ToJsonTestModel;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("throws error when strict mode is not set to throw", () => {
|
|
62
|
+
// Create a schema with virtuals but without strict mode
|
|
63
|
+
const testSchema = new mongoose.Schema({name: String});
|
|
64
|
+
testSchema.set("toObject", {virtuals: true});
|
|
65
|
+
testSchema.set("toJSON", {virtuals: true});
|
|
66
|
+
// Not setting strict to "throw"
|
|
67
|
+
|
|
68
|
+
if (mongoose.models.StrictTestModel) {
|
|
69
|
+
delete mongoose.models.StrictTestModel;
|
|
70
|
+
}
|
|
71
|
+
mongoose.model("StrictTestModel", testSchema);
|
|
72
|
+
|
|
73
|
+
const spy = spyOn(mongoose, "modelNames").mockReturnValue(["StrictTestModel"]);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
expect(() => checkModelsStrict()).toThrow("is not set to strict mode");
|
|
77
|
+
} finally {
|
|
78
|
+
spy.mockRestore();
|
|
79
|
+
delete mongoose.models.StrictTestModel;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("passes when all checks pass", () => {
|
|
84
|
+
// Create a properly configured schema
|
|
85
|
+
const testSchema = new mongoose.Schema({name: String});
|
|
86
|
+
testSchema.set("toObject", {virtuals: true});
|
|
87
|
+
testSchema.set("toJSON", {virtuals: true});
|
|
88
|
+
testSchema.set("strict", "throw");
|
|
89
|
+
|
|
90
|
+
if (mongoose.models.GoodTestModel) {
|
|
91
|
+
delete mongoose.models.GoodTestModel;
|
|
92
|
+
}
|
|
93
|
+
mongoose.model("GoodTestModel", testSchema);
|
|
94
|
+
|
|
95
|
+
const spy = spyOn(mongoose, "modelNames").mockReturnValue(["GoodTestModel"]);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
expect(() => checkModelsStrict()).not.toThrow();
|
|
99
|
+
} finally {
|
|
100
|
+
spy.mockRestore();
|
|
101
|
+
delete mongoose.models.GoodTestModel;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("skips strict mode check for ignored models", () => {
|
|
106
|
+
// Create a properly configured model
|
|
107
|
+
const goodSchema = new mongoose.Schema({name: String});
|
|
108
|
+
goodSchema.set("toObject", {virtuals: true});
|
|
109
|
+
goodSchema.set("toJSON", {virtuals: true});
|
|
110
|
+
goodSchema.set("strict", "throw");
|
|
111
|
+
|
|
112
|
+
if (mongoose.models.GoodModel) {
|
|
113
|
+
delete mongoose.models.GoodModel;
|
|
114
|
+
}
|
|
115
|
+
mongoose.model("GoodModel", goodSchema);
|
|
116
|
+
|
|
117
|
+
// Create a model without strict mode that we'll ignore
|
|
118
|
+
const badSchema = new mongoose.Schema({name: String});
|
|
119
|
+
badSchema.set("toObject", {virtuals: true});
|
|
120
|
+
badSchema.set("toJSON", {virtuals: true});
|
|
121
|
+
// Not setting strict - should fail unless ignored
|
|
122
|
+
|
|
123
|
+
if (mongoose.models.IgnoredModel) {
|
|
124
|
+
delete mongoose.models.IgnoredModel;
|
|
125
|
+
}
|
|
126
|
+
mongoose.model("IgnoredModel", badSchema);
|
|
127
|
+
|
|
128
|
+
const spy = spyOn(mongoose, "modelNames").mockReturnValue(["GoodModel", "IgnoredModel"]);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Without ignoring, should throw for IgnoredModel
|
|
132
|
+
expect(() => checkModelsStrict()).toThrow("is not set to strict mode");
|
|
133
|
+
|
|
134
|
+
// With ignoring IgnoredModel, should pass
|
|
135
|
+
expect(() => checkModelsStrict(["IgnoredModel"])).not.toThrow();
|
|
136
|
+
} finally {
|
|
137
|
+
spy.mockRestore();
|
|
138
|
+
delete mongoose.models.GoodModel;
|
|
139
|
+
delete mongoose.models.IgnoredModel;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("handles multiple models and validates all", () => {
|
|
144
|
+
// Create three properly configured models
|
|
145
|
+
const schema1 = new mongoose.Schema({name: String});
|
|
146
|
+
schema1.set("toObject", {virtuals: true});
|
|
147
|
+
schema1.set("toJSON", {virtuals: true});
|
|
148
|
+
schema1.set("strict", "throw");
|
|
149
|
+
|
|
150
|
+
const schema2 = new mongoose.Schema({value: Number});
|
|
151
|
+
schema2.set("toObject", {virtuals: true});
|
|
152
|
+
schema2.set("toJSON", {virtuals: true});
|
|
153
|
+
schema2.set("strict", "throw");
|
|
154
|
+
|
|
155
|
+
const schema3 = new mongoose.Schema({active: Boolean});
|
|
156
|
+
schema3.set("toObject", {virtuals: true});
|
|
157
|
+
schema3.set("toJSON", {virtuals: true});
|
|
158
|
+
schema3.set("strict", "throw");
|
|
159
|
+
|
|
160
|
+
if (mongoose.models.MultiModel1) delete mongoose.models.MultiModel1;
|
|
161
|
+
if (mongoose.models.MultiModel2) delete mongoose.models.MultiModel2;
|
|
162
|
+
if (mongoose.models.MultiModel3) delete mongoose.models.MultiModel3;
|
|
163
|
+
|
|
164
|
+
mongoose.model("MultiModel1", schema1);
|
|
165
|
+
mongoose.model("MultiModel2", schema2);
|
|
166
|
+
mongoose.model("MultiModel3", schema3);
|
|
167
|
+
|
|
168
|
+
const spy = spyOn(mongoose, "modelNames").mockReturnValue([
|
|
169
|
+
"MultiModel1",
|
|
170
|
+
"MultiModel2",
|
|
171
|
+
"MultiModel3",
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
expect(() => checkModelsStrict()).not.toThrow();
|
|
176
|
+
} finally {
|
|
177
|
+
spy.mockRestore();
|
|
178
|
+
delete mongoose.models.MultiModel1;
|
|
179
|
+
delete mongoose.models.MultiModel2;
|
|
180
|
+
delete mongoose.models.MultiModel3;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("handles empty model list", () => {
|
|
185
|
+
const spy = spyOn(mongoose, "modelNames").mockReturnValue([]);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
expect(() => checkModelsStrict()).not.toThrow();
|
|
189
|
+
} finally {
|
|
190
|
+
spy.mockRestore();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
13
193
|
});
|
|
14
194
|
});
|