@lutzklai/pavo 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +54 -0
- package/package.json +25 -0
- package/src/app.js +45 -0
- package/src/appEvents.js +131 -0
- package/src/appTest.js +42 -0
- package/src/bootstrap.js +101 -0
- package/src/helperUtils/authStrategyHelper.js +44 -0
- package/src/helperUtils/diHelper.js +252 -0
- package/src/helperUtils/loggerHelper.js +61 -0
- package/src/module/moduleBootstrap.js +126 -0
- package/src/module/moduleBootstrapTest.js +40 -0
- package/src/module/modulePlugins.js +37 -0
- package/src/module/moduleTesting.js +86 -0
- package/src/moduleFinder.js +51 -0
package/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
|
|
3
|
+
const statusCodes = require('http-status-codes');
|
|
4
|
+
|
|
5
|
+
const app = require('./src/app');
|
|
6
|
+
const appEvents = require('./src/appEvents');
|
|
7
|
+
const appTest = require('./src/appTest');
|
|
8
|
+
const bootstrap = require('./src/bootstrap');
|
|
9
|
+
const moduleFinder = require('./src/moduleFinder');
|
|
10
|
+
const authStrategyHelper = require('./src/helperUtils/authStrategyHelper');
|
|
11
|
+
const diHelper = require('./src/helperUtils/diHelper');
|
|
12
|
+
const loggerHelper = require('./src/helperUtils/loggerHelper');
|
|
13
|
+
const moduleBootstrap = require('./src/module/moduleBootstrap');
|
|
14
|
+
const moduleBootstrapTest = require('./src/module/moduleBootstrapTest');
|
|
15
|
+
const modulePlugins = require('./src/module/modulePlugins');
|
|
16
|
+
const moduleTesting = require('./src/module/moduleTesting');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Injects items into the given dependency injector.
|
|
20
|
+
*
|
|
21
|
+
* @param {{register: function}} diContainer
|
|
22
|
+
*/
|
|
23
|
+
function inject(diContainer) {
|
|
24
|
+
const previousSource = diContainer.setRegisterSource(__filename);
|
|
25
|
+
|
|
26
|
+
const pavoStatusCodes = Object.assign({}, statusCodes);
|
|
27
|
+
pavoStatusCodes.$filename = __filename;
|
|
28
|
+
|
|
29
|
+
diContainer.register('usingPavo', true, true, {filename: __filename});
|
|
30
|
+
diContainer.register('pavoAuthStrategyHelper', authStrategyHelper);
|
|
31
|
+
diContainer.register('statusCodes', pavoStatusCodes);
|
|
32
|
+
|
|
33
|
+
diContainer.setRegisterSource(previousSource);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
app,
|
|
38
|
+
appEvents,
|
|
39
|
+
appTest,
|
|
40
|
+
bootstrap,
|
|
41
|
+
inject,
|
|
42
|
+
module: {
|
|
43
|
+
bootstrap: moduleBootstrap,
|
|
44
|
+
testing: moduleTesting,
|
|
45
|
+
testingSetup: moduleBootstrapTest
|
|
46
|
+
},
|
|
47
|
+
moduleFinder,
|
|
48
|
+
plugins: modulePlugins,
|
|
49
|
+
utils: {
|
|
50
|
+
authStrategyHelper,
|
|
51
|
+
diHelper,
|
|
52
|
+
loggerHelper
|
|
53
|
+
}
|
|
54
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lutzklai/pavo",
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"description": "The Clevyr Pavo API framework.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"author": "Project Content <admin@projectcontent.com>",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@lutzklai/functional-di": "^0.3.3",
|
|
13
|
+
"async-eventemitter": "^0.2.4",
|
|
14
|
+
"colors": "^1.1.2",
|
|
15
|
+
"dataloader": "^1.4.0",
|
|
16
|
+
"http-status-codes": "^1.3.0",
|
|
17
|
+
"lodash": "^4.17.4",
|
|
18
|
+
"winston": "^2.4.0",
|
|
19
|
+
"winston-rollbar2": "^2.1.4"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"registry": "https://registry.npmjs.org/",
|
|
23
|
+
"access": "public"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/app.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const bootstrap = require('./bootstrap');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The Pavo app.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} projectBaseDir
|
|
9
|
+
* @param {{inject: function}} appDiContainer
|
|
10
|
+
* @param {boolean} isTesting
|
|
11
|
+
* @returns {Promise}
|
|
12
|
+
*/
|
|
13
|
+
function pavoApp(projectBaseDir, appDiContainer, isTesting) {
|
|
14
|
+
let pavoAppServer = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Allows the application to retrieve the server object after initialization.
|
|
18
|
+
* This must be registered in the application diContainer before the app is bootstrapped!
|
|
19
|
+
*
|
|
20
|
+
* @returns {{}}
|
|
21
|
+
*/
|
|
22
|
+
function pavoAppServerFactory() {
|
|
23
|
+
return pavoAppServer;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pavoAppServerFactory.$singleton = true;
|
|
27
|
+
pavoAppServerFactory.$filename = __filename;
|
|
28
|
+
appDiContainer.register('pavoAppServer', pavoAppServerFactory);
|
|
29
|
+
|
|
30
|
+
// Bootstrap the application.
|
|
31
|
+
return bootstrap(projectBaseDir, appDiContainer, isTesting).then(function startServer() {
|
|
32
|
+
// By sending true, we tell the server to start up.
|
|
33
|
+
const resolvedServer = appDiContainer.resolve('server', {shouldStart: true});
|
|
34
|
+
|
|
35
|
+
if (resolvedServer && resolvedServer.then) {
|
|
36
|
+
resolvedServer.then((setup) => {
|
|
37
|
+
pavoAppServer = setup;
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
pavoAppServer = resolvedServer;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = pavoApp;
|
package/src/appEvents.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const AsyncEventEmitter = require('async-eventemitter');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The PavoEvents object.
|
|
7
|
+
* Triggers and processes events during the setup process of the app server.
|
|
8
|
+
*/
|
|
9
|
+
class PavoEvents extends AsyncEventEmitter {
|
|
10
|
+
/**
|
|
11
|
+
* Defines the class properties.
|
|
12
|
+
*/
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
/* The Pavo Events. */
|
|
17
|
+
this.EVENT_BEFORE_MIDDLEWARE = 'EVENT_BEFORE_MIDDLEWARE';
|
|
18
|
+
this.EVENT_PROCESS_MIDDLEWARE = 'EVENT_PROCESS_MIDDLEWARE';
|
|
19
|
+
this.EVENT_PROCESS_AUTH_STRATEGIES = 'EVENT_PROCESS_AUTH_STRATEGIES';
|
|
20
|
+
this.EVENT_AFTER_MIDDLEWARE = 'EVENT_AFTER_MIDDLEWARE';
|
|
21
|
+
this.EVENT_PROCESS_MODULES = 'EVENT_PROCESS_MODULES';
|
|
22
|
+
this.EVENT_BEFORE_START = 'EVENT_BEFORE_START';
|
|
23
|
+
this.EVENT_AFTER_START = 'EVENT_AFTER_START';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Emits an event and returns a promise after all listeners have finished.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} event
|
|
30
|
+
* @param {*} data
|
|
31
|
+
* @returns {Promise}
|
|
32
|
+
*/
|
|
33
|
+
emitPromise(event, data) {
|
|
34
|
+
if (!this[event] || String(this[event]) !== event) {
|
|
35
|
+
return Promise.reject(new Error(
|
|
36
|
+
`Invalid event '${event}' given to app event system. Check pavo.appEvents for valid events.`
|
|
37
|
+
));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
this.emit(event, data, (error, response) => {
|
|
42
|
+
if (error) {
|
|
43
|
+
reject(error);
|
|
44
|
+
} else {
|
|
45
|
+
resolve(response);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Processes the given handler before the app middleware is processed.
|
|
53
|
+
*
|
|
54
|
+
* @param {function} handler
|
|
55
|
+
* @returns {Promise}
|
|
56
|
+
*/
|
|
57
|
+
onBeforeMiddleware(handler) {
|
|
58
|
+
return this.on(this.EVENT_BEFORE_MIDDLEWARE, handler);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Processes the given handler when the middlewares are ready to be processed.
|
|
63
|
+
*
|
|
64
|
+
* @param {function} handler
|
|
65
|
+
* @returns {Promise}
|
|
66
|
+
*/
|
|
67
|
+
onProcessMiddleware(handler) {
|
|
68
|
+
return this.on(this.EVENT_PROCESS_MIDDLEWARE, handler);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Processes the given handler when the authentication strategies are ready to be processed.
|
|
73
|
+
*
|
|
74
|
+
* @param {function} handler
|
|
75
|
+
* @returns {Promise}
|
|
76
|
+
*/
|
|
77
|
+
onProcessAuthStrategies(handler) {
|
|
78
|
+
return this.on(this.EVENT_PROCESS_AUTH_STRATEGIES, handler);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Processes the given handler after the app middleware is processed.
|
|
83
|
+
*
|
|
84
|
+
* @param {function} handler
|
|
85
|
+
* @returns {Promise}
|
|
86
|
+
*/
|
|
87
|
+
onAfterMiddleware(handler) {
|
|
88
|
+
return this.on(this.EVENT_AFTER_MIDDLEWARE, handler);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Processes the given handler when the modules are ready to be processed.
|
|
93
|
+
*
|
|
94
|
+
* @param {function} handler
|
|
95
|
+
* @returns {Promise}
|
|
96
|
+
*/
|
|
97
|
+
onProcessModules(handler) {
|
|
98
|
+
return this.on(this.EVENT_PROCESS_MODULES, handler);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Processes the given handler before the app server is started.
|
|
103
|
+
*
|
|
104
|
+
* @param {function} handler
|
|
105
|
+
* @returns {Promise}
|
|
106
|
+
*/
|
|
107
|
+
onBeforeStart(handler) {
|
|
108
|
+
return this.on(this.EVENT_BEFORE_START, handler);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Processes the given handler after the app server has been started.
|
|
113
|
+
*
|
|
114
|
+
* @param {function} handler
|
|
115
|
+
* @returns {Promise}
|
|
116
|
+
*/
|
|
117
|
+
onAfterStart(handler) {
|
|
118
|
+
return this.on(this.EVENT_AFTER_START, handler);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Initializes a new PavoEvents object.
|
|
124
|
+
*
|
|
125
|
+
* @returns {PavoEvents}
|
|
126
|
+
*/
|
|
127
|
+
function pavoEventsFactory() {
|
|
128
|
+
return new PavoEvents();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = pavoEventsFactory;
|
package/src/appTest.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const bootstrap = require('./bootstrap');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initializes the app for testing purposes.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} baseDir
|
|
9
|
+
* @param {function} appDiFactory
|
|
10
|
+
* @returns {function}
|
|
11
|
+
*/
|
|
12
|
+
function initAppForTesting(baseDir, appDiFactory) {
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the application for testing purposes.
|
|
15
|
+
*
|
|
16
|
+
* @param {string=} modName
|
|
17
|
+
* @param {Object=} testingConfig
|
|
18
|
+
* @returns {Promise<Object.<string, Object>>} Promise that returns the Hapi server.
|
|
19
|
+
*/
|
|
20
|
+
return function testApplication(modName, testingConfig) {
|
|
21
|
+
// First setup the application dependency injector.
|
|
22
|
+
const appDiContainer = appDiFactory();
|
|
23
|
+
|
|
24
|
+
// Define any testing setup information
|
|
25
|
+
appDiContainer.register('testingConfig', testingConfig || {});
|
|
26
|
+
|
|
27
|
+
// Bootstrap the application.
|
|
28
|
+
return bootstrap(baseDir, appDiContainer, true).then(function getTestingServer(diContainers) {
|
|
29
|
+
if (!diContainers[modName]) {
|
|
30
|
+
throw new Error(`Bootstrap did not return DI container for module '${modName}'.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
[modName]: diContainers[modName],
|
|
35
|
+
app: diContainers.app,
|
|
36
|
+
mod: diContainers[modName]
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = initAppForTesting;
|
package/src/bootstrap.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lodash = require('lodash');
|
|
4
|
+
|
|
5
|
+
const moduleFinder = require('./moduleFinder.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Bootstraps the application by initializing the dependency injector, loading each module,
|
|
9
|
+
* and registering the module plugins with the DI.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} baseDir
|
|
12
|
+
* @param {Object} appDiContainer
|
|
13
|
+
* @param {boolean} isTesting
|
|
14
|
+
* @returns {Promise<Object.<string, Object>>} A promise that returns the application DI container.
|
|
15
|
+
*/
|
|
16
|
+
function bootstrapApp(baseDir, appDiContainer, isTesting) {
|
|
17
|
+
let allModDiContainers = {};
|
|
18
|
+
let allModServices = {};
|
|
19
|
+
let moduleLoaders = [];
|
|
20
|
+
let modulePlugins = {};
|
|
21
|
+
|
|
22
|
+
// Module finder will auto detect each module and return each of their loaders.
|
|
23
|
+
return moduleFinder(baseDir).then(function afterModulesFound(foundModuleFactories) {
|
|
24
|
+
// Up the event maxListeners cap to 2 per modules found.
|
|
25
|
+
// This prevents sequelize from throwing a warning.
|
|
26
|
+
process.setMaxListeners(foundModuleFactories.length * 2);
|
|
27
|
+
|
|
28
|
+
moduleLoaders = foundModuleFactories.map(function startEachModule(moduleFactory) {
|
|
29
|
+
return {
|
|
30
|
+
directory: moduleFactory.directory,
|
|
31
|
+
module: moduleFactory.module()
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Order of module setup operation:
|
|
37
|
+
* 1. initModule (Sync)
|
|
38
|
+
* 2. loadModuleServices (Sync)
|
|
39
|
+
* 3. registerAppServices (Sync)
|
|
40
|
+
* 4. configureModuleTesting (Async)
|
|
41
|
+
* 5. resolveModulePlugins (Sync)
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
moduleLoaders = moduleLoaders.map((moduleLoader) => {
|
|
45
|
+
const modDiContainers = moduleLoader.module.initModule(appDiContainer, moduleLoader.directory);
|
|
46
|
+
|
|
47
|
+
if (modDiContainers) {
|
|
48
|
+
allModDiContainers = Object.assign({}, allModDiContainers, modDiContainers);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return moduleLoader.module;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
moduleLoaders.forEach((moduleLoader) => {
|
|
55
|
+
const modServices = moduleLoader.loadModuleServices();
|
|
56
|
+
|
|
57
|
+
// Store each service into a collection of all module services.
|
|
58
|
+
if (modServices) {
|
|
59
|
+
allModServices = Object.assign({}, allModServices, modServices);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Register the mod services with the app di container.
|
|
64
|
+
lodash.forEach(allModServices, function registerNewServices(service, serviceName) {
|
|
65
|
+
appDiContainer.register(serviceName, service);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
moduleLoaders.forEach((moduleLoader) => {
|
|
69
|
+
moduleLoader.registerAppServices(allModServices);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (isTesting) {
|
|
73
|
+
return Promise.all(moduleLoaders.map((moduleLoader) => {
|
|
74
|
+
return moduleLoader.configureModuleTesting();
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
}).then(function afterModulesAreConfigured() {
|
|
80
|
+
moduleLoaders.forEach((moduleLoader) => {
|
|
81
|
+
const newPlugins = moduleLoader.resolveModulePlugins();
|
|
82
|
+
|
|
83
|
+
if (newPlugins) {
|
|
84
|
+
modulePlugins = Object.assign({}, modulePlugins, newPlugins);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const modulePluginsFactory = function modulePluginsFactory() {
|
|
89
|
+
return lodash.values(modulePlugins);
|
|
90
|
+
};
|
|
91
|
+
modulePluginsFactory.$filename = __filename;
|
|
92
|
+
|
|
93
|
+
appDiContainer.register('modulePlugins', modulePluginsFactory);
|
|
94
|
+
|
|
95
|
+
return Object.assign({}, allModDiContainers, {
|
|
96
|
+
app: appDiContainer
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = bootstrapApp;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds the authStrategyHelper.
|
|
5
|
+
*
|
|
6
|
+
* @returns {{}}
|
|
7
|
+
*/
|
|
8
|
+
function authStrategyHelperFactory() {
|
|
9
|
+
/**
|
|
10
|
+
* Translates a promise strategy into a passport callback strategy.
|
|
11
|
+
*
|
|
12
|
+
* @param {function} strategy The promise based
|
|
13
|
+
* @returns {function}
|
|
14
|
+
*/
|
|
15
|
+
function strategyWrapper(strategy) {
|
|
16
|
+
return function promiseStrategy() {
|
|
17
|
+
const args = Array.from(arguments);
|
|
18
|
+
|
|
19
|
+
let done = null;
|
|
20
|
+
while (args.length && (!done || typeof done !== 'function')) {
|
|
21
|
+
done = args.pop();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
strategy.apply(null, args).then(
|
|
25
|
+
(strategyResponse) => {
|
|
26
|
+
if (strategyResponse.invalid) {
|
|
27
|
+
return done(null, false, strategyResponse.invalid);
|
|
28
|
+
}
|
|
29
|
+
return done(null, strategyResponse);
|
|
30
|
+
},
|
|
31
|
+
done
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
strategyWrapper
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
authStrategyHelperFactory.$inject = true;
|
|
42
|
+
authStrategyHelperFactory.$filename = __filename;
|
|
43
|
+
|
|
44
|
+
module.exports = authStrategyHelperFactory;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const diContainerFactory = require('@lutzklai/functional-di');
|
|
4
|
+
const lodash = require('lodash');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Requiring colors alters the string prototype to allow color mutations.
|
|
9
|
+
require('colors');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sub folder glob pattern.
|
|
13
|
+
*/
|
|
14
|
+
const SUB_FOLDER_GLOB = '/**/*';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The default file matching regexps for different file types.
|
|
18
|
+
* @type {Object.<string, RegExp>}
|
|
19
|
+
*/
|
|
20
|
+
const FILE_REGEXPS = {
|
|
21
|
+
authStrategies: /Strategy\.js$/i,
|
|
22
|
+
config: /Config\.js$/i,
|
|
23
|
+
constants: /Constants\.js$/i,
|
|
24
|
+
helperUtils: /\.js$/i,
|
|
25
|
+
models: /Model\.js$/i,
|
|
26
|
+
policies: /Polic(y|ies)\.js$/i,
|
|
27
|
+
routes: /Route\.js$/i,
|
|
28
|
+
schemas: /Schema\.js$/i,
|
|
29
|
+
services: /Service\.js$/i
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The default list of module folders.
|
|
34
|
+
* @type {Object.<string, string>}
|
|
35
|
+
*/
|
|
36
|
+
const MODULE_FOLDERS = {
|
|
37
|
+
authStrategies: 'authStrategies',
|
|
38
|
+
config: 'config',
|
|
39
|
+
constants: 'constants',
|
|
40
|
+
helperUtils: 'helperUtils',
|
|
41
|
+
models: 'models',
|
|
42
|
+
policies: 'policies',
|
|
43
|
+
routes: 'routes' + SUB_FOLDER_GLOB,
|
|
44
|
+
schemas: 'schemas',
|
|
45
|
+
services: 'services'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The regexp used to match when a file is a test file.
|
|
50
|
+
* @const {RegExp}
|
|
51
|
+
*/
|
|
52
|
+
const TEST_MATCH_REGEXP = /\.test\.js$/i;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* File/directory not found error.
|
|
56
|
+
* @const {string}
|
|
57
|
+
*/
|
|
58
|
+
const ENOENT = 'ENOENT';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds the diHelper.
|
|
62
|
+
*
|
|
63
|
+
* @param {Object.<string, RegExp>=} overrideFileRegexps
|
|
64
|
+
* @param {Object.<string, string>=} overrideModuleFolders
|
|
65
|
+
* @param {RegExp=} overrideTestRegexp
|
|
66
|
+
* @param {boolean=} logNotFound
|
|
67
|
+
* @returns {{}}
|
|
68
|
+
*/
|
|
69
|
+
function diHelperFactory(overrideFileRegexps, overrideModuleFolders, overrideTestRegexp, logNotFound) {
|
|
70
|
+
const fileRegexps = Object.assign({}, FILE_REGEXPS, overrideFileRegexps || {});
|
|
71
|
+
const moduleFolders = Object.assign({}, MODULE_FOLDERS, overrideModuleFolders || {});
|
|
72
|
+
const testRegexp = overrideTestRegexp || TEST_MATCH_REGEXP;
|
|
73
|
+
const logNotFoundErrors = logNotFound || false;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Loads either a new Di Container or a clone of the given parent Di Container.
|
|
77
|
+
*
|
|
78
|
+
* @param {{}=} parentDiContainer
|
|
79
|
+
* @returns {{}}
|
|
80
|
+
*/
|
|
81
|
+
function getDiContainer(parentDiContainer) {
|
|
82
|
+
let diContainer;
|
|
83
|
+
if (parentDiContainer) {
|
|
84
|
+
diContainer = parentDiContainer.clone();
|
|
85
|
+
} else {
|
|
86
|
+
diContainer = diContainerFactory();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return diContainer;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Injects all the files (or regexp matching files) from a folder into the dependency injector.
|
|
94
|
+
* Note: Files with no '.' or extension in the name will be skipped.
|
|
95
|
+
*
|
|
96
|
+
* @param {{register: function}} diContainer
|
|
97
|
+
* @param {string|string[]} folderPath
|
|
98
|
+
* @param {RegExp=} fileRegexp
|
|
99
|
+
* @param {Object.<string, string>=} injectionNameMap
|
|
100
|
+
* @param {{followSubFolders: boolean}=} folderOptions
|
|
101
|
+
* @returns {{}}
|
|
102
|
+
*/
|
|
103
|
+
function injectFilesFromFolder(diContainer, folderPath, fileRegexp, injectionNameMap, folderOptions) {
|
|
104
|
+
const safeInjectionMap = injectionNameMap || {};
|
|
105
|
+
const safeFolderOptions = folderOptions || {};
|
|
106
|
+
const followSubFolders = Boolean(safeFolderOptions.followSubFolders);
|
|
107
|
+
|
|
108
|
+
let safeFolderPath = folderPath;
|
|
109
|
+
if (Array.isArray(folderPath)) {
|
|
110
|
+
safeFolderPath = path.join.apply(null, folderPath);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let files = [];
|
|
114
|
+
try {
|
|
115
|
+
files = fs.readdirSync(safeFolderPath); // eslint-disable-line no-sync
|
|
116
|
+
} catch (readDirError) {
|
|
117
|
+
if (readDirError.code && readDirError.code === ENOENT) {
|
|
118
|
+
if (logNotFoundErrors) {
|
|
119
|
+
console.log( // eslint-disable-line no-console
|
|
120
|
+
`Skipping injections from folder ${safeFolderPath}`.yellow
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
throw readDirError;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
files.forEach((fileName) => {
|
|
129
|
+
if (fileName.indexOf('.') === -1) {
|
|
130
|
+
if (followSubFolders) {
|
|
131
|
+
const newFolderPath = [safeFolderPath, fileName];
|
|
132
|
+
injectFilesFromFolder(diContainer, newFolderPath, fileRegexp, injectionNameMap, safeFolderOptions);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
} else if (fileRegexp && !fileName.match(fileRegexp)) {
|
|
136
|
+
return;
|
|
137
|
+
} else if (fileName.match(testRegexp)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let injectionName = null;
|
|
142
|
+
if (safeInjectionMap[fileName] === false) {
|
|
143
|
+
return;
|
|
144
|
+
} else if (safeInjectionMap[fileName]) {
|
|
145
|
+
injectionName = safeInjectionMap[fileName];
|
|
146
|
+
} else {
|
|
147
|
+
const fileNameParts = String(fileName).split('.');
|
|
148
|
+
injectionName = fileNameParts[0];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const fullFilePath = path.join(safeFolderPath, '/', fileName);
|
|
152
|
+
diContainer.register(injectionName, require(fullFilePath)); // eslint-disable-line global-require
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return diContainer;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Automagically injects all the files for the module.
|
|
160
|
+
*
|
|
161
|
+
* @param {{register: function}} diContainer
|
|
162
|
+
* @param {string|string[]} moduleFolderPath
|
|
163
|
+
* @param {Object.<string, string>=} injectionNameMap
|
|
164
|
+
* @returns {{}}
|
|
165
|
+
*/
|
|
166
|
+
function injectFilesForModule(diContainer, moduleFolderPath, injectionNameMap) {
|
|
167
|
+
let safeFolderPath = moduleFolderPath;
|
|
168
|
+
if (Array.isArray(moduleFolderPath)) {
|
|
169
|
+
safeFolderPath = path.join.apply(null, moduleFolderPath);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let files = [];
|
|
173
|
+
try {
|
|
174
|
+
files = fs.readdirSync(safeFolderPath); // eslint-disable-line no-sync
|
|
175
|
+
} catch (readDirError) {
|
|
176
|
+
if (readDirError.code && readDirError.code === ENOENT) {
|
|
177
|
+
if (logNotFoundErrors) {
|
|
178
|
+
console.log( // eslint-disable-line no-console
|
|
179
|
+
`Skipping injections from folder ${safeFolderPath}`.yellow
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
throw readDirError;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const injectFolderOptions = {followSubFolders: false};
|
|
188
|
+
|
|
189
|
+
files.forEach((fileName) => {
|
|
190
|
+
// Skip non-folder files.
|
|
191
|
+
if (fileName.indexOf('.') !== -1) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let matchedFolderName = null;
|
|
196
|
+
let followSubFolders = false;
|
|
197
|
+
lodash.values(moduleFolders).forEach((moduleFolderName) => {
|
|
198
|
+
// If the module folder name ends in '/**/*' then it will cause sub folders to be searched as well.
|
|
199
|
+
if (moduleFolderName.substr(0 - SUB_FOLDER_GLOB.length) === SUB_FOLDER_GLOB) {
|
|
200
|
+
const onlyFolderName = moduleFolderName.substring(0, moduleFolderName.length - SUB_FOLDER_GLOB.length);
|
|
201
|
+
if (fileName === onlyFolderName) {
|
|
202
|
+
matchedFolderName = fileName;
|
|
203
|
+
followSubFolders = true;
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (fileName === moduleFolderName) {
|
|
209
|
+
matchedFolderName = fileName;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (!matchedFolderName) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
injectFilesFromFolder(
|
|
218
|
+
diContainer,
|
|
219
|
+
[moduleFolderPath, matchedFolderName],
|
|
220
|
+
fileRegexps[matchedFolderName],
|
|
221
|
+
injectionNameMap,
|
|
222
|
+
{...injectFolderOptions, followSubFolders}
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return diContainer;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Automagically injects the top level files for the project.
|
|
231
|
+
* NOTE: This does not inject any module files.
|
|
232
|
+
*
|
|
233
|
+
* @param {{register: function}} diContainer
|
|
234
|
+
* @param {string|string[]} projectSrcFolderPath
|
|
235
|
+
* @param {Object.<string, string>=} injectionNameMap
|
|
236
|
+
* @returns {{}}
|
|
237
|
+
*/
|
|
238
|
+
function injectFilesForProject(diContainer, projectSrcFolderPath, injectionNameMap) {
|
|
239
|
+
return injectFilesForModule(diContainer, projectSrcFolderPath, injectionNameMap);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
getDiContainer,
|
|
244
|
+
injectFilesFromFolder,
|
|
245
|
+
injectFilesForModule,
|
|
246
|
+
injectFilesForProject,
|
|
247
|
+
fileRegexp: Object.assign({}, fileRegexps),
|
|
248
|
+
moduleFolders: Object.assign({}, moduleFolders)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = diHelperFactory;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const winston = require('winston');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Exposes 'winston.transports.Rollbar'.
|
|
7
|
+
*/
|
|
8
|
+
require('winston-rollbar2');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Gets a new logger instance.
|
|
12
|
+
*
|
|
13
|
+
* @param {{}} options
|
|
14
|
+
* @returns {{}}
|
|
15
|
+
*/
|
|
16
|
+
function getNewLogger(options) {
|
|
17
|
+
const loggerOptions = options || {};
|
|
18
|
+
|
|
19
|
+
const defaultConsoleOptions = loggerOptions.console || {};
|
|
20
|
+
|
|
21
|
+
const transports = loggerOptions.transports || [
|
|
22
|
+
getTransportByName('console', defaultConsoleOptions)
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const newLogger = new (winston.Logger)({
|
|
26
|
+
transports: transports
|
|
27
|
+
});
|
|
28
|
+
newLogger.$filename = __filename;
|
|
29
|
+
|
|
30
|
+
return newLogger;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Gets a logging transport by the given name and options.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} name
|
|
37
|
+
* @param {{}} options
|
|
38
|
+
* @returns {{}}
|
|
39
|
+
* @throws {Error} If the transport could not be found.
|
|
40
|
+
*/
|
|
41
|
+
function getTransportByName(name, options) {
|
|
42
|
+
const transportOptions = options || {};
|
|
43
|
+
|
|
44
|
+
switch (name) {
|
|
45
|
+
case 'console':
|
|
46
|
+
return new (winston.transports.Console)(transportOptions);
|
|
47
|
+
case 'http':
|
|
48
|
+
return new (winston.transports.Http)(transportOptions);
|
|
49
|
+
case 'file':
|
|
50
|
+
return new (winston.transports.File)(transportOptions);
|
|
51
|
+
case 'rollbar':
|
|
52
|
+
return new (winston.transports.Rollbar)(transportOptions);
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`Could not find transport named '${name}'`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
getNewLogger,
|
|
60
|
+
getTransportByName
|
|
61
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lodash = require('lodash');
|
|
4
|
+
|
|
5
|
+
const MOD_KEY_MAX = Number.MAX_SAFE_INTEGER;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Provides the list of functions for bootstrapping a module.
|
|
9
|
+
*
|
|
10
|
+
* @param {{name: string}} modConfig
|
|
11
|
+
* @param {function} moduleDiFactory
|
|
12
|
+
* @param {function} testingSetup
|
|
13
|
+
* @param {string[]} serviceNames
|
|
14
|
+
* @returns {{
|
|
15
|
+
* initModule: function,
|
|
16
|
+
* loadModuleServices: function,
|
|
17
|
+
* registerAppServices: function,
|
|
18
|
+
* configureModuleTesting: function,
|
|
19
|
+
* resolveModulePlugins: function
|
|
20
|
+
* }}
|
|
21
|
+
*/
|
|
22
|
+
function modBootstrapFactory(modConfig, moduleDiFactory, testingSetup, serviceNames) {
|
|
23
|
+
let modName = modConfig.name;
|
|
24
|
+
let modKey = (modName || '') + Math.floor((Math.random() * MOD_KEY_MAX));
|
|
25
|
+
|
|
26
|
+
const safeServiceNames = serviceNames || [];
|
|
27
|
+
|
|
28
|
+
let modDiContainer = null;
|
|
29
|
+
let dependencyArgs = {};
|
|
30
|
+
let modServices = {};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initializes the module by loading its dependency injector.
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} parentDiContainer
|
|
36
|
+
* @param {string} directoryName
|
|
37
|
+
* @returns {Promise.<{Object}>}
|
|
38
|
+
*/
|
|
39
|
+
function initModule(parentDiContainer, directoryName) {
|
|
40
|
+
if (directoryName && !modName) {
|
|
41
|
+
modName = `${directoryName}Module`;
|
|
42
|
+
modKey = modName + modKey;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
modDiContainer = moduleDiFactory(parentDiContainer);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
[modKey]: modDiContainer
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Loads the services for this module.
|
|
54
|
+
*
|
|
55
|
+
* @returns {Object.<string, function>}
|
|
56
|
+
*/
|
|
57
|
+
function loadModuleServices() {
|
|
58
|
+
modServices = {};
|
|
59
|
+
safeServiceNames.forEach(function mapServices(serviceName) {
|
|
60
|
+
modServices[serviceName] = modDiContainer.resolveCurry(serviceName);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return modServices;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Registers other application services with this module's DI container.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object.<string, function>} appServices
|
|
70
|
+
*/
|
|
71
|
+
function registerAppServices(appServices) {
|
|
72
|
+
if (!appServices) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
lodash.forEach(appServices, function registerNewServices(service, serviceName) {
|
|
77
|
+
if (modServices[serviceName]) {
|
|
78
|
+
// Do not register our own module services.
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
modDiContainer.register(serviceName, service);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Runs testing configuration for this module.
|
|
88
|
+
*
|
|
89
|
+
* @returns {Promise}
|
|
90
|
+
*/
|
|
91
|
+
function configureModuleTesting() {
|
|
92
|
+
return testingSetup(
|
|
93
|
+
modDiContainer,
|
|
94
|
+
modDiContainer.resolve('testingConfig')
|
|
95
|
+
).then((testingDependencyArgs) => {
|
|
96
|
+
dependencyArgs = Object.assign({}, dependencyArgs, testingDependencyArgs);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolves and returns the module plugins.
|
|
102
|
+
*
|
|
103
|
+
* @returns {Object.<string, Object>}
|
|
104
|
+
*/
|
|
105
|
+
function resolveModulePlugins() {
|
|
106
|
+
const moduleAsPlugin = modDiContainer.resolve('moduleAsPlugin', dependencyArgs || null)();
|
|
107
|
+
|
|
108
|
+
if (modName && !moduleAsPlugin.name) {
|
|
109
|
+
moduleAsPlugin.name = modName;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
[modKey]: moduleAsPlugin
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
initModule,
|
|
119
|
+
loadModuleServices,
|
|
120
|
+
registerAppServices,
|
|
121
|
+
configureModuleTesting,
|
|
122
|
+
resolveModulePlugins
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = modBootstrapFactory;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initializes the module testing bootstrap.
|
|
5
|
+
*
|
|
6
|
+
* @param {{createSeededDatabase: function}} dbSeed
|
|
7
|
+
* @returns {function}
|
|
8
|
+
*/
|
|
9
|
+
function initModuleTestingBootstrap(dbSeed) {
|
|
10
|
+
/**
|
|
11
|
+
* Initializes the application for testing purposes.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} modDiContainer
|
|
14
|
+
* @param {{routeName: string, onDbSeeded: function}} config
|
|
15
|
+
* @returns {Promise<{moduleRoutes: string[]}>}
|
|
16
|
+
*/
|
|
17
|
+
return function setupModuleTesting(modDiContainer, config) {
|
|
18
|
+
const safeConfig = config || {};
|
|
19
|
+
const routeName = safeConfig.routeName || '';
|
|
20
|
+
const onDbSeeded = safeConfig.onDbSeeded || null;
|
|
21
|
+
|
|
22
|
+
const returnArgs = {
|
|
23
|
+
moduleRoutes: [routeName]
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (!dbSeed) {
|
|
27
|
+
return Promise.resolve(returnArgs);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return dbSeed.createSeededDatabase(routeName + 'Test', modDiContainer).then(() => {
|
|
31
|
+
if (onDbSeeded && typeof onDbSeeded === 'function') {
|
|
32
|
+
onDbSeeded(modDiContainer);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return returnArgs;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = initModuleTestingBootstrap;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Requiring colors alters the string prototype to allow color mutations.
|
|
4
|
+
require('colors');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Initializes the module plugins parser.
|
|
8
|
+
*
|
|
9
|
+
* @param {{}} app
|
|
10
|
+
* @param {boolean=} skipLogging
|
|
11
|
+
* @returns {{registerWithApp: function}}
|
|
12
|
+
*/
|
|
13
|
+
function initModulePlugins(app, skipLogging) {
|
|
14
|
+
/**
|
|
15
|
+
* Registers each plugin with the server/app.
|
|
16
|
+
*
|
|
17
|
+
* @param {Array.<{register: function}>} modulePlugins
|
|
18
|
+
* @returns {Array.<{}>}
|
|
19
|
+
*/
|
|
20
|
+
function registerWithApp(modulePlugins) {
|
|
21
|
+
const registerPromises = modulePlugins.map((modulePlugin) => {
|
|
22
|
+
if (modulePlugin.name && !skipLogging) {
|
|
23
|
+
console.log('Loading module', String(modulePlugin.name).cyan.bold); // eslint-disable-line no-console
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Promise.resolve(modulePlugin.register(app));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return Promise.all(registerPromises);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
registerWithApp
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = initModulePlugins;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts the given arguments to a testing config object.
|
|
5
|
+
*
|
|
6
|
+
* @param {string=} routeName
|
|
7
|
+
* @param {function=} onDbSeeded
|
|
8
|
+
* @returns {Object}
|
|
9
|
+
*/
|
|
10
|
+
function convertArgumentsToConfig(routeName, onDbSeeded) {
|
|
11
|
+
return {
|
|
12
|
+
routeName: routeName ? String(routeName) : '',
|
|
13
|
+
onDbSeeded: onDbSeeded || null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Provides the list of functions for testing a module.
|
|
19
|
+
*
|
|
20
|
+
* @param {function} appTesting
|
|
21
|
+
* @param {{name: string}} modConfig
|
|
22
|
+
* @returns {{
|
|
23
|
+
* bootstrap: function,
|
|
24
|
+
* bootstrapServer: function,
|
|
25
|
+
* setup: function
|
|
26
|
+
* }}
|
|
27
|
+
*/
|
|
28
|
+
function modTestingFactory(appTesting, modConfig) {
|
|
29
|
+
const modName = modConfig.name;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initializes the application for testing purposes and returns the module's DI container.
|
|
33
|
+
*
|
|
34
|
+
* @param {string=} routeName
|
|
35
|
+
* @param {function=} onDbSeeded
|
|
36
|
+
* @returns {Promise<Object.<string, Object>>} Promise that returns the module's DI container.
|
|
37
|
+
*/
|
|
38
|
+
function bootstrapTestApplication(routeName, onDbSeeded) {
|
|
39
|
+
return appTesting(
|
|
40
|
+
modName,
|
|
41
|
+
convertArgumentsToConfig(routeName, onDbSeeded)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initializes the application server for testing purposes.
|
|
47
|
+
*
|
|
48
|
+
* @param {string=} routeName
|
|
49
|
+
* @param {function=} onDbSeeded
|
|
50
|
+
* @returns {Promise<Object>} Promise that returns the Hapi server.
|
|
51
|
+
*/
|
|
52
|
+
function bootstrapTestAppServer(routeName, onDbSeeded) {
|
|
53
|
+
const mockAdminUser = {
|
|
54
|
+
id: 1001,
|
|
55
|
+
isAdmin: true
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return appTesting(
|
|
59
|
+
modName,
|
|
60
|
+
convertArgumentsToConfig(routeName, onDbSeeded)
|
|
61
|
+
).then(function getServerFromModDiContainer(diContainers) {
|
|
62
|
+
// By sending false, we are preventing the server from starting.
|
|
63
|
+
return diContainers.app.resolve('server', {shouldStart: false});
|
|
64
|
+
}).then(function getMockServer(server) {
|
|
65
|
+
return {
|
|
66
|
+
inject: (call) => {
|
|
67
|
+
if (!call.credentials) {
|
|
68
|
+
call.credentials = {};
|
|
69
|
+
}
|
|
70
|
+
if (!call.credentials.user) {
|
|
71
|
+
call.credentials.user = mockAdminUser;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return server.inject(call);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
bootstrap: bootstrapTestApplication,
|
|
82
|
+
bootstrapServer: bootstrapTestAppServer
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = modTestingFactory;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* eslint-disable global-require */
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
// Requiring colors alters the string prototype to allow color mutations.
|
|
9
|
+
require('colors');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loads the main module loader factories for all modules in the modules directory.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} baseDirectory
|
|
15
|
+
* @returns {Promise.<Array.<Object>>}
|
|
16
|
+
*/
|
|
17
|
+
function moduleFinder(baseDirectory) {
|
|
18
|
+
const modulesPath = path.join(baseDirectory, 'modules');
|
|
19
|
+
|
|
20
|
+
return new Promise(function moduleFinderPromise(resolve, reject) {
|
|
21
|
+
fs.readdir(modulesPath, function afterReadModulesDir(err, files) {
|
|
22
|
+
if (err) {
|
|
23
|
+
if (err.code === 'ENOENT') {
|
|
24
|
+
console.log( // eslint-disable-line no-console
|
|
25
|
+
`Could not find required module folder at ${modulesPath}`.red.bold
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
reject(err);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resolve(files);
|
|
34
|
+
});
|
|
35
|
+
}).then(function parseOutFiles(files) {
|
|
36
|
+
return files.filter(function filterOutFilesWithDots(fileName) {
|
|
37
|
+
return (fileName.indexOf('.') < 0);
|
|
38
|
+
});
|
|
39
|
+
}).then(function requireFoundModules(directoryNames) {
|
|
40
|
+
return directoryNames.map(function requireModule(directoryName) {
|
|
41
|
+
return {
|
|
42
|
+
directory: directoryName,
|
|
43
|
+
module: require(
|
|
44
|
+
path.join(modulesPath, directoryName, 'index.js')
|
|
45
|
+
)
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = moduleFinder;
|