Big progress commit
This commit is contained in:
parent
5fecd50e7c
commit
a37aaf0576
123
app/Container.js
Normal file
123
app/Container.js
Normal file
@ -0,0 +1,123 @@
|
||||
'use strict';
|
||||
|
||||
const errors = require('errors');
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const autoLoad = require('./config/container-autoload');
|
||||
|
||||
/**
|
||||
* Determine the appropriate path to a module relative to the 'app' folder
|
||||
*
|
||||
* @private
|
||||
* @param {string} modulePath - the raw path to the module
|
||||
* @return {string} - the normalized path to the module
|
||||
*/
|
||||
function normalizeIncludePath(modulePath) {
|
||||
const basePath = path.resolve(path.join(__dirname, '../'));
|
||||
|
||||
let includePath = modulePath;
|
||||
|
||||
// Allow referencing local modules without using a ./
|
||||
// eg. util/route-loader instead of ./util/route-loader
|
||||
if (
|
||||
modulePath.includes('/') &&
|
||||
! (modulePath.startsWith('./') || modulePath.includes(__dirname))
|
||||
) {
|
||||
includePath = path.join(__dirname, modulePath);
|
||||
}
|
||||
|
||||
return includePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for keeping track of dependencies
|
||||
*/
|
||||
class Container {
|
||||
constructor() {
|
||||
const app = express();
|
||||
let container = new Map();
|
||||
|
||||
// Save the base app object
|
||||
container.set('app', app);
|
||||
|
||||
// Preload some configured modules
|
||||
autoLoad.map((module) => {
|
||||
let moduleMap = module;
|
||||
|
||||
// Normalize config into [key, value]
|
||||
if (! Array.isArray(module)) {
|
||||
moduleMap = [module, module];
|
||||
}
|
||||
|
||||
// Actually require the module
|
||||
moduleMap[1] = require(normalizeIncludePath(moduleMap[1]));
|
||||
|
||||
container.set.apply(container, moduleMap);
|
||||
});
|
||||
|
||||
this._container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item exists in the container
|
||||
*
|
||||
* @param {string} name - name of the item
|
||||
* @return {boolean} - whether the item exists in the container
|
||||
*/
|
||||
has(name) {
|
||||
return this._container.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an existing object instance
|
||||
*
|
||||
* @param {string} name - name of the item
|
||||
* @return {Object|undefined} - the item, or undefined if it doesn't exist
|
||||
*/
|
||||
get(name) {
|
||||
if (this.has(name)) {
|
||||
return this._container.get(name);
|
||||
}
|
||||
|
||||
try {
|
||||
return this._require(name);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an object in the container
|
||||
*
|
||||
* @param {string} name - name to associate with object
|
||||
* @param {Object} object - item to keep track of
|
||||
* @return {Container} - the container instance
|
||||
*/
|
||||
set(name, object) {
|
||||
this._container.set(name, object);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a native require, relative to the lib folder,
|
||||
* and returns the value
|
||||
*
|
||||
* @private
|
||||
* @param {string} modulePath - name of the module to require
|
||||
* @return {*} - the value returned from require
|
||||
*/
|
||||
_require(modulePath) {
|
||||
// If the value is already saved, just return it
|
||||
if (this.has(modulePath)) {
|
||||
return this.get(modulePath);
|
||||
}
|
||||
|
||||
// Save the item for later
|
||||
let item = require(normalizeIncludePath(modulePath));
|
||||
this.set(modulePath, item);
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Container();
|
104
app/base/ApiModel.js
Normal file
104
app/base/ApiModel.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('../Container'),
|
||||
axios = require('axios');
|
||||
const Model = container.get('base/Model');
|
||||
|
||||
/**
|
||||
* Base class for API Models
|
||||
*
|
||||
* Wraps convenience methods for making API requests.
|
||||
*
|
||||
* @param {string} [baseUrl] - a base url for api requests
|
||||
* @extends Model
|
||||
*/
|
||||
class ApiModel extends Model {
|
||||
/**
|
||||
* Create a new APIModel
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} [baseUrl] - a base url for api requests
|
||||
*/
|
||||
constructor(baseUrl) {
|
||||
super();
|
||||
|
||||
let apiConfig = {};
|
||||
|
||||
if (baseUrl) {
|
||||
apiConfig.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
this.client = axios(apiConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a 'HEAD' HTTP request
|
||||
*
|
||||
* @param {string} url - the url to request
|
||||
* @param {Object} [config] - options for the current request
|
||||
* @return {Promise} - promise wrapping the request
|
||||
*/
|
||||
head(url, config) {
|
||||
return this.client.head(url, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a 'GET' HTTP request
|
||||
*
|
||||
* @param {string} url - the url to request
|
||||
* @param {Object} [config] - options for the current request
|
||||
* @return {Promise} - promise wrapping the request
|
||||
*/
|
||||
get(url, config) {
|
||||
return this.client.get(url, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a 'DELETE' HTTP request
|
||||
*
|
||||
* @param {string} url - the url to request
|
||||
* @param {Object} [config] - options for the current request
|
||||
* @return {Promise} - promise wrapping the request
|
||||
*/
|
||||
delete(url, config) {
|
||||
return this.client.delete(url, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a 'POST' HTTP request
|
||||
*
|
||||
* @param {string} url - the url to request
|
||||
* @param {Object} data - the data for the current request
|
||||
* @param {Object} [config] - options for the current request
|
||||
* @return {Promise} - promise wrapping the request
|
||||
*/
|
||||
post(url, data, config) {
|
||||
return this.client.post(url, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a 'PUT' HTTP request
|
||||
*
|
||||
* @param {string} url - the url to request
|
||||
* @param {Object} data - the data for the current request
|
||||
* @param {Object} [config] - options for the current request
|
||||
* @return {Promise} - promise wrapping the request
|
||||
*/
|
||||
put(url, data, config) {
|
||||
return this.client.put(url, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a 'PATCH' HTTP request
|
||||
*
|
||||
* @param {string} url - the url to request
|
||||
* @param {Object} data - the data for the current request
|
||||
* @param {Object} [config] - options for the current request
|
||||
* @return {Promise} - promise wrapping the request
|
||||
*/
|
||||
patch(url, data, config) {
|
||||
return this.client.patch(url, data, config);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApiModel;
|
182
app/base/Config.js
Normal file
182
app/base/Config.js
Normal file
@ -0,0 +1,182 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('../Container');
|
||||
const path = container.get('path');
|
||||
|
||||
// Load environment file
|
||||
require('dotenv').config({
|
||||
path: path.resolve(__dirname, '../../.env'),
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const glob = require('glob');
|
||||
const configSslPath = path.resolve(__dirname, '../config/ssl');
|
||||
const configPath = path.resolve(__dirname, '../config');
|
||||
|
||||
const defaultConfig = new Map([
|
||||
['host', 'localhost'],
|
||||
['http', false],
|
||||
['http-port', 80],
|
||||
['https', true],
|
||||
['https-port', 443],
|
||||
['https-config-cert', path.join(configSslPath, 'localhost.crt')],
|
||||
['https-config-key', path.join(configSslPath, 'localhost.key')],
|
||||
['node-env', 'development'],
|
||||
]);
|
||||
|
||||
const configFiles = glob.sync(`${configPath}/**/*.js`);
|
||||
|
||||
/**
|
||||
* Config management class
|
||||
*
|
||||
* Hierarchy of config options
|
||||
* 1. Directly defined config options
|
||||
* 2. Environment variables
|
||||
* 3. Default values
|
||||
*/
|
||||
class Config {
|
||||
constructor() {
|
||||
let _configMap = new Map();
|
||||
|
||||
// Load files in config folder under their
|
||||
// own respective namespaces
|
||||
configFiles.forEach((fullPath) => {
|
||||
let key = path.basename(fullPath, '.js');
|
||||
_configMap.set(key, require(fullPath));
|
||||
});
|
||||
|
||||
this._config = _configMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available configuration keys
|
||||
*
|
||||
* @return {array} - the list of configuration keys
|
||||
*/
|
||||
keys() {
|
||||
let keys = [];
|
||||
|
||||
for (let key of this._config.keys()) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a config value exists
|
||||
*
|
||||
* @param {string|symbol} key - the config value key
|
||||
* @return {boolean} - whether the config value exists
|
||||
*/
|
||||
has(key) {
|
||||
return this._config.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an environment variable is currently defined
|
||||
*
|
||||
* @param {string} key - the environment varaible to look for
|
||||
* @return {boolean} - whether the environment variable is defined
|
||||
*/
|
||||
hasEnv(key) {
|
||||
return process.env[this._envName(key)] != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a config value
|
||||
*
|
||||
* @param {string|symbol} key - the name of the config value
|
||||
* @return {*} - the configuration value
|
||||
*/
|
||||
get(key) {
|
||||
if (! this.has(key)) {
|
||||
// Fallback to environment variables
|
||||
let envKey = key.toUpperCase().replace('-', '_');
|
||||
if (this.hasEnv(envKey)) {
|
||||
let envValue = this.getEnv(envKey);
|
||||
this._config.set(key, envValue);
|
||||
return envValue;
|
||||
}
|
||||
|
||||
// Fallback to default values
|
||||
if (defaultConfig.has(key)) {
|
||||
let defaultValue = defaultConfig.get(key);
|
||||
this._config.set(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return this._config.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of an environment variable
|
||||
*
|
||||
* @param {string} key - the environment variable to get
|
||||
* @return {string|undefined} - the value of the environment variable
|
||||
*/
|
||||
getEnv(key) {
|
||||
let raw = process.env[this._envName(key)];
|
||||
return this._normalizeValue(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get equivalent environment variable for config key
|
||||
*
|
||||
* @private
|
||||
* @param {string} key - the config key
|
||||
* @return {string} - the environment variable name
|
||||
*/
|
||||
_envName(key) {
|
||||
return key.toUpperCase().replace('-', '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse javascript types from strings
|
||||
*
|
||||
* @private
|
||||
* @param {string} val - the string value
|
||||
* @return {string|number|boolean} - the 'parsed' value
|
||||
*/
|
||||
_normalizeValue(val) {
|
||||
let bool = /^true|false$/;
|
||||
let integer = /^([0-9]+)$/;
|
||||
|
||||
if (String(val).search(integer) !== -1) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
|
||||
if (String(val).search(bool) !== -1) {
|
||||
switch (val) {
|
||||
case 'true':
|
||||
return true;
|
||||
|
||||
// return overrides break
|
||||
|
||||
case 'false':
|
||||
return false;
|
||||
|
||||
// return overrides break
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a config variable (mainly for testing)
|
||||
*
|
||||
* @private
|
||||
* @param {mixed} key - the key to set
|
||||
* @param {mixed} val - the value for the key
|
||||
* @return {Config} - the config instance
|
||||
*/
|
||||
_set(key, val) {
|
||||
this._config.set(key, val);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Config();
|
74
app/base/Controller.js
Normal file
74
app/base/Controller.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('../Container');
|
||||
const express = require('express');
|
||||
const errors = require('errors');
|
||||
const getArgs = require('getargs');
|
||||
const loadRoutes = container.get('base/util/route-loader');
|
||||
const _ = container.get('_');
|
||||
|
||||
/**
|
||||
* Controller setup and utility class
|
||||
*/
|
||||
class Controller {
|
||||
|
||||
/**
|
||||
* Get route mapping
|
||||
*
|
||||
* @param {string} baseRoutePath - the path to the folder containing
|
||||
* controllers/routes
|
||||
* @return {map} - Maps route prefixes to their respective functions
|
||||
*/
|
||||
static getRouteMap(baseRoutePath) {
|
||||
const routeMap = {};
|
||||
const pathFileMap = loadRoutes(baseRoutePath);
|
||||
|
||||
_(pathFileMap).forEach((routeFile, routePath) => {
|
||||
let rawRoutes = require(routeFile);
|
||||
routeMap[routePath] = Controller._parseRoutes(rawRoutes);
|
||||
});
|
||||
|
||||
return routeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify route generation with an object
|
||||
*
|
||||
* @private
|
||||
* @param {Object} routeObject - the object laying out the routes for the
|
||||
* current controller
|
||||
* @return {Router} - the updated express.Router object
|
||||
*/
|
||||
static _parseRoutes(routeObject) {
|
||||
const router = express.Router();
|
||||
|
||||
_(routeObject).forEach((httpMethods, currentPath) => {
|
||||
_(httpMethods).forEach((routeFunction, currentHttpMethod) => {
|
||||
let routerMethod = router[currentHttpMethod];
|
||||
return router[currentHttpMethod](currentPath, routeFunction);
|
||||
});
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a specific http error code
|
||||
*
|
||||
* @param {number} code - The error Number
|
||||
* @param {Object} [options={}] - Options, such as message and explanation
|
||||
* @param {function} next - The callback to pass the error to
|
||||
* @return {void}
|
||||
*/
|
||||
static HttpError(/*code, options, next*/) {
|
||||
let args = getArgs('code:Number, [options]:object, next:function', arguments);
|
||||
args.options = args.options || {};
|
||||
|
||||
let methodName = `Http${args.code}Error`;
|
||||
let err = new errors[methodName](args.options);
|
||||
|
||||
return args.next(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controller;
|
33
app/base/HttpServer.js
Normal file
33
app/base/HttpServer.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const http = require('http'),
|
||||
logger = require('winston');
|
||||
|
||||
const Server = require('./Server');
|
||||
|
||||
/**
|
||||
* Class for creating an http server
|
||||
*
|
||||
* @extends Server
|
||||
* @param {Express} app - current express instance
|
||||
* @param {number} port - the port to listen on
|
||||
*/
|
||||
class HttpServer extends Server {
|
||||
constructor(app, port) {
|
||||
super();
|
||||
let server = http.createServer(app);
|
||||
server.listen(port);
|
||||
server.on('error', this.onError);
|
||||
server.on('listening', () => {
|
||||
let addr = server.address();
|
||||
let bind = typeof addr === 'string'
|
||||
? `pipe ${addr}`
|
||||
: `port ${addr.port}`;
|
||||
logger.info(`Listening on ${bind}`);
|
||||
});
|
||||
|
||||
this.server = server;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HttpServer;
|
34
app/base/HttpsServer.js
Normal file
34
app/base/HttpsServer.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const https = require('https'),
|
||||
logger = require('winston');
|
||||
|
||||
const Server = require('./Server');
|
||||
|
||||
/**
|
||||
* Class for creating an https server
|
||||
*
|
||||
* @extends Server
|
||||
* @param {Express} app - current express instance
|
||||
* @param {number} port - the port to listen on
|
||||
* @param {Object} options - https server options
|
||||
*/
|
||||
class HttpsServer extends Server {
|
||||
constructor(app, port, options) {
|
||||
super();
|
||||
let server = https.createServer(options, app);
|
||||
server.listen(port);
|
||||
server.on('error', this.onError);
|
||||
server.on('listening', () => {
|
||||
let addr = server.address();
|
||||
let bind = typeof addr === 'string'
|
||||
? `pipe ${addr}`
|
||||
: `port ${addr.port}`;
|
||||
logger.info(`Listening on ${bind}`);
|
||||
});
|
||||
|
||||
this.server = server;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HttpsServer;
|
@ -1,5 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Base Model class
|
||||
*/
|
||||
class Model {
|
||||
|
||||
}
|
42
app/base/Server.js
Normal file
42
app/base/Server.js
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Base Class for wrapping HTTP/HTTPS servers
|
||||
*/
|
||||
class Server {
|
||||
constructor() {}
|
||||
/**
|
||||
* Event listener for HTTP(s) server "error" event.
|
||||
*
|
||||
* @param {error} error - the error object
|
||||
* @return {null} - Does not return a value
|
||||
* @throws {error}
|
||||
*/
|
||||
onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let port = this.server.address().port;
|
||||
|
||||
let bind = typeof port === 'string'
|
||||
? `Pipe ${port}`
|
||||
: `Port ${port}`;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
logger.error(`${bind} requires elevated privileges`);
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
logger.error(`${bind} is already in use`);
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Server;
|
@ -5,15 +5,16 @@ const glob = require('glob');
|
||||
/**
|
||||
* Map Routes to route files
|
||||
*
|
||||
* @param {String} path - folder with Routes
|
||||
* @param {string} path - folder with Routes
|
||||
* @return {Object} - Object mapping routes to their files
|
||||
*/
|
||||
module.exports = function routeLoader(path) {
|
||||
|
||||
const basePath = path;
|
||||
const basePath = path.replace(/\\/g, '/');
|
||||
|
||||
let paths = glob.sync(`${path}/**/*.js`);
|
||||
paths = paths.sort();
|
||||
paths = paths.map((path) => path.replace('\\', '/'));
|
||||
|
||||
let routes = {};
|
||||
|
27
app/bootstrap.js
vendored
Normal file
27
app/bootstrap.js
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('./Container');
|
||||
const _ = container.get('_');
|
||||
const app = container.get('app');
|
||||
const path = container.get('path');
|
||||
const Controller = container.get('base/Controller');
|
||||
const Config = container.get('base/Config');
|
||||
|
||||
module.exports = (function () {
|
||||
const baseRoutePath = path.join(__dirname, 'controllers');
|
||||
|
||||
// load middleware
|
||||
Config.get('middleware').forEach((mw) => app.use(mw));
|
||||
|
||||
// automatically set up routing by folder structure
|
||||
let routeMap = Controller.getRouteMap(baseRoutePath);
|
||||
_(routeMap).forEach((routeFunction, routePrefix) => {
|
||||
app.use(routePrefix, routeFunction);
|
||||
});
|
||||
|
||||
// load error handlers
|
||||
Config.get('error-handlers').forEach((handler) => app.use(handler));
|
||||
|
||||
return app;
|
||||
|
||||
}());
|
15
app/config/container-autoload.js
Normal file
15
app/config/container-autoload.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A list of modules to insert into the Container at start up
|
||||
*
|
||||
* Modules to be loaded with an alias should have an array with the alias
|
||||
* then the module name. Otherwise, just a string with the module name
|
||||
*
|
||||
* @type {array}
|
||||
*/
|
||||
module.exports = [
|
||||
['_', 'lodash'],
|
||||
'path',
|
||||
['promisify', 'helpers/promisify'],
|
||||
];
|
@ -6,23 +6,29 @@
|
||||
|
||||
const container = require('../Container');
|
||||
const app = container.get('app');
|
||||
const HTTP_CODE_MAP = require('http').STATUS_CODES;
|
||||
const errors = require('errors');
|
||||
|
||||
let errorHandlers = [
|
||||
let errorHandlers = new Set([
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
(req, res, next) => {
|
||||
let err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
function handle404(req, res, next) {
|
||||
// if no route matches, send a 404
|
||||
if (! req.route) {
|
||||
let err = new errors.Http404Error();
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
|
||||
// general error handler
|
||||
(err, req, res, next) => {
|
||||
res.status(err.status || 500);
|
||||
function handleError(err, req, res, next) {
|
||||
let httpStatus = err.status || 500;
|
||||
let message = err.message || HTTP_CODE_MAP[httpStatus];
|
||||
|
||||
res.status(httpStatus);
|
||||
|
||||
let output = {
|
||||
status: err.status,
|
||||
message: err.message,
|
||||
status: httpStatus,
|
||||
message: message,
|
||||
};
|
||||
|
||||
// Show stack trace in development environment
|
||||
@ -32,6 +38,6 @@ let errorHandlers = [
|
||||
|
||||
res.json(output);
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
module.exports = errorHandlers;
|
@ -11,7 +11,7 @@ const bodyParser = require('body-parser'),
|
||||
requestLogger = require('morgan'),
|
||||
path = require('path');
|
||||
|
||||
let middleware = [
|
||||
let middleware = new Set([
|
||||
|
||||
// some security settings controlled by helmet
|
||||
helmet.frameguard(),
|
||||
@ -26,6 +26,6 @@ let middleware = [
|
||||
bodyParser.urlencoded({ extended: false }),
|
||||
cookieParser(),
|
||||
express.static(path.join(__dirname, '../../public')),
|
||||
];
|
||||
]);
|
||||
|
||||
module.exports = middleware;
|
20
app/controllers/index.js
Normal file
20
app/controllers/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
'/': {
|
||||
|
||||
// Get homepage
|
||||
get: (req, res) => {
|
||||
return res.json({
|
||||
status: 200,
|
||||
data: {
|
||||
index: { title: 'Express' },
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
put: (req, res, next) => {
|
||||
return next();
|
||||
},
|
||||
},
|
||||
};
|
26
app/helpers/promisify.js
Normal file
26
app/helpers/promisify.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
/*eslint-disable prefer-arrow-callback*/
|
||||
/**
|
||||
* Function to convert a callback function into a promise
|
||||
*
|
||||
* @see http://eddmann.com/posts/promisifying-error-first-asynchronous-callbacks-in-javascript/
|
||||
* @example promisify(fs.readFile)('hello.txt', 'utf8')
|
||||
* .then(console.log)
|
||||
* .catch(console.error)
|
||||
* @param {Function} fn - the callback function to convert
|
||||
* @return {Promise} - the new promise
|
||||
*/
|
||||
function promisify(fn) {
|
||||
return function () {
|
||||
let args = [].slice.call(arguments);
|
||||
return new Promise(function (resolve, reject) {
|
||||
fn.apply(undefined, args.concat((error, value) => {
|
||||
return error ? reject(error) : resolve(value);
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = promisify;
|
||||
/*eslint-enable prefer-arrow-callback*/
|
62
gulpfile.js
62
gulpfile.js
@ -13,7 +13,14 @@ const apidoc = require('gulp-apidoc'),
|
||||
/*
|
||||
* Path(s) to all source files
|
||||
*/
|
||||
const SRC_FILES = ['lib/**/*.js'];
|
||||
const SRC_FILES = [
|
||||
'app/base/**/*.js',
|
||||
'app/config/**/*.js',
|
||||
'app/controllers/**/*.js',
|
||||
'app/helpers/**/*.js',
|
||||
'app/models/**/*.js',
|
||||
'app/*.js',
|
||||
];
|
||||
|
||||
/*
|
||||
* Path to unit test files
|
||||
@ -23,7 +30,7 @@ const UNIT_TEST_FILES = ['test/unit/**/*_test.js'];
|
||||
/*
|
||||
* Path to integration test files
|
||||
*/
|
||||
const INTEGRATION_TEST_FILES = ['test/end-to-end/**/*_test.js'];
|
||||
const INTEGRATION_TEST_FILES = ['test/integration/**/*_test.js'];
|
||||
|
||||
/*
|
||||
* Path(s) to all test files
|
||||
@ -76,9 +83,23 @@ const ESLINT_SETTINGS = {
|
||||
'callback-return': [1], // return when invoking a callback
|
||||
'object-shorthand': [1, 'methods'], // Prefer shorthand for functions in object literals/classes, but avoid property shorthand
|
||||
'prefer-template': [1], // Prefer template strings eg. `Hello ${name}`, to string concatenation
|
||||
'no-case-declarations': [2], // Don't define variables in switch labels
|
||||
'no-const-assign': [2], // Highlight instances where assigning to const declaration
|
||||
'no-new-symbol': [2], // Symbol is not a constructor, don't use the new keyword
|
||||
'no-unused-labels': [2], // Error on labels in code that aren't used
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Configuration values for mocha
|
||||
*/
|
||||
const MOCHA_SETTINGS = {
|
||||
ui: 'tdd',
|
||||
bail: true,
|
||||
slow: 1000,
|
||||
timeout: 5000,
|
||||
};
|
||||
|
||||
/*
|
||||
* Check syntax and style of test/miscellaneous files
|
||||
*/
|
||||
@ -143,7 +164,7 @@ gulp.task('src-docs', () => {
|
||||
*/
|
||||
gulp.task('api-docs', (done) => {
|
||||
apidoc({
|
||||
src: 'lib/',
|
||||
src: 'app/',
|
||||
dest: 'public/api-docs/',
|
||||
}, done);
|
||||
});
|
||||
@ -153,18 +174,28 @@ gulp.task('api-docs', (done) => {
|
||||
*/
|
||||
gulp.task('docs', ['src-docs', 'api-docs']);
|
||||
|
||||
/*
|
||||
* Run integration tests
|
||||
*/
|
||||
gulp.task('integration-test', ['lint'], () => {
|
||||
return gulp.src(INTEGRATION_TEST_FILES)
|
||||
.pipe(mocha(MOCHA_SETTINGS));
|
||||
});
|
||||
|
||||
/*
|
||||
* Run unit tests
|
||||
*/
|
||||
gulp.task('unit-test', ['lint'], () => {
|
||||
return gulp.src(UNIT_TEST_FILES)
|
||||
.pipe(mocha(MOCHA_SETTINGS));
|
||||
});
|
||||
|
||||
/*
|
||||
* Run all tests
|
||||
*/
|
||||
gulp.task('test', ['lint'], () => {
|
||||
return pipe(gulp.src(TEST_FILES), [
|
||||
mocha({
|
||||
ui: 'tdd',
|
||||
bail: true,
|
||||
slow: 1000,
|
||||
timeout: 5000,
|
||||
}),
|
||||
]);
|
||||
return gulp.src(TEST_FILES)
|
||||
.pipe(mocha(MOCHA_SETTINGS));
|
||||
});
|
||||
|
||||
/*
|
||||
@ -180,16 +211,11 @@ gulp.task('pre-coverage', () => {
|
||||
/*
|
||||
* Run unit tests and generate code coverage
|
||||
*
|
||||
* Does not run end-to-end tests
|
||||
* Does not run integration tests
|
||||
*/
|
||||
gulp.task('coverage', ['lint', 'pre-coverage'], () => {
|
||||
return pipe(gulp.src(UNIT_TEST_FILES), [
|
||||
mocha({
|
||||
ui: 'tdd',
|
||||
bail: true,
|
||||
slow: 1000,
|
||||
timeout: 5000,
|
||||
}),
|
||||
mocha(MOCHA_SETTINGS),
|
||||
istanbul.writeReports({
|
||||
dir: 'public/coverage',
|
||||
reporters:['lcov', 'lcovonly', 'html', 'text'],
|
||||
|
@ -1,96 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express'),
|
||||
path = require('path');
|
||||
|
||||
/**
|
||||
* Container for keeping track of dependencies
|
||||
*/
|
||||
class Container {
|
||||
|
||||
/**
|
||||
* Create the object container
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
const app = express();
|
||||
this._container = {
|
||||
app: app,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item exists in the container
|
||||
*
|
||||
* @param {String} name - name of the item
|
||||
* @return {Boolean} - whether the item exists in the container
|
||||
*/
|
||||
has(name) {
|
||||
return (name in this._container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an existing object instance
|
||||
*
|
||||
* @param {String} name - name of the item
|
||||
* @return {Object|Null} - the item, or null if it doesn't exist
|
||||
*/
|
||||
get(name) {
|
||||
if (! this.has(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._container[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an object in the container
|
||||
*
|
||||
* @param {String} name - name to associate with object
|
||||
* @param {Object} object - item to keep track of
|
||||
* @return {Container} - the container instance
|
||||
*/
|
||||
set(name, object) {
|
||||
this._container[name] = object;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a native require, relative to the lib folder,
|
||||
* and returns the value
|
||||
*
|
||||
* @param {String} modulePath - name of the module to require
|
||||
* @return {mixed} - the value returned from require
|
||||
*/
|
||||
require(modulePath) {
|
||||
// If the value is already saved, just return it
|
||||
if (this.has(modulePath)) {
|
||||
return this.get(modulePath);
|
||||
}
|
||||
|
||||
let includeName = modulePath;
|
||||
|
||||
// Allow referencing local modules without using a ./
|
||||
// eg. util/route-loader instead of ./util/route-loader
|
||||
if (
|
||||
modulePath.includes('/') &&
|
||||
! (modulePath.startsWith('./') || modulePath.includes(__dirname))
|
||||
) {
|
||||
includeName = path.join(__dirname, modulePath);
|
||||
}
|
||||
|
||||
return require(includeName);
|
||||
}
|
||||
}
|
||||
|
||||
let instance = null;
|
||||
|
||||
module.exports = (function () {
|
||||
|
||||
if (instance === null) {
|
||||
instance = new Container();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}());
|
23
lib/app.js
23
lib/app.js
@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('./Container');
|
||||
const loadRoutes = container.require('util/route-loader'),
|
||||
path = container.require('path');
|
||||
|
||||
const app = container.get('app');
|
||||
const middleware = container.require('config/middleware');
|
||||
const routes = loadRoutes(path.join(__dirname, 'routes'));
|
||||
const errorHandlers = container.require('config/error-handlers');
|
||||
|
||||
// load middleware
|
||||
middleware.forEach((mw) => app.use(mw));
|
||||
|
||||
// automatically set up routing by folder structure
|
||||
Object.keys(routes).reverse().forEach((path) => {
|
||||
app.use(path, require(routes[path]));
|
||||
});
|
||||
|
||||
// load error handlers
|
||||
errorHandlers.forEach((handler) => app.use(handler));
|
||||
|
||||
module.exports = app;
|
@ -1,10 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('../Container');
|
||||
const Model = container.require('base/Model');
|
||||
|
||||
class ApiModel extends Model {
|
||||
|
||||
}
|
||||
|
||||
module.exports = ApiModel;
|
@ -1,32 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
let nconf = require('nconf');
|
||||
|
||||
/**
|
||||
* Config management class
|
||||
*/
|
||||
class Config {
|
||||
constructor() {
|
||||
// Use, in order of preference:
|
||||
// - command line arguments
|
||||
// - environment variables
|
||||
// - config files
|
||||
nconf.argv()
|
||||
.env()
|
||||
.file({
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreive a config value
|
||||
*
|
||||
* @param {String} key - the name of the config value
|
||||
* @return {mixed} - the configuration value
|
||||
*/
|
||||
get(key) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Config;
|
@ -1,69 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const http = require('http'),
|
||||
logger = require('winston');
|
||||
|
||||
/**
|
||||
* Class for creating an http server
|
||||
*
|
||||
* @param {Express} app - current express instance
|
||||
* @param {Number} port - the port to listen on
|
||||
*/
|
||||
class HttpServer {
|
||||
/**
|
||||
* Creates an HTTP Server
|
||||
*
|
||||
* @constructor
|
||||
* @param {Express} app - current express instance
|
||||
* @param {Number} port - the port to listen on
|
||||
*/
|
||||
constructor(app, port) {
|
||||
let server = http.createServer(app);
|
||||
server.listen(port);
|
||||
server.on('error', this.onError);
|
||||
server.on('listening', () => {
|
||||
let addr = server.address();
|
||||
let bind = typeof addr === 'string'
|
||||
? `pipe ${addr}`
|
||||
: `port ${addr.port}`;
|
||||
logger.info(`Listening on ${bind}`);
|
||||
});
|
||||
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*
|
||||
* @param {Error} error - the error object
|
||||
* @return {Null} - Does not return a value
|
||||
* @throws {Error}
|
||||
*/
|
||||
onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let port = this.server.address().port;
|
||||
|
||||
let bind = typeof port === 'string'
|
||||
? `Pipe ${port}`
|
||||
: `Port ${port}`;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
logger.error(`${bind} requires elevated privileges`);
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
logger.error(`${bind} is already in use`);
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HttpServer;
|
@ -1,16 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
let router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', (req, res, next) => {
|
||||
res.json({
|
||||
status: 200,
|
||||
data: {
|
||||
index: { title: 'Express' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -1,11 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
let router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', (req, res, next) => {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
34
server.js
34
server.js
@ -1,9 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const container = require('./lib/Container');
|
||||
const app = container.require('./app'),
|
||||
logger = require('winston'),
|
||||
HttpServer = container.require('base/HttpServer');
|
||||
const fs = require('fs');
|
||||
const container = require('./app/Container');
|
||||
const app = container.get('./bootstrap');
|
||||
const config = container.get('base/Config');
|
||||
const HttpServer = container.get('base/HttpServer');
|
||||
const HttpsServer = container.get('base/HttpsServer');
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
@ -28,10 +30,22 @@ function normalizePort(val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Get port from environment and store in Express.
|
||||
let port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
// Create HTTP Server
|
||||
let server = new HttpServer(app, port);
|
||||
if (true === config.get('http')) {
|
||||
let port = normalizePort(config.get('http-port'));
|
||||
app.set('port', port);
|
||||
container.set('http-server', new HttpServer(app, port));
|
||||
}
|
||||
|
||||
// Create HTTPs Server
|
||||
if (true === config.get('https')) {
|
||||
const httpsPort = normalizePort(config.get('https-port'));
|
||||
const httpsConfig = {
|
||||
key: fs.readFileSync(config.get('https-config-key')),
|
||||
cert: fs.readFileSync(config.get('https-config-cert')),
|
||||
};
|
||||
app.set('https-port', httpsPort);
|
||||
container.set('https-server', new HttpsServer(app, httpsPort, httpsConfig));
|
||||
}
|
||||
|
||||
// End of server.js
|
@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const supertest = require('supertest'),
|
||||
expect = require('chai').expect;
|
||||
const testBase = require('../test-base');
|
||||
const expect = testBase.expect;
|
||||
const supertest = require('supertest');
|
||||
|
||||
let server = supertest.agent('http://localhost:3000');
|
||||
|
||||
suite('Example tests', () => {
|
||||
suite('Example integration tests', () => {
|
||||
test('Index page works as expected', (done) => {
|
||||
server.get('/')
|
||||
.expect('Content-type', /json/)
|
||||
@ -19,33 +20,34 @@ suite('Example tests', () => {
|
||||
index: { title: 'Express' },
|
||||
},
|
||||
});
|
||||
done();
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
|
||||
test('404 page works as expected', (done) => {
|
||||
server.get('/foo')
|
||||
.expect('Content-type', /json/)
|
||||
.expect(404)
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.equal(404);
|
||||
expect(res.body.status).to.equal(res.status);
|
||||
expect(res.body.status).to.equal(String(res.status));
|
||||
expect(res.body.message).to.be.ok;
|
||||
|
||||
let expected = {
|
||||
status: 404,
|
||||
status: '404',
|
||||
message: 'Not Found',
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
expected.error = {
|
||||
status: 404,
|
||||
code: '404',
|
||||
status: '404',
|
||||
name: 'Http404Error',
|
||||
message: 'Not Found',
|
||||
};
|
||||
}
|
||||
|
||||
expect(res.body).to.deep.equal(expected);
|
||||
|
||||
done();
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
});
|
@ -2,6 +2,17 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// Set up chai as promised to allow for
|
||||
// better testing of promises
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// Load environment file
|
||||
require('dotenv').config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Base Object for unit test utilities
|
||||
*/
|
||||
@ -10,7 +21,7 @@ const testBase = {
|
||||
/**
|
||||
* Chai expect assertion library
|
||||
*/
|
||||
expect: require('chai').expect,
|
||||
expect: chai.expect,
|
||||
|
||||
/**
|
||||
* Determine the appropriate path to a module relative to the root folder
|
||||
@ -38,7 +49,7 @@ const testBase = {
|
||||
* @param {String} modulePath - path to the module, relative to the tests/ folder
|
||||
* @return {mixed} - whatever the module returns
|
||||
*/
|
||||
testRequire(modulePath) {
|
||||
require(modulePath) {
|
||||
const includePath = testBase._normalizeIncludePath(modulePath);
|
||||
return require(includePath);
|
||||
},
|
||||
@ -50,7 +61,7 @@ const testBase = {
|
||||
* @param {String} modulePath - path to the module, relative to the tests/ folder
|
||||
* @return {mixed} - whatever the module returns
|
||||
*/
|
||||
testRequireNoCache(modulePath) {
|
||||
requireNoCache(modulePath) {
|
||||
const includePath = testBase._normalizeIncludePath(modulePath);
|
||||
delete require.cache[includePath];
|
||||
return require(includePath);
|
||||
|
109
test/unit/base/Config_test.js
Normal file
109
test/unit/base/Config_test.js
Normal file
@ -0,0 +1,109 @@
|
||||
'use strict';
|
||||
|
||||
const testBase = require('../../test-base');
|
||||
const expect = testBase.expect;
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
||||
suite('Config class tests', () => {
|
||||
let config = null;
|
||||
setup(() => {
|
||||
config = testBase.require('app/base/Config');
|
||||
});
|
||||
|
||||
test('Get a non-existent config value', () => {
|
||||
let actual = config.get('foo');
|
||||
expect(actual).to.be.undefined;
|
||||
});
|
||||
|
||||
suite('Value cascade', () => {
|
||||
|
||||
suite('Explicit values are loaded first', () => {
|
||||
setup(() => {
|
||||
config._set('foo', 'bar');
|
||||
});
|
||||
|
||||
test(`'foo' exists in config`, () => {
|
||||
expect(config.has('foo')).to.be.true;
|
||||
});
|
||||
test(`value of 'foo' is returned by get method`, () => {
|
||||
expect(config.get('foo')).to.be.equal('bar');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Environment values are loaded before defaults', () => {
|
||||
setup(() => {
|
||||
process.env.BAR = 'baz';
|
||||
});
|
||||
|
||||
test(`'bar' is not directly defined in config`, () => {
|
||||
expect(config.has('bar')).to.be.false;
|
||||
});
|
||||
test(`'bar' is defined in environment`, () => {
|
||||
expect(config.hasEnv('bar')).to.be.true;
|
||||
expect(config.getEnv('bar')).to.be.equal(process.env.BAR);
|
||||
});
|
||||
test(`Gets 'bar' from environment`, () => {
|
||||
expect(config.get('bar')).to.be.equal(config.getEnv('bar'));
|
||||
});
|
||||
test(`'foo' is now cached in config`, () => {
|
||||
expect(config.has('bar')).to.be.true;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
delete process.env.BAR;
|
||||
});
|
||||
});
|
||||
|
||||
suite('Default values are loaded as a last resort', () => {
|
||||
setup(() => {
|
||||
delete process.env.HOST;
|
||||
});
|
||||
|
||||
test(`'host' is not directly defined in config`, () => {
|
||||
expect(config.has('host')).to.be.false;
|
||||
});
|
||||
test(`'host' is not defined in environment`, () => {
|
||||
expect(config.hasEnv('host')).to.be.false;
|
||||
});
|
||||
test(`Get default value of 'host'`, () => {
|
||||
expect(config.get('host')).to.equal('localhost');
|
||||
});
|
||||
test(`'host' is now cached in config`, () => {
|
||||
expect(config.has('host')).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('normalizeValue', () => {
|
||||
test(`'true' returns true`, () => {
|
||||
let val = config._normalizeValue('true');
|
||||
expect(val).to.be.true;
|
||||
});
|
||||
test(`'false' returns false`, () => {
|
||||
let val = config._normalizeValue('false');
|
||||
expect(val).to.be.false;
|
||||
});
|
||||
test(`'436' returns 436`, () => {
|
||||
let val = config._normalizeValue('436');
|
||||
expect(val).to.be.equal(436);
|
||||
});
|
||||
test(`'foo' returns 'foo'`, () => {
|
||||
let val = config._normalizeValue('foo');
|
||||
expect(val).to.be.equal('foo');
|
||||
});
|
||||
});
|
||||
|
||||
test('Keys returns values in config', () => {
|
||||
// Get the list of keys for files
|
||||
let configFiles = glob.sync(
|
||||
path.resolve(__dirname, '../../../app/config/**/*.js')
|
||||
);
|
||||
let expected = configFiles.map((fullPath) => {
|
||||
return path.basename(fullPath, '.js');
|
||||
});
|
||||
let actual = config.keys();
|
||||
|
||||
expect(actual).to.be.deep.equal(expected);
|
||||
});
|
||||
});
|
@ -11,11 +11,11 @@ suite('Dependency Container tests', () => {
|
||||
// at an earlier time. The container module has a saved state to keep track
|
||||
// of modules during normal use. Removing the cached version ensures that
|
||||
// a new instance of the module is used.
|
||||
container = testBase.testRequireNoCache('lib/Container');
|
||||
container = testBase.requireNoCache('app/Container');
|
||||
});
|
||||
|
||||
test('Multiple requires return the same instance', () => {
|
||||
let container2 = testBase.testRequire('lib/Container');
|
||||
let container2 = testBase.require('app/Container');
|
||||
expect(container2).to.be.equal(container);
|
||||
});
|
||||
|
||||
@ -54,21 +54,21 @@ suite('Dependency Container tests', () => {
|
||||
expect(actual).to.be.equal(obj);
|
||||
});
|
||||
|
||||
test('Attempt to get non-existent item returns null', () => {
|
||||
expect(container.get('aseiutj')).to.be.null;
|
||||
test('Attempt to get non-existent item returns undefined', () => {
|
||||
expect(container.get('aseiutj')).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
suite('require method', () => {
|
||||
suite('get method require', () => {
|
||||
test('Returns same object as testInclude', () => {
|
||||
let containerFile = container.require('app');
|
||||
let testFile = testBase.testRequire('lib/app');
|
||||
let containerFile = container.get('./bootstrap');
|
||||
let testFile = testBase.require('app/bootstrap');
|
||||
|
||||
expect(containerFile).to.be.equal(testFile);
|
||||
});
|
||||
|
||||
test('Returns same object as native require', () => {
|
||||
let containerFile = container.require('express');
|
||||
let containerFile = container.get('express');
|
||||
let nativeRequire = require('express');
|
||||
|
||||
expect(containerFile).to.be.equal(nativeRequire);
|
||||
|
28
test/unit/base/Controller_test.js
Normal file
28
test/unit/base/Controller_test.js
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const testBase = require('../../test-base');
|
||||
const errors = require('errors');
|
||||
const expect = testBase.expect;
|
||||
|
||||
suite('Controller tests', () => {
|
||||
let controller = null;
|
||||
|
||||
setup(() => {
|
||||
controller = testBase.requireNoCache('app/base/Controller');
|
||||
});
|
||||
|
||||
suite('HttpError Tests', () => {
|
||||
test('500 Error', (done) => {
|
||||
controller.HttpError(500, (err) => {
|
||||
expect(err).to.deep.equal(new errors.Http500Error());
|
||||
return done();
|
||||
});
|
||||
});
|
||||
test('401 Error', (done) => {
|
||||
controller.HttpError(401, (err) => {
|
||||
expect(err).to.deep.equal(new errors.Http401Error());
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
25
test/unit/base/util/route-loader_test.js
Normal file
25
test/unit/base/util/route-loader_test.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const testBase = require('../../../test-base');
|
||||
const expect = testBase.expect;
|
||||
|
||||
let routeLoader = testBase.require('app/base/util/route-loader');
|
||||
|
||||
function getPath(filePath) {
|
||||
return path.join(__dirname, filePath)
|
||||
.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
suite('Util - Route Loader', () => {
|
||||
test('routeLoader creates accurate route mapping', () => {
|
||||
let actual = routeLoader(path.join(__dirname, 'test-routes'));
|
||||
let expected = {
|
||||
'/api/foo/bar': getPath('test-routes/api/foo/bar.js'),
|
||||
'/api/foo': getPath('test-routes/api/foo.js'),
|
||||
'/': getPath('test-routes/index.js'),
|
||||
};
|
||||
|
||||
expect(expected).to.be.deep.equal(actual);
|
||||
});
|
||||
});
|
21
test/unit/helpers/promisify_test.js
Normal file
21
test/unit/helpers/promisify_test.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const testBase = require('../../test-base');
|
||||
const expect = testBase.expect;
|
||||
const fs = require('fs');
|
||||
const promisify = testBase.require('app/helpers/promisify');
|
||||
|
||||
suite('Promisify', () => {
|
||||
test('Promisify returns a promise', () => {
|
||||
let actual = promisify(fs.readFile)('../../test-base.js');
|
||||
expect(actual).to.be.a('Promise');
|
||||
});
|
||||
test('Promisify fs.readFile resolves', () => {
|
||||
let actual = promisify(fs.readFile)('../../test-base.js');
|
||||
expect(actual).to.be.fulfilled;
|
||||
});
|
||||
test('Promisify fs.readFile fails on non-existent file', () => {
|
||||
let actual = promisify(fs.readFile)('foo.txt');
|
||||
expect(actual).to.be.rejected;
|
||||
});
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect,
|
||||
path = require('path');
|
||||
let routeLoader = require(path.join(__dirname, '/../../../lib/util/route-loader'));
|
||||
|
||||
suite('Util - Route Loader', () => {
|
||||
test('routeLoader creates accurate route mapping', () => {
|
||||
let actual = routeLoader(path.join(__dirname, 'test-routes'));
|
||||
let expected = {
|
||||
'/api/foo/bar': path.join(__dirname, 'test-routes/api/foo/bar.js'),
|
||||
'/api/foo': path.join(__dirname, 'test-routes/api/foo.js'),
|
||||
'/': path.join(__dirname, 'test-routes/index.js'),
|
||||
};
|
||||
|
||||
expect(expected).to.be.deep.equal(actual);
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user