рдЫреЛрдЯреЛрдВ рдХреЗ рд▓рд┐рдП рд╣рд╛рдкреА

Hapi.js рд╡реЗрдм рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдХреЗ рдирд┐рд░реНрдорд╛рдг рдХреЗ рд▓рд┐рдП рдПрдХ рд░реВрдкрд░реЗрдЦрд╛ рд╣реИред рдЗрд╕ рдкреЛрд╕реНрдЯ рдореЗрдВ рдПрдХ рдЧрд░реНрдо рд╢реБрд░реБрдЖрдд рдХреЗ рд▓рд┐рдП рд╕рднреА рдЖрд╡рд╢реНрдпрдХ рд╣реИрдВред рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рд▓реЗрдЦрдХ рдмрд┐рд▓реНрдХреБрд▓ рднреА рд▓реЗрдЦрдХ рдирд╣реАрдВ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдмрд╣реБрдд рд╕рд╛рд░реЗ рдХреЛрдб рдФрд░ рдХреБрдЫ рд╢рдмреНрдж рд╣реЛрдВрдЧреЗред





рдПрдорд╡реАрдкреА


рдирд┐рд░реНрднрд░рддрд╛ рдХрд╛ рдПрдХ рдЧреБрдЪреНрдЫрд╛ рд░рдЦреЛ:


npm i @hapi/hapi @hapi/boom filepaths hapi-boom-decorators 

  • hapi / hapi - рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рд╣рдорд╛рд░рд╛ рд╕рд░реНрд╡рд░
  • hapi / рдмреВрдо - рдорд╛рдирдХ рдЙрддреНрддрд░ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдореЙрдбреНрдпреВрд▓
  • рд╣реИрдкреНрдкреА-рдмреВрдо-рдбреЗрдХреЛрд░реЗрдЯрд░реНрд╕ - рд╣реЗрд▓реНрдкреА / рдмреВрдо рдХреЗ рд▓рд┐рдП рд╕рд╣рд╛рдпрдХ
  • filepaths - рдПрдХ рдЙрдкрдпреЛрдЧрд┐рддрд╛ рдЬреЛ рдкреБрдирд░рд╛рд╡рд░реНрддреА рдлрд╝реЛрд▓реНрдбрд░ рдкрдврд╝рддреА рд╣реИ

рдПрдХ рдлрд╝реЛрд▓реНрдбрд░ рд╕рдВрд░рдЪрдирд╛ рдФрд░ рдкреНрд░рд╛рд░рдВрдн рдлрд╝рд╛рдЗрд▓реЛрдВ рдХрд╛ рдПрдХ рдЧреБрдЪреНрдЫрд╛ рдмрдирд╛рдПрдБ:





//Src/routes/ рдореЗрдВ, рд╣рдо рдПрдкреАрдЖрдИ рдПрдВрдбрдкреЙрдЗрдВрдЯреНрд╕, 1 рдлрд╛рдЗрд▓ - 1 рдПрдВрдбрдкреЙрдЗрдВрдЯ рдХрд╛ рд╡рд┐рд╡рд░рдг рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ:


 // ./src/routes/home.js async function response() { // content-type            return { result: 'ok', message: 'Hello World!' }; } module.exports = { method: 'GET', //  path: '/', //  options: { handler: response // ,  ,  hapi > 17    } }; 

./src/server.js - рд╡рд╣ рдореЙрдбреНрдпреВрд▓ рдЬреЛ рд╕рд░реНрд╡рд░ рдХреЛ рд╕реНрд╡рдпрдВ рдирд┐рд░реНрдпрд╛рдд рдХрд░рддрд╛ рд╣реИред


 // ./src/server.js 'use strict'; const Hapi = require('@hapi/hapi'); const filepaths = require('filepaths'); const hapiBoomDecorators = require('hapi-boom-decorators'); const config = require('../config'); async function createServer() { //   const server = await new Hapi.Server(config.server); //   await server.register([ hapiBoomDecorators ]); //      ./src/routes/ let routes = filepaths.getSync(__dirname + '/routes/'); for(let route of routes) server.route( require(route) ); //   try { await server.start(); console.log(`Server running at: ${server.info.uri}`); } catch(err) { //    ,   console.log(JSON.stringify(err)); } //      return server; } module.exports = createServer; 

In//verver.js рд╣рдо рд╕рднреА рдХрд░рддреЗ рд╣реИрдВ рдХреЙрд▓ рдХреНрд░рд┐рдПрдЯрд╡рд░ ()


 #!/usr/bin/env node const createServer = require('./src/server'); createServer(); 

рд╣рдо рд▓реЙрдиреНрдЪ рдХрд░рддреЗ рд╣реИрдВ


 node server.js 

рдФрд░ рдЬрд╛рдВрдЪреЗрдВ:


 curl http://127.0.0.1:3030/ {"result":"ok","message":"Hello World!"} curl http://127.0.0.1:3030/test {"statusCode":404,"error":"Not Found","message":"Not Found"} 


рдЬрдВрдЧрд▓реА рдореЗрдВ


рдПрдХ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ, рдХрдо рд╕реЗ рдХрдо, рд╣рдореЗрдВ рдПрдХ рдбреЗрдЯрд╛рдмреЗрд╕, рдПрдХ рд▓рдХрдбрд╝рд╣рд╛рд░рд╛, рдкреНрд░рд╛рдзрд┐рдХрд░рдг, рддреНрд░реБрдЯрд┐ рд╕реЗ рдирд┐рдкрдЯрдиреЗ рдФрд░ рдмрд╣реБрдд рдХреБрдЫ рдЪрд╛рд╣рд┐рдПред



рд╕реАрдХреНрд╡рд▓ рдЬреЛрдбрд╝реЗрдВ


ORM рдЕрдЧрд▓реА рдХрдбрд╝реА рдХреЛ рдПрдХ рдореЙрдбреНрдпреВрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИ:


 ... const Sequelize = require('sequelize'); ... await server.register([ ... { plugin: require('hapi-sequelizejs'), options: [ { name: config.db.database, // identifier models: [__dirname + '/models/*.js'], //    //ignoredModels: [__dirname + '/server/models/**/*.js'], //  -     sequelize: new Sequelize(config.db), //  sync: true, // default false forceSync: false, // force sync (drops tables) - default false }, ] } ... ]); 

рдХреЙрд▓ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдорд╛рд░реНрдЧ рдХреЗ рдЕрдВрджрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдЙрдкрд▓рдмреНрдз рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ:


 async function response(request) { const model = request.getModel('_', '_'); } 


рдЕрдиреБрд░реЛрдз рдореЗрдВ рдЕрддрд┐рд░рд┐рдХреНрдд рдореЙрдбреНрдпреВрд▓ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░реЗрдВ


"OnRequest" рдИрд╡реЗрдВрдЯ рдХреЛ рд░реЛрдХрдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реИ, рдЬрд┐рд╕рдХреЗ рдЕрдВрджрд░ рд╣рдо рдЕрдиреБрд░реЛрдз рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рдХреЙрдиреНрдлрд┐рдЧрд░ рдФрд░ рд▓реЙрдЧрд░ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░реЗрдВрдЧреЗ:


 ... const Logger = require('./libs/Logger'); ... async function createServer(logLVL=config.logLVL) { ... const logger = new Logger(logLVL, 'my-hapi-app'); ... server.ext({ type: 'onRequest', method: async function (request, h) { request.server.config = Object.assign({}, config); request.server.logger = logger; return h.continue; } }); ... } 


рдЙрд╕рдХреЗ рдмрд╛рдж, рдЕрдиреБрд░реЛрдз рд╣реИрдВрдбрд▓рд░ рдХреЗ рдЕрдВрджрд░, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░, рд▓реЙрдЧрд░ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рддрдХ рдкрд╣реБрдВрдЪ рд╣реЛрдЧреА, рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛ рдореЙрдбреНрдпреВрд▓ рдмреЙрдбреА рдореЗрдВ рдХреБрдЫ рднреА рд╢рд╛рдорд┐рд▓ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреЗ рдмрд┐рдирд╛:


 // ./src/routes/home.js async function response(request) { //  request.server.logger.error('request error', 'something went wrong'); //  console.log(request.server.config); //   const messages = request.getModel(request.server.config.db.database, '_'); return { result: 'ok', message: 'Hello World!' }; } module.exports = { method: 'GET', //  path: '/', //  options: { handler: response // ,  ,  hapi > 17    } }; 

рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдЗрдирдкреБрдЯ рдкрд░ рдЕрдиреБрд░реЛрдз рд╣реИрдВрдбрд▓рд░ рдХреЛ рдЗрд╕рдХреЗ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╕рдм рдХреБрдЫ рдкреНрд░рд╛рдкреНрдд рд╣реЛрдЧрд╛, рдФрд░ рд╕рдордп-рд╕рдордп рдкрд░ рдПрдХ рд╣реА рдореЙрдбреНрдпреВрд▓ рдХреЛ рд╢рд╛рдорд┐рд▓ рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рдирд╣реАрдВ рд╣реЛрдЧрд╛ред



рдкреНрд░рд╛рдзрд┐рдХрд░рдг


рд╣рд╛рдкреА рдореЗрдВ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдореЙрдбреНрдпреВрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред


 ... const AuthBearer = require('hapi-auth-bearer-token'); ... async function createServer(logLVL=config.logLVL) { ... await server.register([ AuthBearer, ... ]); server.auth.strategy('token', 'bearer-access-token', {// 'token' -   ,  allowQueryToken: false, unauthorized: function() { //  ,  validate  isValid=false throw Boom.unauthorized(); }, validate: function(request, token) { if( token == 'asd' ) { return { //    isValid: true, credentials: {} }; } else { return { //   isValid: false, credentials: {} }; } } }); server.auth.default('token'); //    ... } 

рдФрд░ рдорд╛рд░реНрдЧ рдХреЗ рдЕрдВрджрд░ рдЖрдкрдХреЛ рдпрд╣ рднреА рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ рдХрд┐ рдХрд┐рд╕ рдкреНрд░рдХрд╛рд░ рдХреЗ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╣реИ:


 module.exports = { method: 'GET', path: '/', auth: 'token', //  false,     options: { handler: response } }; 

рдпрджрд┐ рдХрдИ рдкреНрд░рдХрд╛рд░ рдХреЗ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ:


 auth: { strategies: ['token1', 'token2', 'something_else'] }, 


рд╣реИрдВрдбрд▓рд┐рдВрдЧ рдореЗрдВ рддреНрд░реБрдЯрд┐


рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ, рдмреВрдо рдПрдХ рдорд╛рдирдХ рддрд░реАрдХреЗ рд╕реЗ рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЛ рдЙрддреНрдкрдиреНрди рдХрд░рддрд╛ рд╣реИ, рдЕрдХреНрд╕рд░ рдЗрди рдЙрддреНрддрд░реЛрдВ рдХреЛ рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рдкреНрд░рд╛рд░реВрдк рдореЗрдВ рд▓рдкреЗрдЯрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИред


 server.ext('onPreResponse', function (request, h) { //      Boom,     if ( !request.response.isBoom ) { return h.continue; } //  -     let responseObj = { message: request.response.output.statusCode === 401 ? 'AuthError' : 'ServerError', status: request.response.message } //     logger.error('code: ' + request.response.output.statusCode, request.response.message); return h.response(responseObj).code(request.response.output.statusCode); }); 


рдбреЗрдЯрд╛ рд╕реНрдХреАрдорд╛


рдпрд╣ рдПрдХ рдЫреЛрдЯрд╛ рд▓реЗрдХрд┐рди рдмрд╣реБрдд рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╡рд┐рд╖рдп рд╣реИред рдбреЗрдЯрд╛ рдпреЛрдЬрдирд╛рдПрдВ рдЖрдкрдХреЛ рдЕрдиреБрд░реЛрдз рдХреА рд╡реИрдзрддрд╛ рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреА рд╢реБрджреНрдзрддрд╛ рдХреА рдЬрд╛рдВрдЪ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреА рд╣реИрдВред рдЖрдк рдЗрди рдпреЛрдЬрдирд╛рдУрдВ рдХрд╛ рдХрд┐рддрдирд╛ рдЕрдЪреНрдЫрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╕реНрд╡реИрдЧрд░ рдФрд░ рдСрдЯреЛрдЯреЗрд╕реНрдЯ рдРрд╕реА рдЧреБрдгрд╡рддреНрддрд╛ рдХреЗ рд╣реЛрдВрдЧреЗред


рд╕рднреА рдбреЗрдЯрд╛ рдпреЛрдЬрдирд╛рдУрдВ рдХреЛ рдЬреЛрдИ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд╡рд░реНрдгрд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдЖрдЗрдП рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдХреЗ рд▓рд┐рдП рдПрдХ рдЙрджрд╛рд╣рд░рдг рдмрдирд╛рддреЗ рд╣реИрдВ:


 const Joi = require('@hapi/joi'); const Boom = require('boom'); async function response(request) { //   const accessTokens = request.getModel(request.server.config.db.database, 'access_tokens'); const users = request.getModel(request.server.config.db.database, 'users'); //     let userRecord = await users.findOne({ where: { email: request.query.login } }); //   ,     if ( !userRecord ) { throw Boom.unauthorized(); } // ,    if ( !userRecord.verifyPassword(request.query.password) ) { throw Boom.unauthorized();//  ,    ,    } // ,    let token = await accessTokens.createAccessToken(userRecord); //    return { meta: { total: 1 }, data: [ token.dataValues ] }; } //   ,      const tokenScheme = Joi.object({ id: Joi.number().integer().example(1), user_id: Joi.number().integer().example(2), expires_at: Joi.date().example('2019-02-16T15:38:48.243Z'), token: Joi.string().example('4443655c28b42a4349809accb3f5bc71'), updatedAt: Joi.date().example('2019-02-16T15:38:48.243Z'), createdAt: Joi.date().example('2019-02-16T15:38:48.243Z') }); //   const responseScheme = Joi.object({ meta: Joi.object({ total: Joi.number().integer().example(3) }), data: Joi.array().items(tokenScheme) }); //   const requestScheme =Joi.object({ login: Joi.string().email().required().example('pupkin@gmail.com'), password: Joi.string().required().example('12345') }); module.exports = { method: 'GET', path: '/auth', options: { handler: response, validate: { query: requestScheme }, response: { schema: responseScheme } } }; 

рдкрд░реАрдХреНрд╖рдг:


 curl -X GET "http://localhost:3030/auth?login=pupkin@gmail.com&password=12345" 




рдЕрдм рдореЗрд▓ рдХреЗ рдмрдЬрд╛рдп рднреЗрдЬреЗрдВ, рдХреЗрд╡рд▓ рд▓реЙрдЧрд┐рди рдХрд░реЗрдВ:


 curl -X GET "http://localhost:3030/auth?login=pupkin&password=12345" 




рдпрджрд┐ рдЙрддреНрддрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдпреЛрдЬрдирд╛ рд╕реЗ рдореЗрд▓ рдирд╣реАрдВ рдЦрд╛рддрд╛ рд╣реИ, рддреЛ рд╕рд░реНрд╡рд░ 500 рддреНрд░реБрдЯрд┐ рдореЗрдВ рднреА рдЧрд┐рд░ рдЬрд╛рдПрдЧрд╛ред


рдпрджрд┐ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдкреНрд░рддрд┐ рдШрдВрдЯреЗ 1 рд╕реЗ рдЕрдзрд┐рдХ рдЕрдиреБрд░реЛрдз рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░ рджреЗрддреА рд╣реИ, рддреЛ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рдУрдВ рдХреЗ рд╕рддреНрдпрд╛рдкрди рдХреЛ рд╕реАрдорд┐рдд рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ рд╕рдХрддрд╛ рд╣реИ, рдЬреИрд╕рд╛ рдХрд┐ рд╕рддреНрдпрд╛рдкрди рдПрдХ рд╕рдВрд╕рд╛рдзрди-рдЧрд╣рди рд╕рдВрдЪрд╛рд▓рди рд╣реИред рдЗрд╕рдХреЗ рд▓рд┐рдП рдПрдХ рдкреИрд░рд╛рдореАрдЯрд░ рд╣реИ: "рдирдореВрдирд╛"


 module.exports = { method: 'GET', path: '/auth', options: { handler: response, validate: { query: requestScheme }, response: { sample: 50, schema: responseScheme } } }; 

рдЬреИрд╕реЗ, рдХреЗрд╡рд▓ 50% рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рдорд╛рдиреНрдп рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛рдПрдВ рдорд┐рд▓реЗрдВрдЧреАред


рдбрд┐рдлрд╝реЙрд▓реНрдЯ рдорд╛рдиреЛрдВ рдФрд░ рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рдирд╛ рдмрд╣реБрдд рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╣реИ, рднрд╡рд┐рд╖реНрдп рдореЗрдВ рдЙрдирдХрд╛ рдЙрдкрдпреЛрдЧ рдкреНрд░рд▓реЗрдЦрди рдФрд░ рдСрдЯреЛрдЯреЗрд╕реНрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред



рд╕реНрд╡реИрдЧрд░ / рдУрдкрдирдПрдкреАрдЖрдИ


рд╣рдореЗрдВ рдЕрддрд┐рд░рд┐рдХреНрдд рдореЙрдбреНрдпреВрд▓ рдХрд╛ рдПрдХ рдЧреБрдЪреНрдЫрд╛ рдЪрд╛рд╣рд┐рдП:


 npm i hapi-swagger @hapi/inert @hapi/vision 

рд╣рдо рдЙрдиреНрд╣реЗрдВ server.js рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░рддреЗ рд╣реИрдВ


 ... const Inert = require('@hapi/inert'); const Vision = require('@hapi/vision'); const HapiSwagger = require('hapi-swagger'); const Package = require('../package'); ... const swaggerOptions = { info: { title: Package.name + ' API Documentation', description: Package.description }, jsonPath: '/documentation.json', documentationPath: '/documentation', schemes: ['https', 'http'], host: config.swaggerHost, debug: true }; ... async function createServer(logLVL=config.logLVL) { ... await server.register([ ... Inert, Vision, { plugin: HapiSwagger, options: swaggerOptions }, ... ]); ... }); 

рдФрд░ рдкреНрд░рддреНрдпреЗрдХ рдорд╛рд░реНрдЧ рдореЗрдВ рдЖрдкрдХреЛ "рдПрдкреАрдЖрдИ" рдЯреИрдЧ рд▓рдЧрд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:


 module.exports = { method: 'GET', path: '/auth', options: { handler: response, tags: [ 'api' ], //    swagger'     validate: { query: requestScheme }, response: { sample: 50, schema: responseScheme } } }; 

рдЕрдм http: // рд▓реЛрдХрд▓рд╣реЛрд╕реНрдЯ: 3030 / рдбреЙрдХреНрдпреВрдореЗрдВрдЯреЗрд╢рди рдореЗрдВ рд╡реЗрдм-рдлреЗрд╕ рдХреЗ рд╕рд╛рде рдбреЙрдХреНрдпреВрдореЗрдВрдЯреЗрд╢рди рдЙрдкрд▓рдмреНрдз рд╣реЛрдЧрд╛, рдФрд░ http: // рд▓реЛрдХрд▓рд╣реЛрд╕реНрдЯ: 3030 / рдбреЙрдХреНрдпреВрдореЗрдВрдЯреЗрд╢рдиред Jsonред рд╡рд┐рд╡рд░рдг рдХреЗ рд▓рд┐рдПред





рдСрдЯреЛ рдЯреЗрд╕реНрдЯ рдЬрдирд░реЗрд╢рди


рдпрджрд┐ рд╣рдордиреЗ рдЧреБрдгрд╛рддреНрдордХ рд░реВрдк рд╕реЗ рдЕрдиреБрд░реЛрдз рдФрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдпреЛрдЬрдирд╛рдУрдВ рдХрд╛ рд╡рд░реНрдгрди рдХрд┐рдпрд╛ рд╣реИ, рддреЛ рдЕрдиреБрд░реЛрдз рдХреЗ рдЙрджрд╛рд╣рд░рдгреЛрдВ рдореЗрдВ рд╡рд░реНрдгрд┐рдд рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХреЗ рдЕрдиреБрд░реВрдк рдПрдХ рдмреАрдЬ рдбреЗрдЯрд╛рдмреЗрд╕ рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ рд╣реИ, рдлрд┐рд░, рдкреНрд░рд╕рд┐рджреНрдз рдпреЛрдЬрдирд╛рдУрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ, рдЖрдк рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдЕрдиреБрд░реЛрдз рдЙрддреНрдкрдиреНрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рд╕рд░реНрд╡рд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХреЛрдб рдХреА рдЬрд╛рдВрдЪ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред


рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, GET рдореЗрдВ: / рд╕рд╛рдорд╛рдиреНрдп рд▓реЙрдЧрд┐рди рдФрд░ рдкрд╛рд╕рд╡рд░реНрдб рдкреИрд░рд╛рдореАрдЯрд░ рдЕрдкреЗрдХреНрд╖рд┐рдд рд╣реИрдВ, рд╣рдо рдЙрдиреНрд╣реЗрдВ рдЙрди рдЙрджрд╛рд╣рд░рдгреЛрдВ рд╕реЗ рд▓реЗрдВрдЧреЗ рдЬрд┐рдиреНрд╣реЗрдВ рд╣рдордиреЗ рдЖрд░реЗрдЦ рдореЗрдВ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд┐рдпрд╛ рд╣реИ:


 const requestScheme =Joi.object({ login: Joi.string().email().required().example('pupkin@gmail.com'), password: Joi.string().required().example('12345') }); 

рдФрд░ рдпрджрд┐ рд╕рд░реНрд╡рд░ HTTP-200-OK рдХреЗ рд╕рд╛рде рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдХрд░рддрд╛ рд╣реИ, рддреЛ рд╣рдо рдорд╛рди рд▓реЗрдВрдЧреЗ рдХрд┐ рдкрд░реАрдХреНрд╖рдг рдкрд╛рд╕ рд╣реЛ рдЧрдпрд╛ рд╣реИред


рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдХреЛрдИ рддреИрдпрд╛рд░-рдЙрдкрдпреБрдХреНрдд рдЙрдкрдпреБрдХреНрдд рдореЙрдбреНрдпреВрд▓ рдирд╣реАрдВ рдерд╛, рдЖрдкрдХреЛ рдереЛрдбрд╝реА рд╕реА рдмрд╛рдд рдХрд░рдиреА рд╣реЛрдЧреА:


 // ./test/autogenerate.js const assert = require('assert'); const rp = require('request-promise'); const filepaths = require('filepaths'); const rsync = require('sync-request'); const config = require('./../config'); const createServer = require('../src/server'); const API_URL = 'http://0.0.0.0:3030'; const AUTH_USER = { login: 'pupkin@gmail.com', pass: '12345' }; const customExamples = { 'string': 'abc', 'number': 2, 'boolean': true, 'any': null, 'date': new Date() }; const allowedStatusCodes = { 200: true, 404: true }; function getExampleValue(joiObj) { if( joiObj == null ) // if joi is null return joiObj; if( typeof(joiObj) != 'object' ) //If it's not joi object return joiObj; if( typeof(joiObj._examples) == 'undefined' ) return customExamples[ joiObj._type ]; if( joiObj._examples.length <= 0 ) return customExamples[ joiObj._type ]; return joiObj._examples[ 0 ].value; } function generateJOIObject(schema) { if( schema._type == 'object' ) return generateJOIObject(schema._inner.children); if( schema._type == 'string' ) return getExampleValue(schema); let result = {}; let _schema; if( Array.isArray(schema) ) { _schema = {}; for(let item of schema) { _schema[ item.key ] = item.schema; } } else { _schema = schema; } for(let fieldName in _schema) { if( _schema[ fieldName ]._type == 'array' ) { result[ fieldName ] = [ generateJOIObject(_schema[ fieldName ]._inner.items[ 0 ]) ]; } else { if( Array.isArray(_schema[ fieldName ]) ) { result[ fieldName ] = getExampleValue(_schema[ fieldName ][ 0 ]); } else if( _schema[ fieldName ]._type == 'object' ) { result[ fieldName ] = generateJOIObject(_schema[ fieldName ]._inner); } else { result[ fieldName ] = getExampleValue(_schema[ fieldName ]); } } } return result } function generateQuiryParams(queryObject) { let queryArray = []; for(let name in queryObject) queryArray.push(`${name}=${queryObject[name]}`); return queryArray.join('&'); } function generatePath(basicPath, paramsScheme) { let result = basicPath; if( !paramsScheme ) return result; let replaces = generateJOIObject(paramsScheme); for(let key in replaces) result = result.replace(`{${key}}`, replaces[ key ]); return result; } function genAuthHeaders() { let result = {}; let respToken = rsync('GET', API_URL + `/auth?login=${AUTH_USER.login}&password=${AUTH_USER.pass}`); let respTokenBody = JSON.parse(respToken.getBody('utf8')); result[ 'token' ] = { Authorization: 'Bearer ' + respTokenBody.data[ 0 ].token }; return result; } function generateRequest(route, authKeys) { if( !route.options.validate ) { return false; } let options = { method: route.method, url: API_URL + generatePath(route.path, route.options.validate.params) + '?' + generateQuiryParams( generateJOIObject(route.options.validate.query || {}) ), headers: authKeys[ route.options.auth ] ? authKeys[ route.options.auth ] : {}, body: generateJOIObject(route.options.validate.payload || {}), json: true, timeout: 15000 } return options; } let authKeys = genAuthHeaders(); let testSec = [ 'POST', 'PUT', 'GET', 'DELETE' ]; let routeList = []; for(let route of filepaths.getSync(__dirname + '/../src/routes/')) routeList.push(require(route)); describe('Autogenerate Hapi Routes TEST', async () => { for(let metod of testSec) for(let testRoute of routeList) { if( testRoute.method != metod ) { continue; } it(`TESTING: ${testRoute.method} ${testRoute.path}`, async function () { let options = generateRequest(testRoute, authKeys); if( !options ) return false; let statusCode = 0; try { let result = await rp( options ); statusCode = 200; } catch(err) { statusCode = err.statusCode; } if( !allowedStatusCodes[ statusCode ] ) { console.log('*** TEST STACK FOR:', `${testRoute.method} ${testRoute.path}`); console.log('options:', options); console.log('StatusCode:', statusCode); } return assert.ok(allowedStatusCodes[ statusCode ]); }); } }); 

рдирд┐рд░реНрднрд░рддрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдордд рднреВрд▓рдирд╛:


 npm i request-promise mocha sync-request 

рдФрд░ package.json рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ


 ... "scripts": { "test": "mocha", "dbinit": "node ./scripts/dbInit.js" }, ... 

рд╣рдо рдЬрд╛рдБрдЪ рдХрд░рддреЗ рд╣реИрдВ:


 npm test 



рдФрд░ рдЕрдЧрд░ рддрд╛рд▓рд╛ рдХрд┐рд╕реА рдкреНрд░рдХрд╛рд░ рдХреА рдбреЗрдЯрд╛ рдпреЛрдЬрдирд╛ рд╣реИ, рдпрд╛ рдЙрддреНрддрд░ рдпреЛрдЬрдирд╛ рд╕реЗ рдореЗрд▓ рдирд╣реАрдВ рдЦрд╛рддрд╛ рд╣реИ:





рдФрд░ рдпрд╣ рдордд рднреВрд▓реЛ рдХрд┐ рдЬрд┐рддрдиреА рдЬрд▓реНрджреА рдпрд╛ рдмрд╛рдж рдореЗрдВ рдкрд░реАрдХреНрд╖рдг рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдбреЗрдЯрд╛ рдХреЗ рдкреНрд░рддрд┐ рд╕рдВрд╡реЗрджрдирд╢реАрд▓ рд╣реЛ рдЬрд╛рдПрдВрдЧреЗред рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ, рдЖрдкрдХреЛ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЛ рдХрдо рд╕реЗ рдХрдо рдкреЛрдВрдЫрдирд╛ рд╣реЛрдЧрд╛ред


рд╕рдВрдкреВрд░реНрдг рд╕реНрд░реЛрдд

Source: https://habr.com/ru/post/hi467099/


All Articles