strong-tie/inbound-calls
0
1'use strict'2 3const { test } = require('node:test')4 5const Ajv = require('ajv')6const ajv = new Ajv({ coerceTypes: true })7 8const validation = require('../../lib/validation')9const { normalizeSchema } = require('../../lib/schemas')10const symbols = require('../../lib/validation').symbols11const { kSchemaVisited } = require('../../lib/symbols')12 13test('Symbols', t => {14 t.plan(5)15 t.assert.strictEqual(typeof symbols.responseSchema, 'symbol')16 t.assert.strictEqual(typeof symbols.bodySchema, 'symbol')17 t.assert.strictEqual(typeof symbols.querystringSchema, 'symbol')18 t.assert.strictEqual(typeof symbols.paramsSchema, 'symbol')19 t.assert.strictEqual(typeof symbols.headersSchema, 'symbol')20})21 22;['compileSchemasForValidation',23 'compileSchemasForSerialization'].forEach(func => {24 test(`${func} schema - missing schema`, t => {25 t.plan(2)26 const context = {}27 validation[func](context)28 t.assert.strictEqual(typeof context[symbols.bodySchema], 'undefined')29 t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined')30 })31 32 test(`${func} schema - missing output schema`, t => {33 t.plan(1)34 const context = { schema: {} }35 validation[func](context, null)36 t.assert.strictEqual(typeof context[symbols.responseSchema], 'undefined')37 })38})39 40test('build schema - output schema', t => {41 t.plan(2)42 const opts = {43 schema: {44 response: {45 '2xx': {46 type: 'object',47 properties: {48 hello: { type: 'string' }49 }50 },51 201: {52 type: 'object',53 properties: {54 hello: { type: 'number' }55 }56 }57 }58 }59 }60 validation.compileSchemasForSerialization(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))61 t.assert.strictEqual(typeof opts[symbols.responseSchema]['2xx'], 'function')62 t.assert.strictEqual(typeof opts[symbols.responseSchema]['201'], 'function')63})64 65test('build schema - body schema', t => {66 t.plan(1)67 const opts = {68 schema: {69 body: {70 type: 'object',71 properties: {72 hello: { type: 'string' }73 }74 }75 }76 }77 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))78 t.assert.strictEqual(typeof opts[symbols.bodySchema], 'function')79})80 81test('build schema - body with multiple content type schemas', t => {82 t.plan(2)83 const opts = {84 schema: {85 body: {86 content: {87 'application/json': {88 schema: {89 type: 'object',90 properties: {91 hello: { type: 'string' }92 }93 }94 },95 'text/plain': {96 schema: { type: 'string' }97 }98 }99 }100 }101 }102 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))103 t.assert.ok(opts[symbols.bodySchema]['application/json'], 'function')104 t.assert.ok(opts[symbols.bodySchema]['text/plain'], 'function')105})106 107test('build schema - avoid repeated normalize schema', t => {108 t.plan(3)109 const serverConfig = {}110 const opts = {111 schema: {112 query: {113 type: 'object',114 properties: {115 hello: { type: 'string' }116 }117 }118 }119 }120 opts.schema = normalizeSchema(opts.schema, serverConfig)121 t.assert.notStrictEqual(kSchemaVisited, undefined)122 t.assert.strictEqual(opts.schema[kSchemaVisited], true)123 t.assert.strictEqual(opts.schema, normalizeSchema(opts.schema, serverConfig))124})125 126test('build schema - query schema', t => {127 t.plan(2)128 const serverConfig = {}129 const opts = {130 schema: {131 query: {132 type: 'object',133 properties: {134 hello: { type: 'string' }135 }136 }137 }138 }139 opts.schema = normalizeSchema(opts.schema, serverConfig)140 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))141 t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')142 t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')143})144 145test('build schema - query schema abbreviated', t => {146 t.plan(2)147 const serverConfig = {}148 const opts = {149 schema: {150 query: {151 type: 'object',152 properties: {153 hello: { type: 'string' }154 }155 }156 }157 }158 opts.schema = normalizeSchema(opts.schema, serverConfig)159 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))160 t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')161 t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')162})163 164test('build schema - querystring schema', t => {165 t.plan(2)166 const opts = {167 schema: {168 querystring: {169 type: 'object',170 properties: {171 hello: { type: 'string' }172 }173 }174 }175 }176 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))177 t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')178 t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')179})180 181test('build schema - querystring schema abbreviated', t => {182 t.plan(2)183 const serverConfig = {}184 const opts = {185 schema: {186 querystring: {187 type: 'object',188 properties: {189 hello: { type: 'string' }190 }191 }192 }193 }194 opts.schema = normalizeSchema(opts.schema, serverConfig)195 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))196 t.assert.ok(typeof opts[symbols.querystringSchema].schema.type === 'string')197 t.assert.strictEqual(typeof opts[symbols.querystringSchema], 'function')198})199 200test('build schema - must throw if querystring and query schema exist', t => {201 t.plan(2)202 try {203 const serverConfig = {}204 const opts = {205 schema: {206 query: {207 type: 'object',208 properties: {209 hello: { type: 'string' }210 }211 },212 querystring: {213 type: 'object',214 properties: {215 hello: { type: 'string' }216 }217 }218 }219 }220 opts.schema = normalizeSchema(opts.schema, serverConfig)221 } catch (err) {222 t.assert.strictEqual(err.code, 'FST_ERR_SCH_DUPLICATE')223 t.assert.strictEqual(err.message, 'Schema with \'querystring\' already present!')224 }225})226 227test('build schema - params schema', t => {228 t.plan(1)229 const opts = {230 schema: {231 params: {232 type: 'object',233 properties: {234 hello: { type: 'string' }235 }236 }237 }238 }239 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))240 t.assert.strictEqual(typeof opts[symbols.paramsSchema], 'function')241})242 243test('build schema - headers schema', t => {244 t.plan(1)245 const opts = {246 schema: {247 headers: {248 type: 'object',249 properties: {250 'content-type': { type: 'string' }251 }252 }253 }254 }255 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))256 t.assert.strictEqual(typeof opts[symbols.headersSchema], 'function')257})258 259test('build schema - headers are lowercase', t => {260 t.plan(1)261 const opts = {262 schema: {263 headers: {264 type: 'object',265 properties: {266 'Content-Type': { type: 'string' }267 }268 }269 }270 }271 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {272 t.assert.ok(schema.properties['content-type'], 'lowercase content-type exists')273 return () => { }274 })275})276 277test('build schema - headers are not lowercased in case of custom object', t => {278 t.plan(1)279 280 class Headers { }281 const opts = {282 schema: {283 headers: new Headers()284 }285 }286 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {287 t.assert.ok(schema, Headers)288 return () => { }289 })290})291 292test('build schema - headers are not lowercased in case of custom validator provided', t => {293 t.plan(1)294 295 class Headers { }296 const opts = {297 schema: {298 headers: new Headers()299 }300 }301 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {302 t.assert.ok(schema, Headers)303 return () => { }304 }, true)305})306 307test('build schema - uppercased headers are not included', t => {308 t.plan(1)309 const opts = {310 schema: {311 headers: {312 type: 'object',313 properties: {314 'Content-Type': { type: 'string' }315 }316 }317 }318 }319 validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => {320 t.assert.ok(!('Content-Type' in schema.properties), 'uppercase does not exist')321 return () => { }322 })323})324 325test('build schema - mixed schema types are individually skipped or normalized', t => {326 t.plan(2)327 328 class CustomSchemaClass { }329 330 const testCases = [{331 schema: {332 body: new CustomSchemaClass()333 },334 assertions: (schema) => {335 t.assert.ok(schema.body, CustomSchemaClass)336 }337 }, {338 schema: {339 response: {340 200: new CustomSchemaClass()341 }342 },343 assertions: (schema) => {344 t.assert.ok(schema.response[200], CustomSchemaClass)345 }346 }]347 348 testCases.forEach((testCase) => {349 const result = normalizeSchema(testCase.schema, {})350 testCase.assertions(result)351 })352})353 