strong-tie/inbound-calls
0
1'use strict'2 3const sget = require('simple-get').concat4const dns = require('node:dns').promises5const stream = require('node:stream')6const { promisify } = require('node:util')7const symbols = require('../lib/symbols')8 9module.exports.sleep = promisify(setTimeout)10 11/**12 * @param method HTTP request method13 * @param t tap instance14 * @param isSetErrorHandler true: using setErrorHandler15 */16module.exports.payloadMethod = function (method, t, isSetErrorHandler = false) {17 const test = t.test18 const fastify = require('..')()19 20 if (isSetErrorHandler) {21 fastify.setErrorHandler(function (err, request, reply) {22 t.type(request, 'object')23 t.type(request, fastify[symbols.kRequest].parent)24 reply25 .code(err.statusCode)26 .type('application/json; charset=utf-8')27 .send(err)28 })29 }30 31 const upMethod = method.toUpperCase()32 const loMethod = method.toLowerCase()33 34 const schema = {35 schema: {36 response: {37 '2xx': {38 type: 'object',39 properties: {40 hello: {41 type: 'string'42 }43 }44 }45 }46 }47 }48 49 test(`${upMethod} can be created`, t => {50 t.plan(1)51 try {52 fastify[loMethod]('/', schema, function (req, reply) {53 reply.code(200).send(req.body)54 })55 t.pass()56 } catch (e) {57 t.fail()58 }59 })60 61 test(`${upMethod} without schema can be created`, t => {62 t.plan(1)63 try {64 fastify[loMethod]('/missing', function (req, reply) {65 reply.code(200).send(req.body)66 })67 t.pass()68 } catch (e) {69 t.fail()70 }71 })72 73 test(`${upMethod} with body and querystring`, t => {74 t.plan(1)75 try {76 fastify[loMethod]('/with-query', function (req, reply) {77 req.body.hello = req.body.hello + req.query.foo78 reply.code(200).send(req.body)79 })80 t.pass()81 } catch (e) {82 t.fail()83 }84 })85 86 test(`${upMethod} with bodyLimit option`, t => {87 t.plan(1)88 try {89 fastify[loMethod]('/with-limit', { bodyLimit: 1 }, function (req, reply) {90 reply.send(req.body)91 })92 t.pass()93 } catch (e) {94 t.fail()95 }96 })97 98 fastify.listen({ port: 0 }, function (err) {99 if (err) {100 t.error(err)101 return102 }103 104 t.teardown(() => { fastify.close() })105 106 test(`${upMethod} - correctly replies`, t => {107 t.plan(3)108 sget({109 method: upMethod,110 url: 'http://localhost:' + fastify.server.address().port,111 body: {112 hello: 'world'113 },114 json: true115 }, (err, response, body) => {116 t.error(err)117 t.equal(response.statusCode, 200)118 t.same(body, { hello: 'world' })119 })120 })121 122 test(`${upMethod} - correctly replies with very large body`, t => {123 t.plan(3)124 125 const largeString = 'world'.repeat(13200)126 sget({127 method: upMethod,128 url: 'http://localhost:' + fastify.server.address().port,129 body: { hello: largeString },130 json: true131 }, (err, response, body) => {132 t.error(err)133 t.equal(response.statusCode, 200)134 t.same(body, { hello: largeString })135 })136 })137 138 test(`${upMethod} - correctly replies if the content type has the charset`, t => {139 t.plan(3)140 sget({141 method: upMethod,142 url: 'http://localhost:' + fastify.server.address().port,143 body: JSON.stringify({ hello: 'world' }),144 headers: {145 'content-type': 'application/json; charset=utf-8'146 }147 }, (err, response, body) => {148 t.error(err)149 t.equal(response.statusCode, 200)150 t.same(body.toString(), JSON.stringify({ hello: 'world' }))151 })152 })153 154 test(`${upMethod} without schema - correctly replies`, t => {155 t.plan(3)156 sget({157 method: upMethod,158 url: 'http://localhost:' + fastify.server.address().port + '/missing',159 body: {160 hello: 'world'161 },162 json: true163 }, (err, response, body) => {164 t.error(err)165 t.equal(response.statusCode, 200)166 t.same(body, { hello: 'world' })167 })168 })169 170 test(`${upMethod} with body and querystring - correctly replies`, t => {171 t.plan(3)172 sget({173 method: upMethod,174 url: 'http://localhost:' + fastify.server.address().port + '/with-query?foo=hello',175 body: {176 hello: 'world'177 },178 json: true179 }, (err, response, body) => {180 t.error(err)181 t.equal(response.statusCode, 200)182 t.same(body, { hello: 'worldhello' })183 })184 })185 186 test(`${upMethod} with no body - correctly replies`, t => {187 t.plan(6)188 189 sget({190 method: upMethod,191 url: 'http://localhost:' + fastify.server.address().port + '/missing',192 headers: { 'Content-Length': '0' }193 }, (err, response, body) => {194 t.error(err)195 t.equal(response.statusCode, 200)196 t.equal(body.toString(), '')197 })198 199 // Must use inject to make a request without a Content-Length header200 fastify.inject({201 method: upMethod,202 url: '/missing'203 }, (err, res) => {204 t.error(err)205 t.equal(res.statusCode, 200)206 t.equal(res.payload.toString(), '')207 })208 })209 210 test(`${upMethod} returns 415 - incorrect media type if body is not json`, t => {211 t.plan(2)212 sget({213 method: upMethod,214 url: 'http://localhost:' + fastify.server.address().port + '/missing',215 body: 'hello world'216 217 }, (err, response, body) => {218 t.error(err)219 t.equal(response.statusCode, 415)220 })221 })222 223 if (loMethod === 'options') {224 test('OPTIONS returns 415 - should return 415 if Content-Type is not json or plain text', t => {225 t.plan(2)226 sget({227 method: upMethod,228 url: 'http://localhost:' + fastify.server.address().port + '/missing',229 body: 'hello world',230 headers: {231 'Content-Type': 'text/xml'232 }233 }, (err, response, body) => {234 t.error(err)235 t.equal(response.statusCode, 415)236 })237 })238 }239 240 test(`${upMethod} returns 400 - Bad Request`, t => {241 t.plan(4)242 243 sget({244 method: upMethod,245 url: 'http://localhost:' + fastify.server.address().port,246 body: 'hello world',247 headers: {248 'Content-Type': 'application/json'249 }250 }, (err, response, body) => {251 t.error(err)252 t.equal(response.statusCode, 400)253 })254 255 sget({256 method: upMethod,257 url: 'http://localhost:' + fastify.server.address().port,258 headers: {259 'Content-Type': 'application/json',260 'Content-Length': '0'261 }262 }, (err, response, body) => {263 t.error(err)264 t.equal(response.statusCode, 400)265 })266 })267 268 test(`${upMethod} returns 413 - Payload Too Large`, t => {269 t.plan(upMethod === 'OPTIONS' ? 4 : 6)270 271 sget({272 method: upMethod,273 url: 'http://localhost:' + fastify.server.address().port,274 headers: {275 'Content-Type': 'application/json',276 'Content-Length': 1024 * 1024 + 1277 }278 }, (err, response, body) => {279 t.error(err)280 t.equal(response.statusCode, 413)281 })282 283 // Node errors for OPTIONS requests with a stream body and no Content-Length header284 if (upMethod !== 'OPTIONS') {285 let chunk = Buffer.alloc(1024 * 1024 + 1, 0)286 const largeStream = new stream.Readable({287 read () {288 this.push(chunk)289 chunk = null290 }291 })292 sget({293 method: upMethod,294 url: 'http://localhost:' + fastify.server.address().port,295 headers: { 'Content-Type': 'application/json' },296 body: largeStream297 }, (err, response, body) => {298 t.error(err)299 t.equal(response.statusCode, 413)300 })301 }302 303 sget({304 method: upMethod,305 url: `http://localhost:${fastify.server.address().port}/with-limit`,306 headers: { 'Content-Type': 'application/json' },307 body: {},308 json: true309 }, (err, response, body) => {310 t.error(err)311 t.equal(response.statusCode, 413)312 })313 })314 315 test(`${upMethod} should fail with empty body and application/json content-type`, t => {316 if (upMethod === 'OPTIONS') return t.end()317 318 t.plan(12)319 320 fastify.inject({321 method: `${upMethod}`,322 url: '/',323 headers: {324 'Content-Type': 'application/json'325 }326 }, (err, res) => {327 t.error(err)328 t.same(JSON.parse(res.payload), {329 error: 'Bad Request',330 code: 'FST_ERR_CTP_EMPTY_JSON_BODY',331 message: 'Body cannot be empty when content-type is set to \'application/json\'',332 statusCode: 400333 })334 })335 336 sget({337 method: upMethod,338 url: `http://localhost:${fastify.server.address().port}`,339 headers: {340 'Content-Type': 'application/json'341 }342 }, (err, res, body) => {343 t.error(err)344 t.same(JSON.parse(body.toString()), {345 error: 'Bad Request',346 code: 'FST_ERR_CTP_EMPTY_JSON_BODY',347 message: 'Body cannot be empty when content-type is set to \'application/json\'',348 statusCode: 400349 })350 })351 352 fastify.inject({353 method: `${upMethod}`,354 url: '/',355 headers: {356 'Content-Type': 'application/json'357 },358 payload: null359 }, (err, res) => {360 t.error(err)361 t.same(JSON.parse(res.payload), {362 error: 'Bad Request',363 code: 'FST_ERR_CTP_EMPTY_JSON_BODY',364 message: 'Body cannot be empty when content-type is set to \'application/json\'',365 statusCode: 400366 })367 })368 369 sget({370 method: upMethod,371 url: `http://localhost:${fastify.server.address().port}`,372 headers: {373 'Content-Type': 'application/json'374 },375 payload: null376 }, (err, res, body) => {377 t.error(err)378 t.same(JSON.parse(body.toString()), {379 error: 'Bad Request',380 code: 'FST_ERR_CTP_EMPTY_JSON_BODY',381 message: 'Body cannot be empty when content-type is set to \'application/json\'',382 statusCode: 400383 })384 })385 386 fastify.inject({387 method: `${upMethod}`,388 url: '/',389 headers: {390 'Content-Type': 'application/json'391 },392 payload: undefined393 }, (err, res) => {394 t.error(err)395 t.same(JSON.parse(res.payload), {396 error: 'Bad Request',397 code: 'FST_ERR_CTP_EMPTY_JSON_BODY',398 message: 'Body cannot be empty when content-type is set to \'application/json\'',399 statusCode: 400400 })401 })402 403 sget({404 method: upMethod,405 url: `http://localhost:${fastify.server.address().port}`,406 headers: {407 'Content-Type': 'application/json'408 },409 payload: undefined410 }, (err, res, body) => {411 t.error(err)412 t.same(JSON.parse(body.toString()), {413 error: 'Bad Request',414 code: 'FST_ERR_CTP_EMPTY_JSON_BODY',415 message: 'Body cannot be empty when content-type is set to \'application/json\'',416 statusCode: 400417 })418 })419 })420 })421}422 423function lookupToIp (lookup) {424 return lookup.family === 6 ? `[${lookup.address}]` : lookup.address425}426 427module.exports.getLoopbackHost = async () => {428 const lookup = await dns.lookup('localhost')429 return [lookup.address, lookupToIp(lookup)]430}431 432module.exports.plainTextParser = function (request, callback) {433 let body = ''434 request.setEncoding('utf8')435 request.on('error', onError)436 request.on('data', onData)437 request.on('end', onEnd)438 function onError (err) {439 callback(err, null)440 }441 function onData (chunk) {442 body += chunk443 }444 function onEnd () {445 callback(null, body)446 }447}448 449module.exports.getServerUrl = function (app) {450 const { address, port } = app.server.address()451 return address === '::1'452 ? `http://[${address}]:${port}`453 : `http://${address}:${port}`454}455 