@terreno/api 0.13.2 → 0.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var bun_test_1 = require("bun:test");
|
|
43
|
+
var express_1 = __importDefault(require("express"));
|
|
44
|
+
var supertest_1 = __importDefault(require("supertest"));
|
|
45
|
+
var api_1 = require("./api");
|
|
46
|
+
var openApiValidator_1 = require("./openApiValidator");
|
|
47
|
+
(0, bun_test_1.afterEach)(function () {
|
|
48
|
+
(0, openApiValidator_1.resetOpenApiValidatorConfig)();
|
|
49
|
+
});
|
|
50
|
+
var createApp = function () {
|
|
51
|
+
var app = (0, express_1.default)();
|
|
52
|
+
app.use(express_1.default.json());
|
|
53
|
+
return app;
|
|
54
|
+
};
|
|
55
|
+
var errorHandler = function (err, _req, res, _next) {
|
|
56
|
+
res.status(err.status || 500).json({ error: err.title || err.message });
|
|
57
|
+
};
|
|
58
|
+
(0, bun_test_1.describe)("asyncHandler with bodySchema validation", function () {
|
|
59
|
+
(0, bun_test_1.it)("validates and accepts a conforming body", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
60
|
+
var app, res;
|
|
61
|
+
return __generator(this, function (_a) {
|
|
62
|
+
switch (_a.label) {
|
|
63
|
+
case 0:
|
|
64
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
65
|
+
app = createApp();
|
|
66
|
+
app.post("/test", (0, api_1.asyncHandler)(function (_req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
67
|
+
return __generator(this, function (_a) {
|
|
68
|
+
res.json({ ok: true });
|
|
69
|
+
return [2 /*return*/];
|
|
70
|
+
});
|
|
71
|
+
}); }, {
|
|
72
|
+
bodySchema: { name: { required: true, type: "string" } },
|
|
73
|
+
validate: true,
|
|
74
|
+
}));
|
|
75
|
+
app.use(errorHandler);
|
|
76
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).post("/test").send({ name: "hello" }).expect(200)];
|
|
77
|
+
case 1:
|
|
78
|
+
res = _a.sent();
|
|
79
|
+
(0, bun_test_1.expect)(res.body.ok).toBe(true);
|
|
80
|
+
return [2 /*return*/];
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}); });
|
|
84
|
+
(0, bun_test_1.it)("rejects a body missing a required field", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
85
|
+
var app;
|
|
86
|
+
return __generator(this, function (_a) {
|
|
87
|
+
switch (_a.label) {
|
|
88
|
+
case 0:
|
|
89
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
90
|
+
app = createApp();
|
|
91
|
+
app.post("/test", (0, api_1.asyncHandler)(function (_req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
92
|
+
return __generator(this, function (_a) {
|
|
93
|
+
res.json({ ok: true });
|
|
94
|
+
return [2 /*return*/];
|
|
95
|
+
});
|
|
96
|
+
}); }, {
|
|
97
|
+
bodySchema: { name: { required: true, type: "string" } },
|
|
98
|
+
validate: true,
|
|
99
|
+
}));
|
|
100
|
+
app.use(errorHandler);
|
|
101
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).post("/test").send({}).expect(400)];
|
|
102
|
+
case 1:
|
|
103
|
+
_a.sent();
|
|
104
|
+
return [2 /*return*/];
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}); });
|
|
108
|
+
(0, bun_test_1.it)("skips body validation when validate is false", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
109
|
+
var app, res;
|
|
110
|
+
return __generator(this, function (_a) {
|
|
111
|
+
switch (_a.label) {
|
|
112
|
+
case 0:
|
|
113
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
114
|
+
app = createApp();
|
|
115
|
+
app.post("/test", (0, api_1.asyncHandler)(function (_req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
116
|
+
return __generator(this, function (_a) {
|
|
117
|
+
res.json({ ok: true });
|
|
118
|
+
return [2 /*return*/];
|
|
119
|
+
});
|
|
120
|
+
}); }, {
|
|
121
|
+
bodySchema: { name: { required: true, type: "string" } },
|
|
122
|
+
validate: false,
|
|
123
|
+
}));
|
|
124
|
+
app.use(errorHandler);
|
|
125
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).post("/test").send({}).expect(200)];
|
|
126
|
+
case 1:
|
|
127
|
+
res = _a.sent();
|
|
128
|
+
(0, bun_test_1.expect)(res.body.ok).toBe(true);
|
|
129
|
+
return [2 /*return*/];
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}); });
|
|
133
|
+
});
|
|
134
|
+
(0, bun_test_1.describe)("asyncHandler with querySchema validation", function () {
|
|
135
|
+
(0, bun_test_1.it)("validates and accepts conforming query params", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
136
|
+
var app, res;
|
|
137
|
+
return __generator(this, function (_a) {
|
|
138
|
+
switch (_a.label) {
|
|
139
|
+
case 0:
|
|
140
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
141
|
+
app = createApp();
|
|
142
|
+
app.get("/test", (0, api_1.asyncHandler)(function (_req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
143
|
+
return __generator(this, function (_a) {
|
|
144
|
+
res.json({ ok: true });
|
|
145
|
+
return [2 /*return*/];
|
|
146
|
+
});
|
|
147
|
+
}); }, {
|
|
148
|
+
querySchema: { page: { type: "integer" } },
|
|
149
|
+
validate: true,
|
|
150
|
+
}));
|
|
151
|
+
app.use(errorHandler);
|
|
152
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).get("/test?page=1").expect(200)];
|
|
153
|
+
case 1:
|
|
154
|
+
res = _a.sent();
|
|
155
|
+
(0, bun_test_1.expect)(res.body.ok).toBe(true);
|
|
156
|
+
return [2 /*return*/];
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}); });
|
|
160
|
+
(0, bun_test_1.it)("rejects invalid query params", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
161
|
+
var app;
|
|
162
|
+
return __generator(this, function (_a) {
|
|
163
|
+
switch (_a.label) {
|
|
164
|
+
case 0:
|
|
165
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
166
|
+
app = createApp();
|
|
167
|
+
app.get("/test", (0, api_1.asyncHandler)(function (_req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
168
|
+
return __generator(this, function (_a) {
|
|
169
|
+
res.json({ ok: true });
|
|
170
|
+
return [2 /*return*/];
|
|
171
|
+
});
|
|
172
|
+
}); }, {
|
|
173
|
+
querySchema: { page: { required: true, type: "integer" } },
|
|
174
|
+
validate: true,
|
|
175
|
+
}));
|
|
176
|
+
app.use(errorHandler);
|
|
177
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).get("/test").expect(400)];
|
|
178
|
+
case 1:
|
|
179
|
+
_a.sent();
|
|
180
|
+
return [2 /*return*/];
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}); });
|
|
184
|
+
});
|
|
185
|
+
(0, bun_test_1.describe)("asyncHandler with both schemas", function () {
|
|
186
|
+
(0, bun_test_1.it)("runs both body and query validators sequentially", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
187
|
+
var app, res;
|
|
188
|
+
return __generator(this, function (_a) {
|
|
189
|
+
switch (_a.label) {
|
|
190
|
+
case 0:
|
|
191
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
192
|
+
app = createApp();
|
|
193
|
+
app.post("/test", (0, api_1.asyncHandler)(function (_req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
194
|
+
return __generator(this, function (_a) {
|
|
195
|
+
res.json({ ok: true });
|
|
196
|
+
return [2 /*return*/];
|
|
197
|
+
});
|
|
198
|
+
}); }, {
|
|
199
|
+
bodySchema: { name: { required: true, type: "string" } },
|
|
200
|
+
querySchema: { page: { type: "integer" } },
|
|
201
|
+
validate: true,
|
|
202
|
+
}));
|
|
203
|
+
app.use(errorHandler);
|
|
204
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).post("/test?page=1").send({ name: "hi" }).expect(200)];
|
|
205
|
+
case 1:
|
|
206
|
+
res = _a.sent();
|
|
207
|
+
(0, bun_test_1.expect)(res.body.ok).toBe(true);
|
|
208
|
+
return [2 /*return*/];
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}); });
|
|
212
|
+
(0, bun_test_1.it)("forwards handler errors through next", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
213
|
+
var app, res;
|
|
214
|
+
return __generator(this, function (_a) {
|
|
215
|
+
switch (_a.label) {
|
|
216
|
+
case 0:
|
|
217
|
+
(0, openApiValidator_1.configureOpenApiValidator)({});
|
|
218
|
+
app = createApp();
|
|
219
|
+
app.post("/test", (0, api_1.asyncHandler)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
220
|
+
return __generator(this, function (_a) {
|
|
221
|
+
throw new Error("handler boom");
|
|
222
|
+
});
|
|
223
|
+
}); }, {
|
|
224
|
+
bodySchema: { name: { type: "string" } },
|
|
225
|
+
validate: true,
|
|
226
|
+
}));
|
|
227
|
+
app.use(errorHandler);
|
|
228
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).post("/test").send({ name: "ok" }).expect(500)];
|
|
229
|
+
case 1:
|
|
230
|
+
res = _a.sent();
|
|
231
|
+
(0, bun_test_1.expect)(res.body.error).toBe("handler boom");
|
|
232
|
+
return [2 /*return*/];
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}); });
|
|
236
|
+
});
|
|
@@ -262,6 +262,70 @@ var setupFreshApp = function () { return __awaiter(void 0, void 0, void 0, funct
|
|
|
262
262
|
});
|
|
263
263
|
}); });
|
|
264
264
|
});
|
|
265
|
+
(0, bun_test_1.describe)("per-route validation: object-form options", function () {
|
|
266
|
+
(0, bun_test_1.it)("applies object validation options with per-operation control", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
267
|
+
var removedProps, errorsCaught, freshApp, admin, res;
|
|
268
|
+
return __generator(this, function (_a) {
|
|
269
|
+
switch (_a.label) {
|
|
270
|
+
case 0:
|
|
271
|
+
(0, openApiValidator_1.configureOpenApiValidator)({ removeAdditional: true });
|
|
272
|
+
removedProps = [];
|
|
273
|
+
errorsCaught = [];
|
|
274
|
+
return [4 /*yield*/, setupFreshApp()];
|
|
275
|
+
case 1:
|
|
276
|
+
freshApp = _a.sent();
|
|
277
|
+
freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, __assign(__assign({}, requiredRouterOptions), { validation: {
|
|
278
|
+
excludeFromCreate: ["about"],
|
|
279
|
+
onAdditionalPropertiesRemoved: function (props) {
|
|
280
|
+
removedProps.push.apply(removedProps, __spreadArray([], __read(props), false));
|
|
281
|
+
},
|
|
282
|
+
onError: function (errors) {
|
|
283
|
+
errorsCaught.push(errors);
|
|
284
|
+
},
|
|
285
|
+
validateCreate: true,
|
|
286
|
+
validateQuery: true,
|
|
287
|
+
validateUpdate: true,
|
|
288
|
+
} })));
|
|
289
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
|
|
290
|
+
case 2:
|
|
291
|
+
admin = _a.sent();
|
|
292
|
+
return [4 /*yield*/, admin.post("/required").send({ name: "Validated" }).expect(201)];
|
|
293
|
+
case 3:
|
|
294
|
+
res = _a.sent();
|
|
295
|
+
(0, bun_test_1.expect)(res.body.data.name).toBe("Validated");
|
|
296
|
+
return [2 /*return*/];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}); });
|
|
300
|
+
(0, bun_test_1.it)("disables create validation when validateCreate is false", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
301
|
+
var freshApp, admin, res;
|
|
302
|
+
return __generator(this, function (_a) {
|
|
303
|
+
switch (_a.label) {
|
|
304
|
+
case 0:
|
|
305
|
+
(0, openApiValidator_1.configureOpenApiValidator)({ removeAdditional: true });
|
|
306
|
+
return [4 /*yield*/, setupFreshApp()];
|
|
307
|
+
case 1:
|
|
308
|
+
freshApp = _a.sent();
|
|
309
|
+
freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, __assign(__assign({}, requiredRouterOptions), { validation: {
|
|
310
|
+
excludeFromUpdate: ["about"],
|
|
311
|
+
validateCreate: false,
|
|
312
|
+
validateUpdate: true,
|
|
313
|
+
} })));
|
|
314
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
|
|
315
|
+
case 2:
|
|
316
|
+
admin = _a.sent();
|
|
317
|
+
return [4 /*yield*/, admin
|
|
318
|
+
.post("/required")
|
|
319
|
+
.send({ extraField: "ignored", name: "NoCreateValidation" })
|
|
320
|
+
.expect(201)];
|
|
321
|
+
case 3:
|
|
322
|
+
res = _a.sent();
|
|
323
|
+
(0, bun_test_1.expect)(res.body.data.name).toBe("NoCreateValidation");
|
|
324
|
+
return [2 /*return*/];
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}); });
|
|
328
|
+
});
|
|
265
329
|
(0, bun_test_1.describe)("sanitization of non-standard mongoose-to-swagger types", function () {
|
|
266
330
|
(0, bun_test_1.it)("validates models with ObjectId and DateOnly fields after sanitization", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
267
331
|
var freshApp, admin, res;
|
package/package.json
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {afterEach, describe, expect, it} from "bun:test";
|
|
2
|
+
import express, {type NextFunction, type Request, type Response} from "express";
|
|
3
|
+
import supertest from "supertest";
|
|
4
|
+
|
|
5
|
+
import {asyncHandler} from "./api";
|
|
6
|
+
import {configureOpenApiValidator, resetOpenApiValidatorConfig} from "./openApiValidator";
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
resetOpenApiValidatorConfig();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const createApp = (): express.Application => {
|
|
13
|
+
const app = express();
|
|
14
|
+
app.use(express.json());
|
|
15
|
+
return app;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const errorHandler = (
|
|
19
|
+
err: {status?: number; title?: string; message?: string},
|
|
20
|
+
_req: Request,
|
|
21
|
+
res: Response,
|
|
22
|
+
_next: NextFunction
|
|
23
|
+
): void => {
|
|
24
|
+
res.status(err.status || 500).json({error: err.title || err.message});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("asyncHandler with bodySchema validation", () => {
|
|
28
|
+
it("validates and accepts a conforming body", async () => {
|
|
29
|
+
configureOpenApiValidator({});
|
|
30
|
+
const app = createApp();
|
|
31
|
+
app.post(
|
|
32
|
+
"/test",
|
|
33
|
+
asyncHandler(
|
|
34
|
+
async (_req: Request, res: Response) => {
|
|
35
|
+
res.json({ok: true});
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
bodySchema: {name: {required: true, type: "string"}},
|
|
39
|
+
validate: true,
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
app.use(errorHandler);
|
|
44
|
+
|
|
45
|
+
const res = await supertest(app).post("/test").send({name: "hello"}).expect(200);
|
|
46
|
+
expect(res.body.ok).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("rejects a body missing a required field", async () => {
|
|
50
|
+
configureOpenApiValidator({});
|
|
51
|
+
const app = createApp();
|
|
52
|
+
app.post(
|
|
53
|
+
"/test",
|
|
54
|
+
asyncHandler(
|
|
55
|
+
async (_req: Request, res: Response) => {
|
|
56
|
+
res.json({ok: true});
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
bodySchema: {name: {required: true, type: "string"}},
|
|
60
|
+
validate: true,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
app.use(errorHandler);
|
|
65
|
+
|
|
66
|
+
await supertest(app).post("/test").send({}).expect(400);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("skips body validation when validate is false", async () => {
|
|
70
|
+
configureOpenApiValidator({});
|
|
71
|
+
const app = createApp();
|
|
72
|
+
app.post(
|
|
73
|
+
"/test",
|
|
74
|
+
asyncHandler(
|
|
75
|
+
async (_req: Request, res: Response) => {
|
|
76
|
+
res.json({ok: true});
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
bodySchema: {name: {required: true, type: "string"}},
|
|
80
|
+
validate: false,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
app.use(errorHandler);
|
|
85
|
+
|
|
86
|
+
const res = await supertest(app).post("/test").send({}).expect(200);
|
|
87
|
+
expect(res.body.ok).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("asyncHandler with querySchema validation", () => {
|
|
92
|
+
it("validates and accepts conforming query params", async () => {
|
|
93
|
+
configureOpenApiValidator({});
|
|
94
|
+
const app = createApp();
|
|
95
|
+
app.get(
|
|
96
|
+
"/test",
|
|
97
|
+
asyncHandler(
|
|
98
|
+
async (_req: Request, res: Response) => {
|
|
99
|
+
res.json({ok: true});
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
querySchema: {page: {type: "integer"}},
|
|
103
|
+
validate: true,
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
app.use(errorHandler);
|
|
108
|
+
|
|
109
|
+
const res = await supertest(app).get("/test?page=1").expect(200);
|
|
110
|
+
expect(res.body.ok).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("rejects invalid query params", async () => {
|
|
114
|
+
configureOpenApiValidator({});
|
|
115
|
+
const app = createApp();
|
|
116
|
+
app.get(
|
|
117
|
+
"/test",
|
|
118
|
+
asyncHandler(
|
|
119
|
+
async (_req: Request, res: Response) => {
|
|
120
|
+
res.json({ok: true});
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
querySchema: {page: {required: true, type: "integer"}},
|
|
124
|
+
validate: true,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
app.use(errorHandler);
|
|
129
|
+
|
|
130
|
+
await supertest(app).get("/test").expect(400);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("asyncHandler with both schemas", () => {
|
|
135
|
+
it("runs both body and query validators sequentially", async () => {
|
|
136
|
+
configureOpenApiValidator({});
|
|
137
|
+
const app = createApp();
|
|
138
|
+
app.post(
|
|
139
|
+
"/test",
|
|
140
|
+
asyncHandler(
|
|
141
|
+
async (_req: Request, res: Response) => {
|
|
142
|
+
res.json({ok: true});
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
bodySchema: {name: {required: true, type: "string"}},
|
|
146
|
+
querySchema: {page: {type: "integer"}},
|
|
147
|
+
validate: true,
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
app.use(errorHandler);
|
|
152
|
+
|
|
153
|
+
const res = await supertest(app).post("/test?page=1").send({name: "hi"}).expect(200);
|
|
154
|
+
expect(res.body.ok).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("forwards handler errors through next", async () => {
|
|
158
|
+
configureOpenApiValidator({});
|
|
159
|
+
const app = createApp();
|
|
160
|
+
app.post(
|
|
161
|
+
"/test",
|
|
162
|
+
asyncHandler(
|
|
163
|
+
async () => {
|
|
164
|
+
throw new Error("handler boom");
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
bodySchema: {name: {type: "string"}},
|
|
168
|
+
validate: true,
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
app.use(errorHandler);
|
|
173
|
+
|
|
174
|
+
const res = await supertest(app).post("/test").send({name: "ok"}).expect(500);
|
|
175
|
+
expect(res.body.error).toBe("handler boom");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -161,6 +161,64 @@ describe("openApiValidator", () => {
|
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
describe("per-route validation: object-form options", () => {
|
|
165
|
+
it("applies object validation options with per-operation control", async () => {
|
|
166
|
+
configureOpenApiValidator({removeAdditional: true});
|
|
167
|
+
|
|
168
|
+
const removedProps: string[] = [];
|
|
169
|
+
const errorsCaught: ErrorObject[][] = [];
|
|
170
|
+
|
|
171
|
+
const freshApp = await setupFreshApp();
|
|
172
|
+
freshApp.use(
|
|
173
|
+
"/required",
|
|
174
|
+
modelRouter(RequiredModel, {
|
|
175
|
+
...requiredRouterOptions,
|
|
176
|
+
validation: {
|
|
177
|
+
excludeFromCreate: ["about"],
|
|
178
|
+
onAdditionalPropertiesRemoved: (props) => {
|
|
179
|
+
removedProps.push(...props);
|
|
180
|
+
},
|
|
181
|
+
onError: (errors) => {
|
|
182
|
+
errorsCaught.push(errors);
|
|
183
|
+
},
|
|
184
|
+
validateCreate: true,
|
|
185
|
+
validateQuery: true,
|
|
186
|
+
validateUpdate: true,
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
const admin = await authAsUser(freshApp, "admin");
|
|
191
|
+
|
|
192
|
+
const res = await admin.post("/required").send({name: "Validated"}).expect(201);
|
|
193
|
+
expect(res.body.data.name).toBe("Validated");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("disables create validation when validateCreate is false", async () => {
|
|
197
|
+
configureOpenApiValidator({removeAdditional: true});
|
|
198
|
+
|
|
199
|
+
const freshApp = await setupFreshApp();
|
|
200
|
+
freshApp.use(
|
|
201
|
+
"/required",
|
|
202
|
+
modelRouter(RequiredModel, {
|
|
203
|
+
...requiredRouterOptions,
|
|
204
|
+
validation: {
|
|
205
|
+
excludeFromUpdate: ["about"],
|
|
206
|
+
validateCreate: false,
|
|
207
|
+
validateUpdate: true,
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
const admin = await authAsUser(freshApp, "admin");
|
|
212
|
+
|
|
213
|
+
// Even with extra field, should pass since create validation is disabled
|
|
214
|
+
const res = await admin
|
|
215
|
+
.post("/required")
|
|
216
|
+
.send({extraField: "ignored", name: "NoCreateValidation"})
|
|
217
|
+
.expect(201);
|
|
218
|
+
expect(res.body.data.name).toBe("NoCreateValidation");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
164
222
|
describe("sanitization of non-standard mongoose-to-swagger types", () => {
|
|
165
223
|
it("validates models with ObjectId and DateOnly fields after sanitization", async () => {
|
|
166
224
|
configureOpenApiValidator({removeAdditional: true});
|