You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					824 lines
				
				17 KiB
			
		
		
			
		
	
	
					824 lines
				
				17 KiB
			| 
											3 years ago
										 | 'use strict' | ||
|  | 
 | ||
|  | const { test } = require('tap') | ||
|  | const Joi = require('@hapi/joi') | ||
|  | const AJV = require('ajv') | ||
|  | const S = require('fluent-json-schema') | ||
|  | const Fastify = require('..') | ||
|  | const ajvMergePatch = require('ajv-merge-patch') | ||
|  | const ajvErrors = require('ajv-errors') | ||
|  | 
 | ||
|  | const buildValidatorAJV8 = require('@fastify/ajv-compiler-8') | ||
|  | 
 | ||
|  | test('Ajv8 usage instead of the bundle one', t => { | ||
|  |   t.plan(2) | ||
|  | 
 | ||
|  |   t.test('use new ajv8 option', t => { | ||
|  |     t.plan(2) | ||
|  |     const fastify = Fastify({ | ||
|  |       ajv: { | ||
|  |         customOptions: { strictRequired: true } | ||
|  |       }, | ||
|  |       schemaController: { | ||
|  |         compilersFactory: { | ||
|  |           buildValidator: buildValidatorAJV8() | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       schema: { | ||
|  |         body: { | ||
|  |           type: 'object', | ||
|  |           required: ['missing'], | ||
|  |           properties: { | ||
|  |             foo: { | ||
|  |               type: 'string' | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       }, | ||
|  |       handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.ready(err => { | ||
|  |       t.ok(err) | ||
|  |       t.match(err.message, 'strictRequired', 'the new ajv8 option trigger a startup error') | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   t.test('use new ajv8 option within a response schema', t => { | ||
|  |     t.plan(2) | ||
|  |     const fastify = Fastify({ | ||
|  |       schemaController: { | ||
|  |         compilersFactory: { | ||
|  |           buildValidator: buildValidatorAJV8() | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.post('/', { | ||
|  |       schema: { | ||
|  |         body: { | ||
|  |           type: 'object', | ||
|  |           required: ['missing'], | ||
|  |           properties: { | ||
|  |             foo: { | ||
|  |               type: 'string' | ||
|  |             } | ||
|  |           } | ||
|  |         }, | ||
|  |         response: { | ||
|  |           '2xx': { | ||
|  |             type: 'object', | ||
|  |             properties: { | ||
|  |               ok: { | ||
|  |                 type: 'integer' | ||
|  |               } | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       }, | ||
|  |       handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.ready(err => { | ||
|  |       t.error(err) | ||
|  |       t.pass('startup successful') | ||
|  |     }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('Ajv8 usage with plugins', t => { | ||
|  |   t.plan(2) | ||
|  | 
 | ||
|  |   t.test('use new ajv8 option', t => { | ||
|  |     t.plan(3) | ||
|  |     const fastify = Fastify({ | ||
|  |       ajv: { | ||
|  |         customOptions: { validateFormats: true }, | ||
|  |         plugins: [require('ajv-formats')] | ||
|  |       }, | ||
|  |       schemaController: { | ||
|  |         compilersFactory: { | ||
|  |           buildValidator: buildValidatorAJV8() | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  | 
 | ||
|  |     callIt(fastify, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 400) | ||
|  |       t.equal(res.json().message, 'body must match format "date"') | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   t.test('use new ajv8 option - avoid check', t => { | ||
|  |     t.plan(2) | ||
|  |     const fastify = Fastify({ | ||
|  |       ajv: { | ||
|  |         customOptions: { validateFormats: false } | ||
|  |       }, | ||
|  |       schemaController: { | ||
|  |         compilersFactory: { | ||
|  |           buildValidator: buildValidatorAJV8() | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  | 
 | ||
|  |     callIt(fastify, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 200) | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   function callIt (fastify, cb) { | ||
|  |     fastify.post('/', { | ||
|  |       schema: { | ||
|  |         body: { | ||
|  |           type: 'object', | ||
|  |           properties: { | ||
|  |             foo: { | ||
|  |               type: 'string', | ||
|  |               format: 'date' | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       }, | ||
|  |       handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { foo: '99' } | ||
|  |     }, cb) | ||
|  |   } | ||
|  | }) | ||
|  | 
 | ||
|  | test('Ajv plugins array parameter', t => { | ||
|  |   t.plan(3) | ||
|  |   const fastify = Fastify({ | ||
|  |     ajv: { | ||
|  |       customOptions: { | ||
|  |         jsonPointers: true, | ||
|  |         allErrors: true | ||
|  |       }, | ||
|  |       plugins: [ | ||
|  |         [ajvErrors, { singleError: '@@@@' }] | ||
|  |       ] | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.post('/', { | ||
|  |     schema: { | ||
|  |       body: { | ||
|  |         type: 'object', | ||
|  |         properties: { | ||
|  |           foo: { | ||
|  |             type: 'number', | ||
|  |             minimum: 2, | ||
|  |             maximum: 10, | ||
|  |             multipleOf: 2, | ||
|  |             errorMessage: { | ||
|  |               type: 'should be number', | ||
|  |               minimum: 'should be >= 2', | ||
|  |               maximum: 'should be <= 10', | ||
|  |               multipleOf: 'should be multipleOf 2' | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject({ | ||
|  |     method: 'POST', | ||
|  |     url: '/', | ||
|  |     payload: { foo: 99 } | ||
|  |   }, (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 400) | ||
|  |     t.equal(res.json().message, 'body/foo should be <= 10@@@@should be multipleOf 2') | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('Should handle root $merge keywords in header', t => { | ||
|  |   t.plan(5) | ||
|  |   const fastify = Fastify({ | ||
|  |     ajv: { | ||
|  |       plugins: [ | ||
|  |         ajvMergePatch | ||
|  |       ] | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.route({ | ||
|  |     method: 'GET', | ||
|  |     url: '/', | ||
|  |     schema: { | ||
|  |       headers: { | ||
|  |         $merge: { | ||
|  |           source: { | ||
|  |             type: 'object', | ||
|  |             properties: { | ||
|  |               q: { type: 'string' } | ||
|  |             } | ||
|  |           }, | ||
|  |           with: { required: ['q'] } | ||
|  |         } | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.ready(err => { | ||
|  |     t.error(err) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'GET', | ||
|  |       url: '/' | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 400) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'GET', | ||
|  |       url: '/', | ||
|  |       headers: { q: 'foo' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 200) | ||
|  |     }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('Should handle root $patch keywords in header', t => { | ||
|  |   t.plan(5) | ||
|  |   const fastify = Fastify({ | ||
|  |     ajv: { | ||
|  |       plugins: [ | ||
|  |         ajvMergePatch | ||
|  |       ] | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.route({ | ||
|  |     method: 'GET', | ||
|  |     url: '/', | ||
|  |     schema: { | ||
|  |       headers: { | ||
|  |         $patch: { | ||
|  |           source: { | ||
|  |             type: 'object', | ||
|  |             properties: { | ||
|  |               q: { type: 'string' } | ||
|  |             } | ||
|  |           }, | ||
|  |           with: [ | ||
|  |             { | ||
|  |               op: 'add', | ||
|  |               path: '/properties/q', | ||
|  |               value: { type: 'number' } | ||
|  |             } | ||
|  |           ] | ||
|  |         } | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.ready(err => { | ||
|  |     t.error(err) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'GET', | ||
|  |       url: '/', | ||
|  |       headers: { | ||
|  |         q: 'foo' | ||
|  |       } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 400) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'GET', | ||
|  |       url: '/', | ||
|  |       headers: { q: 10 } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 200) | ||
|  |     }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('Should handle $merge keywords in body', t => { | ||
|  |   t.plan(5) | ||
|  |   const fastify = Fastify({ | ||
|  |     ajv: { | ||
|  |       plugins: [ajvMergePatch] | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.post('/', { | ||
|  |     schema: { | ||
|  |       body: { | ||
|  |         $merge: { | ||
|  |           source: { | ||
|  |             type: 'object', | ||
|  |             properties: { | ||
|  |               q: { | ||
|  |                 type: 'string' | ||
|  |               } | ||
|  |             } | ||
|  |           }, | ||
|  |           with: { | ||
|  |             required: ['q'] | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.ready(err => { | ||
|  |     t.error(err) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/' | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 400) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { q: 'foo' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 200) | ||
|  |     }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('Should handle $patch keywords in body', t => { | ||
|  |   t.plan(5) | ||
|  |   const fastify = Fastify({ | ||
|  |     ajv: { | ||
|  |       plugins: [ajvMergePatch] | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.post('/', { | ||
|  |     schema: { | ||
|  |       body: { | ||
|  |         $patch: { | ||
|  |           source: { | ||
|  |             type: 'object', | ||
|  |             properties: { | ||
|  |               q: { | ||
|  |                 type: 'string' | ||
|  |               } | ||
|  |             } | ||
|  |           }, | ||
|  |           with: [ | ||
|  |             { | ||
|  |               op: 'add', | ||
|  |               path: '/properties/q', | ||
|  |               value: { type: 'number' } | ||
|  |             } | ||
|  |           ] | ||
|  |         } | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, reply) { reply.send({ ok: 1 }) } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.ready(err => { | ||
|  |     t.error(err) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { q: 'foo' } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 400) | ||
|  |     }) | ||
|  | 
 | ||
|  |     fastify.inject({ | ||
|  |       method: 'POST', | ||
|  |       url: '/', | ||
|  |       payload: { q: 10 } | ||
|  |     }, (err, res) => { | ||
|  |       t.error(err) | ||
|  |       t.equal(res.statusCode, 200) | ||
|  |     }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test("serializer read validator's schemas", t => { | ||
|  |   t.plan(4) | ||
|  |   const ajvInstance = new AJV() | ||
|  | 
 | ||
|  |   const baseSchema = { | ||
|  |     $id: 'http://example.com/schemas/base', | ||
|  |     definitions: { | ||
|  |       hello: { type: 'string' } | ||
|  |     }, | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: '#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const refSchema = { | ||
|  |     $id: 'http://example.com/schemas/ref', | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: 'http://example.com/schemas/base#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   ajvInstance.addSchema(baseSchema) | ||
|  |   ajvInstance.addSchema(refSchema) | ||
|  | 
 | ||
|  |   const fastify = Fastify({ | ||
|  |     schemaController: { | ||
|  |       bucket: function factory (storeInit) { | ||
|  |         t.notOk(storeInit, 'is always empty because fastify.addSchema is not called') | ||
|  |         return { | ||
|  |           getSchemas () { | ||
|  |             return { | ||
|  |               [baseSchema.$id]: ajvInstance.getSchema(baseSchema.$id).schema, | ||
|  |               [refSchema.$id]: ajvInstance.getSchema(refSchema.$id).schema | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.setValidatorCompiler(function ({ schema }) { | ||
|  |     return ajvInstance.compile(schema) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.get('/', { | ||
|  |     schema: { | ||
|  |       response: { | ||
|  |         '2xx': ajvInstance.getSchema('http://example.com/schemas/ref').schema | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, res) { res.send({ hello: 'world', evict: 'this' }) } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject('/', (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(res.json(), { hello: 'world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('setSchemaController in a plugin', t => { | ||
|  |   t.plan(5) | ||
|  |   const baseSchema = { | ||
|  |     $id: 'urn:schema:base', | ||
|  |     definitions: { | ||
|  |       hello: { type: 'string' } | ||
|  |     }, | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: '#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const refSchema = { | ||
|  |     $id: 'urn:schema:ref', | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: 'urn:schema:base#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const ajvInstance = new AJV() | ||
|  |   ajvInstance.addSchema(baseSchema) | ||
|  |   ajvInstance.addSchema(refSchema) | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  |   fastify.register(schemaPlugin) | ||
|  |   fastify.get('/', { | ||
|  |     schema: { | ||
|  |       query: ajvInstance.getSchema('urn:schema:ref').schema, | ||
|  |       response: { | ||
|  |         '2xx': ajvInstance.getSchema('urn:schema:ref').schema | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, res) { | ||
|  |       res.send({ hello: 'world', evict: 'this' }) | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject('/', (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(res.json(), { hello: 'world' }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   async function schemaPlugin (server) { | ||
|  |     server.setSchemaController({ | ||
|  |       bucket () { | ||
|  |         t.pass('the bucket is created') | ||
|  |         return { | ||
|  |           addSchema (source) { | ||
|  |             ajvInstance.addSchema(source) | ||
|  |           }, | ||
|  |           getSchema (id) { | ||
|  |             return ajvInstance.getSchema(id).schema | ||
|  |           }, | ||
|  |           getSchemas () { | ||
|  |             return { | ||
|  |               'urn:schema:base': baseSchema, | ||
|  |               'urn:schema:ref': refSchema | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  |     server.setValidatorCompiler(function ({ schema }) { | ||
|  |       t.pass('the querystring schema is compiled') | ||
|  |       return ajvInstance.compile(schema) | ||
|  |     }) | ||
|  |   } | ||
|  |   schemaPlugin[Symbol.for('skip-override')] = true | ||
|  | }) | ||
|  | 
 | ||
|  | test('side effect on schema let the server crash', async t => { | ||
|  |   const firstSchema = { | ||
|  |     $id: 'example1', | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       name: { | ||
|  |         type: 'string' | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const reusedSchema = { | ||
|  |     $id: 'example2', | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       name: { | ||
|  |         oneOf: [ | ||
|  |           { | ||
|  |             $ref: 'example1' | ||
|  |           } | ||
|  |         ] | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  |   fastify.addSchema(firstSchema) | ||
|  | 
 | ||
|  |   fastify.post('/a', { | ||
|  |     handler: async () => 'OK', | ||
|  |     schema: { | ||
|  |       body: reusedSchema, | ||
|  |       response: { 200: reusedSchema } | ||
|  |     } | ||
|  |   }) | ||
|  |   fastify.post('/b', { | ||
|  |     handler: async () => 'OK', | ||
|  |     schema: { | ||
|  |       body: reusedSchema, | ||
|  |       response: { 200: reusedSchema } | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   await fastify.ready() | ||
|  | }) | ||
|  | 
 | ||
|  | test('only response schema trigger AJV pollution', async t => { | ||
|  |   const ShowSchema = S.object().id('ShowSchema').prop('name', S.string()) | ||
|  |   const ListSchema = S.array().id('ListSchema').items(S.ref('ShowSchema#')) | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  |   fastify.addSchema(ListSchema) | ||
|  |   fastify.addSchema(ShowSchema) | ||
|  | 
 | ||
|  |   const routeResponseSchemas = { | ||
|  |     schema: { response: { 200: S.ref('ListSchema#') } } | ||
|  |   } | ||
|  | 
 | ||
|  |   fastify.register( | ||
|  |     async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, | ||
|  |     { prefix: '/prefix1' } | ||
|  |   ) | ||
|  |   fastify.register( | ||
|  |     async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, | ||
|  |     { prefix: '/prefix2' } | ||
|  |   ) | ||
|  | 
 | ||
|  |   await fastify.ready() | ||
|  | }) | ||
|  | 
 | ||
|  | test('only response schema trigger AJV pollution #2', async t => { | ||
|  |   const ShowSchema = S.object().id('ShowSchema').prop('name', S.string()) | ||
|  |   const ListSchema = S.array().id('ListSchema').items(S.ref('ShowSchema#')) | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  |   fastify.addSchema(ListSchema) | ||
|  |   fastify.addSchema(ShowSchema) | ||
|  | 
 | ||
|  |   const routeResponseSchemas = { | ||
|  |     schema: { | ||
|  |       params: S.ref('ListSchema#'), | ||
|  |       response: { 200: S.ref('ListSchema#') } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   fastify.register( | ||
|  |     async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, | ||
|  |     { prefix: '/prefix1' } | ||
|  |   ) | ||
|  |   fastify.register( | ||
|  |     async (app) => { app.get('/resource/', routeResponseSchemas, () => ({})) }, | ||
|  |     { prefix: '/prefix2' } | ||
|  |   ) | ||
|  | 
 | ||
|  |   await fastify.ready() | ||
|  | }) | ||
|  | 
 | ||
|  | test('setSchemaController in a plugin with head routes', t => { | ||
|  |   t.plan(6) | ||
|  |   const baseSchema = { | ||
|  |     $id: 'urn:schema:base', | ||
|  |     definitions: { | ||
|  |       hello: { type: 'string' } | ||
|  |     }, | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: '#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const refSchema = { | ||
|  |     $id: 'urn:schema:ref', | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: 'urn:schema:base#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const ajvInstance = new AJV() | ||
|  |   ajvInstance.addSchema(baseSchema) | ||
|  |   ajvInstance.addSchema(refSchema) | ||
|  | 
 | ||
|  |   const fastify = Fastify({ exposeHeadRoutes: true }) | ||
|  |   fastify.register(schemaPlugin) | ||
|  |   fastify.get('/', { | ||
|  |     schema: { | ||
|  |       query: ajvInstance.getSchema('urn:schema:ref').schema, | ||
|  |       response: { | ||
|  |         '2xx': ajvInstance.getSchema('urn:schema:ref').schema | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, res) { | ||
|  |       res.send({ hello: 'world', evict: 'this' }) | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject('/', (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(res.json(), { hello: 'world' }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   async function schemaPlugin (server) { | ||
|  |     server.setSchemaController({ | ||
|  |       bucket () { | ||
|  |         t.pass('the bucket is created') | ||
|  |         return { | ||
|  |           addSchema (source) { | ||
|  |             ajvInstance.addSchema(source) | ||
|  |           }, | ||
|  |           getSchema (id) { | ||
|  |             return ajvInstance.getSchema(id).schema | ||
|  |           }, | ||
|  |           getSchemas () { | ||
|  |             return { | ||
|  |               'urn:schema:base': baseSchema, | ||
|  |               'urn:schema:ref': refSchema | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  |     server.setValidatorCompiler(function ({ schema }) { | ||
|  |       if (schema.$id) { | ||
|  |         const stored = ajvInstance.getSchema(schema.$id) | ||
|  |         if (stored) { | ||
|  |           t.pass('the schema is reused') | ||
|  |           return stored | ||
|  |         } | ||
|  |       } | ||
|  |       t.pass('the schema is compiled') | ||
|  | 
 | ||
|  |       return ajvInstance.compile(schema) | ||
|  |     }) | ||
|  |   } | ||
|  |   schemaPlugin[Symbol.for('skip-override')] = true | ||
|  | }) | ||
|  | 
 | ||
|  | test('multiple refs with the same ids', t => { | ||
|  |   t.plan(3) | ||
|  |   const baseSchema = { | ||
|  |     $id: 'urn:schema:base', | ||
|  |     definitions: { | ||
|  |       hello: { type: 'string' } | ||
|  |     }, | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: '#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const refSchema = { | ||
|  |     $id: 'urn:schema:ref', | ||
|  |     type: 'object', | ||
|  |     properties: { | ||
|  |       hello: { $ref: 'urn:schema:base#/definitions/hello' } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  | 
 | ||
|  |   fastify.addSchema(baseSchema) | ||
|  |   fastify.addSchema(refSchema) | ||
|  |   fastify.get('/', { | ||
|  |     schema: { | ||
|  |       query: refSchema, | ||
|  |       response: { | ||
|  |         '2xx': refSchema | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, res) { | ||
|  |       res.send({ hello: 'world', evict: 'this' }) | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.head('/', { | ||
|  |     schema: { | ||
|  |       query: refSchema, | ||
|  |       response: { | ||
|  |         '2xx': refSchema | ||
|  |       } | ||
|  |     }, | ||
|  |     handler (req, res) { | ||
|  |       res.send({ hello: 'world', evict: 'this' }) | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject('/', (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(res.json(), { hello: 'world' }) | ||
|  |   }) | ||
|  | }) | ||
|  | 
 | ||
|  | test('JOI validation overwrite request headers', t => { | ||
|  |   t.plan(3) | ||
|  |   const schemaValidator = ({ schema }) => data => { | ||
|  |     const validationResult = schema.validate(data) | ||
|  |     return validationResult | ||
|  |   } | ||
|  | 
 | ||
|  |   const fastify = Fastify() | ||
|  |   fastify.setValidatorCompiler(schemaValidator) | ||
|  | 
 | ||
|  |   fastify.get('/', { | ||
|  |     schema: { | ||
|  |       headers: Joi.object({ | ||
|  |         'user-agent': Joi.string().required(), | ||
|  |         host: Joi.string().required() | ||
|  |       }) | ||
|  |     } | ||
|  |   }, (request, reply) => { | ||
|  |     reply.send(request.headers) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.inject('/', (err, res) => { | ||
|  |     t.error(err) | ||
|  |     t.equal(res.statusCode, 200) | ||
|  |     t.same(res.json(), { | ||
|  |       'user-agent': 'lightMyRequest', | ||
|  |       host: 'localhost:80' | ||
|  |     }) | ||
|  |   }) | ||
|  | }) |