А так же о всякой фигне
Т.к. в каждом роуте мы уже описали схему запроса и схему ответа, то мы можем автоматически сгенерировать запрос на каждый роут, и проверить, правильно ли отвечает сервер.
На самом деле, сервер сам будет проверять себя, а мы будем проверять только коды ответа.
И так, возьмём за основу наш прошлый проектик: https://github.com/hololoev/api_hapi_example_5 и немного его подготовим:
1. Нам нужно вынести инициализацию сервера от самого модуля, который описывает сервер. Для этого уносим оригинальный server.js в ./src и из него экспортим createServer() функцию, а в корне, в server.js оставляем только инициализацию сервера:
#!/usr/bin/env node 'use strict'; const createServer = require('./src/server'); createServer();
2. Ставим mocha и подправляем package.json
npm i mocha
... "scripts": { "dbinit": "node ./scripts/dbInit.js", "lint": "eslint --ext .js *.js ./src/", "lint-fix": "eslint --fix --ext .js *.js ./src/", "test": "mocha" }, ...
3. Создаём папку "test" и в ней уже скриптик autogenerate.js
Алгоритм работы будет следующий:
let authKeys = genAuthHeaders();
let routeList = []; for(let route of filepaths.getSync(__dirname + '/../src/routes/')) routeList.push(require(route));
... for(let metod of testSec) for(let testRoute of routeList) { ...
... 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 ]); ...
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 AUTH_GGROUP = { api_key: '00:dd:fa:de', name: 'autotest-gate', pub_key: '---fake---key' }; 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; } if( testRoute.path.indexOf('cron') > -1 ) { // skip cron continue; } if (testRoute.path === '/api/decipher') { 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 ]); }); } });
В случае, если в роуте описаны примеры запроса, то испульзуем их, если нет, то юзаем примеры из:
const customExamples = { 'string': 'abc', 'number': 2, 'boolean': true, 'any': null, 'date': new Date() };
Ну и запускаются тесты стандартно:
npm test
И если запрос вернёт ответ с "плохим" кодом ответа, то тест отвалится:
Этот простенький скриптик, съэкономит кучу времени на написании автотестов. Но, конечно, он не сможет полностью заменить настоящие автотесты, специально бьющие по всяким слабым местам.
ВАЖНО! качество автогенератора автотестов напрямую завязано на то, как качественно Вы опишите входные и выходные схемы данных.
Исходники, как обычно на гите: https://github.com/hololoev/api_hapi_example_6
Вызвали в налоговую для дачи пояснений по уплате налогов ОПС и ОМС
Лол, прилетел вызов в налоговую для дачи пояснений по уплате ОПС и ОМС. Это при том, что я эти взносы оплатил в полном объёме ещё в середине января.
А что если сделать вебсайт на голом html?
На самом деле 90% всяких лендингов и сайтов-визиток можно сделать на голом html, без использования всяких CMS, фреймворков итд. Ведь типовому лендингу/визитке нафиг не нужны постоянные обновления контента, не нужны какие-то выгрузки товаров, форумы-хуёромы итд