//dependencies
const express = require('express');
const path = require('path');
const logger = require('@marketto/js-logger').global();
const { TypeHandler } = require('./type-handler.class');
class Mockettaro {
/**
* @description Creates an instance of Mockettaro.
* @param {string} [directory = './'] Mock source path
* @param {number} [responseDelay = 0] Response delay to simulate
* @param {number} [cacheLifetime = 3000] Response Codes and Bodies cache life time
* @param {string} [cwd = __dirname] Current working dir, __dirname as default
* @param {Object} [verbose = false] Verbose log level
* @param {Object} [errors = true] Error log level
* @param {Object} [info = true] Info log level
* @returns {Express} Express instance
* @example new Mockettaro({directory: './'})
*/
constructor({
directory = './',
responseDelay = 0,
cacheLifetime = 3000,
cwd = process.cwd(),
verbose = false,
errors = true,
info = true
} = {}) {
logger.config = {
'error': errors || verbose,
info,
'debug': verbose,
'warn': errors || verbose
};
//Binding route methods to 'this'
[
'returnResponse',
'resourceConfigRoute'
].forEach(routeMethodName => {
this[routeMethodName] = this[routeMethodName].bind(this);
});
this.srcPath = this.constructor.ABSOLUTE_PATH_MATCHER.test(directory) ? directory : path.join(cwd, directory);
this.cacheLifetime = cacheLifetime;
this.responseDelay = responseDelay;
this.server = express();
this.server.use(this.resourceConfigRoute, TypeHandler.router, this.returnResponse);
this.server.use(this.constructor.errorHandler);
return this.server;
}
/**
* @readonly
* @static
* @property {Regexp} ABSOLUTE_PATH_MATCHER Regular expression to match an absolute win/*nix path
* @description Absolute path win/*nix
* @memberof Mockettaro
* @example Mockettaro.ABSOLUTE_PATH_MATCHER
*/
static get ABSOLUTE_PATH_MATCHER() {
return /(?:^\/)|(?:^\w:\\)/i;
}
/**
* @method resourceConfigRoute
* @description Configuration Route
* @param {Express.Request} req Request / previous route params
* @param {Express.Response} res Response instance
* @param {Function} next Callback to be called to step forward in route chain
* @memberof Mockettaro
* @example const express = require('express');<br>const app = express();<br>...<br>app.use(Mockettaro.resourceConfigRoute);
*/
resourceConfigRoute( req, res, next ) {
const {srcPath, cacheLifetime} = this;
[req.servicePath] = (/^([^?]+)/).exec(req.url);
req.workingDir = srcPath;
req.cacheLifetime = cacheLifetime;
next();
}
/**
* @method returnResponse
* @description Configuration Route
* @param {Express.Request} req Request / previous route params
* @param {Express.Response} res Response instance
* @memberof Mockettaro
* @example const express = require('express');<br>const app = express();<br>...<br>app.use(Mockettaro.returnResponse);
*/
returnResponse( req, res ) {
const {responseDelay} = this;
res.append('Access-Control-Allow-Origin', '*');
res.append('Access-Control-Allow-Headers', '*');
res.append('Access-Control-Allow-Methods', '*');
if (req.resConfig || req.resBody){
const config = req.resConfig || {};
setTimeout(() => {
Object.entries(config.headers || {})
.forEach(( [key, value] ) => {
res.append(key, value);
});
res
.status(config.status || 200)
.end(req.resBody);
logger.debug(`Headers: ${JSON.stringify(config.headers || {}, 4)}`);
}, isNaN(config.delay) ? responseDelay : config.delay);
} else {
res
.status(404)
.send();
}
}
/**
* @method errorHandler
* @description Error Handler Route
* @param {string|Error} err Raised error
* @param {Express.Request} [req] Request / previous route params
* @param {Express.Response} res Response instance
* @param {Function} [next] Callback to be called to step forward in route chain, needed for Express to manage it as an Error Handler
* @memberof Mockettaro
* @example const express = require('express');<br>const app = express();<br>...<br>app.use(Mockettaro.errorHandler);
*/
static errorHandler(err, req, res, next) { //eslint-disable-line no-unused-vars
logger.warn(err.message);
res
.status(500)
.send(err.message);
}
}
module.exports = config => new Mockettaro(config);