UNPKG

10 kBJavaScriptView Raw
1'use strict'
2
3const t = require('tap')
4const test = t.test
5const Fastify = require('..')
6const fp = require('fastify-plugin')
7
8const ajvMergePatch = require('ajv-merge-patch')
9const AJV = require('ajv')
10const fastClone = require('rfdc')({ circles: false, proto: true })
11
12const schemaUsed = {
13 $id: 'urn:schema:foo',
14 definitions: {
15 foo: { type: 'string' }
16 },
17 type: 'object',
18 properties: {
19 foo: { $ref: '#/definitions/foo' }
20 }
21}
22const schemaParent = {
23 $id: 'urn:schema:response',
24 type: 'object',
25 required: ['foo'],
26 properties: {
27 foo: { $ref: 'urn:schema:foo#/definitions/foo' }
28 }
29}
30
31const schemaRequest = {
32 $id: 'urn:schema:request',
33 type: 'object',
34 required: ['foo'],
35 properties: {
36 foo: { $ref: 'urn:schema:response#/properties/foo' }
37 }
38}
39
40test('Should use the ref resolver - response', t => {
41 t.plan(3)
42 const fastify = Fastify()
43 const ajv = new AJV()
44 ajv.addSchema(fastClone(schemaParent))
45 ajv.addSchema(fastClone(schemaUsed))
46
47 fastify.setSchemaCompiler(schema => ajv.compile(schema))
48 fastify.setSchemaResolver((ref) => {
49 t.ok(['urn:schema:response', 'urn:schema:foo'].includes(ref))
50 return ajv.getSchema(ref).schema
51 })
52
53 fastify.route({
54 method: 'GET',
55 url: '/',
56 schema: {
57 response: {
58 '2xx': { $ref: 'urn:schema:response#' }
59 }
60 },
61 handler (req, reply) {
62 reply.send({ foo: 'bar' })
63 }
64 })
65
66 fastify.ready(t.error)
67})
68
69test('Should use the ref resolver - body', t => {
70 t.plan(4)
71 const fastify = Fastify()
72 const ajv = new AJV()
73 ajv.addSchema(fastClone(schemaParent))
74 ajv.addSchema(fastClone(schemaUsed))
75
76 fastify.setSchemaCompiler(schema => ajv.compile(schema))
77 fastify.setSchemaResolver((ref) => {
78 t.ok(['urn:schema:response', 'urn:schema:foo'].includes(ref))
79 return ajv.getSchema(ref).schema
80 })
81
82 fastify.route({
83 method: 'POST',
84 url: '/',
85 schema: {
86 body: { $ref: 'urn:schema:response#' }
87 },
88 handler (req, reply) {
89 reply.send({ foo: 'bar' })
90 }
91 })
92
93 fastify.inject({
94 method: 'POST',
95 url: '/',
96 payload: { foo: 'bar' }
97 }, (err, res) => {
98 t.error(err)
99 t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
100 })
101})
102
103test('Encapsulation', t => {
104 t.plan(14)
105 const fastify = Fastify()
106 const ajv = new AJV()
107 ajv.addSchema(fastClone(schemaParent))
108 ajv.addSchema(fastClone(schemaUsed))
109
110 fastify.register((instance, opts, next) => {
111 instance.setSchemaCompiler(schema => ajv.compile(schema))
112 instance.setSchemaResolver((ref) => {
113 return ajv.getSchema(ref).schema
114 })
115 instance.route({
116 method: 'POST',
117 url: '/',
118 schema: {
119 body: { $ref: 'urn:schema:response#' }
120 },
121 handler (req, reply) {
122 reply.send({ foo: 'bar' })
123 }
124 })
125
126 instance.register((instance, opts, next) => {
127 instance.route({
128 method: 'POST',
129 url: '/two',
130 schema: {
131 body: { $ref: 'urn:schema:response#' }
132 },
133 handler (req, reply) {
134 reply.send({ foo: 'bar' })
135 }
136 })
137 next()
138 })
139 next()
140 })
141
142 fastify.register((instance, opts, next) => {
143 instance.route({
144 method: 'POST',
145 url: '/clean',
146 handler (req, reply) {
147 reply.send({ foo: 'bar' })
148 }
149 })
150 next()
151 })
152
153 fastify.ready(err => {
154 t.error(err)
155
156 fastify.inject({
157 method: 'POST',
158 url: '/',
159 payload: { foo: 'bar' }
160 }, (err, res) => {
161 t.error(err)
162 t.equals(res.statusCode, 200)
163 t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
164 })
165
166 fastify.inject({
167 method: 'POST',
168 url: '/',
169 payload: { wrongFoo: 'bar' }
170 }, (err, res) => {
171 t.error(err)
172 t.equals(res.statusCode, 400)
173 })
174
175 fastify.inject({
176 method: 'POST',
177 url: '/two',
178 payload: { foo: 'bar' }
179 }, (err, res) => {
180 t.error(err)
181 t.equals(res.statusCode, 200)
182 t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
183 })
184
185 fastify.inject({
186 method: 'POST',
187 url: '/two',
188 payload: { wrongFoo: 'bar' }
189 }, (err, res) => {
190 t.error(err)
191 t.equals(res.statusCode, 400)
192 })
193
194 fastify.inject({
195 method: 'POST',
196 url: '/clean',
197 payload: { wrongFoo: 'bar' }
198 }, (err, res) => {
199 t.error(err)
200 t.equals(res.statusCode, 200)
201 t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
202 })
203 })
204})
205
206test('Schema resolver without schema compiler', t => {
207 t.plan(2)
208 const fastify = Fastify()
209
210 fastify.setSchemaResolver(() => { t.fail('the schema resolver will never be called') })
211 fastify.route({
212 method: 'POST',
213 url: '/',
214 schema: {},
215 handler (req, reply) {
216 reply.send({ foo: 'bar' })
217 }
218 })
219
220 fastify.ready(err => {
221 t.is(err.code, 'FST_ERR_SCH_MISSING_COMPILER')
222 t.isLike(err.message, /You must provide a schemaCompiler to route POST \/ to use the schemaResolver/)
223 })
224})
225
226test('Triple $ref deep', t => {
227 t.plan(6)
228
229 const fastify = Fastify()
230 const ajv = new AJV()
231 ajv.addSchema(fastClone(schemaParent))
232 ajv.addSchema(fastClone(schemaUsed))
233 ajv.addSchema(fastClone(schemaRequest))
234
235 fastify.setSchemaCompiler(schema => ajv.compile(schema))
236 fastify.setSchemaResolver((ref) => {
237 return ajv.getSchema(ref).schema
238 })
239
240 fastify.route({
241 method: 'POST',
242 url: '/',
243 schema: {
244 body: { $ref: 'urn:schema:request#' },
245 response: {
246 '2xx': { $ref: 'urn:schema:response#' }
247 }
248 },
249 handler (req, reply) {
250 reply.send({ foo: 'bar' })
251 }
252 })
253
254 fastify.inject({
255 method: 'POST',
256 url: '/',
257 payload: { foo: 'bar' }
258 }, (err, res) => {
259 t.error(err)
260 t.equals(res.statusCode, 200)
261 t.deepEquals(JSON.parse(res.payload), { foo: 'bar' })
262 })
263
264 fastify.inject({
265 method: 'POST',
266 url: '/',
267 payload: { fool: 'bar' }
268 }, (err, res) => {
269 t.error(err)
270 t.equals(res.statusCode, 400)
271 t.deepEquals(JSON.parse(res.payload).message, "body should have required property 'foo'")
272 })
273})
274
275test('$ref with a simple $id', t => {
276 t.plan(6)
277 const fastify = Fastify()
278 const ajv = new AJV()
279 ajv.addSchema(fastClone(schemaUsed))
280 ajv.addSchema({
281 $id: 'urn:schema:response',
282 type: 'object',
283 required: ['foo'],
284 properties: {
285 foo: { $ref: 'urn:schema:foo' }
286 }
287 })
288 ajv.addSchema({
289 $id: 'urn:schema:request',
290 type: 'object',
291 required: ['foo'],
292 properties: {
293 foo: { $ref: 'urn:schema:foo' }
294 }
295 })
296
297 fastify.setSchemaCompiler(schema => ajv.compile(schema))
298 fastify.setSchemaResolver((ref) => {
299 t.ok(['urn:schema:request', 'urn:schema:response', 'urn:schema:foo'].includes(ref))
300 return ajv.getSchema(ref).schema
301 })
302
303 fastify.route({
304 method: 'POST',
305 url: '/',
306 schema: {
307 body: { $ref: 'urn:schema:request#' },
308 response: {
309 '2xx': { $ref: 'urn:schema:response#' }
310 }
311 },
312 handler (req, reply) {
313 reply.send({ foo: { foo: 'bar', bar: 'foo' } })
314 }
315 })
316
317 fastify.inject({
318 method: 'POST',
319 url: '/',
320 payload: { foo: { foo: 'bar' } }
321 }, (err, res) => {
322 t.error(err)
323 t.equals(res.statusCode, 200)
324 t.deepEquals(JSON.parse(res.payload), { foo: { foo: 'bar' } })
325 })
326})
327
328test('Should handle root $merge keywords in header', t => {
329 t.plan(5)
330 const fastify = Fastify({
331 ajv: {
332 plugins: [
333 ajvMergePatch
334 ]
335 }
336 })
337
338 fastify.route({
339 method: 'GET',
340 url: '/',
341 schema: {
342 headers: {
343 $merge: {
344 source: {
345 type: 'object',
346 properties: {
347 q: {
348 type: 'string'
349 }
350 }
351 },
352 with: {
353 required: ['q']
354 }
355 }
356 }
357 },
358 handler (req, reply) {
359 reply.send({ ok: 1 })
360 }
361 })
362
363 fastify.ready(err => {
364 t.error(err)
365
366 fastify.inject({
367 method: 'GET',
368 url: '/'
369 }, (err, res) => {
370 t.error(err)
371 t.equals(res.statusCode, 400)
372 })
373
374 fastify.inject({
375 method: 'GET',
376 url: '/',
377 headers: {
378 q: 'foo'
379 }
380 }, (err, res) => {
381 t.error(err)
382 t.equals(res.statusCode, 200)
383 })
384 })
385})
386
387test('Should handle root $patch keywords in header', t => {
388 t.plan(5)
389 const fastify = Fastify({
390 ajv: {
391 plugins: [
392 ajvMergePatch
393 ]
394 }
395 })
396
397 fastify.route({
398 method: 'GET',
399 url: '/',
400 schema: {
401 headers: {
402 $patch: {
403 source: {
404 type: 'object',
405 properties: {
406 q: {
407 type: 'string'
408 }
409 }
410 },
411 with: [
412 {
413 op: 'add',
414 path: '/properties/q',
415 value: { type: 'number' }
416 }
417 ]
418 }
419 }
420 },
421 handler (req, reply) {
422 reply.send({ ok: 1 })
423 }
424 })
425
426 fastify.ready(err => {
427 t.error(err)
428
429 fastify.inject({
430 method: 'GET',
431 url: '/',
432 headers: {
433 q: 'foo'
434 }
435 }, (err, res) => {
436 t.error(err)
437 t.equals(res.statusCode, 400)
438 })
439
440 fastify.inject({
441 method: 'GET',
442 url: '/',
443 headers: {
444 q: 10
445 }
446 }, (err, res) => {
447 t.error(err)
448 t.equals(res.statusCode, 200)
449 })
450 })
451})
452
453test('Add schema order should not break the startup', t => {
454 t.plan(1)
455 const fastify = Fastify()
456
457 fastify.get('/', { schema: { random: 'options' } }, () => {})
458
459 fastify.register(fp((f, opts) => {
460 f.addSchema({
461 $id: 'https://example.com/bson/objectId',
462 type: 'string',
463 pattern: '\\b[0-9A-Fa-f]{24}\\b'
464 })
465 return Promise.resolve() // avoid async for node 6
466 }))
467
468 fastify.get('/:id', {
469 schema: {
470 params: {
471 type: 'object',
472 properties: {
473 id: { $ref: 'https://example.com/bson/objectId#' }
474 }
475 }
476 }
477 }, () => {})
478
479 fastify.ready(err => { t.error(err) })
480})