diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..23e6a3cf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*.{js,json}] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env b/.env deleted file mode 100644 index 738e45e2..00000000 --- a/.env +++ /dev/null @@ -1,13 +0,0 @@ -ELASTICIO_TASK={"_id":"5559edd38968ec0736000003","data":{"step_1":{"uri":"546456456456456"}},"recipe":{"nodes":[{"id":"step_1","function":"datas_and_errors"}]}} -ELASTICIO_STEP_ID=step_1 - -ELASTICIO_AMQP_URI=amqp://guest:guest@localhost:5672 -ELASTICIO_LISTEN_MESSAGES_ON=5559edd38968ec0736000003:test_exec:step_1:messages -ELASTICIO_PUBLISH_MESSAGES_TO=5527f0ea43238e5d5f000002_exchange -ELASTICIO_ERROR_ROUTING_KEY=5559edd38968ec0736000003.test_exec.step_1.error -ELASTICIO_REBOUND_ROUTING_KEY=5559edd38968ec0736000003.test_exec.step_1.rebound -ELASTICIO_DATA_ROUTING_KEY=5559edd38968ec0736000003.test_exec.step_1.message -ELASTICIO_SNAPSHOT_ROUTING_KEY=5559edd38968ec0736000003.test_exec.step_1.snapshot - -ELASTICIO_COMPONENT_PATH=/spec/component/ -DEBUG=debug diff --git a/.eslintignore b/.eslintignore index 97219ed3..81c66232 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1 @@ -syntax_error_trigger.js \ No newline at end of file +syntax_error_trigger.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index cfcc4e26..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,150 +0,0 @@ -'use strict'; - -const ERROR = 'error'; -const WARN = 'warn'; -const ALWAYS = 'always'; -const NEVER = 'never'; - -module.exports = { - 'env': { - es6: true, - node: true, - }, - 'parserOptions': { - 'ecmaVersion': 8 - }, - 'extends': 'eslint:recommended', - 'plugins': [ - 'mocha' - ], - 'rules': { - 'indent': [ - ERROR, - 4, - { - SwitchCase: 1 - } - ], - 'linebreak-style': ERROR, - 'quotes': [ - ERROR, - 'single', - { - avoidEscape: true, - allowTemplateLiterals: true - } - ], - 'semi': [ - ERROR, - ALWAYS - ], - 'func-names': ERROR, - 'no-empty': ERROR, - 'no-empty-function': ERROR, - 'brace-style': [ - ERROR, - '1tbs', - { allowSingleLine: true } - ], - 'no-multiple-empty-lines': ERROR, - 'no-multi-spaces': ERROR, - 'one-var': [ - ERROR, - NEVER - ], - 'quote-props': [ - ERROR, - 'consistent-as-needed' - ], - 'key-spacing': ERROR, - 'space-unary-ops': [ - ERROR, - { - words: true, - nonwords: false - } - ], - 'no-spaced-func': ERROR, - 'space-before-function-paren': [ - ERROR, - { - anonymous: ALWAYS, - named: NEVER - } - ], - 'arrow-body-style': [ - ERROR, - 'as-needed' - ], - 'array-bracket-spacing': ERROR, - 'space-in-parens': ERROR, - 'comma-dangle': ERROR, - 'no-trailing-spaces': ERROR, - 'yoda': ERROR, - 'max-len': [ - ERROR, - 120 - ], - 'camelcase': [ - ERROR, - { - properties: 'never' - } - ], - 'new-cap': [ - ERROR, - { - capIsNewExceptions: ['Q'] - } - ], - 'comma-style': ERROR, - 'curly': ERROR, - 'object-curly-spacing': [ - ERROR, - ALWAYS - ], - 'template-curly-spacing': ERROR, - 'dot-notation': ERROR, - 'dot-location': [ - ERROR, - 'property' - ], - 'func-style': [ - ERROR, - 'declaration', - { - allowArrowFunctions: true - } - ], - 'eol-last': ERROR, - 'space-infix-ops': ERROR, - 'keyword-spacing': ERROR, - 'space-before-blocks': ERROR, - 'no-invalid-this': ERROR, - 'consistent-this': ERROR, - 'no-this-before-super': ERROR, - 'no-unreachable': ERROR, - 'no-sparse-arrays': ERROR, - 'array-callback-return': ERROR, - 'eqeqeq': ERROR, - 'no-use-before-define': WARN, - 'no-undef': ERROR, - 'no-unused-vars': WARN, - 'no-mixed-spaces-and-tabs': ERROR, - 'operator-linebreak': [ - ERROR, - 'before' - ], - 'no-console': [ - WARN, - { - 'allow': [ - 'warn', - 'error' - ] - } - ], - 'mocha/no-skipped-tests': ERROR, - 'mocha/no-exclusive-tests': ERROR - } -}; \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..4a620215 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,46 @@ +{ + "root": true, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "script", + "ecmaFeatures": { + "impliedStrict": true + } + }, + "plugins": [ + "eslint-plugin-mocha" + ], + "extends": [ + "standard" + ], + "rules": { + "mocha/no-exclusive-tests": "error", + "mocha/no-skipped-tests": "error", + "node/no-deprecated-api": "warn", + "semi": [ + "error", + "always" + ], + "indent": [ + "error", + 4 + ], + "max-len": [ + "warn", + 120, + 4, + { + "ignoreUrls": true, + "ignoreTemplateLiterals": true + } + ], + "space-before-function-paren": [ + "warn", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ] + } +} diff --git a/.gitignore b/.gitignore index 2537ed55..6387edba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -coverage +.nyc_output node_modules .DS_Store .idea +.*.sw[op] diff --git a/.npmignore b/.npmignore index 71e44b10..63039113 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,6 @@ -.env .idea .circleci -mocha_spec -spec \ No newline at end of file +node_modules +.nyc_output +spec +.*.swp diff --git a/createQueues.js b/createQueues.js deleted file mode 100644 index 0c1a28e0..00000000 --- a/createQueues.js +++ /dev/null @@ -1,148 +0,0 @@ -var amqp = require('./lib/amqp.js'); -var settings = require('./lib/settings.js').readFrom(process.env); -var Q = require('q'); -var util = require('util'); - -var execId = "1432205514864"; - -var task = { - "id" : "5559edd38968ec0736000003", - "user" : "5527f0ea43238e5d5f000001", - "data" : { - "step_2" : { - "_account" : "554b53aed5178d6540000001" - }, - "step_3" : { - "mapper" : { - "data" : { - "qty" : "2" - }, - "product" : "Btestsku" - } - }, - "step_1" : { - "interval" : "minute", - "_account" : "5559ed6b8968ec0736000002" - } - }, - "recipe" : { - "nodes" : [ - { - "first" : true, - "id" : "step_1", - "function" : "getProducts", - "compId" : "shopware" - }, - { - "id" : "step_3", - "function" : "map", - "compId" : "mapper" - }, - { - "id" : "step_2", - "function" : "updateInventory", - "compId" : "magento" - } - ], - "connections" : [ - { - "to" : "step_3", - "from" : "step_1" - }, - { - "to" : "step_2", - "from" : "step_3" - } - ] - } -}; - - -var amqpConnection = new amqp.AMQPConnection(settings); -amqpConnection.connect(settings.AMQP_URI).then(function(){ - сreateQueuesAndExchanges(execId, task, "step_1", "step_2"); -}); - -function сreateQueuesAndExchanges(execId, task, stepId, nextStepId){ - - var EXCHANGE_NAME = 'userexchange:' + task.user; - - var MESSAGE_TAG = util.format('%s:%s:%s:message', task.id, stepId, execId); - var ERROR_TAG = util.format('%s:%s:%s:error', task.id, stepId, execId); - var REBOUND_TAG = util.format('%s:%s:%s:rebound', task.id, stepId, execId); - - var MESSAGES_QUEUE = util.format('%s:%s:%s:messages', task.id, stepId, execId); - var MESSAGES_LISTENING_QUEUE = util.format('%s:%s:%s:messages', task.id, nextStepId, execId); - var REBOUNDS_QUEUE = util.format('%s:%s:%s:rebounds', task.id, stepId, execId); - - console.log('INCOMING_MESSAGES_QUEUE=%s', MESSAGES_QUEUE); - console.log('EXCHANGE_NAME=%s', EXCHANGE_NAME); - console.log('MESSAGE_TAG=%s', MESSAGE_TAG); - console.log('ERROR_TAG=%s', ERROR_TAG); - console.log('REBOUND_TAG=%s', REBOUND_TAG); - console.log('MESSAGES_QUEUE=%s', MESSAGES_QUEUE); - console.log('REBOUNDS_QUEUE=%s', REBOUNDS_QUEUE); - - var userExchange = { - name: EXCHANGE_NAME, - type: 'direct', - options: { - durable: true, - autoDelete: false - } - }; - - var messagesQueue = { - name: MESSAGES_QUEUE, - options: { - durable: true, - autoDelete: false - } - }; - - var messagesListeningQueue = { - name: MESSAGES_LISTENING_QUEUE, - options: { - durable: true, - autoDelete: false - } - }; - - var REBOUND_QUEUE_TTL = 10 * 60 * 1000; // 10 min - - var reboundsQueue = { - name: REBOUNDS_QUEUE, - options: { - durable: true, - autoDelete: false, - arguments: { - 'x-message-ttl': REBOUND_QUEUE_TTL, - 'x-dead-letter-exchange': EXCHANGE_NAME, // send dead rebounded queues back to exchange - 'x-dead-letter-routing-key': MESSAGE_TAG // with tag as message - } - } - }; - - return Q.all([ - assertExchange(amqpConnection.publishChannel, userExchange), // check that exchange exists - assertQueue(amqpConnection.publishChannel, messagesQueue), // create messages queue - assertQueue(amqpConnection.publishChannel, messagesListeningQueue), // create messages queue - amqpConnection.publishChannel.bindQueue(messagesListeningQueue.name, userExchange.name, MESSAGE_TAG), - assertQueue(amqpConnection.publishChannel, reboundsQueue), // create rebounds queue - amqpConnection.publishChannel.bindQueue(reboundsQueue.name, userExchange.name, REBOUND_TAG) - ]).then(function(){ - console.log('Successfully asserted all queues'); - }).done(); - - function assertQueue(channel, queue) { - return channel.assertQueue(queue.name, queue.options).then(function assertQueueSuccess() { - console.log('Succesfully asserted queue: ' + queue.name); - }); - } - - function assertExchange(channel, exchange) { - return channel.assertExchange(exchange.name, exchange.type, exchange.options).then(function assertExchangeSuccess() { - console.log('Succesfully asserted exchange: ' + exchange.name); - }); - } -} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 72838439..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,31 +0,0 @@ -var gulp = require('gulp'); -var jasmine = require('gulp-jasmine'); -var istanbul = require('gulp-istanbul'); -var del = require('del'); - -var paths = { - code: ['./lib/*.js'], - spec: ['./spec/**/*.spec.js'], - coverageReport: './coverage/lcov.info', - coverage: './coverage' -}; - -gulp.task('clean:coverage', function (cb) { - del([paths.coverage], cb); -}); - -gulp.task('istanbul', function (cb) { - gulp - .src(paths.code) - .pipe(istanbul()) // Covering files - .pipe(istanbul.hookRequire()) // Force `require` to return covered files - .on('finish', function () { - gulp - .src(paths.spec) - .pipe(jasmine()) - .pipe(istanbul.writeReports()) // Creating the reports after tests runned - .on('end', cb); - }); -}); - -gulp.task('coverage', ['clean:coverage', 'istanbul']); diff --git a/lib/AmqpConnWrapper.js b/lib/AmqpConnWrapper.js new file mode 100644 index 00000000..68ca4cd6 --- /dev/null +++ b/lib/AmqpConnWrapper.js @@ -0,0 +1,54 @@ +const amqplib = require('amqplib'); +// FIXME TESTS +class AmqpConnWrapper { + constructor(amqpUri, logger) { + this._logger = logger; + this._amqpUri = amqpUri; + } + async start() { + // FIXME safety for double calls + // TODO handle channel errors. Recoonect???? + this._amqp = await amqplib.connect(this._amqpUri); + // FIXME + // this.amqp.on('error', this._logger.criticalerrorandExit); + // this.amqp.on('close', this._logger.criticalerrorandExit); + this._logger.debug('Connected to amqp'); + + this._subscribeChannel = await this._amqp.createChannel(); + // FIXME + // this.subscribeChannel.on('error', this._logger.criticalErrorAndExit); + this._logger.debug('Opened subscribe channel'); + + this._publishChannel = await this._amqp.createConfirmChannel(); + // FIXME + // this.publishChannel.on('error', this._logger.criticalErrorAndExit); + this._logger.debug('Opened publish channel'); + } + async stop() { + // FIXME safety for double calls + this._logger.trace('Close AMQP connections'); + try { + await this._subscribeChannel.close(); + } catch (alreadyClosed) { + this._logger.debug('Subscribe channel is closed already'); + } + try { + await this._publishChannel.close(); + } catch (alreadyClosed) { + this._logger.debug('Publish channel is closed already'); + } + try { + await this._amqp.close(); + } catch (alreadyClosed) { + this._logger.debug('AMQP connection is closed already'); + } + this._logger.debug('Successfully closed AMQP connections'); + } + getPublishChannel() { + return this._publishChannel; + } + getSubscribeChannel() { + return this._subscribeChannel; + } +} +module.exports = AmqpConnWrapper; diff --git a/lib/App.js b/lib/App.js new file mode 100644 index 00000000..62bd9f9f --- /dev/null +++ b/lib/App.js @@ -0,0 +1,114 @@ +class App { + /** + * Start application + * Implemented as SAFE in terms of exception + */ + async start() { + process.on('uncaughtException', (e) => { + try { + this._safeLogError(e, 'Uncaught exception'); + this._safeFatalErrorHook(e); + } catch (error) { + console.error('Error handling uncaught exception', error); + } finally { + process.exit(this.constructor.EXIT_CODES.UNCAUGHT_EXCEPTION); + } + }); + process.on('unhandledRejection', (e) => { + try { + this._safeLogError(e, 'Uncaught rejection'); + this._safeFatalErrorHook(e); + } catch (error) { + console.error('Error handling uncaught rejection', error); + } finally { + process.exit(this.constructor.EXIT_CODES.UNCAUGHT_REJECTION); + } + }); + process.on('SIGTERM', this.stop.bind(this)); + process.on('SIGINT', this.stop.bind(this)); + + try { + await this._start(); + } catch (e) { + this._safeLogError(e, 'Failed to start'); + this._safeFatalErrorHook(e); + process.exit(this.constructor.EXIT_CODES.FAILED_TO_START); + } + } + + /** + * Gracefully stop application. + * part of standard shutdown procedure + * Implemented as SAFE in terms of exception + */ + async stop() { + try { + await this._stop(); + } catch (e) { + this._safeLogError(e, 'Failed to stop gracefully'); + process.exit(this.constructor.EXIT_CODES.FAILED_TO_STOP); + } finally { + // FIXME unsubscribe more preciesly + process.removeAllListeners('SIGTERM'); + process.removeAllListeners('SIGINT'); + process.removeAllListeners('uncaughtException'); + process.removeAllListeners('unhandledRejection'); + } + } + + /** + * Start procedure implementation. + * Not safe in terms of exceptions. May throw. + * In case of error, exception will be caught, logged + * and process will be destroyed in "cruel" way + */ + _start() { + throw new Error('implement me'); + } + + /** + * Graceful stop procedure implementation. + * Not safe in terms of exceptions. May throw. + * In case of error, exception will be caught, logged + * and process will be destroyed in "cruel" way + */ + _stop() { + throw new Error('implement me'); + } + + _logError() { + throw new Error('implement me'); + } + + async _handleFatalError() { + throw new Error('implement me'); + } + + _safeLogError() { + console.error(...arguments); + try { + this._logError(...arguments); + } catch (e) { + console.error('Uncaught rejection', e); + } + } + + _safeFatalErrorHook() { + try { + this._handleFatalError(...arguments); + } catch (e) { + this._safeLogError(e, 'Uncaught rejection'); + } + } + + static get EXIT_CODES() { + return { + SUCCESS: 0, + FAILED_TO_START: 1, + FAILED_TO_STOP: 2, + UNCAUGHT_EXCEPTION: 3, + UNCAUGHT_REJECTION: 4 + }; + } +} +module.exports = App; diff --git a/lib/MultiApp.js b/lib/MultiApp.js new file mode 100644 index 00000000..5586435a --- /dev/null +++ b/lib/MultiApp.js @@ -0,0 +1,176 @@ +const assert = require('assert'); +const uuid = require('uuid'); +const App = require('./App.js'); + +class MultiApp extends App { + async _start() { + const { MultiAppLogger } = require('./logging.js'); + const AmqpConnWrapper = require('./AmqpConnWrapper.js'); + const { AmqpCommunicationLayer } = require('./amqp.js'); + const { MultisailorConfig } = require('./settings.js'); + const RestApiClient = require('elasticio-rest-node'); + + this._sailors = {}; + this._config = MultisailorConfig.fromEnv(); + this._logger = new MultiAppLogger(this._config); + this._amqpConn = new AmqpConnWrapper(this._config.AMQP_URI, this._logger); + await this._amqpConn.start(); + this._apiClient = RestApiClient( // eslint-disable-line + this._config.API_USERNAME, + this._config.API_KEY, + { + retryCount: this._config.API_REQUEST_RETRY_ATTEMPTS, + retryDelay: this._config.API_REQUEST_RETRY_DELAY + } + ); + this._sharedCommunicationLayer = new AmqpCommunicationLayer(this._amqpConn, this._config, this._logger); + // FIXME probably componetn should assert queue for itself??? + // at least until admirals (or any other microservice) will manage them properly + await this._sharedCommunicationLayer.listenQueue(this._dispatch.bind(this)); + } + + async _stop() { + if (this._sharedCommunicationLayer) { + await this._sharedCommunicationLayer.unlistenQueue(); + } + await this._stopAllSailors(); + if (this._amqpConn) { + await this._amqpConn.stop(); + this._amqpConn = null; + } + } + _stopAllSailors() { + return Promise.all(Object.values(this._sailors).reduce( + (promises, stepsTable) => Object.values(stepsTable).reduce( + (promises, sailor) => promises.concat(sailor.stop()), + promises + ), + [] + )); + } + async _handleFatalError() { + // FIXME empty + } + _logError() { + // FIXME empty + } + + async _dispatch(payload, msg) { + try { + const sailor = await this._getSailor(msg); + await sailor.processMessage(payload, msg); + } catch (e) { + console.error(e.stack); + this._logger.error(e, 'Can not handle message'); + } + } + _parseRouingKey(rk) { + // $workspaceId.$taskId/$taskType.$stepId.$queue_suffix + const parts = rk.split('.'); + // FIXME handle incorrect routing key; log error, skip and reject message to make it disappear in queue + assert(parts.length === 4); + // Traffic to service component should be forwarded with input routing key + // because input routing key contains step id of CURRENT_STEP + // (As opposite messages routing key contains previous step id); + assert(parts[3] === 'input'); + return { + flowId: parts[1].split('/')[0], // FIXME add asserts + stepId: parts[2] + }; + } + async _getSailor(msg) { + const { SingleAppLogger } = require('./logging.js'); // FIXME proper logger + const Sailor = require('./sailor.js'); + const { AmqpCommunicationLayer } = require('./amqp.js'); + + let sailor; + const { flowId, stepId } = this._parseRouingKey(msg.fields.routingKey); + try { + this._sailors[flowId] = this._sailors[flowId] || {}; + if (this._sailors[flowId][stepId]) { + return this._sailors[flowId][stepId]; + } + const [config, stepData] = await this._restoreExecContext(flowId, stepId, msg); + const logger = new SingleAppLogger(config); + const communicationLayer = new AmqpCommunicationLayer(this._amqpConn, config, logger); + sailor = new Sailor( + communicationLayer, + config, + logger + ); + sailor.stepData = stepData; // FIXME fucking ugly + await sailor.prepare(); + /** + * TODO skip atm. Generally it's possible to fetch data from api. + * to handle if startup is requried. + * Startup is requried if no hooks data and sailor supports startup hook + * if (STARTUP_REQUIERD) + * await sailor.startup(); + * } + */ + await sailor.init(); + // NOTICE do not call sailor.run here. + // message dispatching is done in different ways in MultiSailor and SingleSailor modes + return this._sailors[flowId][stepId] = sailor; // eslint-disable-line + } catch (e) { + console.log('got error', e); + // FIXME hell knows if this works + await sailor && sailor.reportError(e); + // FIXME try to log error in context of current message + throw e; + } + } + + async _restoreExecContext(flowId, stepId, msg) { + // FIXME every step should be injected with it's own user + pass + // retrieveStep should provide user + pass + const manadatoryValues = Object.assign({}, this._config); + manadatoryValues.FLOW_ID = flowId; + manadatoryValues.STEP_ID = stepId; + manadatoryValues.EXEC_ID = msg.properties.headers.execId; + manadatoryValues.USER_ID = msg.properties.headers.userId; + const stepData = await this._apiClient.tasks.retrieveStep(manadatoryValues.FLOW_ID, manadatoryValues.STEP_ID); + const flowType = stepData.flow_type; + // FIXME, probably that's not required anymore + // Debug tasks works in different way, lookout does not consume all + // messages with routing keys ends with debug; + const suffix = flowType === 'debug' ? '_debug' : ''; + + // NOTICE we generate one "pseudo-container" per flow + step; + manadatoryValues.CONTAINER_ID = uuid.v4(); + + manadatoryValues.WORKSPACE_ID = stepData.workspace_id;// FIXME assert + manadatoryValues.COMP_ID = stepData.comp_id; // FIXME assert + manadatoryValues.FUNCTION = stepData.function; // FIXME ASSERT + + manadatoryValues.COMP_NAME = stepData.comp_name; // FIXME assert + manadatoryValues.EXEC_TYPE = 'flow-step'; // FIXME should match admiral ENV_VARS_CREATOR + manadatoryValues.FLOW_VERSION = stepData.flow_version; // FIXME assert + manadatoryValues.TENANT_ID = stepData.tenant_id; // FIXME assert + manadatoryValues.CONTRACT_ID = stepData.contract_id; // FIXME assert + // manadatoryValues.TASK_USER_EMAIL = stepData.XXXX + // manadatoryValues.EXECUTION_RESULT_ID = 'ZZZZ'// FIXME skip ATM one time execs + + // used by communication layer only + // FIXME this knowledge is currently shared between this code and admiral. + // have no idea how to fix it normally. + // Probably sailor to monorepo, and reuse code between admiral and sailor. + // Other option: step info endpoint should return this data + manadatoryValues.PUBLISH_MESSAGES_TO = `${manadatoryValues.WORKSPACE_ID}_org`; + manadatoryValues.DATA_ROUTING_KEY = `${manadatoryValues.WORKSPACE_ID}.` + + `${manadatoryValues.FLOW_ID}/${flowType}.` + + `${manadatoryValues.STEP_ID}.message${suffix}`; + manadatoryValues.ERROR_ROUTING_KEY = `${manadatoryValues.WORKSPACE_ID}.` + + `${manadatoryValues.FLOW_ID}/${flowType}.` + + `${manadatoryValues.STEP_ID}.error${suffix}`; + manadatoryValues.REBOUND_ROUTING_KEY = `${manadatoryValues.WORKSPACE_ID}.` + + `${manadatoryValues.FLOW_ID}/${flowType}.` + + `${manadatoryValues.STEP_ID}.rebound${suffix}`; + manadatoryValues.SNAPSHOT_ROUTING_KEY = `${manadatoryValues.WORKSPACE_ID}.` + + `${manadatoryValues.FLOW_ID}/${flowType}.` + + `${manadatoryValues.STEP_ID}.snapshot${suffix}`; + + return [manadatoryValues, stepData]; + } +} +module.exports = MultiApp; diff --git a/lib/SingleApp.js b/lib/SingleApp.js new file mode 100644 index 00000000..af873e14 --- /dev/null +++ b/lib/SingleApp.js @@ -0,0 +1,58 @@ +const App = require('./App.js'); + +class SingleApp extends App { + async _start() { + const { SingleAppLogger } = require('./logging.js'); + const Sailor = require('./sailor.js'); + const AmqpConnWrapper = require('./AmqpConnWrapper.js'); + const { AmqpCommunicationLayer } = require('./amqp.js'); + const { SingleSailorConfig } = require('./settings.js'); + + this._config = SingleSailorConfig.fromEnv(); + this._logger = new SingleAppLogger(this._config); + this._amqpConn = new AmqpConnWrapper(this._config.AMQP_URI, this._logger); + this._communicationLayer = new AmqpCommunicationLayer(this._amqpConn, this._config, this._logger); + this._sailor = new Sailor(this._communicationLayer, this._config, this._logger); + if (this._config.HOOK_SHUTDOWN) { + await this._sailor.prepare(); + await this._sailor.shutdown(); + } else { + await this._amqpConn.start(); + await this._sailor.prepare(); + + if (this._config.STARTUP_REQUIRED) { + await this._sailor.startup(); + } + + await this._sailor.init(); + await this._sailor.run(); + } + } + + async _stop() { + if (this._communicationLayer) { + await this._communicationLayer.unlistenQueue(); + } + + if (this._sailor) { + await this._sailor.stop(); + } + if (this._amqpConn) { + await this._amqpConn.stop(); + this._amqpConn = null; + } + } + + async _handleFatalError(error) { + if (this._sailor && !this._config.HOOK_SHUTDOWN) { + await this._sailor.reportError(error); + } + } + + _logError() { + if (this._logger) { + this._logger.error(...arguments); + } + } +} +module.exports = SingleApp; diff --git a/lib/amqp.js b/lib/amqp.js index 14f8895d..fde6ae85 100644 --- a/lib/amqp.js +++ b/lib/amqp.js @@ -1,166 +1,203 @@ -const log = require('./logging.js'); -const amqplib = require('amqplib'); -const encryptor = require('./encryptor.js'); -const co = require('co'); +const assert = require('assert'); const _ = require('lodash'); const eventToPromise = require('event-to-promise'); +const pThrottle = require('p-throttle'); + +const encryptor = require('./encryptor.js'); const HEADER_ROUTING_KEY = 'x-eio-routing-key'; const HEADER_ERROR_RESPONSE = 'x-eio-error-response'; +function copyAmqpHeadersToMessage(amqpMsg, msg) { + const source = amqpMsg.properties.headers; -class Amqp { - constructor(settings) { - this.settings = settings; + if (!msg.headers) { + msg.headers = {}; } - connect(uri) { - return co(function* connect() { - this.amqp = yield amqplib.connect(uri); - if (process.env.NODE_ENV !== 'test') { - this.amqp.on('error', log.criticalErrorAndExit); - this.amqp.on('close', log.criticalErrorAndExit); - } - log.debug('Connected to AMQP'); - - this.subscribeChannel = yield this.amqp.createChannel(); - this.subscribeChannel.on('error', log.criticalErrorAndExit); - log.debug('Opened subscribe channel'); - - this.publishChannel = yield this.amqp.createConfirmChannel(); - this.publishChannel.on('error', log.criticalErrorAndExit); - log.debug('Opened publish channel'); - }.bind(this)); + if (source.reply_to) { + msg.headers.reply_to = source.reply_to; } +} - disconnect() { - log.trace('Close AMQP connections'); - try { - this.subscribeChannel.close(); - } catch (alreadyClosed) { - log.debug('Subscribe channel is closed already'); - } - try { - this.publishChannel.close(); - } catch (alreadyClosed) { - log.debug('Publish channel is closed already'); - } - try { - this.amqp.close(); - } catch (alreadyClosed) { - log.debug('AMQP connection is closed already'); - } - log.debug('Successfully closed AMQP connections'); - return Promise.resolve(); +function getRoutingKeyFromHeaders(headers) { + if (!headers) { + return null; } - listenQueue(queueName, callback) { - //eslint-disable-next-line consistent-this - const self = this; - - this.subscribeChannel.prefetch(this.settings.RABBITMQ_PREFETCH_SAILOR); - - return this.subscribeChannel.consume(queueName, function decryptMessage(message) { - log.trace('Message received: %j', message); - - if (message === null) { - log.warn('NULL message received'); - return; - } + function headerNamesToLowerCase(result, value, key) { + result[key.toLowerCase()] = value; + } - let decryptedContent; - try { - decryptedContent = encryptor.decryptMessageContent(message.content, message.properties.headers); - } catch (err) { - log.error(err, - 'Error occurred while parsing message #%j payload', - message.fields.deliveryTag, - ); - return self.reject(message); - } + const lowerCaseHeaders = _.transform(headers, headerNamesToLowerCase, {}); - copyAmqpHeadersToMessage(message, decryptedContent); + return lowerCaseHeaders[HEADER_ROUTING_KEY]; +} - try { - // pass to callback both decrypted content & original message - callback(decryptedContent, message); - } catch (err) { - log.error(err, 'Failed to process message #%j, reject', message.fields.deliveryTag); - return self.reject(message); - } - }); +function filterMessageHeaders(headers = {}) { + return _.transform(headers, (result, value, key) => { + if ([HEADER_ROUTING_KEY].includes(key.toLowerCase())) { + return; + } + result[key] = value; + }, {}); +} +// FIXME transform to tree of files => base/children +/** + * Interface that abstracts communucation layer + * Potentially will be used for to start sailor locally over non-amqp transport + */ +class BaseCommunicationLayer { + listen() { + throw new Error('implement me'); } - ack(message) { - log.debug('Message #%j ack', message.fields.deliveryTag); - this.subscribeChannel.ack(message); + ack() { + throw new Error('implement me'); + } + reject() { + throw new Error('implement me'); + } + sendData() { + throw new Error('implement me'); + } + sendHttpReply() { + throw new Error('implement me'); + } + sendError() { + throw new Error('implement me'); + } + sendRebound() { + throw new Error('implement me'); + } + sendSnapshot() { + throw new Error('implement me'); } - reject(message) { - log.debug('Message #%j reject', message.fields.deliveryTag); - return this.subscribeChannel.reject(message, false); + getEncryptionId() { + throw new Error('implement me'); } +} - async sendToExchange(exchangeName, routingKey, payload, options, throttle) { - if (throttle) { - await throttle(); - } - log.trace('Pushing to exchange=%s, routingKey=%s, data=%j, ' - + 'options=%j', exchangeName, routingKey, payload, options); - log.debug('Current memory usage: %s Mb', process.memoryUsage().heapUsed / 1048576); +/** + * + * MESSAGE_CRYPTO_PASSWORD + * MESSAGE_CRYPTO_IV + * + * DATA_RATE_LIMIT + * RATA_RATE_INTERVAL + * + * LISTEN_MESSAGES_ON + * PUBLISH_MESSAGES_TO + * DATA_ROUTING_KEY + * ERROR_ROUTING_KEY + * REBOUND_ROUTING_KEY + * SNAPSHOT_ROUTING_KEY + * + * REBOUND_INITIAL_EXPIRATION + * REBOUND_LIMIT + * PRPCESS_AMQP_DRAIN + * AMQP_PUBLISH_RETRY_DELAY + * AMQP_PUBLISH_RETRY_ATTEMPTS + * RABBITMQ_PREFETCH_SAILOR + */ +class AmqpCommunicationLayer extends BaseCommunicationLayer { + constructor(amqpConn, settings, logger) { + super(); + this._conn = amqpConn; + this._logger = logger; + // this._conn.getSubscribeChannel() = amqpConn.getSubscribeChannel(); + // this._conn.getPublishChannel() = amqpConn.getPublishChannel(); + this.settings = settings; + this._cryptoSettings = { + password: settings.MESSAGE_CRYPTO_PASSWORD, + cryptoIV: settings.MESSAGE_CRYPTO_IV + }; - const result = await this.publishMessage(exchangeName, routingKey, Buffer.from(payload), options, 0); - if (this.settings.PROCESS_AMQP_DRAIN) { - if (result) { - return result; - } else { - log.debug('Amqp buffer is full: waiting for drain event..'); - return eventToPromise(this.publishChannel, 'drain').then(() => true); - } - } else { - return result; - } + // FIXME tests for throttling + this._throttles = { + // 100 Messages per Second + data: pThrottle( + () => Promise.resolve(true), + Number(settings.DATA_RATE_LIMIT), // FIXME numerization into config module + Number(settings.RATE_INTERVAL) + ), + error: pThrottle( + () => Promise.resolve(true), + Number(settings.ERROR_RATE_LIMIT), + Number(settings.RATE_INTERVAL) + ), + snapshot: pThrottle( + () => Promise.resolve(true), + Number(settings.SNAPSHOT_RATE_LIMIT), + Number(settings.RATE_INTERVAL) + ) + }; } - async publishMessage(exchangeName, routingKey, payloadBuffer, options, iteration) { - const settings = this.settings; - const result = this.publishChannel.publish(exchangeName, routingKey, payloadBuffer, options); - if (!result) { - log.warn('Buffer full when publishing a message to ' - + 'exchange=%s with routingKey=%s', exchangeName, routingKey); - } - - try { - await this.publishChannel.waitForConfirms(); - } catch (error) { - log.error('Failed on publishing message to queue'); - await new Promise(resolve => { setTimeout(resolve, settings.AMQP_PUBLISH_RETRY_DELAY); }); - iteration += 1; - if (iteration < settings.AMQP_PUBLISH_RETRY_ATTEMPTS) { - return await this.publishMessage(exchangeName, routingKey, payloadBuffer, options, iteration); - } else { - throw new Error(`failed on publishing ${options.headers.messageId} message to MQ: ` + error); - } + async listenQueue(callback) { + assert(!this._consumerTag, 'Can not listen more then once'); + // FIXME numerization in cofnig + this._conn.getSubscribeChannel().prefetch(Number(this.settings.RABBITMQ_PREFETCH_SAILOR)); + this._logger.debug('Start listening for messages on %s', this.settings.LISTEN_MESSAGES_ON); + this._consumerTag = await this._conn.getSubscribeChannel() + .consume(this.settings.LISTEN_MESSAGES_ON, async (message) => { + this._logger.trace('Message received: %j', message); + + if (message === null) { + this._logger.warn('NULL message received'); + return; + } + + let decryptedContent; + try { + decryptedContent = encryptor.decryptMessageContent(this._cryptoSettings, message.content); + } catch (err) { + this._logger.error(err, + 'Error occurred while parsing message #%j payload', + message.fields.deliveryTag + ); + return this.reject(message); + } + + copyAmqpHeadersToMessage(message, decryptedContent); + + // FIXME that's not communication layer responsibility to handle errors + try { + // pass to callback both decrypted content & original message + await callback(decryptedContent, message); + } catch (err) { + this._logger.error(err, 'Failed to process message #%j, reject', message.fields.deliveryTag); + return this.reject(message); + } + }); + } + // FIXME TESTS + async unlistenQueue() { + if (this._consumerTag) { + await this._conn.getSubscribeChannel().cancel(this._consumerTag.consumerTag); + this._consumerTag = null; } - - return result; } - async prepareMessageAndSendToExchange(data, properties, routingKey, throttle) { - const settings = this.settings; - data.headers = filterMessageHeaders(data.headers); - const encryptedData = encryptor.encryptMessageContent(data); + ack(message) { + this._logger.debug('Message #%j ack', message.fields.deliveryTag); + this._conn.getSubscribeChannel().ack(message); + } - return this.sendToExchange(settings.PUBLISH_MESSAGES_TO, routingKey, encryptedData, properties, throttle); + reject(message) { + this._logger.debug('Message #%j reject', message.fields.deliveryTag); + return this._conn.getSubscribeChannel().reject(message, false); } - async sendData(data, properties, throttle) { + async sendData(data, properties) { + const throttle = this._throttles.data; const settings = this.settings; const msgHeaders = data.headers || {}; const routingKey = getRoutingKeyFromHeaders(msgHeaders) || settings.DATA_ROUTING_KEY; - return this.prepareMessageAndSendToExchange(data, properties, routingKey, throttle); + return this._prepareMessageAndSendToExchange(data, properties, routingKey, throttle); } async sendHttpReply(data, properties) { @@ -169,14 +206,15 @@ class Amqp { if (!routingKey) { throw new Error(`Component emitted 'httpReply' event but 'reply_to' was not found in AMQP headers`); } - return this.prepareMessageAndSendToExchange(data, properties, routingKey); + return this._prepareMessageAndSendToExchange(data, properties, routingKey); } - async sendError(err, properties, originalMessageContent, throttle) { + async sendError(err, properties, originalMessageContent) { + const throttle = this._throttles.error; const settings = this.settings; const headers = properties.headers; - const encryptedError = encryptor.encryptMessageContent({ + const encryptedError = encryptor.encryptMessageContent(this._cryptoSettings, { name: err.name, message: err.message, stack: err.stack @@ -191,14 +229,14 @@ class Amqp { } const errorPayload = JSON.stringify(payload); - let result = this.sendToExchange(settings.PUBLISH_MESSAGES_TO, settings.ERROR_ROUTING_KEY, + let result = await this._sendToExchange(settings.PUBLISH_MESSAGES_TO, settings.ERROR_ROUTING_KEY, errorPayload, properties, throttle); if (headers.reply_to) { - log.debug('Sending error to %s', headers.reply_to); + this._logger.debug('Sending error to %s', headers.reply_to); const replyToOptions = _.cloneDeep(properties); replyToOptions.headers[HEADER_ERROR_RESPONSE] = true; - result = this.sendToExchange(settings.PUBLISH_MESSAGES_TO, + result = await this._sendToExchange(settings.PUBLISH_MESSAGES_TO, headers.reply_to, encryptedError, replyToOptions); } @@ -207,8 +245,19 @@ class Amqp { async sendRebound(reboundError, originalMessage, properties) { const settings = this.settings; + function getReboundIteration(previousIteration) { + if (previousIteration && typeof previousIteration === 'number') { + return previousIteration + 1; + } + return 1; + } - log.trace('Rebound message: %j', originalMessage); + // retry in 15 sec, 30 sec, 1 min, 2 min, 4 min, 8 min, etc. + function getExpiration(iteration) { + return Math.pow(2, iteration - 1) * settings.REBOUND_INITIAL_EXPIRATION; + } + + this._logger.trace('Rebound message: %j', originalMessage); const reboundIteration = getReboundIteration(originalMessage.properties.headers.reboundIteration); if (reboundIteration > settings.REBOUND_LIMIT) { @@ -220,71 +269,81 @@ class Amqp { } else { properties.expiration = getExpiration(reboundIteration); properties.headers.reboundIteration = reboundIteration; - - return this.sendToExchange( + return this._sendToExchange( settings.PUBLISH_MESSAGES_TO, settings.REBOUND_ROUTING_KEY, originalMessage.content, properties ); } - - function getReboundIteration(previousIteration) { - if (previousIteration && typeof previousIteration === 'number') { - return previousIteration + 1; - } - return 1; - } - - // retry in 15 sec, 30 sec, 1 min, 2 min, 4 min, 8 min, etc. - function getExpiration(iteration) { - return Math.pow(2, iteration - 1) * settings.REBOUND_INITIAL_EXPIRATION; - } } - async sendSnapshot(data, properties, throttle) { + async sendSnapshot(data, properties) { + const throttle = this._throttles.snapshot; const settings = this.settings; const exchange = settings.PUBLISH_MESSAGES_TO; const routingKey = settings.SNAPSHOT_ROUTING_KEY; let payload; payload = JSON.stringify(data); - return this.sendToExchange(exchange, routingKey, payload, properties, throttle); + return this._sendToExchange(exchange, routingKey, payload, properties, throttle); } -} -function copyAmqpHeadersToMessage(amqpMsg, msg) { - const source = amqpMsg.properties.headers; + async _sendToExchange(exchangeName, routingKey, payload, options, throttle) { + if (throttle) { + await throttle(); + } + this._logger.trace('Pushing to exchange=%s, routingKey=%s, data=%j, ' + + 'options=%j', exchangeName, routingKey, payload, options); + this._logger.debug('Current memory usage: %s Mb', process.memoryUsage().heapUsed / 1048576); - if (!msg.headers) { - msg.headers = {}; + const result = await this._publishMessage(exchangeName, routingKey, Buffer.from(payload), options, 0); + if (this.settings.PROCESS_AMQP_DRAIN) { + if (result) { + return result; + } else { + this._logger.debug('Amqp buffer is full: waiting for drain event..'); + return eventToPromise(this._conn.getPublishChannel(), 'drain').then(() => true); + } + } else { + return result; + } } - if (source.reply_to) { - msg.headers.reply_to = source.reply_to; + getEncryptionId() { + return encryptor.getEncryptionId(); } -} -function getRoutingKeyFromHeaders(headers) { - if (!headers) { - return null; - } + async _publishMessage(exchangeName, routingKey, payloadBuffer, options, iteration) { + const settings = this.settings; + const result = this._conn.getPublishChannel().publish(exchangeName, routingKey, payloadBuffer, options); + if (!result) { + this._logger.warn('Buffer full when publishing a message to ' + + 'exchange=%s with routingKey=%s', exchangeName, routingKey); + } - function headerNamesToLowerCase(result, value, key) { - result[key.toLowerCase()] = value; - } + try { + await this._conn.getPublishChannel().waitForConfirms(); + } catch (error) { + this._logger.error('Failed on publishing message to queue'); + await new Promise(resolve => { setTimeout(resolve, settings.AMQP_PUBLISH_RETRY_DELAY); }); + iteration += 1; + if (iteration < settings.AMQP_PUBLISH_RETRY_ATTEMPTS) { + return this._publishMessage(exchangeName, routingKey, payloadBuffer, options, iteration); + } else { + throw new Error(`failed on publishing ${options.headers.messageId} message to MQ: ` + error); + } + } - const lowerCaseHeaders = _.transform(headers, headerNamesToLowerCase, {}); + return result; + } - return lowerCaseHeaders[HEADER_ROUTING_KEY]; -} + async _prepareMessageAndSendToExchange(data, properties, routingKey, throttle) { + const settings = this.settings; + data.headers = filterMessageHeaders(data.headers); + const encryptedData = encryptor.encryptMessageContent(this._cryptoSettings, data); -function filterMessageHeaders(headers = {}) { - return _.transform(headers, (result, value, key) => { - if ([HEADER_ROUTING_KEY].includes(key.toLowerCase())) { - return; - } - result[key] = value; - }, {}); + return this._sendToExchange(settings.PUBLISH_MESSAGES_TO, routingKey, encryptedData, properties, throttle); + } } -exports.Amqp = Amqp; +module.exports.AmqpCommunicationLayer = AmqpCommunicationLayer; diff --git a/lib/cipher.js b/lib/cipher.js index 5ddcd54a..769f4a5c 100644 --- a/lib/cipher.js +++ b/lib/cipher.js @@ -1,56 +1,35 @@ -var _ = require('lodash'); -var crypto = require('crypto'); -var debug = require('debug')('sailor:cipher'); +const crypto = require('crypto'); +const _ = require('lodash'); +// TODO remove this shit. Use bunyan +const debug = require('debug')('sailor:cipher'); -var ALGORYTHM = 'aes-256-cbc'; -var PASSWORD = process.env.ELASTICIO_MESSAGE_CRYPTO_PASSWORD; -var VECTOR = process.env.ELASTICIO_MESSAGE_CRYPTO_IV; +const ALGORYTHM = 'aes-256-cbc'; -exports.id = 1; -exports.encrypt = encryptIV; -exports.decrypt = decryptIV; - -function encryptIV(rawData) { +function encryptIV(cryptoSettings, rawData) { debug('About to encrypt:', rawData); if (!_.isString(rawData)) { throw new Error('RabbitMQ message cipher.encryptIV() accepts only string as parameter.'); } - if (!PASSWORD) { - return rawData; - } - - if (!VECTOR) { - throw new Error('process.env.ELASTICIO_MESSAGE_CRYPTO_IV is not set'); - } - - var encodeKey = crypto.createHash('sha256').update(PASSWORD, 'utf-8').digest(); - var cipher = crypto.createCipheriv(ALGORYTHM, encodeKey, VECTOR); + const encodeKey = crypto.createHash('sha256').update(cryptoSettings.password, 'utf-8').digest(); + const cipher = crypto.createCipheriv(ALGORYTHM, encodeKey, cryptoSettings.cryptoIV); return cipher.update(rawData, 'utf-8', 'base64') + cipher.final('base64'); } -function decryptIV(encData, options) { +function decryptIV(cryptoSettings, encData) { debug('About to decrypt:', encData); - options = options || {}; - if (!_.isString(encData)) { throw new Error('RabbitMQ message cipher.decryptIV() accepts only string as parameter.'); } - if (!PASSWORD) { - return encData; - } - - if (!VECTOR) { - throw new Error('process.env.ELASTICIO_MESSAGE_CRYPTO_IV is not set'); - } - - var decodeKey = crypto.createHash('sha256').update(PASSWORD, 'utf-8').digest(); - var cipher = crypto.createDecipheriv(ALGORYTHM, decodeKey, VECTOR); - - var result = cipher.update(encData, 'base64', 'utf-8') + cipher.final('utf-8'); + const decodeKey = crypto.createHash('sha256').update(cryptoSettings.password, 'utf-8').digest(); + const cipher = crypto.createDecipheriv(ALGORYTHM, decodeKey, cryptoSettings.cryptoIV); - return result; + return cipher.update(encData, 'base64', 'utf-8') + cipher.final('utf-8'); } + +exports.id = 1; +exports.encrypt = encryptIV; +exports.decrypt = decryptIV; diff --git a/lib/component_reader.js b/lib/component_reader.js index 5e69779a..ab731d5d 100644 --- a/lib/component_reader.js +++ b/lib/component_reader.js @@ -1,99 +1,96 @@ -var log = require('./logging.js'); -var Q = require('q'); -var _ = require('lodash'); -var path = require('path'); -var fs = require('fs'); -var util = require('util'); +const path = require('path'); +const fs = require('fs'); +const util = require('util'); +const _ = require('lodash'); -exports.ComponentReader = ComponentReader; - -function ComponentReader() { - this.componentPath = null; - this.componentJson = null; -} - -ComponentReader.prototype.init = function init(componentPath) { - this.componentPath = path.join(process.cwd(), componentPath || ''); - log.debug('Component path is: %s', this.componentPath); - - log.trace('Root files: %j', fs.readdirSync(this.componentPath)); - - var componentJsonPath = path.join(this.componentPath, 'component.json'); - return this.promiseLoadJson(componentJsonPath); -}; - -ComponentReader.prototype.promiseLoadJson = function promiseLoadJson(jsonFilePath) { - try { - this.componentJson = require(jsonFilePath); - log.debug('Successfully loaded %s', jsonFilePath); - log.debug('Triggers: %j', _.keys(this.componentJson.triggers)); - log.debug('Actions: %j', _.keys(this.componentJson.actions)); - return Q.resolve(); - } catch (err) { - return Q.reject(new Error('Failed to load component.json from ' + this.componentPath)); +class ComponentReader { + constructor(logger) { + this._logger = logger; + this.componentPath = null; + this.componentJson = null; } -}; -ComponentReader.prototype.findTriggerOrAction = function findTriggerOrAction(name) { - if (this.componentJson === null) { - throw new Error('Component.json was not loaded'); + async init(componentPath) { + this.componentPath = path.join(process.cwd(), componentPath || ''); + this._logger.debug('Component path is: %s', this.componentPath); + try { + const dirContent = await util.promisify(fs.readdir.bind(fs))(this.componentPath); + this._loger.trace('Root files: %j', dirContent); + } catch (e) { + // skip intentionally. Don't make debug mechanism to kill everything around + } + + const componentJsonPath = path.join(this.componentPath, 'component.json'); + return this._promiseLoadJson(componentJsonPath); } - if (this.componentJson.triggers && this.componentJson.triggers[name]) { - return this.componentJson.triggers[name].main; - } else if (this.componentJson.actions && this.componentJson.actions[name]) { - return this.componentJson.actions[name].main; - } else { - throw new Error('Trigger or action "' + name + '" is not found in component.json!'); + async _promiseLoadJson(jsonFilePath) { + try { + this.componentJson = require(jsonFilePath); + this._logger.debug('Successfully loaded %s', jsonFilePath); + this._logger.debug('Triggers: %j', _.keys(this.componentJson.triggers)); + this._logger.debug('Actions: %j', _.keys(this.componentJson.actions)); + } catch (err) { + throw new Error('Failed to load component.json from ' + this.componentPath); + } } -}; -ComponentReader.prototype.loadTriggerOrAction = function loadTriggerOrAction(name) { - var filename; - var modulePath; - var result; + _findTriggerOrAction(name) { + if (this.componentJson === null) { + throw new Error('Component.json was not loaded'); + } - try { - filename = this.findTriggerOrAction(name); - modulePath = path.join(this.componentPath, filename); - } catch (err) { - log.trace(err); - return Q.reject(err); + if (this.componentJson.triggers && this.componentJson.triggers[name]) { + return this.componentJson.triggers[name].main; + } else if (this.componentJson.actions && this.componentJson.actions[name]) { + return this.componentJson.actions[name].main; + } else { + throw new Error('Trigger or action "' + name + '" is not found in component.json!'); + } } - try { - log.trace('Trying to find module at: %s', modulePath); - result = require(modulePath); - return Q.resolve(result); - } catch (err) { - var message; + async loadTriggerOrAction(name) { + let filename; + let modulePath; - if (err.code === 'MODULE_NOT_FOUND') { - message = 'Failed to load file \'%s\': %s'; - err.message = util.format(message, filename, err.message); - } else { - message = 'Trigger or action \'%s\' is found, but can not be loaded. ' - + 'Please check if the file \'%s\' is correct.'; - err.message = util.format(message, name, filename); + try { + filename = this._findTriggerOrAction(name); + modulePath = path.join(this.componentPath, filename); + } catch (err) { + this._logger.trace(err); + throw err; } - log.trace(err); - return Q.reject(err); - } -}; + try { + this._logger.trace('Trying to find module at: %s', modulePath); + return require(modulePath); + } catch (err) { + let message; + + if (err.code === 'MODULE_NOT_FOUND') { + message = 'Failed to load file \'%s\': %s'; + err.message = util.format(message, filename, err.message); + } else { + message = 'Trigger or action \'%s\' is found, but can not be loaded. ' + + 'Please check if the file \'%s\' is correct.'; + err.message = util.format(message, name, filename); + } -ComponentReader.prototype.loadVerifyCredentials = function loadVerifyCredentials() { - function verifyStub(cfg, cb) { - return cb(null, { verified: true }); + this._logger.trace(err); + throw err; + } } - try { - var modulePath = path.join(this.componentPath, 'verifyCredentials'); - log.trace('Trying to find verifyCredentials.js at: %s', modulePath); - var verifyFunc = require(modulePath); - return Q.resolve(verifyFunc); - } catch (err) { - log.trace(err); - return Q.resolve(verifyStub); + async loadVerifyCredentials() { + try { + const modulePath = path.join(this.componentPath, 'verifyCredentials'); + this._logger.trace('Trying to find verifyCredentials.js at: %s', modulePath); + return require(modulePath); + } catch (err) { + this._logger.trace(err); + return (cfg, cb) => cb(null, { verified: true }); + } } -}; +} + +exports.ComponentReader = ComponentReader; diff --git a/lib/emitter.js b/lib/emitter.js index d5aa238f..2fd39250 100644 --- a/lib/emitter.js +++ b/lib/emitter.js @@ -1,6 +1,3 @@ -'use strict'; - - /** * Object#toString reference. */ @@ -65,7 +62,7 @@ EventEmitter.prototype.once = function once(name, fn) { function on() { that.removeListener(name, on); - //eslint-disable-next-line no-invalid-this + // eslint-disable-next-line no-invalid-this fn.apply(this, arguments); } diff --git a/lib/encryptor.js b/lib/encryptor.js index 742032e3..83750172 100644 --- a/lib/encryptor.js +++ b/lib/encryptor.js @@ -1,21 +1,19 @@ const cipher = require('./cipher.js'); -const log = require('./logging.js'); -exports.encryptMessageContent = encryptMessageContent; -exports.decryptMessageContent = decryptMessageContent; - -function encryptMessageContent(messagePayload) { - return cipher.encrypt(JSON.stringify(messagePayload)); +function encryptMessageContent(cryptoSettings, messagePayload) { + return cipher.encrypt(cryptoSettings, JSON.stringify(messagePayload)); } -function decryptMessageContent(messagePayload, messageHeaders) { +function decryptMessageContent(cryptoSettings, messagePayload) { if (!messagePayload || messagePayload.toString().length === 0) { return null; } try { - return JSON.parse(cipher.decrypt(messagePayload.toString(), messageHeaders)); + return JSON.parse(cipher.decrypt(cryptoSettings, messagePayload.toString())); } catch (err) { - log.error(err, 'Failed to decrypt message'); throw Error('Failed to decrypt message: ' + err.message); } } +exports.encryptMessageContent = encryptMessageContent; +exports.decryptMessageContent = decryptMessageContent; +exports.getEncryptionId = () => cipher.id; diff --git a/lib/executor.js b/lib/executor.js index 06f76ff1..a3e513a4 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -1,71 +1,53 @@ const _ = require('lodash'); -const util = require('util'); -const selfAddressed = require('self-addressed'); const EventEmitter = require('./emitter').EventEmitter; -const log = require('./logging'); -const { ComponentLogger } = log; -exports.TaskExec = TaskExec; - -function TaskExec({ loggerOptions } = {}) { - EventEmitter.call(this); - this.errorCount = 0; - this.logger = new ComponentLogger(loggerOptions); -} - -util.inherits(TaskExec, EventEmitter); - -TaskExec.prototype.process = function process(triggerOrAction, payload, cfg, snapshot) { - //eslint-disable-next-line consistent-this - const self = this; - - if (!_.isFunction(triggerOrAction.process)) { - return onError(new Error('Process function is not found')); +class TaskExec extends EventEmitter { + constructor({ variables } = {}, logger) { + super(); + this.errorCount = 0; + // Notice: logger property is part of interface!!!!!! + // Do not rename!!!!! + // TODO probably transform it to getter to make it explicitly visible + // Or rewrite to typescript :( + this.logger = logger; + // copy variables to protect from outside changes; + this._variables = Object.assign({}, variables || {}); } - new Promise((resolve, reject) => { - const result = triggerOrAction.process.bind(self)(payload, cfg, snapshot); - if (result) { - resolve(result); - } - }) - .then(onPromisedData) - .catch(onError); - - function onPromisedData(data) { - if (data) { - log.debug('Process function is a Promise/generator/etc'); - self.emit('data', data); + async process(triggerOrAction, payload, cfg, snapshot) { + try { + if (!_.isFunction(triggerOrAction.process)) { + throw new Error('Process function is not found'); + } + const possiblyPromise = triggerOrAction.process.bind(this)(payload, cfg, snapshot); + // Required behavior + // 1. if trigger or action returns nothing then nothing should be emitted + // 2. if trigger or action returns promise then end event should be emitted anyway + // 3. if trigger or action returns non empty promise, then data event should be emitted + if (possiblyPromise) { + const data = await possiblyPromise; + if (data) { + this.logger.debug('Process function is a Promise/generator/etc'); + this.emit('data', data); + } + // Notice end should be emitted only in case if data was emitted + // and not otherwise + this.emit('end'); + } + } catch (e) { + this.logger.error(e); + this.emit('error', e); + this.emit('end'); } - self.emit('end'); } - function onError(err) { - log.error(err); - self.emit('error', err); - self.emit('end'); + /** + * Returns flow variables or empty object + * @returns {Object} + */ + getFlowVariables() { + return this._variables; } -}; - -const _emit = TaskExec.prototype.emit; - -TaskExec.emit = (name, data) => { - function mailman(address, envelope) { - _emit.call(address, name, envelope); - } - return selfAddressed(mailman, this, data); // returns a promise -}; - -const _on = TaskExec.prototype.on; +} -TaskExec.on = (name, fn) => { - function onSelfAddressedEnvelope(envelope) { - if (selfAddressed.is(envelope)) { - var result = fn(); - selfAddressed(envelope, result); - envelope.replies = 1; - selfAddressed(envelope); - } - } - _on.call(this, name, onSelfAddressedEnvelope); -}; +exports.TaskExec = TaskExec; diff --git a/lib/hooksData.js b/lib/hooksData.js index 6cc5be44..3e1efb9d 100644 --- a/lib/hooksData.js +++ b/lib/hooksData.js @@ -1,9 +1,6 @@ -'use strict'; - const request = require('requestretry'); class HooksData { - constructor({ FLOW_ID: taskId, API_USERNAME: user, @@ -60,7 +57,6 @@ class HooksData { delete() { return this.request('DELETE'); } - } module.exports.startup = function startup(settings) { diff --git a/lib/logging.js b/lib/logging.js index 4a18933f..6485a124 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -1,86 +1,155 @@ -'use strict'; - +// FIXME logger used inside sailor itself should not stringify nothing +// Logger inside users' code should stringify everything +const assert = require('assert'); const bunyan = require('bunyan'); -const _ = require('lodash'); +// FIXME copy&paste ContainerLogger & ComponebtLogger +function mixinLogger(context, logger, stringify) { + for (let type of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) { + context[type] = function log() { + let args; + if (stringify) { + args = Array.prototype.slice.call(arguments); -let level; + if (args.length && typeof args[0] !== 'string') { + args[0] = String(args[0]); + } + } else { + args = arguments; + } -if (process.env.NODE_ENV === 'test') { - level = bunyan.FATAL + 1; // turn off logging -} else { - level = process.env.LOG_LEVEL || 'info'; + return logger[type](...args); + }; + } } -const data = Object.assign( - _.pick(process.env, [ - 'ELASTICIO_API_USERNAME', - 'ELASTICIO_COMP_ID', - 'ELASTICIO_COMP_NAME', - 'ELASTICIO_CONTAINER_ID', - 'ELASTICIO_CONTRACT_ID', - 'ELASTICIO_EXEC_ID', - 'ELASTICIO_EXEC_TYPE', - 'ELASTICIO_EXECUTION_RESULT_ID', - 'ELASTICIO_FLOW_ID', - 'ELASTICIO_FLOW_VERSION', - 'ELASTICIO_FUNCTION', - 'ELASTICIO_STEP_ID', - 'ELASTICIO_TASK_USER_EMAIL', - 'ELASTICIO_TENANT_ID', - 'ELASTICIO_USER_ID', - 'ELASTICIO_WORKSPACE_ID' - ]), - { tag: process.env.ELASTICIO_FLOW_ID } -); +/** + * Logger for single sailor + */ +class SingleAppLogger { + constructor(config) { + const loggingContext = [ + 'API_USERNAME', + 'COMP_ID', + 'COMP_NAME', + 'CONTAINER_ID', + 'CONTRACT_ID', + 'EXEC_ID', + 'EXEC_TYPE', + 'FLOW_ID', + 'FLOW_VERSION', + 'FUNCTION', + 'STEP_ID', + 'TASK_USER_EMAIL', + 'TENANT_ID', + 'USER_ID', + 'WORKSPACE_ID' + ].reduce( + (context, varName) => Object.assign({}, { [varName]: config[varName] }, context), + { tag: config.FLOW_ID } + ); -const log = bunyan.createLogger({ - name: 'sailor', - level: level, - serializers: bunyan.stdSerializers -}) - .child(data); + const logger = bunyan.createLogger({ + name: 'container', + level: config.LOG_LEVEL, + serializers: bunyan.stdSerializers + }) + .child(loggingContext); + mixinLogger(this, logger, false); + } +} -_.bindAll(log, [ - 'fatal', - 'error', - 'warn', - 'info', - 'debug', - 'trace' -]); +/** + * Logger for multiple sailor service + */ +class MultiAppLogger { + constructor(config) { + const loggingContext = [ + 'COMP_ID', + 'COMP_NAME', + 'CONTAINER_ID' + ].reduce( + (context, varName) => Object.assign({}, { [varName]: config[varName] }, context), + {} + ); -function criticalErrorAndExit(err) { - if (err instanceof Error) { - log.fatal(err); - } else { - log.fatal('Error happened: %s', err); + const logger = bunyan.createLogger({ + name: 'container', + level: config.LOG_LEVEL, + serializers: bunyan.stdSerializers + }) + .child(loggingContext); + mixinLogger(this, logger, false); } - - process.exit(1); } -function ComponentLogger(options) { - const logger = bunyan.createLogger({ - name: 'component', - level: level, - serializers: bunyan.stdSerializers - }) - .child(data) - .child(options); +/** + * @class Logger used to log something + * in context of one message + */ +class MessageLevelLogger { + constructor(config, options, stringify) { + assert(options.threadId); + assert(options.messageId); + assert(options.parentMessageId); + const loggingContext = [ + 'API_USERNAME', + 'COMP_ID', + 'COMP_NAME', + 'CONTAINER_ID', + 'CONTRACT_ID', + 'EXEC_ID', + 'EXEC_TYPE', + 'FLOW_ID', + 'FLOW_VERSION', + 'FUNCTION', + 'STEP_ID', + 'TASK_USER_EMAIL', + 'TENANT_ID', + 'USER_ID', + 'WORKSPACE_ID' + ].reduce( + (context, varName) => Object.assign({}, { [varName]: config[varName] }, context), + { tag: config.FLOW_ID } + ); - for (let type of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) { - this[type] = function log() { - const args = Array.prototype.slice.call(arguments); + const logger = bunyan.createLogger({ + name: 'component', + level: config.LOG_LEVEL, + serializers: bunyan.stdSerializers + }) + .child(loggingContext) + .child(options); + mixinLogger(this, logger, stringify); + } +} - if (args.length && typeof args[0] !== 'string') { - args[0] = String(args[0]); - } +class OneTimeExecutionLogger { + constructor(config) { + const loggingContext = [ + 'API_USERNAME', + 'CONTAINER_ID', + 'EXEC_TYPE', + 'EXECUTION_RESULT_ID', + 'ACTION_OR_TRIGGER', + 'GET_MODEL_METHOD' + ].reduce( + (context, varName) => Object.assign({}, { [varName]: config[varName] }, context), + { tag: config.FLOW_ID } + ); - return logger[type](...args); - }; + const logger = bunyan.createLogger({ + name: 'component', + level: config.LOG_LEVEL, + serializers: bunyan.stdSerializers + }) + .child(loggingContext); + mixinLogger(this, logger); } -} +}; -module.exports = log; -module.exports.ComponentLogger = ComponentLogger; -module.exports.criticalErrorAndExit = criticalErrorAndExit; +module.exports = { + MessageLevelLogger, + SingleAppLogger, + MultiAppLogger, + OneTimeExecutionLogger +}; diff --git a/lib/sailor.js b/lib/sailor.js index 60f2d04f..45ccab1a 100644 --- a/lib/sailor.js +++ b/lib/sailor.js @@ -1,52 +1,91 @@ const ComponentReader = require('./component_reader.js').ComponentReader; -const amqp = require('./amqp.js'); const TaskExec = require('./executor.js').TaskExec; -const log = require('./logging.js'); const _ = require('lodash'); -const Q = require('q'); -const cipher = require('./cipher.js'); const hooksData = require('./hooksData'); const RestApiClient = require('elasticio-rest-node'); const assert = require('assert'); -const co = require('co'); const uuid = require('uuid'); -const pThrottle = require('p-throttle'); -const TIMEOUT = process.env.ELASTICIO_TIMEOUT || 20 * 60 * 1000; // 20 minutes const AMQP_HEADER_META_PREFIX = 'x-eio-meta-'; +const { MessageLevelLogger } = require('./logging.js'); +// FIXME tests +class Semaphor { + constructor() { + this._counter = 0; + this._waiters = []; + } + inc() { + this._counter++; + } + dec() { + this._counter > 0 && this._counter--; + this._notifyWaiters(); + } + _notifyWaiters() { + if (this._counter === 0) { + this._waiters.forEach(([resolve, timer]) => { + clearTimeout(timer); + resolve(); + }); + this._waiters = []; + } + } + async waitForFree(timeout = 30 * 1000) { + return new Promise((resolve, reject) => { + const pair = [ + resolve, + setTimeout(() => { + const waiterIndex = this._waiters.indexOf(pair); + if (this._waitersIndex >= -1) { + this._waiters.splice(waiterIndex, 1); + } + reject(new Error('timeout')); + }, timeout) + ]; + this._waiters.push(pair); + this._notifyWaiters(); + }); + } +} +/** + * API_USERNAME + * API_KEY + * API_URI + * API_REQUEST_RETRY_ATTEMPTS + * API_REQUEST_RETRY_DELAY + * + * FLOW_ID + * STEP_ID + * USER_ID + * CONTAINER_ID + * WORKSPACE_ID + * EXEC_ID + * COMP_ID + * FUNCTION + * + * COMPONENT_PATH + * TIMEOUT + */ class Sailor { - constructor(settings) { + constructor(communicationLayer, settings, logger) { + this._logger = logger; this.settings = settings; this.messagesCount = 0; - this.amqpConnection = new amqp.Amqp(settings); - this.componentReader = new ComponentReader(); + this._communicationLayer = communicationLayer; + this.componentReader = new ComponentReader(this._logger); this.snapshot = {}; - this.stepData = {}; - //eslint-disable-next-line new-cap - this.apiClient = RestApiClient( + this.stepData = null; + // eslint-disable-next-line new-cap + this._apiClient = RestApiClient( settings.API_USERNAME, settings.API_KEY, { retryCount: settings.API_REQUEST_RETRY_ATTEMPTS, retryDelay: settings.API_REQUEST_RETRY_DELAY - }); - this.throttles = { - // 100 Messages per Second - data: pThrottle(() => Promise.resolve(true), - settings.DATA_RATE_LIMIT, - settings.RATE_INTERVAL), - error: pThrottle(() => Promise.resolve(true), - settings.ERROR_RATE_LIMIT, - settings.RATE_INTERVAL), - snapshot: pThrottle(() => Promise.resolve(true), - settings.SNAPSHOT_RATE_LIMIT, - settings.RATE_INTERVAL) - }; - } - - connect() { - return Promise.resolve(this.amqpConnection.connect(this.settings.AMQP_URI)); + } + ); + this._busySemaphor = new Semaphor(); } async prepare() { @@ -56,28 +95,20 @@ class Sailor { FLOW_ID: flowId, STEP_ID: stepId }, - apiClient, componentReader } = this; - - const stepData = await apiClient.tasks.retrieveStep(flowId, stepId); - log.debug('Received step data: %j', stepData); - - Object.assign(this, { - snapshot: stepData.snapshot || {}, - stepData - }); - - this.stepData = stepData; - + // FIXME piece of shit + if (!this.stepData) { + const stepData = await this._apiClient.tasks.retrieveStep(flowId, stepId); + this.stepData = stepData; + this.snapshot = stepData.snapshot || {}; + } else { + this.snapshot = this.stepData.snapshot || {}; + } + this._logger.debug('Received step data: %j', this.stepData); await componentReader.init(compPath); } - disconnect() { - log.debug('Disconnecting, %s messages in processing', this.messagesCount); - return Promise.resolve(this.amqpConnection.disconnect()); - } - reportError(err) { const headers = { execId: this.settings.EXEC_ID, @@ -90,88 +121,87 @@ class Sailor { function: this.settings.FUNCTION }; const props = createDefaultAmqpProperties(headers); - this.amqpConnection.sendError(err, props); + this._communicationLayer.sendError(err, props); } - startup() { - return co(function* doStartup() { - log.debug('Starting up component'); - const result = yield this.invokeModuleFunction('startup'); - log.trace('Startup data', { result }); - const handle = hooksData.startup(this.settings); - try { - const state = _.isEmpty(result) ? {} : result; - yield handle.create(state); - } catch (e) { - if (e.statusCode === 409) { - log.warn('Startup data already exists. Rewriting.'); - yield handle.delete(); - yield handle.create(result); - } else { - log.warn('Component starting error'); - throw e; - } + async startup() { + this._logger.debug('Starting up component'); + const result = await this._invokeModuleFunction('startup'); + this._logger.trace('Startup data', { result }); + const handle = hooksData.startup(this.settings); + try { + const state = _.isEmpty(result) ? {} : result; + await handle.create(state); + } catch (e) { + if (e.statusCode === 409) { + this._logger.warn('Startup data already exists. Rewriting.'); + await handle.delete(); + await handle.create(result); + } else { + this._logger.warn('Component starting error'); + throw e; } - log.debug('Component started up'); - return result; - }.bind(this)); + } + this._logger.debug('Component started up'); + return result; } - shutdown() { - return co(function* doShutdown() { - log.debug('About to shut down'); - const handle = hooksData.startup(this.settings); - const state = yield handle.retrieve(); - yield this.invokeModuleFunction('shutdown', state); - yield handle.delete(); - log.debug('Shut down successfully'); - }.bind(this)); + async shutdown() { + this._logger.debug('About to shut down'); + const handle = hooksData.startup(this.settings); + const state = await handle.retrieve(); + await this._invokeModuleFunction('shutdown', state); + await handle.delete(); + this._logger.debug('Shut down successfully'); } - init() { - return co(function* doInit() { - log.debug('About to initialize component for execution'); - const res = yield this.invokeModuleFunction('init'); - log.debug('Component execution initialized successfully'); - return res; - }.bind(this)); + async init() { + this._logger.debug('About to initialize component for execution'); + const res = await this._invokeModuleFunction('init'); + this._logger.debug('Component execution initialized successfully'); + return res; } - invokeModuleFunction(moduleFunction, data) { - const settings = this.settings; + async _invokeModuleFunction(moduleFunction, data) { const stepData = this.stepData; - return co(function* gen() { - const module = yield this.componentReader.loadTriggerOrAction(settings.FUNCTION); - if (!module[moduleFunction]) { - log.warn(`invokeModuleFunction – ${moduleFunction} is not found`); - return Promise.resolve(); + const module = await this.componentReader.loadTriggerOrAction(this.settings.FUNCTION); + if (!module[moduleFunction]) { + this._logger.warn(`invokeModuleFunction – ${moduleFunction} is not found`); + return; + } + const cfg = _.cloneDeep(stepData.config) || {}; + return new Promise((resolve, reject) => { + try { + resolve(module[moduleFunction](cfg, data)); + } catch (e) { + reject(e); } - const cfg = _.cloneDeep(stepData.config) || {}; - return new Promise((resolve, reject) => { - try { - resolve(module[moduleFunction](cfg, data)); - } catch (e) { - reject(e); - } - }); - }.bind(this)); + }); } - run() { - const incomingQueue = this.settings.LISTEN_MESSAGES_ON; + async run() { + // FIXME that's sailor "ACTIVE" mode + // probably this should be moved to upper layers const processMessage = this.processMessage.bind(this); - log.debug('Start listening for messages on %s', incomingQueue); - return this.amqpConnection.listenQueue(incomingQueue, processMessage); + this._communicationLayer.listenQueue(processMessage); + } + // FIXME tests + async stop() { + await this._communicationLayer.unlistenQueue(); + await this.waitFinishProcessing(); + } + // FIXME tests + async waitFinishProcessing() { + return this._busySemaphor.waitForFree(); } - readIncomingMessageHeaders(message) { - const { settings } = this; + _readIncomingMessageHeaders(message) { const { headers } = message.properties; assert(headers.execId, 'ExecId is missing in message header'); assert(headers.taskId, 'TaskId is missing in message header'); assert(headers.userId, 'UserId is missing in message header'); - assert(headers.taskId === settings.FLOW_ID, 'Message with wrong taskID arrived to the sailor'); + assert(headers.taskId === this.settings.FLOW_ID, 'Message with wrong taskID arrived to the sailor'); const metaHeaderNames = Object.keys(headers) .filter(key => key.toLowerCase().startsWith(AMQP_HEADER_META_PREFIX)); @@ -195,52 +225,62 @@ class Sailor { return result; } - processMessage(payload, message) { - //eslint-disable-next-line consistent-this + async processMessage(payload, message) { + try { + const { headers } = message.properties; + const messageLogger = new MessageLevelLogger( + this.settings, + { + threadId: headers.threadId || headers['x-eio-meta-trace-id'] || 'unknown', + messageId: headers.messageId || 'unknown', + parentMessageId: headers.parentMessageId || 'unknown' + }, + false + ); + this._busySemaphor.inc(); + await this._processMessage(payload, message, messageLogger); + } catch (e) { + this._logger.error(e, 'Failed to process message'); + throw e; + } finally { + this._busySemaphor.dec(); + } + } + + async _processMessage(payload, message, logger) { + // eslint-disable-next-line consistent-this const self = this; const settings = this.settings; let incomingMessageHeaders; const origPassthrough = _.cloneDeep(payload.passthrough) || {}; - self.messagesCount += 1; + this.messagesCount += 1; const timeStart = Date.now(); - const { headers } = message.properties; const { deliveryTag } = message.fields; - const threadId = headers.threadId || headers['x-eio-meta-trace-id'] || 'unknown'; - const messageId = headers.messageId || 'unknown'; - const parentMessageId = headers.parentMessageId || 'unknown'; - - const logger = log.child({ - threadId, - messageId, - parentMessageId, - deliveryTag - }); - logger.trace({ - messagesCount: self.messagesCount, + messagesCount: this.messagesCount, messageProcessingTime: Date.now() - timeStart }, 'processMessage received'); try { - incomingMessageHeaders = this.readIncomingMessageHeaders(message); + incomingMessageHeaders = this._readIncomingMessageHeaders(message); } catch (err) { - log.error(err, 'Invalid message headers'); - return self.amqpConnection.reject(message); + logger.error(err, 'Invalid message headers'); + return this._communicationLayer.reject(message); } - const stepData = self.stepData; + const stepData = this.stepData; if (!stepData) { - log.warn('Invalid trigger or action specification %j', stepData); - return self.amqpConnection.reject(message); + logger.warn('Invalid trigger or action specification %j', stepData); + return this._communicationLayer.reject(message); } const cfg = _.cloneDeep(stepData.config) || {}; - const snapshot = _.cloneDeep(self.snapshot); + const snapshot = _.cloneDeep(this.snapshot); - log.debug('Trigger or action: %s', settings.FUNCTION); + logger.debug('Trigger or action: %s', settings.FUNCTION); let outgoingMessageHeaders = _.clone(incomingMessageHeaders); @@ -254,193 +294,183 @@ class Sailor { compId: settings.COMP_ID, function: settings.FUNCTION, start: new Date().getTime(), - cid: cipher.id + // TODO in ideal world this should be filled by communication layer + cid: this._communicationLayer.getEncryptionId() }); - - return co(function* doProcess() { - const module = yield this.componentReader.loadTriggerOrAction(settings.FUNCTION); + try { + const module = await this.componentReader.loadTriggerOrAction(this.settings.FUNCTION); return processMessageWithModule(module); - }.bind(this)).catch(onModuleNotFound); + } catch (err) { + logger.error(err); + outgoingMessageHeaders.end = new Date().getTime(); + await this._communicationLayer.sendError(err, outgoingMessageHeaders, message.content); + await this._communicationLayer.reject(message); + } function processMessageWithModule(module) { - const deferred = Q.defer(); - const executionTimeout = setTimeout(onTimeout, TIMEOUT); - const subPromises = []; - let endWasEmitted; - - function onTimeout() { - logger.trace({ - messagesCount: self.messagesCount, - messageProcessingTime: Date.now() - timeStart - }, 'processMessage timeout'); - return onEnd(); - } + return new Promise((resolve) => { + const executionTimeout = setTimeout(onTimeout, self.settings.TIMEOUT); + let endWasEmitted; - function promise(p) { - subPromises.push(p); - return p; - } - - const taskExec = new TaskExec({ - loggerOptions: { - threadId, - messageId, - parentMessageId - } - }); - - taskExec - .on('data', onData) - .on('error', onError) - .on('rebound', onRebound) - .on('snapshot', onSnapshot) - .on('updateSnapshot', onUpdateSnapshot) - .on('updateKeys', onUpdateKeys) - .on('httpReply', onHttpReply) - .on('end', onEnd); - - taskExec.process(module, payload, cfg, snapshot); - - async function onData(data) { - const headers = _.clone(outgoingMessageHeaders); - logger.trace({ - messagesCount: self.messagesCount, - messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit data'); - - headers.end = new Date().getTime(); - const props = createAmqpProperties(headers, data.id); - - if (stepData.is_passthrough === true) { - const passthrough = Object.assign({}, _.omit(data, 'passthrough')); - - data.passthrough = Object.assign({}, origPassthrough, { - [self.settings.STEP_ID]: passthrough - }); + function onTimeout() { + logger.trace({ + messagesCount: self.messagesCount, + messageProcessingTime: Date.now() - timeStart + }, 'processMessage timeout'); + return onEnd(); } - return self.amqpConnection.sendData(data, props, self.throttles.data); - } - - async function onHttpReply(reply) { - const headers = _.clone(outgoingMessageHeaders); - const props = createAmqpProperties(headers); - logger.trace({ - messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit HttpReply'); + const { headers } = message.properties; + const clientCodeLogger = new MessageLevelLogger( + self.settings, + { + threadId: headers.threadId || headers['x-eio-meta-trace-id'] || 'unknown', + messageId: headers.messageId || 'unknown', + parentMessageId: headers.parentMessageId || 'unknown' + }, + true + ); + + const taskExec = new TaskExec({ variables: stepData.variables }, clientCodeLogger); + + taskExec + .on('data', onData) + .on('error', onError) + .on('rebound', onRebound) + .on('snapshot', onSnapshot) + .on('updateSnapshot', onUpdateSnapshot) + .on('updateKeys', onUpdateKeys) + .on('httpReply', onHttpReply) + .on('end', onEnd); + + taskExec.process(module, payload, cfg, snapshot); + + async function onData(data) { + const headers = _.clone(outgoingMessageHeaders); + logger.trace({ + messagesCount: self.messagesCount, + messageProcessingTime: Date.now() - timeStart + }, 'processMessage emit data'); - return self.amqpConnection.sendHttpReply(reply, props); - } + headers.end = new Date().getTime(); + const props = createAmqpProperties(headers, data.id); - async function onError(err) { - const headers = _.clone(outgoingMessageHeaders); - err = formatError(err); - taskExec.errorCount++; - logger.trace({ - err, - messagesCount: self.messagesCount, - messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit error'); - headers.end = new Date().getTime(); - const props = createAmqpProperties(headers); - return self.amqpConnection.sendError(err, props, message.content, self.throttles.error); - } + if (stepData.is_passthrough === true) { + const passthrough = Object.assign({}, _.omit(data, 'passthrough')); - async function onRebound(err) { - const headers = _.clone(outgoingMessageHeaders); - err = formatError(err); - logger.trace({ - err, - messagesCount: self.messagesCount, - messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit rebound'); - headers.end = new Date().getTime(); - headers.reboundReason = err.message; - const props = createAmqpProperties(headers); - return self.amqpConnection.sendRebound(err, message, props); - } - - async function onSnapshot(data) { - const headers = _.clone(outgoingMessageHeaders); - headers.snapshotEvent = 'snapshot'; - self.snapshot = data; //replacing `local` snapshot - const props = createAmqpProperties(headers); - return self.amqpConnection.sendSnapshot(data, props, self.throttles.snapshot); - } + data.passthrough = Object.assign({}, origPassthrough, { + [self.settings.STEP_ID]: passthrough + }); + } - async function onUpdateSnapshot(data) { - const headers = _.clone(outgoingMessageHeaders); - headers.snapshotEvent = 'updateSnapshot'; + return self._communicationLayer.sendData(data, props); + } - if (_.isPlainObject(data)) { - if (data.$set) { - return log.warn('ERROR: $set is not supported any more in `updateSnapshot` event'); - } - _.extend(self.snapshot, data); //updating `local` snapshot + async function onHttpReply(reply) { + const headers = _.clone(outgoingMessageHeaders); const props = createAmqpProperties(headers); - return self.amqpConnection.sendSnapshot(data, props); - } else { - log.error('You should pass an object to the `updateSnapshot` event'); + logger.trace({ + messageProcessingTime: Date.now() - timeStart + }, 'processMessage emit HttpReply'); + + return self._communicationLayer.sendHttpReply(reply, props); } - } - async function onUpdateKeys(keys) { - logger.trace({ - messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit updateKeys'); - - try { - await self.apiClient.accounts.update(cfg._account, { keys: keys }); - logger.debug('Successfully updated keys #%s', deliveryTag); - } catch (error) { - logger.error('Failed to updated keys #%s', deliveryTag); - await onError(error); + async function onError(err) { + const headers = _.clone(outgoingMessageHeaders); + err = formatError(err); + taskExec.errorCount++; + logger.trace({ + err, + messagesCount: self.messagesCount, + messageProcessingTime: Date.now() - timeStart + }, 'processMessage emit error'); + headers.end = new Date().getTime(); + const props = createAmqpProperties(headers); + return self._communicationLayer.sendError(err, props, message.content); } - } - function onEnd() { - if (endWasEmitted) { - logger.warn({ + async function onRebound(err) { + const headers = _.clone(outgoingMessageHeaders); + err = formatError(err); + logger.trace({ + err, messagesCount: self.messagesCount, - errorCount: taskExec.errorCount, - promises: subPromises.length, messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit end was called more than once'); - return; + }, 'processMessage emit rebound'); + headers.end = new Date().getTime(); + headers.reboundReason = err.message; + const props = createAmqpProperties(headers); + return self._communicationLayer.sendRebound(err, message, props); } - endWasEmitted = true; + async function onSnapshot(data) { + const headers = _.clone(outgoingMessageHeaders); + headers.snapshotEvent = 'snapshot'; + self.snapshot = data; // replacing `local` snapshot + const props = createAmqpProperties(headers); + return self._communicationLayer.sendSnapshot(data, props); + } - clearTimeout(executionTimeout); + async function onUpdateSnapshot(data) { + const headers = _.clone(outgoingMessageHeaders); + headers.snapshotEvent = 'updateSnapshot'; + + if (_.isPlainObject(data)) { + if (data.$set) { + return logger.warn('ERROR: $set is not supported any more in `updateSnapshot` event'); + } + _.extend(self.snapshot, data); // updating `local` snapshot + const props = createAmqpProperties(headers); + return self._communicationLayer.sendSnapshot(data, props); + } else { + logger.error('You should pass an object to the `updateSnapshot` event'); + } + } - if (taskExec.errorCount > 0) { - self.amqpConnection.reject(message); - } else { - self.amqpConnection.ack(message); + async function onUpdateKeys(keys) { + logger.trace({ + messageProcessingTime: Date.now() - timeStart + }, 'processMessage emit updateKeys'); + + try { + await self._apiClient.accounts.update(cfg._account, { keys: keys }); + logger.debug('Successfully updated keys #%s', deliveryTag); + } catch (error) { + logger.error('Failed to updated keys #%s', deliveryTag); + await onError(error); + } } - self.messagesCount -= 1; - logger.trace({ - messagesCount: self.messagesCount, - errorCount: taskExec.errorCount, - promises: subPromises.length, - messageProcessingTime: Date.now() - timeStart - }, 'processMessage emit end'); - - Q.allSettled(subPromises).then(resolveDeferred); - } - function resolveDeferred() { - deferred.resolve(); - } + function onEnd() { + if (endWasEmitted) { + logger.warn({ + messagesCount: self.messagesCount, + errorCount: taskExec.errorCount, + messageProcessingTime: Date.now() - timeStart + }, 'processMessage emit end was called more than once'); + return; + } - return deferred.promise; - } + endWasEmitted = true; - function onModuleNotFound(err) { - log.error(err); - outgoingMessageHeaders.end = new Date().getTime(); - self.amqpConnection.sendError(err, outgoingMessageHeaders, message.content); - self.amqpConnection.reject(message); + clearTimeout(executionTimeout); + + if (taskExec.errorCount > 0) { + self._communicationLayer.reject(message); + } else { + self._communicationLayer.ack(message); + } + self.messagesCount -= 1; + logger.trace({ + messagesCount: self.messagesCount, + errorCount: taskExec.errorCount, + messageProcessingTime: Date.now() - timeStart + }, 'processMessage emit end'); + + resolve(); + } + }); } function formatError(err) { @@ -478,4 +508,4 @@ function createDefaultAmqpProperties(headers) { return result; } -exports.Sailor = Sailor; +module.exports = Sailor; diff --git a/lib/service.js b/lib/service.js index 8d31ee4f..6e45fce9 100644 --- a/lib/service.js +++ b/lib/service.js @@ -1,264 +1,195 @@ -const Q = require('q'); -const _ = require('lodash'); const assert = require('assert'); -const request = require('requestretry'); +const { EventEmitter } = require('events'); const util = require('util'); -const ComponentReader = require('./component_reader').ComponentReader; -const EventEmitter = require('events').EventEmitter; -const debug = require('debug')('sailor'); -const RestApiClient = require('elasticio-rest-node'); -const log = require('./logging'); -const { ComponentLogger } = log; - -exports.processService = processService; - -function processService(serviceMethod, env) { - - var ALLOWED_METHODS = { - verifyCredentials: verifyCredentials, - getMetaModel: getMetaModel, - selectModel: selectModel - }; - - var POST_RESULT_URL = env.ELASTICIO_POST_RESULT_URL; - var CFG = env.ELASTICIO_CFG; - var ACTION_OR_TRIGGER = env.ELASTICIO_ACTION_OR_TRIGGER; - var GET_MODEL_METHOD = env.ELASTICIO_GET_MODEL_METHOD; - var COMPONENT_PATH = env.ELASTICIO_COMPONENT_PATH; - var API_URI = env.ELASTICIO_API_URI; - var API_USERNAME = env.ELASTICIO_API_USERNAME; - var API_KEY = env.ELASTICIO_API_KEY; - - var compReader = new ComponentReader(); - return Q.fcall(init) - .spread(execService) - .then(onSuccess) - .catch(onError); - - function init() { - debug('About to init'); - - if (!POST_RESULT_URL) { - var err = new Error('ELASTICIO_POST_RESULT_URL is not provided'); - err.sendable = false; - throw err; - } - - assert(ALLOWED_METHODS[serviceMethod], util.format('Unknown service method "%s"', serviceMethod)); - assert(CFG, 'ELASTICIO_CFG is not provided'); - assert(API_URI, 'ELASTICIO_API_URI is not provided'); - assert(API_USERNAME, 'ELASTICIO_API_USERNAME is not provided'); - assert(API_KEY, 'ELASTICIO_API_KEY is not provided'); - - if (serviceMethod === 'getMetaModel' || serviceMethod === 'selectModel') { - assert(ACTION_OR_TRIGGER, 'ELASTICIO_ACTION_OR_TRIGGER is not provided'); - } - if (serviceMethod === 'selectModel') { - assert(GET_MODEL_METHOD, 'ELASTICIO_GET_MODEL_METHOD is not provided'); - } - - var cfg; - try { - cfg = JSON.parse(CFG); - } catch (e) { - throw new Error('Unable to parse CFG'); - } - - debug('Config: %j', cfg); +const _ = require('lodash'); +const request = require('requestretry'); +const debug = require('debug')('sailor'); - var params = { - triggerOrAction: ACTION_OR_TRIGGER, - getModelMethod: GET_MODEL_METHOD - }; +const RestApiClient = require('elasticio-rest-node'); - return [cfg, params]; +const { ComponentReader } = require('./component_reader'); +const { OneTimeExecutionLogger } = require('./logging.js'); +const { OneTimeExecutionConfig } = require('./settings.js'); +const ALLOWED_METHODS = { + verifyCredentials: verifyCredentials, + getMetaModel: getMetaModel, + selectModel: selectModel +}; + +function init(config, serviceMethod) { + debug('About to init'); + + if (!config.POST_RESULT_URL) { + const err = new Error('ELASTICIO_POST_RESULT_URL is not provided'); + err.sendable = false; + throw err; } - function execService(cfg, params) { - debug('Init is complete. About to start execution.'); - - return compReader.init(COMPONENT_PATH).then(callMethod); + assert(ALLOWED_METHODS[serviceMethod], util.format('Unknown service method "%s"', serviceMethod)); - function callMethod() { - return ALLOWED_METHODS[serviceMethod](cfg, params); - } + if (serviceMethod === 'getMetaModel' || serviceMethod === 'selectModel') { + assert(config.ACTION_OR_TRIGGER, 'ELASTICIO_ACTION_OR_TRIGGER is not provided'); } - - function onSuccess(data) { - return sendResponse({ status: 'success', data: data }); + if (serviceMethod === 'selectModel') { + assert(config.GET_MODEL_METHOD, 'ELASTICIO_GET_MODEL_METHOD is not provided'); } - function onError(err) { - if (err.sendable === false) { - throw new Error(err.message); - } - var errorData = { - message: err.message - }; - log.error(err, err.stack); - return sendResponse({ status: 'error', data: errorData }); + let cfg; + try { + cfg = JSON.parse(config.CFG); + } catch (e) { + throw new Error('Unable to parse CFG'); } - function sendResponse(responseBody) { - var opts = { - url: POST_RESULT_URL, - json: true, - forever: true, - headers: { - Connection: 'Keep-Alive' - }, - rejectUnauthorized: false, - body: responseBody, - simple: false, - maxAttempts: parseInt(env.ELASTICIO_API_REQUEST_RETRY_ATTEMPTS), - retryDelay: parseInt(env.ELASTICIO_API_REQUEST_RETRY_DELAY), - retryStrategy: request.RetryStrategies.HTTPOrNetworkError, - fullResponse: true - }; - - debug('About to send response back to the API'); - - return request.post(opts) - .then(checkStatusCode) - .then(() => responseBody); + debug('Config: %j', cfg); - function checkStatusCode(response) { - //eslint-disable-next-line eqeqeq - if (response.statusCode != '200') { + const params = { + triggerOrAction: config.ACTION_OR_TRIGGER, + getModelMethod: config.GET_MODEL_METHOD + }; - debug('Unable to reach API :('); + return [cfg, params]; +} +async function sendResponse(config, responseBody) { + const opts = { + url: config.POST_RESULT_URL, + json: true, + forever: true, + headers: { + Connection: 'Keep-Alive' + }, + rejectUnauthorized: false, + body: responseBody, + simple: false, + maxAttempts: parseInt(config.API_REQUEST_RETRY_ATTEMPTS), + retryDelay: parseInt(config.API_REQUEST_RETRY_DELAY), + retryStrategy: request.RetryStrategies.HTTPOrNetworkError, + fullResponse: true + }; - var error = new Error(util.format( - 'Failed to POST data to %s (%s, %s)', - POST_RESULT_URL, response.statusCode, response.body - )); - error.sendable = false; - throw error; - } - } + debug('About to send response back to the API'); + const response = await request.post(opts); + // eslint-disable-next-line eqeqeq + if (response.statusCode != '200') { + debug('Unable to reach API :('); + + const error = new Error(util.format( + 'Failed to POST data to %s (%s, %s)', + config.POST_RESULT_URL, response.statusCode, response.body + )); + error.sendable = false; + throw error; } - - function verifyCredentials(cfg, params) { - function doVerification(verify) { - return new Promise((resolve, reject) => { - function legacyCallback(e, result) { - if (e) { - return reject(e); - } - resolve(result); - } - var callScope = new EventEmitter(); - callScope.logger = new ComponentLogger(); - const result = verify.call(callScope, cfg, legacyCallback); - - if (result) { - resolve(result); + return responseBody; +} +async function verifyCredentials(apiClient, compReader, logger, cfg, params) { + try { + const verify = await compReader.loadVerifyCredentials(); + const result = await new Promise((resolve, reject) => { + const callScope = new EventEmitter(); + callScope.logger = logger; + const result = verify.call(callScope, cfg, (e, result) => { + if (e) { + return reject(e); } + resolve(result); }); - } + + if (result) { + resolve(result); + } + }); /** * In will allow developers to return Promise.resolve(ANYTHING) in verifyCredentials. */ - function toVerifyCredentialsResponse(result) { - if (!_.has(result, 'verified')) { - return { - verified: true - }; - } - - return result; - } - - function error(e) { + if (!_.has(result, 'verified')) { return { - verified: false, - reason: e.message + verified: true }; } - - return compReader.loadVerifyCredentials() - .then(doVerification) - .then(toVerifyCredentialsResponse) - .catch(error); - } - - function getMetaModel(cfg, params) { - return callModuleMethod(params.triggerOrAction, 'getMetaModel', cfg); - } - - function selectModel(cfg, params) { - return callModuleMethod(params.triggerOrAction, params.getModelMethod, cfg); + return result; + } catch (e) { + return { + verified: false, + reason: e.message + }; } +} - function callModuleMethod(triggerOrAction, method, cfg) { - - var callScope = new EventEmitter(); - callScope.on('updateKeys', onUpdateKeys); - callScope.logger = new ComponentLogger(); - - var finished = Q.defer(); - var subPromises = []; - - compReader.loadTriggerOrAction(triggerOrAction) - .then(validateMethod) - .then(executeMethod) - .then(onExecutionSuccess) - .catch(onExecutionFail) - .done(); - - function validateMethod(module) { - var errorMsg = `Method "${method}" is not found in "${triggerOrAction}" action or trigger`; - assert(_.isFunction(module[method]), errorMsg); - return module; - } +function getMetaModel(apiClient, compReader, logger, cfg, params) { + return callModuleMethod(apiClient, compReader, logger, params.triggerOrAction, 'getMetaModel', cfg); +} - function executeMethod(module) { - return new Promise((resolve, reject) => { - function legacyCallback(e, result) { - if (e) { - return reject(e); - } - resolve(result); - } - const result = module[method].bind(callScope)(cfg, legacyCallback); +function selectModel(apiClient, compReader, logger, cfg, params) { + return callModuleMethod(apiClient, compReader, logger, params.triggerOrAction, params.getModelMethod, cfg); +} - if (result) { - resolve(result); +async function callModuleMethod(apiClient, compReader, logger, triggerOrAction, method, cfg) { + const subPromises = []; + const callScope = new EventEmitter(); + callScope.on('updateKeys', (keys) => subPromises.push(apiClient.accounts.update(cfg._account, { keys: keys }))); + callScope.logger = logger; + + const module = await compReader.loadTriggerOrAction(triggerOrAction); + const errorMsg = `Method "${method}" is not found in "${triggerOrAction}" action or trigger`; + assert(_.isFunction(module[method]), errorMsg); + try { + const data = await new Promise((resolve, reject) => { + const result = module[method].bind(callScope)(cfg, (e, result) => { + if (e) { + return reject(e); } + resolve(result); }); - } - function onExecutionSuccess(data) { - Q.allSettled(subPromises).then(function resolve(res) { - _(res) - .filter({ - state: 'rejected' - }) - .map(result => result.reason) - .each(log.error.bind(log)); - finished.resolve(data); - }).catch(err => log.error(err)); - } + if (result) { + resolve(result); + } + }); - function onExecutionFail(err) { - finished.reject(err); - } + await Promise.all(subPromises.map(async (promise) => { + try { + await promise; + } catch (e) { + logger.error(e); + } + })); + return data; + } catch (e) { + console.log('on exec fail'); + throw e; + } +} - function onUpdateKeys(keys) { - //eslint-disable-next-line new-cap - var apiClient = RestApiClient(API_USERNAME, API_KEY, { - retryCount: parseInt(env.ELASTICIO_API_REQUEST_RETRY_ATTEMPTS), - retryDelay: parseInt(env.ELASTICIO_API_REQUEST_RETRY_DELAY) - }); - addPromise(apiClient.accounts.update(cfg._account, { keys: keys })); - } +async function processService(serviceMethod) { + const config = OneTimeExecutionConfig.fromEnv(); + const logger = new OneTimeExecutionLogger(config); - function addPromise(p) { - subPromises.push(p); - } + try { + const compReader = new ComponentReader(logger); + // eslint-disable-next-line new-cap + const apiClient = RestApiClient(config.API_USERNAME, config.API_KEY, { + retryCount: parseInt(config.API_REQUEST_RETRY_ATTEMPTS), + retryDelay: parseInt(config.API_REQUEST_RETRY_DELAY) + }); - return finished.promise; + const [cfg, params] = init(config, serviceMethod); + debug('Init is complete. About to start execution.'); + await compReader.init(config.COMPONENT_PATH); + const data = await ALLOWED_METHODS[serviceMethod](apiClient, compReader, logger, cfg, params); + return sendResponse(config, { status: 'success', data: data }); + } catch (err) { + if (err.sendable === false) { + throw new Error(err.message); + } + const errorData = { + message: err.message + }; + logger.error(err, err.stack); + return sendResponse(config, { status: 'error', data: errorData }); } } + +exports.processService = processService; diff --git a/lib/settings.js b/lib/settings.js index 10007df1..e97abfc0 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -1,80 +1,156 @@ -const _ = require('lodash'); +// Must have for minimal case +const MANDATORTY_VARS = [ + 'LISTEN_MESSAGES_ON', + 'AMQP_URI', + 'API_URI', + 'API_USERNAME', + 'API_KEY', + 'MESSAGE_CRYPTO_PASSWORD', + 'MESSAGE_CRYPTO_IV' +]; +// Required to sailor to communicate with amqp +// must have for single mode sailor +const ROUTING_ENV_VARS = [ + 'PUBLISH_MESSAGES_TO', + 'DATA_ROUTING_KEY', + 'ERROR_ROUTING_KEY', + 'REBOUND_ROUTING_KEY', + 'SNAPSHOT_ROUTING_KEY' +]; -exports.readFrom = readFrom; +// Required to sailor for work +// must have for single mode sailor +const MANDATORY_SAILOR = [ + 'FLOW_ID', + 'STEP_ID', + 'EXEC_ID', + 'CONTAINER_ID', + 'WORKSPACE_ID', + 'USER_ID', + 'COMP_ID', + 'FUNCTION' +]; +/** + * Required only for logging purposers for normal steps + * FIXME not clear which of this env vars are requied for frontend. They should be declared as MUST_HAVE + * COMP_NAME + * EXEC_TYPE + * FLOW_VERSION + * TENANT_ID + * CONTRACT_ID + * TASK_USER_EMAIL + * Required only for logging purposes for one-time-execs + * EXECUTION_RESULT_ID. Used to identifiy logs of service calls + */ -function readFrom(envVars) { - const result = {}; +/** + * Optional parameters: behavior tuning + * + * RABBITMQ_PREFETCH_SAILOR + * PROCESS_AMQP_DRAIN + * AMQP_PUBLISH_RETRY_DELAY + * AMQP_PUBLISH_RETRY_ATTEMPTS + * REBOUND_INITIAL_EXPIRATION + * REBOUD_LIMIT + * API_REQUEST_RETRY_ATTEMTPTS + * API_REQUEST_RETRY_DELAY + * DATA_RATE_LIMIT + * RATE_INTERVAL + * ERROR_RATE_LIMIT + * SNAPSHOT_RATE_LIMIT + * ELASTICIO_TIMEOUT + */ - // required settings - const requiredAlways = [ - 'FLOW_ID', - 'EXEC_ID', - 'STEP_ID', - 'CONTAINER_ID', - 'WORKSPACE_ID', +const DEFAULTS = { + REBOUND_INITIAL_EXPIRATION: 15000, + REBOUND_LIMIT: 20, + COMPONENT_PATH: '', + RABBITMQ_PREFETCH_SAILOR: 1, + STARTUP_REQUIRED: false, + HOOK_SHUTDOWN: false, + API_REQUEST_RETRY_ATTEMPTS: 3, + API_REQUEST_RETRY_DELAY: 100, + DATA_RATE_LIMIT: 10, // 10 data events every 100ms + ERROR_RATE_LIMIT: 2, // 2 errors every 100ms + SNAPSHOT_RATE_LIMIT: 2, // 2 Snapshots every 100ms + RATE_INTERVAL: 100, // 100ms + PROCESS_AMQP_DRAIN: true, + AMQP_PUBLISH_RETRY_DELAY: 100, // 100ms + AMQP_PUBLISH_RETRY_ATTEMPTS: 10, + TIMEOUT: 20 * 60 * 1000, // 20 minutes + LOG_LEVEL: 'info' +}; - 'USER_ID', - 'COMP_ID', - 'FUNCTION', - - 'API_URI', - 'API_USERNAME', - 'API_KEY' - ]; - - const requiredForMessageProcessing = [ - 'AMQP_URI', - 'LISTEN_MESSAGES_ON', - 'PUBLISH_MESSAGES_TO', - - 'DATA_ROUTING_KEY', - 'ERROR_ROUTING_KEY', - 'REBOUND_ROUTING_KEY', - 'SNAPSHOT_ROUTING_KEY' - ]; - - const optional = { - REBOUND_INITIAL_EXPIRATION: 15000, - REBOUND_LIMIT: 20, - COMPONENT_PATH: '', - RABBITMQ_PREFETCH_SAILOR: 1, - STARTUP_REQUIRED: false, - HOOK_SHUTDOWN: false, - API_REQUEST_RETRY_ATTEMPTS: 3, - API_REQUEST_RETRY_DELAY: 100, - DATA_RATE_LIMIT: 10, // 10 data events every 100ms - ERROR_RATE_LIMIT: 2, // 2 errors every 100ms - SNAPSHOT_RATE_LIMIT: 2, // 2 Snapshots every 100ms - RATE_INTERVAL: 100, // 100ms - PROCESS_AMQP_DRAIN: true, - AMQP_PUBLISH_RETRY_DELAY: 100, // 100ms - AMQP_PUBLISH_RETRY_ATTEMPTS: 10 - }; - - _.forEach(requiredAlways, function readRequired(key) { - const envVarName = 'ELASTICIO_' + key; - result[key] = envVars[envVarName] || throwError(envVarName + ' is missing'); - }); +function normalizeEnvVars(prefix, list, envVars) { + const vars = list.reduce((valueTable, varName) => { + if (!((prefix + varName) in envVars)) { + throw new Error(`${prefix}${varName} is missing`); + } + valueTable[varName] = envVars[prefix + varName]; + return valueTable; + }, {}); + return Object.entries(process.env).reduce((vars, [k, v]) => { + if (k.indexOf(prefix) === 0) { + vars[k.replace(prefix, '')] = v; + } + return vars; + }, vars); +} - if (!envVars.ELASTICIO_HOOK_SHUTDOWN) { - _.forEach(requiredForMessageProcessing, function readRequired(key) { - var envVarName = 'ELASTICIO_' + key; - result[key] = envVars[envVarName] || throwError(envVarName + ' is missing'); - }); +class Config { + constructor(values) { + Object.assign(this, DEFAULTS, values); } - - _.forEach(optional, function readOptional(defaultValue, key) { - const envVarName = 'ELASTICIO_' + key; - if (typeof defaultValue === 'number' && envVars[envVarName]) { - result[key] = parseInt(envVars[envVarName]) || defaultValue; - } else { - result[key] = envVars[envVarName] || defaultValue; + get(varName) { + if (!(varName in this._values)) { + throw new Error(`variable ${varName} is not defined`); } - }); - - function throwError(message) { - throw new Error(message); + return this._values[varName]; + } + set(varName, varValue) { + this._values[varName] = varValue; + } + static fromEnv() { + throw new Error('implement me'); + } +} +class MultisailorConfig extends Config { + static fromEnv() { + const values = normalizeEnvVars('ELASTICIO_', MANDATORTY_VARS, process.env); + return new this(values); } - - return result; } +class SingleSailorConfig extends Config { + static fromEnv() { + const values = normalizeEnvVars( + 'ELASTICIO_', + MANDATORTY_VARS.concat(ROUTING_ENV_VARS, MANDATORY_SAILOR), + process.env + ); + return new this(values); + } +} +class OneTimeExecutionConfig extends Config { + static fromEnv() { + const values = normalizeEnvVars( + 'ELASTICIO_', + [ + 'POST_RESULT_URL', + 'CFG', + 'ACTION_OR_TRIGGER', + 'GET_MODEL_METHOD', + 'API_URI', + 'API_USERNAME', + 'API_KEY' + ], + process.env + ); + return new this(values); + } +} + +module.exports = { + MultisailorConfig, + SingleSailorConfig, + OneTimeExecutionConfig +}; diff --git a/mocha_spec/.eslintrc.js b/mocha_spec/.eslintrc.js deleted file mode 100644 index d5208eae..00000000 --- a/mocha_spec/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const ERROR = 'error'; -const ALWAYS = 'always'; -const NEVER = 'never'; - -module.exports = { - 'env': { - es6: true, - node: true, - mocha: true - }, - 'parserOptions': { - 'ecmaVersion': 8 - } -}; \ No newline at end of file diff --git a/mocha_spec/integration_component/actions/http_reply_action.js b/mocha_spec/integration_component/actions/http_reply_action.js deleted file mode 100644 index 1a579f63..00000000 --- a/mocha_spec/integration_component/actions/http_reply_action.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -exports.process = processAction; - -function processAction(msg, cfg) { - - //eslint-disable-next-line no-invalid-this - this.emit('httpReply', { - statusCode: 200, - body: 'Ok', - headers: { - 'content-type': 'text/plain' - } - }); - //eslint-disable-next-line no-invalid-this - this.emit('end'); - -} diff --git a/mocha_spec/integration_component/triggers/fails_to_init.js b/mocha_spec/integration_component/triggers/fails_to_init.js deleted file mode 100644 index ca535c69..00000000 --- a/mocha_spec/integration_component/triggers/fails_to_init.js +++ /dev/null @@ -1,5 +0,0 @@ -exports.init = init; - -function init(cfg) { - throw new Error('OMG. I cannot init'); -} diff --git a/mocha_spec/integration_helpers.js b/mocha_spec/integration_helpers.js deleted file mode 100644 index b458e5ac..00000000 --- a/mocha_spec/integration_helpers.js +++ /dev/null @@ -1,206 +0,0 @@ -'use strict'; - -const co = require('co'); -const amqplib = require('amqplib'); -const { EventEmitter } = require('events'); -const PREFIX = 'sailor_nodejs_integration_test'; -const nock = require('nock'); - -const env = process.env; - -class AmqpHelper extends EventEmitter { - constructor() { - super(); - - this.httpReplyQueueName = PREFIX + 'request_reply_queue'; - this.httpReplyQueueRoutingKey = PREFIX + 'request_reply_routing_key'; - this.nextStepQueue = PREFIX + '_next_step_queue'; - this.nextStepErrorQueue = PREFIX + '_next_step_queue_errors'; - - this.dataMessages = []; - this.errorMessages = []; - } - - prepareEnv() { - env.ELASTICIO_LISTEN_MESSAGES_ON = PREFIX + ':messages'; - env.ELASTICIO_PUBLISH_MESSAGES_TO = PREFIX + ':exchange'; - env.ELASTICIO_DATA_ROUTING_KEY = PREFIX + ':routing_key:message'; - env.ELASTICIO_ERROR_ROUTING_KEY = PREFIX + ':routing_key:error'; - env.ELASTICIO_REBOUND_ROUTING_KEY = PREFIX + ':routing_key:rebound'; - env.ELASTICIO_SNAPSHOT_ROUTING_KEY = PREFIX + ':routing_key:snapshot'; - - env.ELASTICIO_TIMEOUT = 3000; - } - - publishMessage(message, { parentMessageId, threadId } = {}, headers = {}) { - return this.subscriptionChannel.publish( - env.ELASTICIO_LISTEN_MESSAGES_ON, - env.ELASTICIO_DATA_ROUTING_KEY, - new Buffer(JSON.stringify(message)), { - headers: Object.assign({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - userId: env.ELASTICIO_USER_ID, - threadId, - messageId: parentMessageId - }, headers) - }); - } - - *prepareQueues() { - const amqp = yield amqplib.connect(env.ELASTICIO_AMQP_URI); - const subscriptionChannel = yield amqp.createChannel(); - const publishChannel = yield amqp.createChannel(); - - yield subscriptionChannel.assertQueue(env.ELASTICIO_LISTEN_MESSAGES_ON); - yield publishChannel.assertQueue(this.nextStepQueue); - yield publishChannel.assertQueue(this.nextStepErrorQueue); - - const exchangeOptions = { - durable: true, - autoDelete: false - }; - - yield subscriptionChannel.assertExchange(env.ELASTICIO_LISTEN_MESSAGES_ON, 'direct', exchangeOptions); - yield publishChannel.assertExchange(env.ELASTICIO_PUBLISH_MESSAGES_TO, 'direct', exchangeOptions); - - yield subscriptionChannel.bindQueue( - env.ELASTICIO_LISTEN_MESSAGES_ON, - env.ELASTICIO_LISTEN_MESSAGES_ON, - env.ELASTICIO_DATA_ROUTING_KEY); - - yield publishChannel.bindQueue( - this.nextStepQueue, - env.ELASTICIO_PUBLISH_MESSAGES_TO, - env.ELASTICIO_DATA_ROUTING_KEY); - - yield publishChannel.bindQueue( - this.nextStepErrorQueue, - env.ELASTICIO_PUBLISH_MESSAGES_TO, - env.ELASTICIO_ERROR_ROUTING_KEY); - - yield publishChannel.assertQueue(this.httpReplyQueueName); - yield publishChannel.bindQueue( - this.httpReplyQueueName, - env.ELASTICIO_PUBLISH_MESSAGES_TO, - this.httpReplyQueueRoutingKey); - - yield publishChannel.purgeQueue(this.nextStepQueue); - yield publishChannel.purgeQueue(this.nextStepErrorQueue); - yield publishChannel.purgeQueue(env.ELASTICIO_LISTEN_MESSAGES_ON); - - this.subscriptionChannel = subscriptionChannel; - this.publishChannel = publishChannel; - } - - cleanUp() { - this.removeAllListeners(); - return Promise.all([ - this.publishChannel.cancel('sailor_nodejs_1'), - this.publishChannel.cancel('sailor_nodejs_2'), - this.publishChannel.cancel('sailor_nodejs_3') - ]); - } - - prepare() { - const that = this; - return co(function * gen() { - that.prepareEnv(); - yield that.prepareQueues(); - - yield that.publishChannel.consume( - that.nextStepQueue, - that.consumer.bind(that, that.nextStepQueue), - { consumerTag: 'sailor_nodejs_1' } - ); - - yield that.publishChannel.consume( - that.nextStepErrorQueue, - that.consumer.bind(that, that.nextStepErrorQueue), - { consumerTag: 'sailor_nodejs_2' } - ); - - yield that.publishChannel.consume( - that.httpReplyQueueName, - that.consumer.bind(that, that.httpReplyQueueName), - { consumerTag: 'sailor_nodejs_3' } - ); - }); - } - - consumer(queue, message) { - this.publishChannel.ack(message); - - const emittedMessage = JSON.parse(message.content.toString()); - - const data = { - properties: message.properties, - body: emittedMessage.body, - emittedMessage - }; - - this.dataMessages.push(data); - this.emit('data', data, queue); - - // publishChannel.cancel('sailor_nodejs'); - // done(); - } -} - -function amqp() { - const handle = { - //eslint-disable-next-line no-empty-function - getMessages() { - } - }; - return handle; -} - -function prepareEnv() { - env.ELASTICIO_AMQP_URI = 'amqp://guest:guest@localhost:5672'; - env.ELASTICIO_RABBITMQ_PREFETCH_SAILOR = '10'; - env.ELASTICIO_FLOW_ID = '5559edd38968ec0736000003'; - env.ELASTICIO_STEP_ID = 'step_1'; - env.ELASTICIO_EXEC_ID = 'some-exec-id'; - - env.ELASTICIO_WORKSPACE_ID = '5559edd38968ec073600683'; - env.ELASTICIO_CONTAINER_ID = 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948'; - - env.ELASTICIO_USER_ID = '5559edd38968ec0736000002'; - env.ELASTICIO_COMP_ID = '5559edd38968ec0736000456'; - - env.ELASTICIO_COMPONENT_PATH = '/mocha_spec/integration_component'; - - env.ELASTICIO_API_URI = 'https://apidotelasticidotio'; - env.ELASTICIO_API_USERNAME = 'test@test.com'; - env.ELASTICIO_API_KEY = '5559edd'; - env.ELASTICIO_FLOW_WEBHOOK_URI = 'https://in.elastic.io/hooks/' + env.ELASTICIO_FLOW_ID; - - env.DEBUG = 'sailor:debug'; -} - -function mockApiTaskStepResponse(response) { - const defaultResponse = { - config: { - apiKey: 'secret' - }, - snapshot: { - lastModifiedDate: 123456789 - } - }; - - nock(env.ELASTICIO_API_URI) - .matchHeader('Connection', 'Keep-Alive') - .get(`/v1/tasks/${env.ELASTICIO_FLOW_ID}/steps/${env.ELASTICIO_STEP_ID}`) - .reply(200, Object.assign(defaultResponse, response)); -} - -exports.PREFIX = PREFIX; - -exports.amqp = function amqp() { - return new AmqpHelper(); -}; - -exports.prepareEnv = prepareEnv; -exports.mockApiTaskStepResponse = mockApiTaskStepResponse; diff --git a/mocha_spec/run.spec.js b/mocha_spec/run.spec.js deleted file mode 100644 index 34eb8e42..00000000 --- a/mocha_spec/run.spec.js +++ /dev/null @@ -1,839 +0,0 @@ -'use strict'; - -const nock = require('nock'); -const expect = require('chai').expect; -const co = require('co'); -const sinonjs = require('sinon'); -const logging = require('../lib/logging.js'); -const helpers = require('./integration_helpers'); - -const env = process.env; - -describe('Integration Test', () => { - const customers = [ - { - name: 'Homer Simpson' - }, - { - name: 'Marge Simpson' - } - ]; - const inputMessage = { - headers: {}, - body: { - message: 'Just do it!' - } - }; - - let run; - - beforeEach(() => { - helpers.prepareEnv(); - env.ELASTICIO_FUNCTION = 'init_trigger'; - }); - - afterEach((done) => { - delete env.ELASTICIO_STARTUP_REQUIRED; - delete env.ELASTICIO_FUNCTION; - delete env.ELASTICIO_HOOK_SHUTDOWN; - - co(function* gen() { - yield run.disconnect(); - done(); - }).catch(done); - - nock.cleanAll(); - }); - - let sinon; - beforeEach(() => { - sinon = sinonjs.sandbox.create(); - }); - afterEach(() => { - sinon.restore(); - }); - - describe('when sailor is being invoked for message processing', () => { - const parentMessageId = 'parent_message_1234567890'; - const threadId = helpers.PREFIX + '_thread_id_123456'; - const messageId = 'f45be600-f770-11e6-b42d-b187bfbf19fd'; - - let amqpHelper = helpers.amqp(); - beforeEach(() => amqpHelper.prepare()); - afterEach(() => amqpHelper.cleanUp()); - - it('should run trigger successfully', (done) => { - helpers.mockApiTaskStepResponse(); - - nock('https://api.acme.com') - .post('/subscribe') - .reply(200, { - id: 'subscription_12345' - }) - .get('/customers') - .reply(200, customers); - - amqpHelper.on('data', ({ properties, body }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.deep.equal({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - threadId, - parentMessageId: parentMessageId, - messageId - }); - - delete properties.headers; - - expect(properties).to.deep.equal({ - contentType: 'application/json', - contentEncoding: 'utf8', - deliveryMode: undefined, - priority: undefined, - correlationId: undefined, - replyTo: undefined, - expiration: undefined, - messageId: undefined, - timestamp: undefined, - type: undefined, - userId: undefined, - appId: undefined, - clusterId: undefined - }); - - expect(body).to.deep.equal({ - originalMsg: inputMessage, - customers: customers, - subscription: { - id: 'subscription_12345', - cfg: { - apiKey: 'secret' - } - } - }); - - done(); - }); - - run = requireRun(); - - amqpHelper.publishMessage(inputMessage, { - parentMessageId, - threadId - }); - }); - - it('should augment passthrough property with data', done => { - process.env.ELASTICIO_STEP_ID = 'step_2'; - process.env.ELASTICIO_FLOW_ID = '5559edd38968ec0736000003'; - process.env.ELASTICIO_FUNCTION = 'emit_data'; - - helpers.mockApiTaskStepResponse({ - is_passthrough: true - }); - - const psMsg = Object.assign(inputMessage, { - passthrough: { - step_1: { - id: '34', - body: {}, - attachments: {} - } - } - }); - - amqpHelper.publishMessage(psMsg, { - parentMessageId, - threadId - }); - - amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - expect(emittedMessage.passthrough).to.deep.eql({ - step_1: { - id: '34', - body: {}, - attachments: {} - }, - step_2: { - id: messageId, - headers: { - 'x-custom-component-header': '123_abc' - }, - body: { - id: 'someId', - hai: 'there' - } - } - }); - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.deep.equal({ - taskId: env.ELASTICIO_FLOW_ID, - execId: env.ELASTICIO_EXEC_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - threadId, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - messageId, - parentMessageId - }); - - delete properties.headers; - - expect(properties).to.deep.eql({ - contentType: 'application/json', - contentEncoding: 'utf8', - deliveryMode: undefined, - priority: undefined, - correlationId: undefined, - replyTo: undefined, - expiration: undefined, - messageId: undefined, - timestamp: undefined, - type: undefined, - userId: undefined, - appId: undefined, - clusterId: undefined - }); - - done(); - }); - - run = requireRun(); - }); - - it('should work well with async process function emitting data', done => { - process.env.ELASTICIO_STEP_ID = 'step_2'; - process.env.ELASTICIO_FLOW_ID = '5559edd38968ec0736000003'; - process.env.ELASTICIO_FUNCTION = 'async_trigger'; - process.env.ELASTICIO_DATA_RATE_LIMIT = '1'; - process.env.ELASTICIO_RATE_INTERVAL = '110'; - - helpers.mockApiTaskStepResponse({ - is_passthrough: true - }); - - const psMsg = Object.assign(inputMessage, { - passthrough: { - step_1: { - id: '34', - body: {}, - attachments: {} - } - } - }); - - amqpHelper.publishMessage(psMsg, { - parentMessageId, - threadId - }); - - let counter = 0; - const start = Date.now(); - amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { - - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - expect(emittedMessage.passthrough).to.deep.eql({ - step_1: { - id: '34', - body: {}, - attachments: {} - }, - step_2: { - id: messageId, - headers: { - 'x-custom-component-header': '123_abc' - }, - body: { - id: 'someId', - hai: 'there' - } - } - }); - - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.deep.equal({ - taskId: env.ELASTICIO_FLOW_ID, - execId: env.ELASTICIO_EXEC_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - threadId, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - messageId, - parentMessageId - }); - - delete properties.headers; - - expect(properties).to.deep.eql({ - contentType: 'application/json', - contentEncoding: 'utf8', - deliveryMode: undefined, - priority: undefined, - correlationId: undefined, - replyTo: undefined, - expiration: undefined, - messageId: undefined, - timestamp: undefined, - type: undefined, - userId: undefined, - appId: undefined, - clusterId: undefined - }); - - counter++; - // We need 10 messages - if (counter > 10) { - const duration = Date.now() - start; - console.log(`Test duration was ${duration} milliseconds, it should be more than 1000`); - expect(duration > 1000).to.be.ok; - done(); - } - }); - - run = requireRun(); - }); - - describe('when env ELASTICIO_STARTUP_REQUIRED is set', () => { - beforeEach(() => { - env.ELASTICIO_STARTUP_REQUIRED = '1'; - }); - - describe('when hooks data for the task is not created yet', () => { - it('should execute startup successfully', (done) => { - let startupRegistrationRequest; - const startupRegistrationNock = nock('http://example.com/') - .post('/subscriptions/enable') - .reply(200, (uri, requestBody) => { - startupRegistrationRequest = requestBody; - return { - status: 'ok' - }; - }); - - helpers.mockApiTaskStepResponse(); - - // sailor persists startup data via sailor-support API - let hooksDataRequest; - const hooksDataNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .post('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data', { - subscriptionResult: { - status: 'ok' - } - }) - .reply(201, (uri, requestBody) => { - hooksDataRequest = requestBody; - return requestBody; - }); - - // response for a subscription request, which performed inside of init method - nock('https://api.acme.com') - .post('/subscribe') - .reply(200, { - id: 'subscription_12345' - }) - .get('/customers') - .reply(200, customers); - - amqpHelper.on('data', ({ properties, body }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - expect(startupRegistrationRequest).to.deep.equal({ - data: 'startup' - }); - - expect(hooksDataRequest).to.deep.equal({ - subscriptionResult: { - status: 'ok' - } - }); - - expect(startupRegistrationNock.isDone()).to.be.ok; - expect(hooksDataNock.isDone()).to.be.ok; - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.eql({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - messageId - }); - - expect(body).to.deep.equal({ - originalMsg: inputMessage, - customers: customers, - subscription: { - id: 'subscription_12345', - cfg: { - apiKey: 'secret' - } - } - }); - - done(); - }); - - run = requireRun(); - - amqpHelper.publishMessage(inputMessage); - }); - }); - - describe('when hooks data already exists', () => { - it('should delete previous data and execute startup successfully', (done) => { - let startupRegistrationRequest; - const startupRegistrationNock = nock('http://example.com/') - .post('/subscriptions/enable') - .reply(200, (uri, requestBody) => { - startupRegistrationRequest = requestBody; - return { - status: 'ok' - }; - }); - - helpers.mockApiTaskStepResponse(); - - let hooksDataRequest1; - let hooksDataRequest2; - - // sailor persists startup data via sailor-support API - const hooksDataNock1 = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .post('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data', { - subscriptionResult: { - status: 'ok' - } - }) - .reply(409, (uri, requestBody) => { - hooksDataRequest1 = requestBody; - return { - error: 'Hooks data for the task already exist. Delete previous data first.', - status: 409, - title: 'ConflictError' - }; - }); - - // sailor removes data in order to resolve conflict - const hooksDataDeleteNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .delete('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data') - .reply(204); - - // sailor persists startup data via sailor-support API - const hooksDataNock2 = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .post('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data', { - subscriptionResult: { - status: 'ok' - } - }) - .reply(201, (uri, requestBody) => { - hooksDataRequest2 = requestBody; - return requestBody; - }); - - // response for a subscription request, which performed inside of init method - nock('https://api.acme.com') - .post('/subscribe') - .reply(200, { - id: 'subscription_12345' - }) - .get('/customers') - .reply(200, customers); - - amqpHelper.on('data', ({ properties, body }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - expect(startupRegistrationRequest).to.deep.equal({ - data: 'startup' - }); - - expect(startupRegistrationNock.isDone()).to.be.ok; - expect(hooksDataNock1.isDone()).to.be.ok; - expect(hooksDataNock2.isDone()).to.be.ok; - expect(hooksDataDeleteNock.isDone()).to.be.ok; - - expect(hooksDataRequest1).to.deep.equal({ - subscriptionResult: { - status: 'ok' - } - }); - - expect(hooksDataRequest2).to.deep.equal({ - subscriptionResult: { - status: 'ok' - } - }); - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.eql({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - messageId - }); - - expect(body).to.deep.equal({ - originalMsg: inputMessage, - customers: customers, - subscription: { - id: 'subscription_12345', - cfg: { - apiKey: 'secret' - } - } - }); - done(); - }); - - run = requireRun(); - - amqpHelper.publishMessage(inputMessage); - }); - }); - - describe('when startup method returns empty data', () => { - it('should store an empty object as data and execute trigger successfully', (done) => { - let startupRegistrationRequest; - - env.ELASTICIO_FUNCTION = 'startup_with_empty_data'; - - const startupRegistrationNock = nock('http://example.com/') - .post('/subscriptions/enable') - .reply(200, (uri, requestBody) => { - startupRegistrationRequest = requestBody; - return { - status: 'ok' - }; - }); - - helpers.mockApiTaskStepResponse(); - - // sailor persists startup data via sailor-support API - const hooksDataNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .post('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data', {}) - .reply(201); - - // sailor removes data in order to resolve conflict - const hooksDataDeleteNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .delete('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data') - .reply(400); - - // response for a subscription request, which performed inside of init method - nock('https://api.acme.com') - .post('/subscribe') - .reply(200, { - id: 'subscription_12345' - }) - .get('/customers') - .reply(200, customers); - - amqpHelper.on('data', ({ properties, body }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - expect(startupRegistrationRequest).to.deep.equal({ - data: 'startup' - }); - - expect(startupRegistrationNock.isDone()).to.be.ok; - expect(hooksDataNock.isDone()).to.be.ok; - expect(hooksDataDeleteNock.isDone()).to.not.be.ok; - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.eql({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - messageId - }); - - expect(body).to.deep.equal({ - originalMsg: inputMessage, - customers: customers, - subscription: { - id: 'subscription_12345', - cfg: { - apiKey: 'secret' - } - } - }); - - done(); - }); - - run = requireRun(); - - amqpHelper.publishMessage(inputMessage); - }); - }); - - describe('when startup method does not exist', () => { - it('should store an empty hooks data and run trigger successfully', (done) => { - env.ELASTICIO_FUNCTION = 'trigger_with_no_hooks'; - - helpers.mockApiTaskStepResponse(); - - // sailor persists startup data via sailor-support API - const hooksDataNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .post('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data', {}) - .reply(201); - - // response for a subscription request, which performed inside of init method - nock('https://api.acme.com') - .get('/customers') - .reply(200, customers); - - amqpHelper.on('data', ({ properties, body }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepQueue); - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers).to.eql({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - messageId - }); - - expect(body).to.deep.equal({ - originalMsg: inputMessage, - customers: customers - }); - - expect(hooksDataNock.isDone()).to.be.ok; - - done(); - }); - - run = requireRun(); - - amqpHelper.publishMessage(inputMessage); - }); - }); - }); - - describe('when reply_to header is set', () => { - it('should send http reply successfully', (done) => { - - env.ELASTICIO_FUNCTION = 'http_reply_action'; - - helpers.mockApiTaskStepResponse(); - - nock('https://api.acme.com') - .post('/subscribe') - .reply(200, { - id: 'subscription_12345' - }) - .get('/customers') - .reply(200, customers); - - amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { - expect(queueName).to.eql(amqpHelper.httpReplyQueueName); - - delete properties.headers.start; - delete properties.headers.end; - delete properties.headers.cid; - - expect(properties.headers.messageId).to.be.a('string'); - delete properties.headers.messageId; - - expect(properties.headers).to.eql({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION, - reply_to: amqpHelper.httpReplyQueueRoutingKey - }); - - expect(emittedMessage).to.eql({ - headers: { - 'content-type': 'text/plain' - }, - body: 'Ok', - statusCode: 200 - }); - - done(); - }); - - run = requireRun(); - - amqpHelper.publishMessage(inputMessage, {}, { - reply_to: amqpHelper.httpReplyQueueRoutingKey - }); - }); - }); - - describe('when sailor could not init the module', () => { - it('should publish init errors to RabbitMQ', (done) => { - const logCriticalErrorStub = sinon.stub(logging, 'criticalErrorAndExit'); - - env.ELASTICIO_FUNCTION = 'fails_to_init'; - - helpers.mockApiTaskStepResponse(); - - amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { - expect(queueName).to.eql(amqpHelper.nextStepErrorQueue); - - expect(JSON.parse(emittedMessage.error).message).to.equal('OMG. I cannot init'); - - expect(properties.headers).to.eql({ - execId: env.ELASTICIO_EXEC_ID, - taskId: env.ELASTICIO_FLOW_ID, - workspaceId: env.ELASTICIO_WORKSPACE_ID, - containerId: env.ELASTICIO_CONTAINER_ID, - userId: env.ELASTICIO_USER_ID, - stepId: env.ELASTICIO_STEP_ID, - compId: env.ELASTICIO_COMP_ID, - function: env.ELASTICIO_FUNCTION - }); - - done(); - }); - - run = requireRun(); - }); - }); - }); - - describe('when sailor is being invoked for shutdown', () => { - describe('when hooksdata is found', () => { - it('should execute shutdown successfully', (done) => { - - env.ELASTICIO_HOOK_SHUTDOWN = '1'; - - const subsriptionResponse = { - subId: '507' - }; - - let requestFromShutdownHook; - const requestFromShutdownNock = nock('http://example.com/') - .post('/subscriptions/disable') - .reply(200, (uri, requestBody) => { - requestFromShutdownHook = requestBody; - return { - status: 'ok' - }; - }); - - // sailor retrieves startup data via sailor-support API - const hooksDataGetNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .get('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data') - .reply(200, subsriptionResponse); - - // sailor removes startup data via sailor-support API - const hooksDataDeleteNock = nock('https://apidotelasticidotio') - .matchHeader('Connection', 'Keep-Alive') - .delete('/sailor-support/hooks/task/5559edd38968ec0736000003/startup/data') - .reply(204); - - helpers.mockApiTaskStepResponse(); - - hooksDataDeleteNock.on('replied', () => setTimeout(checkResult, 50)); - - function checkResult() { - expect(hooksDataGetNock.isDone()).to.be.ok; - - expect(requestFromShutdownHook).to.deep.equal({ - cfg: { - apiKey: 'secret' - }, - startupData: subsriptionResponse - }); - - expect(requestFromShutdownNock.isDone()).to.be.ok; - expect(hooksDataDeleteNock.isDone()).to.be.ok; - - done(); - } - - run = requireRun(); - }); - }); - - describe('when request for hooksdata is failed with an error', () => { - // @todo - it('should not execute shutdown'); - }); - - describe('when shutdown hook method is not found', () => { - // @todo - it('should not thrown error and just finish process'); - }); - }); -}); - -function requireRun() { - //@todo it would be great to use something like this https://github.com/jveski/shelltest - const path = '../run.js'; - const resolved = require.resolve(path); - delete require.cache[resolved]; - return require(path); -} diff --git a/package-lock.json b/package-lock.json index e2cbde74..58fca413 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,205 +1,260 @@ { "name": "elasticio-sailor-nodejs", - "version": "2.4.1", + "version": "2.6.0-dev.3", "lockfileVersion": 1, "requires": true, "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", "dev": true, "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "@babel/highlight": "^7.0.0" } }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "@babel/generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", "dev": true, "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } + "@babel/types": "^7.6.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", "dev": true, - "optional": true + "requires": { + "@babel/types": "^7.4.4" + } }, - "amqplib": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz", - "integrity": "sha1-fMz+ur5WwumE6noiQ/fO/m+/xs8=", + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, "requires": { - "bitsyntax": "~0.0.4", - "bluebird": "^3.4.6", - "buffer-more-ints": "0.0.2", - "readable-stream": "1.x >=1.1.9" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", "dev": true }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "@babel/traverse": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true + "@sinonjs/commons": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "acorn": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "dev": true }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "acorn-jsx": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", + "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", "dev": true }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true + "amqplib": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.5.tgz", + "integrity": "sha512-sWx1hbfHbyKMw6bXOK2k6+lHL8TESWxjAx5hG8fBtT7wcxoXNIsFxZMnFyBjxt3yL14vn7WqBDe5U6BGOadtLg==", + "requires": { + "bitsyntax": "~0.1.0", + "bluebird": "^3.5.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "safe-buffer": "~5.1.2", + "url-parse": "~1.4.3" + } }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "color-convert": "^1.9.0" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, "asn1": { @@ -223,16 +278,10 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "assign-symbols": { + "astral-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "asynckit": { @@ -241,12 +290,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -259,101 +302,11 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -363,60 +316,31 @@ "tweetnacl": "^0.14.3" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, "bitsyntax": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", - "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", + "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", "requires": { - "buffer-more-ints": "0.0.2" - } - }, - "bluebird": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", - "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" - }, - "body-parser": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.16.1.tgz", - "integrity": "sha1-UVQNBFrfp6DGmVoBS7ax7ZuAIyk=", - "dev": true, - "requires": { - "bytes": "2.4.0", - "content-type": "~1.0.2", - "debug": "2.6.1", - "depd": "~1.1.0", - "http-errors": "~1.5.1", - "iconv-lite": "0.4.15", - "on-finished": "~2.3.0", - "qs": "6.2.1", - "raw-body": "~2.2.0", - "type-is": "~1.6.14" + "buffer-more-ints": "~1.0.0", + "debug": "~2.6.9", + "safe-buffer": "~5.1.2" }, "dependencies": { "debug": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.1.tgz", - "integrity": "sha1-eYVQkLosTjEVzH2HaUkdWPBJE1E=", - "dev": true, + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "0.7.2" + "ms": "2.0.0" } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true } } }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -426,57 +350,16 @@ "concat-map": "0.0.1" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "buffer-more-ints": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", - "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" }, "bunyan": { "version": "1.8.12", @@ -489,27 +372,16 @@ "safe-json-stringify": "~1" } }, - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" } }, "caller-path": { @@ -528,21 +400,11 @@ "dev": true }, "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -550,16 +412,25 @@ "dev": true }, "chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", - "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "deep-eql": "^0.1.3", - "type-detect": "^1.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, + "chai-uuid": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/chai-uuid/-/chai-uuid-1.0.6.tgz", + "integrity": "sha1-NTo7gX3WaqJgigZg+vaFk/uRjIs=", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -569,32 +440,12 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "check-error": { @@ -609,29 +460,6 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -647,22 +475,22 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "code-quality-js": { "version": "2.0.2", @@ -670,22 +498,6 @@ "integrity": "sha1-aocXem1dHDSGc4mua0RQvKzACRg=", "dev": true }, - "coffeescript": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", - "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -701,32 +513,26 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", "dev": true, "optional": true }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "concat-map": { @@ -734,103 +540,58 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "safe-buffer": "~5.1.1" } }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "array-find-index": "^1.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "dashdash": { @@ -842,12 +603,6 @@ "assert-plus": "^1.0.0" } }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -862,34 +617,28 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "0.1.1" - }, - "dependencies": { - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - } + "type-detect": "^4.0.0" } }, "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz", + "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } }, "deep-is": { "version": "0.1.3", @@ -897,69 +646,22 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "strip-bom": "^3.0.0" } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "object-keys": "^1.0.12" } }, "delayed-stream": { @@ -968,34 +670,10 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "deprecated": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true - }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "doctrine": { @@ -1008,21 +686,12 @@ } }, "dtrace-provider": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", - "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", "optional": true, "requires": { - "nan": "^2.10.0" - } - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" + "nan": "^2.14.0" } }, "ecc-jsbn": { @@ -1035,12 +704,6 @@ "safer-buffer": "^2.1.0" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, "elasticio-rest-node": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/elasticio-rest-node/-/elasticio-rest-node-1.2.3.tgz", @@ -1056,33 +719,39 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "requestretry": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-3.1.0.tgz", + "integrity": "sha512-DkvCPK6qvwxIuVA5TRCvi626WHC2rWjF/n7SCQvVHAr2JX9i1/cmIpSEZlmHAo+c1bj9rjaKoZ9IsKwCpTkoXA==", + "requires": { + "extend": "^3.0.2", + "lodash": "^4.17.10", + "when": "^3.7.7" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + } + } } } }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "end-of-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { - "once": "~1.3.0" - }, - "dependencies": { - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true, - "requires": { - "wrappy": "1" - } - } + "once": "^1.4.0" } }, "error-ex": { @@ -1094,15 +763,39 @@ "is-arrayish": "^0.2.1" } }, - "es6-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz", - "integrity": "sha1-zMSWPmefDKn7GHx3e55YPTx1c8I=" + "es-abstract": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", + "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.0.0", + "string.prototype.trimright": "^2.0.0" + } }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, "escape-string-regexp": { @@ -1111,93 +804,65 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.9.0.tgz", + "integrity": "sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^2.0.0", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "globals": "^11.7.0", + "ignore": "^4.0.6", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^1.0.1", + "regexpp": "^2.0.1", "require-uncached": "^1.0.3", - "semver": "^5.3.0", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1207,104 +872,89 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, - "eslint-plugin-mocha": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-4.12.1.tgz", - "integrity": "sha512-hxWtYHvLA0p/PKymRfDYh9Mxt5dYkg2Goy1vZDarTEEYfELP9ksga7kKG1NUKSQy27C8Qjc7YrSWTLUhOEOksA==", - "dev": true, - "requires": { - "ramda": "^0.25.0" - } + "eslint-config-standard": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", + "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", + "dev": true }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "eslint-module-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "eslint-plugin-es": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz", + "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "eslint-utils": "^1.4.2", + "regexpp": "^2.0.1" } }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "eslint-plugin-import": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", "dev": true, "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", - "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=", - "dev": true - }, - "event-to-promise": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.8.0.tgz", - "integrity": "sha1-S4TxF3K28l93Uvx02XFTGsb1tiY=" - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" }, "dependencies": { "debug": { @@ -1316,227 +966,185 @@ "ms": "2.0.0" } }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true } } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "express": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.14.1.tgz", - "integrity": "sha1-ZGwjf3ZvFIwhIK/wc4F7nk1+DTM=", - "dev": true, - "requires": { - "accepts": "~1.3.3", - "array-flatten": "1.1.1", - "content-disposition": "0.5.2", - "content-type": "~1.0.2", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "~2.2.0", - "depd": "~1.1.0", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "etag": "~1.7.0", - "finalhandler": "0.5.1", - "fresh": "0.3.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.1", - "path-to-regexp": "0.1.7", - "proxy-addr": "~1.1.3", - "qs": "6.2.0", - "range-parser": "~1.2.0", - "send": "0.14.2", - "serve-static": "~1.11.2", - "type-is": "~1.6.14", - "utils-merge": "1.0.0", - "vary": "~1.1.0" + "eslint-plugin-mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-5.2.0.tgz", + "integrity": "sha512-4VTX/qIoxUFRnXLNm6bEhEJyfGnGagmQzV4TWXKzkZgIYyP2FSubEdCjEFTyS/dGwSVRWCWGX7jO7BK8R0kppg==", + "dev": true, + "requires": { + "ramda": "^0.25.0" + } + }, + "eslint-plugin-node": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.0.tgz", + "integrity": "sha512-Y+ln8iQ52scz9+rSPnSWRaAxeWaoJZ4wIveDR0vLHkuSZGe44Vk1J4HX7WvEP5Cm+iXPE8ixo7OM7gAO3/OKpQ==", + "dev": true, + "requires": { + "eslint-plugin-es": "^1.3.1", + "eslint-utils": "^1.3.1", + "ignore": "^5.0.2", + "minimatch": "^3.0.4", + "resolve": "^1.8.1", + "semver": "^5.5.0" }, "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - }, - "qs": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz", - "integrity": "sha1-O3hIwDwt7OaalSKw+ujEEm10Xzs=", + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", "dev": true } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "eslint-plugin-promise": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", + "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", + "dev": true }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "eslint-plugin-standard": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz", + "integrity": "sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA==", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "eslint-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } + "eslint-visitor-keys": "^1.0.0" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", + "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "event-to-promise": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.8.0.tgz", + "integrity": "sha1-S4TxF3K28l93Uvx02XFTGsb1tiY=" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, "fast-json-stable-stringify": { @@ -1570,152 +1178,89 @@ "object-assign": "^4.0.1" } }, - "fileset": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz", - "integrity": "sha1-WI74lzxmI7KnbfRlEFaWuWqsgGc=", + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { - "glob": "5.x", - "minimatch": "2.x" + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" }, "dependencies": { - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "locate-path": "^3.0.0" } }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "brace-expansion": "^1.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "p-try": "^2.0.0" } - } - } - }, - "finalhandler": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.1.tgz", - "integrity": "sha1-LEANjUUwk1vCMlScX6OF7Afeb80=", - "dev": true, - "requires": { - "debug": "~2.2.0", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "statuses": "~1.3.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "ms": "0.7.1" + "p-limit": "^2.0.0" } }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } } } }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", - "dev": true - }, "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "locate-path": "^2.0.0" } }, - "fined": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.1.tgz", - "integrity": "sha512-jQp949ZmEbiYHk3gkbdtpJ0G1+kgtLQBNdP5edFP7Fh+WAYceLQz6yO1SBj72Xkg8GVyTB3bBzAYrHJVh5Xd5g==", + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "is-buffer": "~2.0.3" } }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", - "dev": true - }, - "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true - }, "flat-cache": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -1729,9 +1274,9 @@ }, "dependencies": { "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1753,19 +1298,26 @@ } } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { - "for-in": "^1.0.1" + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } } }, "forever-agent": { @@ -1785,56 +1337,29 @@ "mime-types": "^2.1.12" } }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", - "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=", - "dev": true - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", - "dev": true, - "requires": { - "globule": "~0.1.0" - } + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-func-name": { "version": "2.0.0", @@ -1842,17 +1367,14 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } }, "getpass": { "version": "0.1.7", @@ -1867,6 +1389,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -1875,605 +1398,36 @@ "path-is-absolute": "^1.0.0" } }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "dev": true, - "requires": { - "glob": "^4.3.1", - "glob2base": "^0.0.12", - "minimatch": "^2.0.1", - "ordered-read-streams": "^0.1.0", - "through2": "^0.6.1", - "unique-stream": "^1.0.0" - }, - "dependencies": { - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^2.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "dev": true, - "requires": { - "brace-expansion": "^1.0.0" - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "dev": true, - "requires": { - "gaze": "^0.5.1" - } - }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "dev": true, - "requires": { - "find-index": "^0.1.1" - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, "globals": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", - "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "dev": true, - "requires": { - "glob": "~3.1.21", - "lodash": "~1.0.1", - "minimatch": "~0.2.11" - }, - "dependencies": { - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true, - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - }, - "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", - "dev": true - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, "growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "gulp": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "dev": true, - "requires": { - "archy": "^1.0.0", - "chalk": "^1.0.0", - "deprecated": "^0.0.1", - "gulp-util": "^3.0.0", - "interpret": "^1.0.0", - "liftoff": "^2.1.0", - "minimist": "^1.1.0", - "orchestrator": "^0.3.0", - "pretty-hrtime": "^1.0.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", - "vinyl-fs": "^0.3.0" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "gulp-istanbul": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/gulp-istanbul/-/gulp-istanbul-1.1.1.tgz", - "integrity": "sha1-4JTZj0K/pNmo5TZvQU7ZoJo8ZTc=", - "dev": true, - "requires": { - "gulp-util": "^3.0.1", - "istanbul": "^0.4.0", - "istanbul-threshold-checker": "^0.1.0", - "lodash": "^4.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.1" - } - }, - "gulp-jasmine": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/gulp-jasmine/-/gulp-jasmine-0.2.0.tgz", - "integrity": "sha1-8Z8/L87x9sc+YlMYHQn9+ldAYi0=", - "dev": true, - "requires": { - "gulp-util": "~2.2.0", - "minijasminenode": "~0.2.7", - "require-like": "~0.1.2", - "through2": "~0.4.0" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "dev": true, - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - } - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, - "gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "dev": true, - "requires": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" - }, - "dependencies": { - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "dev": true, - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" - } - } - } - }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "dev": true, - "requires": { - "ansi-regex": "^0.2.0" - } - }, - "lodash._reinterpolate": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", - "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", - "dev": true - }, - "lodash.escape": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", - "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", - "dev": true, - "requires": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, - "lodash.template": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", - "dev": true, - "requires": { - "lodash._escapestringchar": "~2.4.1", - "lodash._reinterpolate": "~2.4.1", - "lodash.defaults": "~2.4.1", - "lodash.escape": "~2.4.1", - "lodash.keys": "~2.4.1", - "lodash.templatesettings": "~2.4.1", - "lodash.values": "~2.4.1" - } - }, - "lodash.templatesettings": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", - "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", - "dev": true, - "requires": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" - } - }, - "minimist": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", - "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "dev": true, - "requires": { - "ansi-regex": "^0.2.1" - } - }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true - }, - "through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", - "dev": true, - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" - }, - "dependencies": { - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", - "dev": true, - "requires": { - "clone-stats": "~0.0.1" - } - }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "dev": true - } - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } - }, "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.4.tgz", + "integrity": "sha512-vvpo6mpK4ScNC1DbGRZ2d5BznS6ht0r1hi20RivsibMc6jNvFAeZQ6qk5VNspo6SOwVOJQbjHyBCpuS7BzA1pw==", "dev": true, "requires": { - "async": "^2.5.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" }, "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "dev": true, - "requires": { - "lodash": "^4.17.10" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2496,41 +1450,15 @@ "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", - "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "function-bind": "^1.1.1" } }, "has-flag": { @@ -2539,73 +1467,33 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "has-value": { + "has-symbols": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "is-stream": "^1.0.1" } }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", "dev": true }, - "http-errors": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", - "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "setprototypeof": "1.0.2", - "statuses": ">= 1.3.1 < 2" - } - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -2618,15 +1506,18 @@ } }, "iconv-lite": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", - "dev": true + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "imurmurhash": { @@ -2635,15 +1526,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2654,80 +1536,60 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.0.4", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.12", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.1.0", "through": "^2.3.6" - } - }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "ipaddr.js": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", - "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=", - "dev": true - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "ansi-regex": "^4.1.0" } } } }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -2735,161 +1597,42 @@ "dev": true }, "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "has": "^1.0.1" } }, "is-resolvable": { @@ -2898,31 +1641,25 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" + "has-symbols": "^1.0.0" } }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, "isarray": { @@ -2936,262 +1673,98 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" }, "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, - "istanbul-threshold-checker": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/istanbul-threshold-checker/-/istanbul-threshold-checker-0.1.0.tgz", - "integrity": "sha1-DhRCwBfLJ6hfeBc0/v0hJkBco5w=", + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "requires": { - "istanbul": "0.3.*", - "lodash": "3.6.*" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "dependencies": { - "escodegen": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz", - "integrity": "sha1-MOz89mypjcZ80v0WKr626vqM5vw=", - "dev": true, - "requires": { - "esprima": "^1.2.2", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.5.0", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", - "integrity": "sha1-CZNQL+r2aBODJXVvMPmlH+7sEek=", - "dev": true - } - } - }, - "esprima": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz", - "integrity": "sha1-84ekb9NEwbGjm6+MIL+0O20AWMw=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "fast-levenshtein": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", - "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=", - "dev": true - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "istanbul": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz", - "integrity": "sha1-PhZNhQIf4ZyYXR8OfvDD4i0BLrY=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.7.x", - "esprima": "2.5.x", - "fileset": "0.2.x", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - } - }, - "levn": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", - "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.0", - "type-check": "~0.3.1" - } - }, - "lodash": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz", - "integrity": "sha1-Umao9J3Zib5Pn2gbbyoMVShdDZo=", - "dev": true - }, - "optionator": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz", - "integrity": "sha1-t1qJlaLUF98ltuTjhi9QqohlE2g=", - "dev": true, - "requires": { - "deep-is": "~0.1.2", - "fast-levenshtein": "~1.0.0", - "levn": "~0.2.5", - "prelude-ls": "~1.1.1", - "type-check": "~0.3.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, - "jasmine-growl-reporter": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.2.1.tgz", - "integrity": "sha1-1fCje5L2qD/VxkgrgJSVyQqLVf4=", - "dev": true, - "requires": { - "growl": "~1.7.0" - } - }, - "jasmine-node": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.16.2.tgz", - "integrity": "sha512-A4AA2WaikuE7s/NYQCqfYsyCczEgObLgNH7IxRQ2SBshLBZg7vUEiiGX4GPbveW5f06nYmXYlzY4UjnZjXjV9g==", + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { - "coffeescript": "~1.12.7", - "gaze": "~1.1.2", - "jasmine-growl-reporter": "~0.2.0", - "jasmine-reporters": "~1.0.0", - "mkdirp": "~0.3.5", - "requirejs": "~2.3.6", - "underscore": "~1.9.1", - "walkdir": "~0.0.12" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" }, "dependencies": { - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "globule": "^1.0.0" + "ms": "^2.1.1" } }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3202,58 +1775,48 @@ "path-is-absolute": "^1.0.0" } }, - "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" + "glob": "^7.1.3" } }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, - "jasmine-reporters": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz", - "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=", + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", "dev": true, "requires": { - "mkdirp": "~0.3.5" - }, - "dependencies": { - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", - "dev": true - } + "handlebars": "^4.1.2" } }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -3266,6 +1829,18 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -3273,9 +1848,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -3290,12 +1865,6 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3308,12 +1877,21 @@ "verror": "1.10.0" } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", "dev": true }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3324,332 +1902,54 @@ "type-check": "~0.3.2" } }, - "liftoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", - "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - } - }, "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._escapehtmlchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", - "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", - "dev": true, - "requires": { - "lodash._htmlescapes": "~2.4.1" + "strip-bom": "^3.0.0" } }, - "lodash._escapestringchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", - "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._htmlescapes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", - "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", - "dev": true - }, - "lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._reunescapedhtml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", - "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", - "dev": true, - "requires": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "^3.0.0", - "lodash._basecreate": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, - "lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } + "chalk": "^2.0.1" } }, "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", "dev": true }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -3660,120 +1960,82 @@ "yallist": "^2.1.2" } }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "kind-of": "^6.0.2" + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "object-visit": "^1.0.0" + "p-defer": "^1.0.0" } }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true } } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", - "dev": true - }, "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", "dev": true }, "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "dev": true, "requires": { - "mime-db": "~1.37.0" + "mime-db": "1.40.0" } }, "mimic-fn": { @@ -3782,12 +2044,6 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "minijasminenode": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/minijasminenode/-/minijasminenode-0.2.7.tgz", - "integrity": "sha1-U0/UVQGZrGjypw3IfGLarpOT5oE=", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3801,27 +2057,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -3831,89 +2066,123 @@ } }, "mocha": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.3.0.tgz", - "integrity": "sha1-0pt0KNP1LILi5l3x7LcGThqrv7U=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.0", - "diff": "3.2.0", + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "json3": "3.3.2", - "lodash.create": "3.1.1", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" }, "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "graceful-readlink": ">= 1.0.0" + "ms": "^2.1.1" } }, - "debug": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", - "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "ms": "0.7.2" + "locate-path": "^3.0.0" } }, "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.2", + "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "moment": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", - "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==", + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", "optional": true }, "ms": { @@ -3921,15 +2190,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -3948,36 +2208,11 @@ } }, "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "optional": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", - "dev": true - }, "natives": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", @@ -3995,12 +2230,37 @@ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", + "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^4.1.0", + "path-to-regexp": "^1.7.0" + } + }, "nock": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", @@ -4018,20 +2278,6 @@ "semver": "^5.5.0" }, "dependencies": { - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -4041,447 +2287,208 @@ "ms": "^2.1.1" } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "qs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", - "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==", - "dev": true - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", "dev": true, "requires": { - "abbrev": "1" + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, - "nsp": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/nsp/-/nsp-2.8.1.tgz", - "integrity": "sha512-jvjDg2Gsw4coD/iZ5eQddsDlkvnwMCNnpG05BproSnuG+Gr1bSQMwWMcQeYje+qdDl3XznmhblMPLpZLecTORQ==", + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "chalk": "^1.1.1", - "cli-table": "^0.3.1", - "cvss": "^1.0.0", - "https-proxy-agent": "^1.0.0", - "joi": "^6.9.1", - "nodesecurity-npm-utils": "^5.0.0", - "path-is-absolute": "^1.0.0", - "rc": "^1.1.2", - "semver": "^5.0.3", - "subcommand": "^2.0.3", - "wreck": "^6.3.0" - }, - "dependencies": { - "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", - "dev": true, - "requires": { - "extend": "~3.0.0", - "semver": "~5.0.1" - }, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, - "requires": { - "colors": "1.0.3" - } - }, - "cliclopts": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cliclopts/-/cliclopts-1.1.1.tgz", - "integrity": "sha1-aUMcfLWvcjd0sNORG0w3USQxkQ8=", - "dev": true - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - }, - "cvss": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cvss/-/cvss-1.0.2.tgz", - "integrity": "sha1-32fpK/EqeW9J6Sh5nI2zunS5/NY=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "locate-path": "^3.0.0" } }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "dev": true - }, - "isemail": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", - "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=", - "dev": true - }, - "joi": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", - "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "hoek": "2.x.x", - "isemail": "1.x.x", - "moment": "2.x.x", - "topo": "1.x.x" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "moment": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", - "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nodesecurity-npm-utils": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nodesecurity-npm-utils/-/nodesecurity-npm-utils-5.0.0.tgz", - "integrity": "sha1-Baow3jDKjIRcQEjpT9eOXgi1Xtk=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "rc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", - "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "p-try": "^2.0.0" } }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "p-limit": "^2.0.0" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "subcommand": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/subcommand/-/subcommand-2.1.0.tgz", - "integrity": "sha1-XkzspaN3njNlsVEeBfhmh3MC92A=", - "dev": true, - "requires": { - "cliclopts": "^1.1.0", - "debug": "^2.1.3", - "minimist": "^1.2.0", - "xtend": "^4.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "topo": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", - "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, - "wreck": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/wreck/-/wreck-6.3.0.tgz", - "integrity": "sha1-oTaXafB7u2LWo3gzanhx/Hc8dAs=", + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "boom": "2.x.x", - "hoek": "2.x.x" + "glob": "^7.1.3" } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true } } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - } + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true }, - "object.map": { + "object-is": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "isobject": "^3.0.1" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "ee-first": "1.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "once": { @@ -4533,93 +2540,103 @@ "wordwrap": "~1.0.0" } }, - "orchestrator": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", - "dev": true, - "requires": { - "end-of-stream": "~0.1.5", - "sequencify": "~0.0.7", - "stream-consume": "~0.1.0" - } - }, - "ordered-read-streams": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", - "dev": true - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-throttle": { + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-2.1.0.tgz", - "integrity": "sha512-DvChtxq2k1PfiK4uZXKA4IvRyuq/gP55tb6MQyMLGfYJifCjJY5lDMb94IQHZss/K/tmZx3fAsSC1IqP0e1OnA==" + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "p-try": "^1.0.0" } }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "p-limit": "^1.1.0" } }, - "parse-node-version": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.0.tgz", - "integrity": "sha512-02GTVHD1u0nWc20n2G7WX/PgdhNFG04j5fi1OkaJzPWLTcf6vh6229Lta1wTmXG/7Dg42tCssgkccVt7qvd8Kg==", - "dev": true + "p-throttle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-3.1.0.tgz", + "integrity": "sha512-rLo81NXBihs3GJQhq89IXa0Egj/sbW1zW8/qnyadOwUhIUrZSUvyGdQ46ISRKELFBkVvmMJ4JUqWki4oAh30Qw==" }, - "parse-passwd": { + "p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "error-ex": "^1.2.0" } }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4631,42 +2648,34 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", "dev": true, "requires": { - "path-root-regex": "^0.1.0" + "isarray": "0.0.1" } }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "pify": "^2.0.0" } }, "pathval": { @@ -4687,19 +2696,13 @@ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "pinkie": "^2.0.0" + "find-up": "^2.1.0" } }, "pluralize": { @@ -4708,30 +2711,12 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -4744,16 +2729,6 @@ "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", "dev": true }, - "proxy-addr": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", - "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", - "dev": true, - "requires": { - "forwarded": "~0.1.0", - "ipaddr.js": "1.4.0" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4761,9 +2736,19 @@ "dev": true }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, "punycode": { "version": "2.1.1", @@ -4772,58 +2757,46 @@ "dev": true }, "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qs": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz", - "integrity": "sha1-zgPF/wk1vB2daanxTL0Y5WjWdiU=", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz", + "integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA==", "dev": true }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, "ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", "dev": true }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", - "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", - "dev": true, - "requires": { - "bytes": "2.4.0", - "iconv-lite": "0.4.15", - "unpipe": "1.0.0" - } - }, "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "^1.0.0", + "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "path-type": "^2.0.0" } }, "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" } }, "readable-stream": { @@ -4837,68 +2810,30 @@ "string_decoder": "~0.10.x" } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "regexp.prototype.flags": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", + "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "define-properties": "^1.1.2" } }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "es6-error": "^4.0.1" } }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -4932,54 +2867,47 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true } } }, "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", "requires": { - "lodash": "^4.13.1" + "lodash": "^4.17.11" } }, "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, "requestretry": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-3.1.0.tgz", - "integrity": "sha512-DkvCPK6qvwxIuVA5TRCvi626WHC2rWjF/n7SCQvVHAr2JX9i1/cmIpSEZlmHAo+c1bj9rjaKoZ9IsKwCpTkoXA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-4.0.0.tgz", + "integrity": "sha512-ST8m0+5FQH2FA+gbzUQyOQjUwHf22kbPQnd6TexveR0p+2UV1YYBg+Roe7BnKQ1Bb/+LtJwwm0QzxK2NA20Cug==", "requires": { "extend": "^3.0.2", "lodash": "^4.17.10", "when": "^3.7.7" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, - "require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "require-uncached": { @@ -4992,43 +2920,26 @@ "resolve-from": "^1.0.0" } }, - "requirejs": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", - "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", - "dev": true + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -5039,16 +2950,11 @@ "signal-exit": "^3.0.2" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, "requires": { "glob": "^6.0.1" } @@ -5062,26 +2968,19 @@ "is-promise": "^2.1.0" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { - "rx-lite": "*" + "tslib": "^1.9.0" } }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-json-stringify": { "version": "1.2.0", @@ -5089,138 +2988,22 @@ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "self-addressed": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/self-addressed/-/self-addressed-0.3.0.tgz", - "integrity": "sha1-AitQYD5zh9poVmG8OW8pp/hxXgs=", - "requires": { - "es6-promise": "2.0.1" - } - }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "send": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.14.2.tgz", - "integrity": "sha1-ObBDiz9RC+Xcb2Z6EfcWiTaM3u8=", - "dev": true, - "requires": { - "debug": "~2.2.0", - "depd": "~1.1.0", - "destroy": "~1.0.4", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "etag": "~1.7.0", - "fresh": "0.3.0", - "http-errors": "~1.5.1", - "mime": "1.3.4", - "ms": "0.7.2", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.3.1" - }, - "dependencies": { - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - }, - "dependencies": { - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - } - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true - } - } - }, - "sequencify": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, - "serve-static": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.2.tgz", - "integrity": "sha1-LPmIm9RDWjIMw2iVyapXvWYuasc=", - "dev": true, - "requires": { - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "parseurl": "~1.3.1", - "send": "0.14.2" - } - }, - "set-value": { + "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setprototypeof": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", - "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "shebang-command": { @@ -5238,207 +3021,89 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "requires": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - }, - "dependencies": { - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - } + "sinon": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.4.2.tgz", + "integrity": "sha512-pY5RY99DKelU3pjNxcWo6XqeB1S118GBcVIIdDi6V+h6hevn1izcg2xv1hTHW/sViRXU7sUOxt4wTUJ3gsW2CQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" } }, + "sinon-chai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", + "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", + "dev": true + }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "spawn-wrap": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "glob": "^7.1.3" } } } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true - }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -5466,20 +3131,11 @@ } }, "spdx-license-ids": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5487,9 +3143,9 @@ "dev": true }, "sshpk": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", - "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -5503,44 +3159,11 @@ "tweetnacl": "~0.14.0" } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, - "stream-consume": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", - "dev": true - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -5551,6 +3174,26 @@ "strip-ansi": "^4.0.0" } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -5563,34 +3206,19 @@ "dev": true, "requires": { "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } } }, "strip-bom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", - "dev": true, - "requires": { - "first-chunk-stream": "^1.0.0", - "is-utf8": "^0.2.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-json-comments": { "version": "2.0.1", @@ -5599,150 +3227,209 @@ "dev": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "has-flag": "^3.0.0" } }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "ansi-regex": "^4.1.0" } } } }, - "tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "os-tmpdir": "~1.0.2" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true }, "tough-cookie": { "version": "2.4.3", @@ -5760,10 +3447,10 @@ } } }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, "tunnel-agent": { @@ -5791,35 +3478,19 @@ } }, "type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", - "dev": true - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", "dev": true, "optional": true, "requires": { - "commander": "~2.17.1", + "commander": "~2.20.0", "source-map": "~0.6.1" }, "dependencies": { @@ -5832,111 +3503,6 @@ } } }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", - "dev": true - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "unique-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -5946,50 +3512,20 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "user-home": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utils-merge": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", - "dev": true - }, - "uuid": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" - }, - "v8flags": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", - "dev": true, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", "requires": { - "user-home": "^1.1.1" + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -6000,12 +3536,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -6017,117 +3547,88 @@ "extsprintf": "^1.2.0" } }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "string-width": "^1.0.2 || 2" } }, - "vinyl-fs": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "defaults": "^1.0.0", - "glob-stream": "^3.1.5", - "glob-watcher": "^0.0.6", - "graceful-fs": "^3.0.0", - "mkdirp": "^0.5.0", - "strip-bom": "^1.0.0", - "through2": "^0.6.1", - "vinyl": "^0.4.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, - "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "dev": true, - "requires": { - "natives": "^1.1.0" - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "number-is-nan": "^1.0.0" } }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" + "ansi-regex": "^2.0.0" } } } }, - "vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", - "dev": true, - "requires": { - "source-map": "^0.5.1" - } - }, - "walkdir": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz", - "integrity": "sha512-HFhaD4mMWPzFSqhpyDG48KDdrjfn409YQuVW7ckZYhW4sE87mYtWifdB/+73RA7+p4s4K18n5Jfx1kHthE1gBw==", - "dev": true - }, - "when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -6142,10 +3643,21 @@ "mkdirp": "^0.5.1" } }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { @@ -6153,6 +3665,204 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } } } } diff --git a/package.json b/package.json index d1054180..a52c27ba 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,49 @@ { "name": "elasticio-sailor-nodejs", "description": "The official elastic.io library for bootstrapping and executing for Node.js connectors", - "version": "2.4.2", + "version": "2.6.0-dev.3", "main": "run.js", "scripts": { - "lint": "./node_modules/.bin/eslint lib spec mocha_spec lib run.js runService.js", + "lint": "./node_modules/.bin/eslint lib spec run.js runService.js", "pretest": "npm run lint", - "test": "npm run test:jasmine && npm run test:mocha", - "test:jasmine": "NODE_ENV=test jasmine-node spec", - "test:mocha": "NODE_ENV=test mocha mocha_spec/**/*.js", - "postpublish": "./postpublish.js" + "test": "npm run test:unit && npm run test:integration", + "test:unit": "NODE_ENV=test mocha spec/unit/**/*.spec.js", + "test:integration": "NODE_ENV=test mocha spec/integration/**/*.spec.js", + "postpublish": "./postpublish.js", + "coverage": "nyc --clean npm run test" }, "engines": { "node": ">=10.15.0" }, "dependencies": { - "amqplib": "0.5.1", - "bunyan": "^1.8.10", - "co": "4.6.0", + "amqplib": "0.5.5", + "bunyan": "1.8.12", "debug": "3.1.0", "elasticio-rest-node": "1.2.3", - "event-to-promise": "^0.8.0", - "lodash": "4.17.4", - "p-throttle": "^2.1.0", - "q": "1.4.1", - "request-promise-native": "^1.0.5", - "requestretry": "^3.1.0", - "self-addressed": "^0.3.0", - "uuid": "3.0.1" + "event-to-promise": "0.8.0", + "lodash": "4.17.15", + "p-throttle": "3.1.0", + "requestretry": "4.0.0", + "uuid": "3.3.3" }, "devDependencies": { - "body-parser": "1.16.1", - "chai": "3.5.0", + "chai": "4.2.0", + "chai-uuid": "1.0.6", "code-quality-js": "2.0.2", - "del": "2.2.2", - "eslint": "^4.19.1", - "eslint-plugin-mocha": "^4.12.1", - "express": "4.14.1", - "gulp": "3.9.1", - "gulp-istanbul": "1.1.1", - "gulp-jasmine": "0.2.0", - "jasmine-node": "^1.15.0", - "mocha": "3.3.0", + "eslint": "5.9.0", + "eslint-config-standard": "12.0.0", + "eslint-plugin-import": "2.14.0", + "eslint-plugin-mocha": "5.2.0", + "eslint-plugin-node": "8.0.0", + "eslint-plugin-promise": "4.0.1", + "eslint-plugin-standard": "4.0.0", + "mocha": "6.2.0", "nock": "10.0.6", - "nsp": "^2.8.1", - "request": "^2.88.0", - "sinon": "^2.1.0" + "request": "2.88.0", + "request-promise-native": "1.0.7", + "sinon": "7.4.2", + "sinon-chai": "3.3.0", + "nyc": "14.1.1" }, "repository": "elasticio/sailor-nodejs", "license": "Apache-2.0" diff --git a/run.js b/run.js index ef2e81e6..c819743c 100644 --- a/run.js +++ b/run.js @@ -1,74 +1,9 @@ -const logger = require('./lib/logging.js'); -const Sailor = require('./lib/sailor.js').Sailor; -const settings = require('./lib/settings.js').readFrom(process.env); -const co = require('co'); - -exports.disconnect = disconnect; - -let sailor; -let disconnectRequired; - -co(function* putOutToSea() { - sailor = new Sailor(settings); - - //eslint-disable-next-line no-extra-boolean-cast - if (!!settings.HOOK_SHUTDOWN) { - disconnectRequired = false; - //eslint-disable-next-line no-empty-function - sailor.reportError = () => { - }; - yield sailor.prepare(); - yield sailor.shutdown(); - return; - } - - disconnectRequired = true; - yield sailor.connect(); - yield sailor.prepare(); - - //eslint-disable-next-line no-extra-boolean-cast - if (!!settings.STARTUP_REQUIRED) { - yield sailor.startup(); - } - - yield sailor.init(); - yield sailor.run(); -}).catch((e) => { - if (sailor) { - sailor.reportError(e); - } - logger.criticalErrorAndExit(e); -}); - -process.on('SIGTERM', function onSigterm() { - logger.info('Received SIGTERM'); - disconnectAndExit(); -}); - -process.on('SIGINT', function onSigint() { - logger.info('Received SIGINT'); - disconnectAndExit(); -}); - -process.on('uncaughtException', logger.criticalErrorAndExit); - -function disconnect() { - return co(function* putIn() { - logger.info('Disconnecting...'); - return yield sailor.disconnect(); - }); -} - -function disconnectAndExit() { - if (!disconnectRequired) { - return; - } - co(function* putIn() { - yield disconnect(); - logger.info('Successfully disconnected'); - process.exit(); - }).catch((err) => { - logger.error('Unable to disconnect', err.stack); - process.exit(-1); - }); +const SingleApp = require('./lib/SingleApp.js'); +const MultiApp = require('./lib/MultiApp.js'); +let app; +if (process.argv.includes('--service')) { + app = new MultiApp(); +} else { + app = new SingleApp(); } +app.start(); diff --git a/spec/.eslintrc.js b/spec/.eslintrc.js deleted file mode 100644 index 4de3c09a..00000000 --- a/spec/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const ERROR = 'error'; -const ALWAYS = 'always'; -const NEVER = 'never'; - -module.exports = { - 'env': { - es6: true, - node: true, - jasmine: true - }, - 'parserOptions': { - 'ecmaVersion': 8 - } -}; \ No newline at end of file diff --git a/spec/.eslintrc.json b/spec/.eslintrc.json new file mode 100644 index 00000000..7e9377a3 --- /dev/null +++ b/spec/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "extends": "../.eslintrc.json", + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "rules": { + "node/no-unpublished-require": "off", + "no-unused-expressions": "off" + } +} diff --git a/spec/amqp.spec.js b/spec/amqp.spec.js deleted file mode 100644 index 6db6f814..00000000 --- a/spec/amqp.spec.js +++ /dev/null @@ -1,689 +0,0 @@ -describe('AMQP', () => { - process.env.ELASTICIO_MESSAGE_CRYPTO_PASSWORD = 'testCryptoPassword'; - process.env.ELASTICIO_MESSAGE_CRYPTO_IV = 'iv=any16_symbols'; - - const envVars = {}; - envVars.ELASTICIO_AMQP_URI = 'amqp://test2/test2'; - envVars.ELASTICIO_FLOW_ID = '5559edd38968ec0736000003'; - envVars.ELASTICIO_STEP_ID = 'step_1'; - envVars.ELASTICIO_EXEC_ID = 'some-exec-id'; - envVars.ELASTICIO_WORKSPACE_ID = '5559edd38968ec073600683'; - envVars.ELASTICIO_CONTAINER_ID = 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948'; - - envVars.ELASTICIO_USER_ID = '5559edd38968ec0736000002'; - envVars.ELASTICIO_COMP_ID = '5559edd38968ec0736000456'; - envVars.ELASTICIO_FUNCTION = 'list'; - - envVars.ELASTICIO_LISTEN_MESSAGES_ON = '5559edd38968ec0736000003:step_1:1432205514864:messages'; - envVars.ELASTICIO_PUBLISH_MESSAGES_TO = 'userexchange:5527f0ea43238e5d5f000001'; - envVars.ELASTICIO_DATA_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:message'; - envVars.ELASTICIO_ERROR_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:error'; - envVars.ELASTICIO_REBOUND_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:rebound'; - envVars.ELASTICIO_SNAPSHOT_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:snapshot'; - - envVars.ELASTICIO_API_URI = 'http://apihost.com'; - envVars.ELASTICIO_API_USERNAME = 'test@test.com'; - envVars.ELASTICIO_API_KEY = '5559edd'; - - const Amqp = require('../lib/amqp.js').Amqp; - const settings = require('../lib/settings.js').readFrom(envVars); - const encryptor = require('../lib/encryptor.js'); - const _ = require('lodash'); - const pThrottle = require('p-throttle'); - - const message = { - fields: { - consumerTag: 'abcde', - deliveryTag: 12345, - exchange: 'test', - routingKey: 'test.hello' - }, - properties: { - contentType: 'application/json', - contentEncoding: 'utf8', - headers: { - taskId: 'task1234567890', - execId: 'exec1234567890', - reply_to: 'replyTo1234567890' - }, - deliveryMode: undefined, - priority: undefined, - correlationId: undefined, - replyTo: undefined, - expiration: undefined, - messageId: undefined, - timestamp: undefined, - type: undefined, - userId: undefined, - appId: undefined, - mandatory: true, - clusterId: '' - }, - content: encryptor.encryptMessageContent({ content: 'Message content' }) - }; - - beforeEach(() => { - spyOn(encryptor, 'decryptMessageContent').andCallThrough(); - }); - - it('Should send message to outgoing channel when process data', () => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }; - - amqp.sendData({ - headers: { - 'some-other-header': 'headerValue' - }, - body: 'Message content' - }, props); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - settings.DATA_ROUTING_KEY, - jasmine.any(Object), - props - ]); - - const payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - expect(payload).toEqual({ - headers: { - 'some-other-header': 'headerValue' - }, - body: 'Message content' - }); - }); - - it('Should send message async to outgoing channel when process data', done => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['on']); - amqp.publishChannel.publish = () => true; - spyOn(amqp.publishChannel, 'publish').andReturn(true); - amqp.publishChannel.waitForConfirms = () => Promise.resolve([null]); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }; - // One request every 500 ms - const throttle = pThrottle(() => Promise.resolve(), 1, 500); - const start = Date.now(); - async function test() { - for (let i = 0; i < 3; i++) { - await amqp.sendData({ - headers: { - 'some-other-header': 'headerValue' - }, - body: 'Message content' - }, props, throttle); - } - } - test().then(() => { - const duration = Math.round((Date.now() - start) / 1000); - // Total duration should be around 1 seconds, because - // first goes through - // second throttled for 500ms - // third throttled for another 500 ms - expect(duration).toEqual(1); - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(3); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - settings.DATA_ROUTING_KEY, - jasmine.any(Object), - props - ]); - - const payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - expect(payload).toEqual({ - headers: { - 'some-other-header': 'headerValue' - }, - body: 'Message content' - }); - done(); - }, done); - }); - - it('Should sendHttpReply to outgoing channel using routing key from headers when process data', () => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const msg = { - statusCode: 200, - headers: { - 'content-type': 'text/plain' - }, - body: 'OK' - }; - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456', - reply_to: 'my-special-routing-key' - } - }; - amqp.sendHttpReply(msg, props); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters[0]).toEqual(settings.PUBLISH_MESSAGES_TO); - expect(publishParameters[1]).toEqual('my-special-routing-key'); - expect(publishParameters[2].toString()).toEqual(encryptor.encryptMessageContent(msg)); - expect(publishParameters[3]).toEqual(props); - - const payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - expect(payload).toEqual(msg); - }); - - it('Should throw error in sendHttpReply if reply_to header not found', done => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const msg = { - statusCode: 200, - headers: { - 'content-type': 'text/plain' - }, - body: 'OK' - }; - async function test() { - await amqp.sendHttpReply(msg, { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }); - } - test().then(() => done(new Error('should throw')),(err) => { - expect(amqp.publishChannel.publish).not.toHaveBeenCalled(); - done(); - }); - - }); - - it('Should send message to outgoing channel using routing key from headers when process data', () => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const msg = { - headers: { - 'X-EIO-Routing-Key': 'my-special-routing-key' - }, - body: { - content: 'Message content' - } - }; - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }; - - amqp.sendData(msg, props); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - 'my-special-routing-key', - jasmine.any(Object), - props - ]); - - const payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - expect(payload).toEqual({ - headers: {}, - body: { - content: 'Message content' - } - }); - }); - - it('Should send message to errors when process error', () => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }; - - amqp.sendError(new Error('Test error'), props, message.content); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - settings.ERROR_ROUTING_KEY, - jasmine.any(Object), - props - ]); - - const payload = JSON.parse(publishParameters[2].toString()); - payload.error = encryptor.decryptMessageContent(payload.error); - payload.errorInput = encryptor.decryptMessageContent(payload.errorInput); - - expect(payload).toEqual({ - error: { - name: 'Error', - message: 'Test error', - stack: jasmine.any(String) - }, - errorInput: { - content: 'Message content' - } - }); - }); - - it('Should send message to errors using routing key from headers when process error', async () => { - const expectedErrorPayload = { - error: { - name: 'Error', - message: 'Test error', - stack: jasmine.any(String) - }, - errorInput: { - content: 'Message content' - } - }; - - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456', - reply_to: 'my-special-routing-key' - } - }; - - await amqp.sendError(new Error('Test error'), props, message.content); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(2); - - let publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters.length).toEqual(4); - expect(publishParameters[0]).toEqual(settings.PUBLISH_MESSAGES_TO); - expect(publishParameters[1]).toEqual('5559edd38968ec0736000003:step_1:1432205514864:error'); - expect(publishParameters[3]).toEqual(props); - - let payload = JSON.parse(publishParameters[2].toString()); - payload.error = encryptor.decryptMessageContent(payload.error); - payload.errorInput = encryptor.decryptMessageContent(payload.errorInput); - - expect(payload).toEqual(expectedErrorPayload); - - - publishParameters = amqp.publishChannel.publish.calls[1].args; - expect(publishParameters.length).toEqual(4); - expect(publishParameters[0]).toEqual(settings.PUBLISH_MESSAGES_TO); - expect(publishParameters[1]).toEqual('my-special-routing-key'); - expect(publishParameters[3]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - 'taskId': 'task1234567890', - 'stepId': 'step_456', - 'reply_to': 'my-special-routing-key', - 'x-eio-error-response': true - } - }); - - console.log('AND BACK'); - //eslint-disable-next-line max-len - console.log(encryptor.decryptMessageContent('+PAlXNRj+5HdYNSuw3cyrfXNSlnUHKH0AtyspQkvT0RFROPAhMgqrj8y1I0EW9zJEhcRzmiEwbK5ftV3a8N3FcMd1Yu2beNt0R2Ou2f1yae0FxZ/aIUOmicX3iWbUKFnfljwUUA39sEKnpp9yP7zprAf755FgEtplt3cSy+hQVCC0u7olkbIeHtmSuw/9YP9PckVk82eM7FfnK5qKEDilzR9CWgpQEak8kZeekko86WczgkRrnMj52ifGVCbIk4aY5K+uBPbQKURI9bbBra4aR0l/2Y/bOBa5jahl2Q6hrX9iAe9BMMIll9GvDxBOEV7n5H5CsZj1IrFbq5nri3qT48LgNFTDlq/ts2kAjJQORPZnp3Fq25B9ToPQt6DGGZLUG+YKGHCv73RNwUCx4Dj2oVJjNyWIYMA4EEJwcHhR+rUrHcAVJZ0SOOTJI1tJPzcasXy3d95XQgKpHSYcbXuUOtmql4oyU5ZP9QEiIscsWFS7fJs+r8Eit+H777vvc37zxjA3DM0LJ8QmB5VbkkGxYbi43dzzd3hOXz4Rvs6C08F3jDK20r+VpAqEDRo/OgBaBH4uhd+XynwVXUpKASHNaJirGGu1K8tpiX1+XOxAGqHyhZjBICeg/f8igqJs54af78AZPpvnoSQzkAhF5pDmvMINMPuJnM/ooK3O9SgJYEi4wMzu/vnAEajROE5t7d0QhSSollCx+IMpiz9XdSALZyRMNPaF2yLb3rw7gwXV7q67u/zPm79AR1GBrWbgxXei7gdA9z3TwgWdT91RfTRdSYZDsgenGCanrcpE+Wi+YEozIan9pC47xhBxzzIL9a3AUVllNIGc4qNfs9Al0M/r+kl+ndk+I2k6QFNr4aIjR/qsk52YjW/ZqmORbe2MoI4bIFS3FwlWRoYhJC78yLXOfghvl3xHJiq0Uir2vxmYdXYXfaY82g7ZtThaSqc63WZcD5CaV1Wy6jfqB1sHwuJsADE6BXPQKFfZ9t8tKE3b58rB47TFTmJb8TETgG/xK6pbaEo/Z7iWjFhJKTrcnnF4PynrJab6kw+pnU08u7/je9ZhDEf+jvK3XnqwC+A8XEktywihnrskQ7Eo9Wdmzuw9ujbY8EwQxIFK+TPpgQ8dv25aXPXspnPgiH+2lt19ok1oRIZTenv2KLXqE3wrvmXQIEbdAHFHXsTLj781/9iNdc8ta645V3ktqvz35s1c8Gr+ZbZIK5WRlrJ8TO1WcokSDK7H8hqY6CbT1QC3oFxr5pVPoqZzBMOR6g5MOPbR41XtcHlQopCKC6XeGAVd4dIuCx1CT4vqG+8RgOABxhrEeLmsHGFpBnwPtlVniZQixmOLSzQWUNoUDWMt2mwrWKb/VmzprnNmN++ybPqXhX8bD+k1NQDb7r5CwPqlzmCypXSNH9kVn0QvpqLT5elQ2295yzasW22c8mEPmSvNPM/rE/tqWJA6vAKbXOy1ktrG/TCbzGV2llAvqQqQPX8zGJrXEzKTYk+mHiIdMKpw1bWJhDUOAjdosi853Lbt2GuUjiVNMGJBXPcLLvmjjvv9oLcSYHBTuIfOkScLKKGUhabzHFPmdxgF1MB0zvVO22ooxhmhvCmq+dlag71bbP5RvTjHf50BzJZ5+ysGyM7FJm99BErHo2lTpHSKdSFF0nAlP9Z/Ybf2zTEunlz8RdmQgsq+0F+kwkxI7SqGTy0SAJbbgawNoNTptdyO33a41zprKd/3Wnp7kfoTOfmjVYdHPVFC1GywMER7ordLV3XpjrjX6R6JTd2eOZajcBCsEc+gzVqg/nR6t5y8jfS8NfzfdCMsRzEqz6vuy+M66zNIEocZiF9Tkm1r8MLwaUCE7QfEXexqkChAk9jaOzcojyOfAlXIxvVMn6yFF1gmmQtgudxsY7I/0ZjdSZlBgBFcPFT6OT+HTZ7cCAVF7J7GsGlVzwrUpqcQzSt9z3QrA0iTd4DUXgsWmFIgcdhWbPFlkaPKyZ+QXxrz2VYKCuzDWi3wzLaioFnHxLXZDt6Puo5mPiRTzSolu3fH4S31yVJ7E6e2n8zwUmnFiZ10TrrkO64b9B3TwLx1mLPap7F39DAnufj7XF4eKCdvGJEKVGc+SsyrElzKimsR4Zs9H/Jw+KOCWc/O9l8yFAc42EXUGWrq9L+B6NIaZ7hDY/sDHI748wyFPeUHhOa99BnR15Sr+IrXBG3tsXbyMgHv+gS66Nkmkllvwjpi5Q/7vJOrxrKyFS1KGl5+6N/PXj1Tn5SqWMN8Wj2mniEGD9zSaLy7DUCxmKYA9Dn3/8WQdY8yWmOyi+SFyrL6VgQ8sUQ5MNnVPhQevxB3ZQSTItofT0sE0Xv7yEYkc/T4HGVsvDRKz6RZwaZvZEg')); - - payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - - expect(payload).toEqual(expectedErrorPayload.error); - }); - - it('Should not provide errorInput if errorInput was empty', () => { - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }; - - amqp.sendError(new Error('Test error'), props, ''); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters[0]).toEqual(settings.PUBLISH_MESSAGES_TO); - expect(publishParameters[1]).toEqual('5559edd38968ec0736000003:step_1:1432205514864:error'); - - const payload = JSON.parse(publishParameters[2].toString()); - payload.error = encryptor.decryptMessageContent(payload.error); - - expect(payload).toEqual({ - error: { - name: 'Error', - message: 'Test error', - stack: jasmine.any(String) - } - // no errorInput should be here - }); - - expect(publishParameters[3]).toEqual(props); - }); - - it('Should not provide errorInput if errorInput was null', () => { - - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: 'task1234567890', - stepId: 'step_456' - } - }; - - amqp.sendError(new Error('Test error'), props, null); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - - expect(publishParameters[0]).toEqual(settings.PUBLISH_MESSAGES_TO); - expect(publishParameters[1]).toEqual('5559edd38968ec0736000003:step_1:1432205514864:error'); - - const payload = JSON.parse(publishParameters[2].toString()); - payload.error = encryptor.decryptMessageContent(payload.error); - - expect(payload).toEqual({ - error: { - name: 'Error', - message: 'Test error', - stack: jasmine.any(String) - } - // no errorInput should be here - }); - - expect(publishParameters[3]).toEqual(props); - }); - - it('Should send message to rebounds when rebound happened', () => { - - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'exec1234567890', - taskId: 'task1234567890', - stepId: 'step_1', - compId: 'comp1', - function: 'list', - start: '1432815685034' - } - }; - - amqp.sendRebound(new Error('Rebound error'), message, props); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - settings.REBOUND_ROUTING_KEY, - jasmine.any(Object), - { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - expiration: 15000, - headers: { - execId: 'exec1234567890', - taskId: 'task1234567890', - stepId: 'step_1', - compId: 'comp1', - function: 'list', - start: '1432815685034', - reboundIteration: 1 - } - } - ]); - - const payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - expect(payload).toEqual({ content: 'Message content' }); - }); - - it('Should send message to rebounds with reboundIteration=3', () => { - - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'exec1234567890', - taskId: 'task1234567890', - stepId: 'step_1', - compId: 'comp1', - function: 'list', - start: '1432815685034' - } - }; - - const clonedMessage = _.cloneDeep(message); - clonedMessage.properties.headers.reboundIteration = 2; - - amqp.sendRebound(new Error('Rebound error'), clonedMessage, props); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - settings.REBOUND_ROUTING_KEY, - jasmine.any(Object), - { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - expiration: 60000, - headers: { - execId: 'exec1234567890', - taskId: 'task1234567890', - stepId: 'step_1', - compId: 'comp1', - function: 'list', - start: '1432815685034', - reboundIteration: 3 - } - } - ]); - - const payload = encryptor.decryptMessageContent(publishParameters[2].toString()); - expect(payload).toEqual({ content: 'Message content' }); - }); - - it('Should send message to errors when rebound limit exceeded', () => { - - const amqp = new Amqp(settings); - amqp.publishChannel = jasmine.createSpyObj('publishChannel', ['publish']); - - const props = { - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'exec1234567890', - taskId: 'task1234567890', - stepId: 'step_1', - compId: 'comp1', - function: 'list', - start: '1432815685034' - } - }; - - const clonedMessage = _.cloneDeep(message); - clonedMessage.properties.headers.reboundIteration = 100; - - amqp.sendRebound(new Error('Rebound error'), clonedMessage, props); - - expect(amqp.publishChannel.publish).toHaveBeenCalled(); - expect(amqp.publishChannel.publish.callCount).toEqual(1); - - const publishParameters = amqp.publishChannel.publish.calls[0].args; - expect(publishParameters).toEqual([ - settings.PUBLISH_MESSAGES_TO, - settings.ERROR_ROUTING_KEY, - jasmine.any(Object), - props - ]); - - const payload = JSON.parse(publishParameters[2].toString()); - console.log(payload); - payload.error = encryptor.decryptMessageContent(payload.error); - payload.errorInput = encryptor.decryptMessageContent(payload.errorInput); - - expect(payload.error.message).toEqual('Rebound limit exceeded'); - expect(payload.errorInput).toEqual({ content: 'Message content' }); - }); - - - it('Should ack message when confirmed', () => { - - const amqp = new Amqp(); - amqp.subscribeChannel = jasmine.createSpyObj('subscribeChannel', ['ack']); - - amqp.ack(message); - - expect(amqp.subscribeChannel.ack).toHaveBeenCalled(); - expect(amqp.subscribeChannel.ack.callCount).toEqual(1); - expect(amqp.subscribeChannel.ack.calls[0].args[0]).toEqual(message); - }); - - it('Should reject message when ack is called with false', () => { - - const amqp = new Amqp(settings); - amqp.subscribeChannel = jasmine.createSpyObj('subscribeChannel', ['reject']); - amqp.reject(message); - - expect(amqp.subscribeChannel.reject).toHaveBeenCalled(); - expect(amqp.subscribeChannel.reject.callCount).toEqual(1); - expect(amqp.subscribeChannel.reject.calls[0].args[0]).toEqual(message); - expect(amqp.subscribeChannel.reject.calls[0].args[1]).toEqual(false); - }); - - it('Should listen queue and pass decrypted message to client function', () => { - - const amqp = new Amqp(settings); - const clientFunction = jasmine.createSpy('clientFunction'); - amqp.subscribeChannel = jasmine.createSpyObj('subscribeChannel', ['consume', 'prefetch']); - amqp.subscribeChannel.consume.andCallFake((queueName, callback) => { - callback(message); - }); - - runs(() => { - amqp.listenQueue('testQueue', clientFunction); - }); - - waitsFor(() => clientFunction.callCount > 0); - - runs(() => { - expect(amqp.subscribeChannel.prefetch).toHaveBeenCalledWith(1); - expect(clientFunction.callCount).toEqual(1); - expect(clientFunction.calls[0].args[0]).toEqual( - { - headers: { - reply_to: 'replyTo1234567890' - }, - content: 'Message content' - } - ); - expect(clientFunction.calls[0].args[1]).toEqual(message); - //eslint-disable-next-line max-len - expect(clientFunction.calls[0].args[1].content).toEqual(encryptor.encryptMessageContent({ content: 'Message content' })); - - expect(encryptor.decryptMessageContent).toHaveBeenCalledWith(message.content, message.properties.headers); - }); - }); - - it('Should disconnect from all channels and connection', () => { - - const amqp = new Amqp(settings); - amqp.subscribeChannel = jasmine.createSpyObj('subscribeChannel', ['close']); - amqp.publishChannel = jasmine.createSpyObj('subscribeChannel', ['close']); - amqp.amqp = jasmine.createSpyObj('amqp', ['close']); - - amqp.disconnect(); - - expect(amqp.subscribeChannel.close.callCount).toEqual(1); - expect(amqp.publishChannel.close.callCount).toEqual(1); - expect(amqp.amqp.close.callCount).toEqual(1); - }); - -}); diff --git a/spec/component/actions/flow_variables.js b/spec/component/actions/flow_variables.js new file mode 100644 index 00000000..ebe44ca0 --- /dev/null +++ b/spec/component/actions/flow_variables.js @@ -0,0 +1,4 @@ +exports.process = function processTrigger() { + this.emit('data', { body: this.getFlowVariables() }); + this.emit('end'); +}; diff --git a/spec/component/actions/httpReply.js b/spec/component/actions/httpReply.js index a2f0ceab..f41e8cc3 100644 --- a/spec/component/actions/httpReply.js +++ b/spec/component/actions/httpReply.js @@ -1,6 +1,4 @@ -exports.process = processAction; - -async function processAction(msg, cfg) { +exports.process = async function processAction() { await this.emit('httpReply', { statusCode: 200, body: 'Ok', @@ -13,4 +11,4 @@ async function processAction(msg, cfg) { body: {} }); await this.emit('end'); -} +}; diff --git a/spec/component/actions/update.js b/spec/component/actions/update.js index 2f64b150..e4ce4e0e 100644 --- a/spec/component/actions/update.js +++ b/spec/component/actions/update.js @@ -1,15 +1,6 @@ -const Q = require('q'); -const request = require('request'); +const request = require('request-promise-native'); -exports.process = processAction; -exports.getMetaModel = getMetaModel; -exports.getModel = getModel; -exports.getModelWithKeysUpdate = getModelWithKeysUpdate; -exports.promiseSelectModel = promiseSelectModel; -exports.promiseRequestSelectModel = promiseRequestSelectModel; -exports.promiseSelectModelRejected = promiseSelectModelRejected; - -function processAction(msg, cfg, snapshot) { +exports.process = function processAction(msg) { if (msg.snapshot) { this.emit('snapshot', msg.snapshot); } @@ -17,9 +8,9 @@ function processAction(msg, cfg, snapshot) { this.emit('updateSnapshot', msg.updateSnapshot); } this.emit('end'); -} +}; -function getMetaModel(cfg, cb) { +exports.getMetaModel = function getMetaModel(cfg, cb) { return cb(null, { in: { type: 'object', @@ -31,41 +22,40 @@ function getMetaModel(cfg, cb) { } } }); -} +}; -function getModel(cfg, cb) { +exports.getModel = function getModel(cfg, cb) { return cb(null, { de: 'Germany', us: 'USA', ua: 'Ukraine' }); -} +}; -function getModelWithKeysUpdate(cfg, cb) { +exports.getModelWithKeysUpdate = function getModelWithKeysUpdate(cfg, cb) { this.emit('updateKeys', { oauth: { access_token: 'newAccessToken' } }); return cb(null, { 0: 'Mr', 1: 'Mrs' }); -} +}; -function promiseSelectModel(cfg) { +exports.promiseSelectModel = function promiseSelectModel() { return Promise.resolve({ de: 'de_DE', at: 'de_AT' }); -} +}; -function promiseRequestSelectModel(cfg) { +exports.promiseRequestSelectModel = function promiseRequestSelectModel() { const options = { uri: 'http://promise_target_url:80/selectmodel', json: true }; - return Q.ninvoke(request, 'get', options) - .spread((req, body) => body); -} + return request.get(options); +}; -function promiseSelectModelRejected(cfg) { +exports.promiseSelectModelRejected = function promiseSelectModelRejected() { return Promise.reject(new Error('Ouch. This promise is rejected')); -} +}; diff --git a/spec/component/actions/update1.js b/spec/component/actions/update1.js index b5f33120..5304faee 100644 --- a/spec/component/actions/update1.js +++ b/spec/component/actions/update1.js @@ -1,9 +1,4 @@ -const Q = require('q'); -const request = require('request'); - -exports.getMetaModel = getMetaModel; - -function getMetaModel(cfg) { +exports.getMetaModel = function getMetaModel() { return Promise.resolve({ in: { type: 'object', @@ -15,4 +10,4 @@ function getMetaModel(cfg) { } } }); -} +}; diff --git a/spec/component/actions/update2.js b/spec/component/actions/update2.js index c35e41bd..590e3500 100644 --- a/spec/component/actions/update2.js +++ b/spec/component/actions/update2.js @@ -1,8 +1,3 @@ -const Q = require('q'); -const request = require('request'); - -exports.getMetaModel = getMetaModel; - -function getMetaModel(cfg) { +exports.getMetaModel = function getMetaModel() { return Promise.reject(new Error('Today no metamodels. Sorry!')); -} +}; diff --git a/spec/component/component.json b/spec/component/component.json index 68186d7a..d3c5b449 100644 --- a/spec/component/component.json +++ b/spec/component/component.json @@ -94,6 +94,10 @@ "http_reply": { "main": "./actions/httpReply.js", "title": "Http Reply" + }, + "use_flow_variables" : { + "main": "./actions/flow_variables.js", + "title": "Flow variables" } } } diff --git a/spec/component/triggers/data_trigger.js b/spec/component/triggers/data_trigger.js index 15b36338..a3f5b778 100644 --- a/spec/component/triggers/data_trigger.js +++ b/spec/component/triggers/data_trigger.js @@ -1,7 +1,4 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - that.emit('data', { items: [1,2,3,4,5,6] }); - that.emit('end'); -} +exports.process = function processTrigger() { + this.emit('data', { items: [1, 2, 3, 4, 5, 6] }); + this.emit('end'); +}; diff --git a/spec/component/triggers/datas_and_errors.js b/spec/component/triggers/datas_and_errors.js index 32fed327..6583930a 100644 --- a/spec/component/triggers/datas_and_errors.js +++ b/spec/component/triggers/datas_and_errors.js @@ -1,15 +1,10 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - - that.emit('data', { content: 'Data 1' }); - that.emit('error', new Error('Error 1')); +exports.process = function processTrigger() { + this.emit('data', { content: 'Data 1' }); + this.emit('error', new Error('Error 1')); setTimeout(() => { - that.emit('data', { content: 'Data 2' }); - that.emit('error', new Error('Error 2')); - that.emit('data', { content: 'Data 3' }); - //throw new Error('Error 4'); + this.emit('data', { content: 'Data 2' }); + this.emit('error', new Error('Error 2')); + this.emit('data', { content: 'Data 3' }); }, 1000); -} +}; diff --git a/spec/component/triggers/end_after_data_twice.js b/spec/component/triggers/end_after_data_twice.js index 446a766c..ae2db7c4 100644 --- a/spec/component/triggers/end_after_data_twice.js +++ b/spec/component/triggers/end_after_data_twice.js @@ -1,8 +1,5 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - that.emit('data', { items: [1,2,3,4,5,6] }); - that.emit('end'); - that.emit('end'); -} +exports.process = function processTrigger() { + this.emit('data', { items: [1, 2, 3, 4, 5, 6] }); + this.emit('end'); + this.emit('end'); +}; diff --git a/spec/component/triggers/end_after_error_twice.js b/spec/component/triggers/end_after_error_twice.js index 28b344fe..3eedffaa 100644 --- a/spec/component/triggers/end_after_error_twice.js +++ b/spec/component/triggers/end_after_error_twice.js @@ -1,8 +1,5 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - that.emit('error', new Error('Some error occurred!')); - that.emit('end'); - that.emit('end'); -} +exports.process = function processTrigger() { + this.emit('error', new Error('Some error occurred!')); + this.emit('end'); + this.emit('end'); +}; diff --git a/spec/component/triggers/error_trigger.js b/spec/component/triggers/error_trigger.js index 664a22ab..db9e41a6 100644 --- a/spec/component/triggers/error_trigger.js +++ b/spec/component/triggers/error_trigger.js @@ -1,8 +1,4 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - - that.emit('error', new Error('Some component error')); - that.emit('end'); -} +exports.process = function processTrigger() { + this.emit('error', new Error('Some component error')); + this.emit('end'); +}; diff --git a/spec/component/triggers/generator_request_trigger.js b/spec/component/triggers/generator_request_trigger.js index 6a3e5ce8..162ae102 100644 --- a/spec/component/triggers/generator_request_trigger.js +++ b/spec/component/triggers/generator_request_trigger.js @@ -1,22 +1,14 @@ -'use strict'; +const request = require('request-promise-native'); -const co = require('co'); -const Q = require('q'); -const request = require('request'); +exports.process = async function processTrigger() { + const options = { + uri: 'http://promise_target_url:80/foo/bar', + json: true + }; -exports.process = processTrigger; + const body = await request.get(options); -function processTrigger(msg, cfg) { - return co(function* gen() { - const options = { - uri: 'http://promise_target_url:80/foo/bar', - json: true - }; - - const [response, body] = yield Q.ninvoke(request, 'get', options); - - return { - body - }; - }); -} + return { + body + }; +}; diff --git a/spec/component/triggers/init_trigger.js b/spec/component/triggers/init_trigger.js index 3573c9ff..dc674d5a 100644 --- a/spec/component/triggers/init_trigger.js +++ b/spec/component/triggers/init_trigger.js @@ -1,16 +1,12 @@ -exports.init = initTrigger; -exports.process = processTrigger; - -function initTrigger(cfg) { +exports.init = function initTrigger() { return Promise.resolve({ subscriptionId: '_subscription_123' }); -} +}; -function processTrigger(msg, cfg) { - var that = this; - that.emit('data', { +exports.process = function processTrigger() { + this.emit('data', { body: {} }); - that.emit('end'); -} + this.emit('end'); +}; diff --git a/spec/component/triggers/init_trigger_returns_string.js b/spec/component/triggers/init_trigger_returns_string.js index 47e9092e..b57195db 100644 --- a/spec/component/triggers/init_trigger_returns_string.js +++ b/spec/component/triggers/init_trigger_returns_string.js @@ -1,14 +1,10 @@ -exports.init = initTrigger; -exports.process = processTrigger; - -function initTrigger(cfg) { +exports.init = function initTrigger() { return 'this_is_a_string'; -} +}; -function processTrigger(msg, cfg) { - var that = this; - that.emit('data', { +exports.process = function processTrigger() { + this.emit('data', { body: {} }); - that.emit('end'); -} + this.emit('end'); +}; diff --git a/spec/component/triggers/keys_trigger.js b/spec/component/triggers/keys_trigger.js index 5bc40030..c386ccec 100644 --- a/spec/component/triggers/keys_trigger.js +++ b/spec/component/triggers/keys_trigger.js @@ -1,7 +1,4 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - that.emit('updateKeys', { oauth: { access_token: 'newAccessToken' } }); - that.emit('end'); -} +exports.process = function processTrigger() { + this.emit('updateKeys', { oauth: { access_token: 'newAccessToken' } }); + this.emit('end'); +}; diff --git a/spec/component/triggers/passthrough.js b/spec/component/triggers/passthrough.js index 07718d2d..f30114ad 100644 --- a/spec/component/triggers/passthrough.js +++ b/spec/component/triggers/passthrough.js @@ -1,7 +1,4 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - that.emit('data', msg); - that.emit('end'); -} +exports.process = function processTrigger(msg) { + this.emit('data', msg); + this.emit('end'); +}; diff --git a/spec/component/triggers/promise_emitting_events.js b/spec/component/triggers/promise_emitting_events.js index c28cc5de..9f93b723 100644 --- a/spec/component/triggers/promise_emitting_events.js +++ b/spec/component/triggers/promise_emitting_events.js @@ -1,36 +1,29 @@ -const co = require('co'); -const Q = require('q'); -const request = require('request'); - -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - - return co(function* gen() { - const tokenOptions = { - uri: 'https://login.acme/oauth2/v2.0/token', - json: true, - body: { - client_id: 'admin', - client_secret: 'secret' - } - }; - - const [, newToken] = yield Q.ninvoke(request, 'post', tokenOptions); - - this.emit('updateKeys', { - oauth: newToken - }); - - const options = { - uri: 'https://login.acme/oauth2/v2.0/contacts', - json: true - }; - - const [, body] = yield Q.ninvoke(request, 'get', options); - - return { - body - }; - }.bind(this)); -} +const request = require('request-promise-native'); + +exports.process = async function processTrigger() { + const tokenOptions = { + uri: 'https://login.acme/oauth2/v2.0/token', + json: true, + body: { + client_id: 'admin', + client_secret: 'secret' + } + }; + + const newToken = await request.post(tokenOptions); + + this.emit('updateKeys', { + oauth: newToken + }); + + const options = { + uri: 'https://login.acme/oauth2/v2.0/contacts', + json: true + }; + + const body = await request.get(options); + + return { + body + }; +}; diff --git a/spec/component/triggers/promise_request_trigger.js b/spec/component/triggers/promise_request_trigger.js index c797ae9f..88c02f47 100644 --- a/spec/component/triggers/promise_request_trigger.js +++ b/spec/component/triggers/promise_request_trigger.js @@ -1,19 +1,13 @@ -'use strict'; +const request = require('request-promise-native'); -const Q = require('q'); -const request = require('request'); - -exports.process = processTrigger; - -function processTrigger(msg, cfg) { +exports.process = async function processTrigger() { const options = { uri: 'http://promise_target_url:80/foo/bar', json: true }; - return Q.ninvoke(request, 'get', options) - .spread((req, body) => body) - .then((data) => ({ - body: data - })); -} + const data = await request.get(options); + return { + body: data + }; +}; diff --git a/spec/component/triggers/promise_resolve_no_data.js b/spec/component/triggers/promise_resolve_no_data.js index 00918fa3..997a9879 100644 --- a/spec/component/triggers/promise_resolve_no_data.js +++ b/spec/component/triggers/promise_resolve_no_data.js @@ -1,5 +1,3 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { +exports.process = function processTrigger() { return Promise.resolve(); -} +}; diff --git a/spec/component/triggers/promise_trigger.js b/spec/component/triggers/promise_trigger.js index d0a60b2d..172adfef 100644 --- a/spec/component/triggers/promise_trigger.js +++ b/spec/component/triggers/promise_trigger.js @@ -1,7 +1,5 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { +exports.process = function processTrigger() { return Promise.resolve({ body: 'I am a simple promise' }); -} +}; diff --git a/spec/component/triggers/rebound_trigger.js b/spec/component/triggers/rebound_trigger.js index 9641a804..ca866a78 100644 --- a/spec/component/triggers/rebound_trigger.js +++ b/spec/component/triggers/rebound_trigger.js @@ -1,7 +1,4 @@ -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - var that = this; - that.emit('rebound', new Error('Rebound reason')); - that.emit('end'); -} +exports.process = function processTrigger() { + this.emit('rebound', new Error('Rebound reason')); + this.emit('end'); +}; diff --git a/spec/component/triggers/test_trigger.js b/spec/component/triggers/test_trigger.js index a545e6b1..1c753341 100644 --- a/spec/component/triggers/test_trigger.js +++ b/spec/component/triggers/test_trigger.js @@ -1,20 +1,15 @@ -exports.process = processTrigger; +exports.process = function processTrigger() { + this.emit('data', 'Data 1'); + this.emit('data', { content: 'Data 2' }); + this.emit('data'); -function processTrigger(msg, cfg) { - var that = this; + this.emit('error', 'Error 1'); + this.emit('error', new Error('Error 2')); + this.emit('error'); - that.emit('data', 'Data 1'); - that.emit('data', { content: 'Data 2' }); - that.emit('data'); + this.emit('rebound', 'Rebound Error 1'); + this.emit('rebound', new Error('Rebound Error 2')); + this.emit('rebound'); - - that.emit('error', 'Error 1'); - that.emit('error', new Error('Error 2')); - that.emit('error'); - - that.emit('rebound', 'Rebound Error 1'); - that.emit('rebound', new Error('Rebound Error 2')); - that.emit('rebound'); - - that.emit('end'); -} + this.emit('end'); +}; diff --git a/spec/component/triggers/trigger_with_wrong_dependency.js b/spec/component/triggers/trigger_with_wrong_dependency.js index 4a2ba319..b4860eb3 100644 --- a/spec/component/triggers/trigger_with_wrong_dependency.js +++ b/spec/component/triggers/trigger_with_wrong_dependency.js @@ -1,7 +1,5 @@ -var dep = require('../not-found-dependency'); +require('../not-found-dependency'); -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - //doesn't matter -} +exports.process = function processTrigger() { + // doesn't matter +}; diff --git a/spec/component2/verifyCredentials.js b/spec/component2/verifyCredentials.js index 22692331..4688f1c8 100644 --- a/spec/component2/verifyCredentials.js +++ b/spec/component2/verifyCredentials.js @@ -1,7 +1,5 @@ -module.exports = verify; - -function verify(credentials, cb) { +module.exports = function verify(credentials, cb) { cb(null, { verified: true }); -} +}; diff --git a/spec/component3/verifyCredentials.js b/spec/component3/verifyCredentials.js index f63e0cb4..10d1df28 100644 --- a/spec/component3/verifyCredentials.js +++ b/spec/component3/verifyCredentials.js @@ -1,7 +1,5 @@ -module.exports = verify; - -function verify(credentials, cb) { +module.exports = function verify(credentials, cb) { cb(null, { verified: false }); -} +}; diff --git a/spec/component4/verifyCredentials.js b/spec/component4/verifyCredentials.js index 8cd93920..f64c2da2 100644 --- a/spec/component4/verifyCredentials.js +++ b/spec/component4/verifyCredentials.js @@ -1,5 +1,3 @@ -module.exports = verify; - -function verify(credentials) { +module.exports = function verify() { return Promise.resolve(); -} +}; diff --git a/spec/component5/verifyCredentials.js b/spec/component5/verifyCredentials.js index 0f72d5d0..64141a2a 100644 --- a/spec/component5/verifyCredentials.js +++ b/spec/component5/verifyCredentials.js @@ -1,5 +1,3 @@ -module.exports = verify; - -function verify(credentials) { +module.exports = function verify() { return Promise.reject(new Error('Your API key is invalid')); -} +}; diff --git a/spec/component6/verifyCredentials.js b/spec/component6/verifyCredentials.js index 888e58cb..38224412 100644 --- a/spec/component6/verifyCredentials.js +++ b/spec/component6/verifyCredentials.js @@ -1,5 +1,3 @@ -module.exports = verify; - -function verify(credentials) { +module.exports = function verify() { throw new Error('Ouch. This occurred during verification.'); -} +}; diff --git a/spec/component_reader.spec.js b/spec/component_reader.spec.js deleted file mode 100644 index cccabe3e..00000000 --- a/spec/component_reader.spec.js +++ /dev/null @@ -1,138 +0,0 @@ -describe('Component reader', () => { - - var ComponentReader = require('../lib/component_reader.js').ComponentReader; - - it('Should find component located on the path', () => { - - var reader = new ComponentReader(); - var promise = reader.init('/spec/component/'); - - waitsFor(() => promise.isFulfilled() || promise.isRejected(), 10000); - - runs(() => { - expect(promise.isFulfilled()).toEqual(true); - expect(reader.componentJson.title).toEqual('Client component'); - }); - }); - - it('Should find component trigger', () => { - - var reader = new ComponentReader(); - var filename; - var error; - reader.init('/spec/component/').then(() => { - try { - filename = reader.findTriggerOrAction('passthrough'); - } catch (err) { - error = err; - } - }); - - waitsFor(() => filename || error, 10000); - - runs(() => { - expect(reader.componentJson.title).toEqual('Client component'); - expect(filename).toContain('triggers/passthrough.js'); - }); - }); - - it('Should return error if trigger not found', () => { - - var reader = new ComponentReader(); - var filename; - var error; - - reader.init('/spec/component/').then(() => { - try { - filename = reader.findTriggerOrAction('some-missing-component'); - } catch (err) { - error = err; - } - }); - - waitsFor(() => filename || error, 10000); - - runs(() => { - expect(error.message).toEqual('Trigger or action "some-missing-component" is not found in component.json!'); - }); - }); - - it('Should return appropriate error if trigger file is missing', () => { - - var reader = new ComponentReader(); - - var promise = reader.init('/spec/component/') - .then(() => reader.loadTriggerOrAction('missing_trigger')); - - waitsFor(() => promise.isFulfilled() || promise.isRejected(), 10000); - - runs(() => { - expect(promise.isRejected()).toEqual(true); - var err = promise.inspect().reason; - expect(err.message).toMatch( - //eslint-disable-next-line no-useless-escape - /Failed to load file \'.\/triggers\/missing_trigger.js\': Cannot find module.+missing_trigger\.js/ - ); - expect(err.code).toEqual('MODULE_NOT_FOUND'); - }); - }); - - it('Should return appropriate error if missing dependency is required by module', () => { - - var reader = new ComponentReader(); - - var promise = reader.init('/spec/component/') - .then(() => reader.loadTriggerOrAction('trigger_with_wrong_dependency')); - - waitsFor(() => promise.isFulfilled() || promise.isRejected(), 10000); - - runs(() => { - expect(promise.isRejected()).toEqual(true); - var err = promise.inspect().reason; - expect(err.message).toEqual( - 'Failed to load file \'./triggers/trigger_with_wrong_dependency.js\': ' - + 'Cannot find module \'../not-found-dependency\'' - ); - expect(err.code).toEqual('MODULE_NOT_FOUND'); - }); - }); - - it('Should return appropriate error if trigger file is presented, but contains syntax error', () => { - - var reader = new ComponentReader(); - - var promise = reader.init('/spec/component/') - .then(() => reader.loadTriggerOrAction('syntax_error_trigger')); - - waitsFor(() => promise.isFulfilled() || promise.isRejected(), 10000); - - runs(() => { - expect(promise.isRejected()).toEqual(true); - var err = promise.inspect().reason; - expect(err.message).toEqual( - "Trigger or action 'syntax_error_trigger' is found, but can not be loaded. " - + "Please check if the file './triggers/syntax_error_trigger.js' is correct." - ); - }); - }); - - it('Should return error if trigger not initialized', () => { - - var reader = new ComponentReader(); - var filename; - var error; - - try { - filename = reader.findTriggerOrAction('some-missing-component'); - } catch (err) { - error = err; - } - - waitsFor(() => filename || error, 10000); - - runs(() => { - expect(error.message).toEqual('Component.json was not loaded'); - }); - }); - -}); diff --git a/spec/config.js b/spec/config.js new file mode 100644 index 00000000..70b45565 --- /dev/null +++ b/spec/config.js @@ -0,0 +1,60 @@ +const _ = require('lodash'); +module.exports.prepareEnv = function prepareEnv(currentStep) { + const WORKSPACE_ID = String(Math.ceil(Math.random() * 10000000)); + const FLOW_ID = String(Math.ceil(Math.random() * 10000000)); + const STEP_ID = currentStep; + const config = { + /** ********************** SAILOR ITSELF CONFIGURATION ***********************************/ + + AMQP_URI: 'amqp://guest:guest@localhost:5672', + API_URI: 'http://apihost.com', + API_USERNAME: 'test@test.com', + API_KEY: '5559edd', + API_REQUEST_RETRY_DELAY: 100, + API_REQUEST_RETRY_ATTEMPTS: 3, + + FLOW_ID, + STEP_ID, + EXEC_ID: 'some-exec-id', + WORKSPACE_ID, + CONTAINER_ID: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + + USER_ID: '5559edd38968ec0736000002', + COMP_ID: '5559edd38968ec0736000456', + FUNCTION: 'list', + + TIMEOUT: 3000, + + /** ********************** COMMUNICATION LAYER SETTINGS ***********************************/ + MESSAGE_CRYPTO_PASSWORD: 'testCryptoPassword', + MESSAGE_CRYPTO_IV: 'iv=any16_symbols', + + LISTEN_MESSAGES_ON: `${WORKSPACE_ID}:${FLOW_ID}/ordinary:${STEP_ID}:messages`, + PUBLISH_MESSAGES_TO: `${WORKSPACE_ID}_org`, + DATA_ROUTING_KEY: `${WORKSPACE_ID}.${FLOW_ID}/ordinary.${STEP_ID}.message`, + ERROR_ROUTING_KEY: `${WORKSPACE_ID}.${FLOW_ID}/ordinary.${STEP_ID}.error`, + REBOUND_ROUTING_KEY: `${WORKSPACE_ID}.${FLOW_ID}/ordinary.${STEP_ID}.rebound`, + SNAPSHOT_ROUTING_KEY: `${WORKSPACE_ID}.${FLOW_ID}/ordinary.${STEP_ID}.snapshot`, + + DATA_RATE_LIMIT: 1000, + ERROR_RATE_LIMIT: 1000, + SNAPSHOT_RATE_LIMIT: 1000, + RATE_INTERVAL: 1000, + + REBOUND_INITIAL_EXPIRATION: 15000, + REBOUND_LIMIT: 5, + + RABBITMQ_PREFETCH_SAILOR: 1, + + /** ***************************** MINOR PARAMETERS ********************************/ + COMP_NAME: 'does_NOT_MATTER', + EXEC_TYPE: 'flow-step', + FLOW_VERSION: '12345', + TENANT_ID: 'tenant_id', + CONTRACT_ID: 'contract_id', + TASK_USER_EMAIL: 'user@email', + EXECUTION_RESULT_ID: '987654321', + COMPONENT_PATH: '/spec/integration/integration_component' + }; + return _.fromPairs(Object.entries(config).map(([k, v]) => ['ELASTICIO_' + k, v])); +}; diff --git a/spec/encryptor.spec.js b/spec/encryptor.spec.js deleted file mode 100644 index c3c02a1d..00000000 --- a/spec/encryptor.spec.js +++ /dev/null @@ -1,74 +0,0 @@ -describe('Cipher', () => { - - process.env.ELASTICIO_MESSAGE_CRYPTO_PASSWORD = 'testCryptoPassword'; - process.env.ELASTICIO_MESSAGE_CRYPTO_IV = 'iv=any16_symbols'; - - var cipher = require('../lib/encryptor.js'); - - beforeEach(() => { - spyOn(global, 'decodeURIComponent').andCallThrough(); - spyOn(global, 'encodeURIComponent').andCallThrough(); - }); - - it('should encrypt & decrypt strings', () => { - var content = 'B2B_L größere Firmenkunden 25% Rabatt'; - var result = cipher.encryptMessageContent(content); - var decryptedResult = cipher.decryptMessageContent(result); - expect(decryptedResult.toString()).toEqual(content.toString()); - expect(global.decodeURIComponent).not.toHaveBeenCalled(); - expect(global.encodeURIComponent).not.toHaveBeenCalled(); - }); - - it('should encrypt & decrypt objects', () => { - var content = { property1: 'Hello world' }; - var result = cipher.encryptMessageContent(content); - var decryptedResult = cipher.decryptMessageContent(result); - expect(decryptedResult).toEqual({ property1: 'Hello world' }); - }); - - it('should encrypt & decrypt buffer', () => { - var content = new Buffer('Hello world'); - var result = cipher.encryptMessageContent(content.toString()); - var decryptedResult = cipher.decryptMessageContent(result); - expect(decryptedResult).toEqual('Hello world'); - }); - - it('should encrypt & decrypt message with buffers', () => { - var content = { - property1: new Buffer('Hello world').toString() - }; - var result = cipher.encryptMessageContent(content); - var decryptedResult = cipher.decryptMessageContent(result); - expect(decryptedResult).toEqual({ property1: 'Hello world' }); - }); - - it('should throw error if failed to decrypt', () => { - var error; - try { - cipher.decryptMessageContent('dsdasdsad'); - } catch (err) { - error = err; - } - expect(error.message).toMatch('Failed to decrypt message'); - }); - - //eslint-disable-next-line mocha/no-skipped-tests - xit('should be compatible with Java-Sailor', () => { - - //eslint-disable-next-line max-len - var javaResult = 'wXTeSuonL1KvG7eKJ1Dk/hUHeLOhr7GMC1mGa7JyGQ9ZGg6AdjrKKn0ktoFMNVU77uB9dRd+tqqe0GNKlH8yuJrM2JWNdMbAWFHDLK5PvSRgL/negMTlmEnk/5/V5wharU8Qs9SW6rFI/E78Nkqlmqgwbd7ovHyzuOQIZj3kT4h6CW7S2fWJ559jpByhwXU1T8ZcGPOs4T+356AqYTXj8q2QgnkduKY7sNTrXNDsQUIZpm7tbBmMkoWuE6BXTitN/56TI2SVpo7TEQ/ef4c11fnrnCkpremZl4qPCCQcXD/47gMTSbSIydZCFQ584PE64pAwwn7UxloSen059tKKYF1BtGmBaqj97mHAL8izh3wsDoG8GuMRo2GhKopHnZTm'; - - var data = { - body: { - incomingProperty1: 'incomingValue1', - incomingProperty2: 'incomingValue2' - }, - attachments: { - incomingAttachment2: 'incomingAttachment2Content', - incomingAttachment1: 'incomingAttachment1Content' - } - }; - - expect(cipher.decryptMessageContent(javaResult)).toEqual(data); - }); -}); diff --git a/spec/executor.spec.js b/spec/executor.spec.js deleted file mode 100644 index 8245add9..00000000 --- a/spec/executor.spec.js +++ /dev/null @@ -1,434 +0,0 @@ -describe('Executor', () => { - - const nock = require('nock'); - - var TaskExec = require('../lib/executor.js').TaskExec; - var payload = { content: 'MessageContent' }; - var cfg = {}; - - it('Should execute passthrough trigger and emit all events - data, end', () => { - var taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/passthrough.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.calls[0].args[0]).toEqual('data'); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - - it('Should reject if module is missing', () => { - var taskexec = new TaskExec(); - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - runs(() => { - taskexec.process({}, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.calls[0].args[0]).toEqual('error'); - expect(taskexec.emit.calls[0].args[1].message).toEqual('Process function is not found'); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - - it('Should execute rebound_trigger and emit all events - rebound, end', () => { - var taskexec = new TaskExec(); - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/rebound_trigger.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.calls[0].args[0]).toEqual('rebound'); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - - it('Should execute complex trigger, and emit all 6 events', () => { - var taskexec = new TaskExec(); - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/datas_and_errors.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount >= 5); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(5); - expect(taskexec.emit.calls[0].args[0]).toEqual('data'); - expect(taskexec.emit.calls[0].args[1]).toEqual({ content: 'Data 1' }); - expect(taskexec.emit.calls[1].args[0]).toEqual('error'); - expect(taskexec.emit.calls[1].args[1].message).toEqual('Error 1'); - expect(taskexec.emit.calls[2].args[0]).toEqual('data'); - expect(taskexec.emit.calls[2].args[1]).toEqual({ content: 'Data 2' }); - expect(taskexec.emit.calls[3].args[0]).toEqual('error'); - expect(taskexec.emit.calls[3].args[1].message).toEqual('Error 2'); - expect(taskexec.emit.calls[4].args[0]).toEqual('data'); - expect(taskexec.emit.calls[4].args[1]).toEqual({ content: 'Data 3' }); - }); - }); - - it('Should execute test_trigger and emit all events - 3 data events, 3 errors, 3 rebounds, 1 end', () => { - var taskexec = new TaskExec(); - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/test_trigger.js'); - taskexec.process(module, payload, cfg); - - waitsFor(() => taskexec.emit.callCount >= 9, 5000); - - runs(() => { - - expect(taskexec.emit).toHaveBeenCalled(); - - var calls = taskexec.emit.calls; - - expect(calls[0].args).toEqual(['data', 'Data 1']); - expect(calls[1].args).toEqual(['data', { content: 'Data 2' }]); - expect(calls[2].args).toEqual(['data']); - - expect(calls[3].args).toEqual(['error', 'Error 1']); - expect(calls[4].args).toEqual(['error', {}]); - expect(calls[5].args).toEqual(['error']); - - expect(calls[6].args).toEqual(['rebound', 'Rebound Error 1']); - expect(calls[7].args).toEqual(['rebound', {}]); - expect(calls[8].args).toEqual(['rebound']); - - expect(calls[9].args[0]).toEqual('end'); - }); - }); - - - describe('Promises', () => { - - it('Should execute a Promise trigger and emit all events - data, end', () => { - var taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/promise_trigger.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(2); - expect(taskexec.emit.calls[0].args[0]).toEqual('data'); - expect(taskexec.emit.calls[0].args[1]).toEqual({ - body: 'I am a simple promise' - }); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - - it('Should execute a Promise.resolve() trigger and emit end', () => { - var taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/promise_resolve_no_data.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 0, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(1); - expect(taskexec.emit.calls[0].args[0]).toEqual('end'); - }); - }); - }); - - - describe('Request Promise', () => { - - beforeEach(() => { - nock('http://promise_target_url:80') - .get('/foo/bar') - .reply(200, { - message: 'Life is good with promises' - }); - }); - - it('Should execute a Promise trigger and emit all events - data, end', () => { - - - var taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/promise_request_trigger.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(2); - expect(taskexec.emit.calls[0].args[0]).toEqual('data'); - expect(taskexec.emit.calls[0].args[1]).toEqual({ - body: { - message: 'Life is good with promises' - } - }); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - }); - - describe('Request Generators', () => { - - it('Should execute a Promise trigger and emit all events - data, end', () => { - - nock('http://promise_target_url:80') - .get('/foo/bar') - .reply(200, { - message: 'Life is good with generators' - }); - - - var taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/generator_request_trigger.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(2); - expect(taskexec.emit.calls[0].args[0]).toEqual('data'); - expect(taskexec.emit.calls[0].args[1]).toEqual({ - body: { - message: 'Life is good with generators' - } - }); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - - it('Should execute a Promise trigger and emit all events - data, end', () => { - - nock('https://login.acme') - .post('/oauth2/v2.0/token', { - client_id: 'admin', - client_secret: 'secret' - }) - .reply(200, { - access_token: 'new_access_token' - }) - .get('/oauth2/v2.0/contacts') - .reply(200, { - result: [ - { - email: 'homer.simpson@acme.org' - }, - { - email: 'marge.simpson@acme.org' - } - ] - }); - - - var taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - var module = require('./component/triggers/promise_emitting_events.js'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1, 5000); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(3); - expect(taskexec.emit.calls[0].args[0]).toEqual('updateKeys'); - expect(taskexec.emit.calls[0].args[1]).toEqual({ - oauth: { - access_token: 'new_access_token' - } - }); - expect(taskexec.emit.calls[1].args[0]).toEqual('data'); - expect(taskexec.emit.calls[1].args[1]).toEqual({ - body: { - result: [ - { - email: 'homer.simpson@acme.org' - }, - { - email: 'marge.simpson@acme.org' - } - ] - } - }); - expect(taskexec.emit.calls[2].args[0]).toEqual('end'); - }); - }); - }); - - describe('async process function', () => { - it('should work', () => { - const taskexec = new TaskExec(); - - //eslint-disable-next-line no-empty-function - taskexec.on('error', () => {}); - spyOn(taskexec, 'emit').andCallThrough(); - - const module = require('./component/triggers/async_process_function'); - - runs(() => { - taskexec.process(module, payload, cfg); - }); - - waitsFor(() => taskexec.emit.callCount > 1); - - runs(() => { - expect(taskexec.emit).toHaveBeenCalled(); - expect(taskexec.emit.callCount).toEqual(2); - expect(taskexec.emit.calls[0].args[0]).toEqual('data'); - expect(taskexec.emit.calls[0].args[1]).toEqual({ - some: 'data' - }); - expect(taskexec.emit.calls[1].args[0]).toEqual('end'); - }); - }); - }); - - describe('executor logger', () => { - function TestStream() { - this.lastRecord = ''; - } - - TestStream.prototype.write = function write(record) { - this.lastRecord = record; - }; - - let taskExec; - let testStream; - - beforeEach(() => { - testStream = new TestStream(); - - taskExec = new TaskExec({ - loggerOptions: { - streams: [ - { - type: 'raw', - stream: testStream - } - ] - } - }); - }); - - it('should check if level is enabled', () => { - expect(taskExec.logger.info()).toBeTruthy(); - }); - - it('should implicitly convert first argument to string', () => { - taskExec.logger.info(undefined); - expect(testStream.lastRecord.msg).toEqual('undefined'); - - taskExec.logger.info(null); - expect(testStream.lastRecord.msg).toEqual('null'); - - taskExec.logger.info({}); - expect(testStream.lastRecord.msg).toEqual('[object Object]'); - }); - - it('should format log message', () => { - taskExec.logger.info('hello %s', 'world'); - - expect(testStream.lastRecord.msg).toEqual('hello world'); - }); - - it('should log extra fields', () => { - const testStream = new TestStream(); - - const taskExec = new TaskExec({ - loggerOptions: { - streams: [ - { - type: 'raw', - stream: testStream - } - ], - - threadId: 'threadId', - messageId: 'messageId', - parentMessageId: 'parentMessageId' - } - }); - - taskExec.logger.info('info'); - - expect(testStream.lastRecord.name).toEqual('component'); - expect(testStream.lastRecord.threadId).toEqual('threadId'); - expect(testStream.lastRecord.messageId).toEqual('messageId'); - expect(testStream.lastRecord.parentMessageId).toEqual('parentMessageId'); - expect(testStream.lastRecord.msg).toEqual('info'); - }); - }); -}); diff --git a/spec/integration/integration_component/actions/http_reply_action.js b/spec/integration/integration_component/actions/http_reply_action.js new file mode 100644 index 00000000..00658691 --- /dev/null +++ b/spec/integration/integration_component/actions/http_reply_action.js @@ -0,0 +1,10 @@ +exports.process = function processAction() { + this.emit('httpReply', { + statusCode: 200, + body: 'Ok', + headers: { + 'content-type': 'text/plain' + } + }); + this.emit('end'); +}; diff --git a/mocha_spec/integration_component/component.json b/spec/integration/integration_component/component.json similarity index 100% rename from mocha_spec/integration_component/component.json rename to spec/integration/integration_component/component.json diff --git a/mocha_spec/integration_component/triggers/async.js b/spec/integration/integration_component/triggers/async.js similarity index 58% rename from mocha_spec/integration_component/triggers/async.js rename to spec/integration/integration_component/triggers/async.js index 60c494e4..aab8c272 100644 --- a/mocha_spec/integration_component/triggers/async.js +++ b/spec/integration/integration_component/triggers/async.js @@ -1,11 +1,5 @@ -'use strict'; - -exports.process = process; - -async function process(msg, cfg, snapshot) { +exports.process = async function process() { for (let i = 0; i < 11; i++) { - console.log('Sending message %s', i); - //eslint-disable-next-line no-invalid-this await this.emit('data', { id: 'f45be600-f770-11e6-b42d-b187bfbf19fd', headers: { @@ -16,6 +10,5 @@ async function process(msg, cfg, snapshot) { hai: 'there' } }); - console.log('Message %s was sent', i); } -} +}; diff --git a/mocha_spec/integration_component/triggers/emit_data.js b/spec/integration/integration_component/triggers/emit_data.js similarity index 64% rename from mocha_spec/integration_component/triggers/emit_data.js rename to spec/integration/integration_component/triggers/emit_data.js index f0419d12..81f1c8be 100644 --- a/mocha_spec/integration_component/triggers/emit_data.js +++ b/spec/integration/integration_component/triggers/emit_data.js @@ -1,9 +1,4 @@ -'use strict'; - -exports.process = process; - -function process(msg, cfg, snapshot) { - //eslint-disable-next-line no-invalid-this +exports.process = function process() { this.emit('data', { id: 'f45be600-f770-11e6-b42d-b187bfbf19fd', headers: { @@ -14,4 +9,4 @@ function process(msg, cfg, snapshot) { hai: 'there' } }); -} +}; diff --git a/spec/integration/integration_component/triggers/fails_to_init.js b/spec/integration/integration_component/triggers/fails_to_init.js new file mode 100644 index 00000000..8fba4a62 --- /dev/null +++ b/spec/integration/integration_component/triggers/fails_to_init.js @@ -0,0 +1,3 @@ +exports.init = function init() { + throw new Error('OMG. I cannot init'); +}; diff --git a/mocha_spec/integration_component/triggers/init_trigger.js b/spec/integration/integration_component/triggers/init_trigger.js similarity index 88% rename from mocha_spec/integration_component/triggers/init_trigger.js rename to spec/integration/integration_component/triggers/init_trigger.js index e2628cc6..45b85b5d 100644 --- a/mocha_spec/integration_component/triggers/init_trigger.js +++ b/spec/integration/integration_component/triggers/init_trigger.js @@ -1,12 +1,5 @@ -'use strict'; - const rp = require('request-promise-native'); -exports.init = initTrigger; -exports.startup = startup; -exports.shutdown = shutdown; -exports.process = processTrigger; - const subscription = {}; function startup() { @@ -51,17 +44,14 @@ function initTrigger(cfg) { }); } -function processTrigger(msg, cfg) { - - //eslint-disable-next-line no-invalid-this - const that = this; +function processTrigger(msg) { const options = { uri: 'https://api.acme.com/customers', json: true }; rp.get(options).then((data) => { - that.emit('data', { + this.emit('data', { id: 'f45be600-f770-11e6-b42d-b187bfbf19fd', body: { originalMsg: msg, @@ -69,7 +59,11 @@ function processTrigger(msg, cfg) { subscription: subscription } }); - that.emit('end'); + this.emit('end'); }); - } + +exports.init = initTrigger; +exports.startup = startup; +exports.shutdown = shutdown; +exports.process = processTrigger; diff --git a/mocha_spec/integration_component/triggers/startup_with_empty_data.js b/spec/integration/integration_component/triggers/startup_with_empty_data.js similarity index 61% rename from mocha_spec/integration_component/triggers/startup_with_empty_data.js rename to spec/integration/integration_component/triggers/startup_with_empty_data.js index 37728270..0a2a23d6 100644 --- a/mocha_spec/integration_component/triggers/startup_with_empty_data.js +++ b/spec/integration/integration_component/triggers/startup_with_empty_data.js @@ -1,14 +1,8 @@ -'use strict'; - const rp = require('request-promise-native'); -exports.init = initTrigger; -exports.startup = startup; -exports.process = processTrigger; - const subscription = {}; -function startup() { +exports.startup = function startup() { const options = { uri: 'http://example.com/subscriptions/enable', json: true, @@ -19,25 +13,12 @@ function startup() { return rp.post(options) .then(() => - //returns empty data + // returns empty data null ); -} +}; -function shutdown(cfg, startupData) { - const options = { - uri: 'http://example.com/subscriptions/disable', - json: true, - body: { - cfg, - startupData - } - }; - - return rp.post(options); -} - -function initTrigger(cfg) { +exports.init = function initTrigger(cfg) { const options = { uri: 'https://api.acme.com/subscribe', json: true, @@ -51,19 +32,16 @@ function initTrigger(cfg) { subscription.id = body.id; subscription.cfg = cfg; }); -} +}; -function processTrigger(msg, cfg) { - - //eslint-disable-next-line no-invalid-this - const that = this; +exports.process = function processTrigger(msg) { const options = { uri: 'https://api.acme.com/customers', json: true }; rp.get(options).then((data) => { - that.emit('data', { + this.emit('data', { id: 'f45be600-f770-11e6-b42d-b187bfbf19fd', body: { originalMsg: msg, @@ -71,7 +49,6 @@ function processTrigger(msg, cfg) { subscription: subscription } }); - that.emit('end'); + this.emit('end'); }); - -} +}; diff --git a/mocha_spec/integration_component/triggers/trigger_with_no_hooks.js b/spec/integration/integration_component/triggers/trigger_with_no_hooks.js similarity index 62% rename from mocha_spec/integration_component/triggers/trigger_with_no_hooks.js rename to spec/integration/integration_component/triggers/trigger_with_no_hooks.js index 5d411bb5..9d95fa2d 100644 --- a/mocha_spec/integration_component/triggers/trigger_with_no_hooks.js +++ b/spec/integration/integration_component/triggers/trigger_with_no_hooks.js @@ -1,27 +1,19 @@ -'use strict'; - const rp = require('request-promise-native'); -exports.process = processTrigger; - -function processTrigger(msg, cfg) { - - //eslint-disable-next-line no-invalid-this - const that = this; +exports.process = function processTrigger(msg) { const options = { uri: 'https://api.acme.com/customers', json: true }; rp.get(options).then((data) => { - that.emit('data', { + this.emit('data', { id: 'f45be600-f770-11e6-b42d-b187bfbf19fd', body: { originalMsg: msg, customers: data } }); - that.emit('end'); + this.emit('end'); }); - -} +}; diff --git a/spec/integration/integration_helpers.js b/spec/integration/integration_helpers.js new file mode 100644 index 00000000..10d59e28 --- /dev/null +++ b/spec/integration/integration_helpers.js @@ -0,0 +1,200 @@ +const { EventEmitter } = require('events'); + +const amqplib = require('amqplib'); + +const nock = require('nock'); + +const encryptor = require('../../lib/encryptor.js'); +const cipher = require('../../lib/cipher.js'); + +const PREFIX = 'sailor_nodejs_integration_test'; +const { prepareEnv } = require('../config.js'); + +class AmqpHelper extends EventEmitter { + constructor(config) { + super(); + this._config = config; + + this._cryptoSettings = { + password: config.ELASTICIO_MESSAGE_CRYPTO_PASSWORD, + cryptoIV: config.ELASTICIO_MESSAGE_CRYPTO_IV + }; + + this.httpReplyQueueName = PREFIX + 'request_reply_queue'; + this.httpReplyQueueRoutingKey = PREFIX + 'request_reply_routing_key'; + this.nextStepQueue = PREFIX + '_next_step_queue'; + this.nextStepErrorQueue = PREFIX + '_next_step_queue_errors'; + + this.dataMessages = []; + this.errorMessages = []; + } + + publishMessage(message, { parentMessageId, threadId } = {}, headers = {}) { + const currentStepRoutingKey = [ + this._config.ELASTICIO_WORKSPACE_ID, + `${this._config.ELASTICIO_FLOW_ID}/ordinary`, + this._config.ELASTICIO_STEP_ID, + 'input' + ].join('.'); + return this.subscriptionChannel.publish( + this._config.ELASTICIO_PUBLISH_MESSAGES_TO, + currentStepRoutingKey, + Buffer.from(encryptor.encryptMessageContent(this._cryptoSettings, message)), + { + headers: Object.assign( + { + execId: this._config.ELASTICIO_EXEC_ID, + taskId: this._config.ELASTICIO_FLOW_ID, + workspaceId: this._config.ELASTICIO_WORKSPACE_ID, + userId: this._config.ELASTICIO_USER_ID, + threadId, + messageId: parentMessageId + }, + headers + ) + } + ); + } + + async _prepareQueues() { + this._amqpConn = await amqplib.connect(this._config.ELASTICIO_AMQP_URI); + const subscriptionChannel = await this._amqpConn.createChannel(); + const publishChannel = await this._amqpConn.createChannel(); + + await subscriptionChannel.assertQueue(this._config.ELASTICIO_LISTEN_MESSAGES_ON); + await publishChannel.assertQueue(this.nextStepQueue); + await publishChannel.assertQueue(this.nextStepErrorQueue); + await publishChannel.assertQueue(this.httpReplyQueueName); + + await publishChannel.purgeQueue(this.nextStepQueue); + await publishChannel.purgeQueue(this.nextStepErrorQueue); + await publishChannel.purgeQueue(this.httpReplyQueueName); + await publishChannel.purgeQueue(this._config.ELASTICIO_LISTEN_MESSAGES_ON); + + const exchangeOptions = { + durable: true, + autoDelete: false + }; + + await publishChannel.assertExchange(this._config.ELASTICIO_PUBLISH_MESSAGES_TO, 'direct', exchangeOptions); + + const currentStepRoutingKey = [ + this._config.ELASTICIO_WORKSPACE_ID, + `${this._config.ELASTICIO_FLOW_ID}/ordinary`, + this._config.ELASTICIO_STEP_ID, + 'input' + ].join('.'); + await subscriptionChannel.bindQueue( + this._config.ELASTICIO_LISTEN_MESSAGES_ON, + this._config.ELASTICIO_PUBLISH_MESSAGES_TO, + currentStepRoutingKey + ); + + await publishChannel.bindQueue( + this.nextStepQueue, + this._config.ELASTICIO_PUBLISH_MESSAGES_TO, + this._config.ELASTICIO_DATA_ROUTING_KEY); + + await publishChannel.bindQueue( + this.nextStepErrorQueue, + this._config.ELASTICIO_PUBLISH_MESSAGES_TO, + this._config.ELASTICIO_ERROR_ROUTING_KEY); + + await publishChannel.bindQueue( + this.httpReplyQueueName, + this._config.ELASTICIO_PUBLISH_MESSAGES_TO, + this.httpReplyQueueRoutingKey); + + this.subscriptionChannel = subscriptionChannel; + this.publishChannel = publishChannel; + } + + async cleanUp() { + this.removeAllListeners(); + await Promise.all([ + this.publishChannel.cancel('sailor_nodejs_1'), + this.publishChannel.cancel('sailor_nodejs_2'), + this.publishChannel.cancel('sailor_nodejs_3') + ]); + if (this._amqpConn) { + await this._amqpConn.close(); + this._amqpConn = null; + } + } + + async prepare() { + await this._prepareQueues(); + + await this.publishChannel.consume( + this.nextStepQueue, + this._consumer.bind(this, this.nextStepQueue), + { consumerTag: 'sailor_nodejs_1' } + ); + + await this.publishChannel.consume( + this.nextStepErrorQueue, + this._consumer.bind(this, this.nextStepErrorQueue), + { consumerTag: 'sailor_nodejs_2' } + ); + + await this.publishChannel.consume( + this.httpReplyQueueName, + this._consumer.bind(this, this.httpReplyQueueName), + { consumerTag: 'sailor_nodejs_3' } + ); + } + + _consumer(queue, message) { + this.publishChannel.ack(message); + let emittedMessage; + if (queue === this.nextStepErrorQueue) { + // Notice errors are encoded in slighlty other way then commond data messages + emittedMessage = { + error: cipher.decrypt(this._cryptoSettings, JSON.parse(message.content.toString()).error) + }; + } else { + emittedMessage = encryptor.decryptMessageContent(this._cryptoSettings, message.content.toString()); + } + + const data = { + properties: message.properties, + body: emittedMessage.body, + emittedMessage + }; + + this.dataMessages.push(data); + this.emit('data', data, queue); + } +} + +function mockApiTaskStepResponse(config, response) { + const defaultResponse = { + config: { + apiKey: 'secret' + }, + snapshot: { + lastModifiedDate: 123456789 + }, + tenant_id: config.ELASTICIO_TENANT_ID, + contract_id: config.ELASTICIO_CONTRACT_ID, + workspace_id: config.ELASTICIO_WORKSPACE_ID, + comp_id: config.ELASTICIO_COMP_ID, + comp_name: config.ELASTICIO_COMP_NAME, + function: config.ELASTICIO_FUNCTION, + flow_version: config.ELASTICIO_FLOW_VERSION, + flow_type: 'ordinary' + }; + nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .get(`/v1/tasks/${config.ELASTICIO_FLOW_ID}/steps/${config.ELASTICIO_STEP_ID}`) + .reply(200, Object.assign(defaultResponse, response)); +} + +exports.PREFIX = PREFIX; + +exports.amqp = function amqp(config) { + return new AmqpHelper(config); +}; + +exports.prepareEnv = prepareEnv; +exports.mockApiTaskStepResponse = mockApiTaskStepResponse; diff --git a/spec/integration/integration_tests_pack.js b/spec/integration/integration_tests_pack.js new file mode 100644 index 00000000..2c3a6066 --- /dev/null +++ b/spec/integration/integration_tests_pack.js @@ -0,0 +1,922 @@ +const nock = require('nock'); +const chai = require('chai'); +const sinon = require('sinon'); + +chai.use(require('sinon-chai')); +chai.use(require('chai-uuid')); +const { expect } = chai; + +const helpers = require('./integration_helpers'); +module.exports = (injectSailor, singleMode = true) => { + const customers = [ + { + name: 'Homer Simpson' + }, + { + name: 'Marge Simpson' + } + ]; + const inputMessage = { + headers: {}, + body: { + message: 'Just do it!' + } + }; + let config; + let amqpHelper; + let sandbox; + let app; + let oldEnv; + + beforeEach(async () => { + oldEnv = Object.assign({}, process.env); + sandbox = sinon.createSandbox(); + // NOTICE sailor is "self-wathing" and "self-maintaing" + // In case if it sees unandles exception it would exit. + // So prevent this behavior on integration tests, as this breaks + // mocha's error reporing (instead we have dead process); + sandbox.stub(process, 'exit'); + sandbox.stub(process, 'on'); + nock('https://api.acme.com') + .post('/subscribe') + .reply(200, { + id: 'subscription_12345' + }) + .get('/customers') + .reply(200, customers); + app = injectSailor(); + }); + + afterEach(async () => { + process.env = oldEnv; + + await amqpHelper.cleanUp(); + nock.cleanAll(); + await app.stop(); + sandbox.restore(); + }); + + describe('when sailor is being invoked for message processing', () => { + const parentMessageId = 'parent_message_1234567890'; + const threadId = helpers.PREFIX + '_thread_id_123456'; + const messageId = 'f45be600-f770-11e6-b42d-b187bfbf19fd'; + + it('should run trigger successfully', async () => { + config = helpers.prepareEnv('step_1'); + config.ELASTICIO_FUNCTION = 'init_trigger'; + Object.assign(process.env, config); + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + helpers.mockApiTaskStepResponse(config); + + const promise = new Promise(resolve => { + amqpHelper.on('data', ({ properties, body }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + if (singleMode) { + expect(properties.headers.containerId).to.equal(config.ELASTICIO_CONTAINER_ID); + } else { + // FIXME in sigle mode this should be config.ELASTICIO_CONTAINER_ID + expect(properties.headers.containerId).to.be.uuid('v4'); + } + delete properties.headers.containerId; + + expect(properties.headers).to.deep.equal({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + threadId, + parentMessageId: parentMessageId, + messageId + }); + + delete properties.headers; + + expect(properties).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + deliveryMode: undefined, + priority: undefined, + correlationId: undefined, + replyTo: undefined, + expiration: undefined, + messageId: undefined, + timestamp: undefined, + type: undefined, + userId: undefined, + appId: undefined, + clusterId: undefined + }); + expect(body).to.deep.equal({ + originalMsg: inputMessage, + customers: customers, + subscription: { + id: 'subscription_12345', + cfg: { + apiKey: 'secret' + } + } + }); + + resolve(); + }); + }); + + await app.start(); + amqpHelper.publishMessage(inputMessage, { + parentMessageId, + threadId + }); + await promise; + }); + it('should augment passthrough property with data', async () => { + config = helpers.prepareEnv('step_2'); + config.ELASTICIO_STEP_ID = 'step_2'; + config.ELASTICIO_FUNCTION = 'emit_data'; + config.ELASTICIO_TIMEOUT = 500; + Object.assign(process.env, config); + + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + + helpers.mockApiTaskStepResponse(config, { + is_passthrough: true + }); + + const psMsg = Object.assign(inputMessage, { + passthrough: { + step_1: { + id: '34', + body: {}, + attachments: {} + } + } + }); + + const promise = new Promise((resolve) => { + amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + expect(emittedMessage.passthrough).to.deep.eql({ + step_1: { + id: '34', + body: {}, + attachments: {} + }, + step_2: { + id: messageId, + headers: { + 'x-custom-component-header': '123_abc' + }, + body: { + id: 'someId', + hai: 'there' + } + } + }); + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + if (singleMode) { + expect(properties.headers.containerId).to.equal(config.ELASTICIO_CONTAINER_ID); + } else { + // FIXME in sigle mode this should be config.ELASTICIO_CONTAINER_ID + expect(properties.headers.containerId).to.be.uuid('v4'); + } + delete properties.headers.containerId; + + expect(properties.headers).to.deep.equal({ + taskId: config.ELASTICIO_FLOW_ID, + execId: config.ELASTICIO_EXEC_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + userId: config.ELASTICIO_USER_ID, + threadId, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + messageId, + parentMessageId + }); + + delete properties.headers; + + expect(properties).to.deep.eql({ + contentType: 'application/json', + contentEncoding: 'utf8', + deliveryMode: undefined, + priority: undefined, + correlationId: undefined, + replyTo: undefined, + expiration: undefined, + messageId: undefined, + timestamp: undefined, + type: undefined, + userId: undefined, + appId: undefined, + clusterId: undefined + }); + resolve(); + }); + }); + await app.start(); + amqpHelper.publishMessage(psMsg, { + parentMessageId, + threadId + }); + + await promise; + }); + + it('should work well with async process function emitting data', async () => { + config = helpers.prepareEnv('step_2'); + config.ELASTICIO_FUNCTION = 'async_trigger'; + config.ELASTICIO_DATA_RATE_LIMIT = '1'; + config.ELASTICIO_RATE_INTERVAL = '110'; + Object.assign(process.env, config); + + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + + helpers.mockApiTaskStepResponse(config, { + is_passthrough: true + }); + + const psMsg = Object.assign(inputMessage, { + passthrough: { + step_1: { + id: '34', + body: {}, + attachments: {} + } + } + }); + + amqpHelper.publishMessage(psMsg, { + parentMessageId, + threadId + }); + + let counter = 0; + const start = Date.now(); + const promise = new Promise(resolve => { + amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + expect(emittedMessage.passthrough).to.deep.eql({ + step_1: { + id: '34', + body: {}, + attachments: {} + }, + step_2: { + id: messageId, + headers: { + 'x-custom-component-header': '123_abc' + }, + body: { + id: 'someId', + hai: 'there' + } + } + }); + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + if (singleMode) { + expect(properties.headers.containerId).to.equal(config.ELASTICIO_CONTAINER_ID); + } else { + // FIXME in sigle mode this should be config.ELASTICIO_CONTAINER_ID + expect(properties.headers.containerId).to.be.uuid('v4'); + } + delete properties.headers.containerId; + + expect(properties.headers).to.deep.equal({ + taskId: config.ELASTICIO_FLOW_ID, + execId: config.ELASTICIO_EXEC_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + userId: config.ELASTICIO_USER_ID, + threadId, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + messageId, + parentMessageId + }); + + delete properties.headers; + + expect(properties).to.deep.eql({ + contentType: 'application/json', + contentEncoding: 'utf8', + deliveryMode: undefined, + priority: undefined, + correlationId: undefined, + replyTo: undefined, + expiration: undefined, + messageId: undefined, + timestamp: undefined, + type: undefined, + userId: undefined, + appId: undefined, + clusterId: undefined + }); + + counter++; + // We need 10 messages + if (counter > 10) { + const duration = Date.now() - start; + expect(duration > 1000).to.be.ok; + resolve(); + } + }); + }); + + await app.start(); + await promise; + }); + describe('when env ELASTICIO_STARTUP_REQUIRED is set', () => { + if (!singleMode) { + // FIXME won't work for CONTAINER as service + return; + } + beforeEach(() => { + config = helpers.prepareEnv('step_1'); + config.ELASTICIO_STARTUP_REQUIRED = '1'; + config.ELASTICIO_FUNCTION = 'init_trigger'; + Object.assign(process.env, config); + }); + + describe('when hooks data for the task is not created yet', () => { + it('should execute startup successfully', async () => { + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + let startupRegistrationRequest; + const startupRegistrationNock = nock('http://example.com/') + .post('/subscriptions/enable') + .reply(200, (uri, requestBody) => { + startupRegistrationRequest = requestBody; + return { + status: 'ok' + }; + }); + + helpers.mockApiTaskStepResponse(config); + + // sailor persists startup data via sailor-support API + let hooksDataRequest; + const hooksDataNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .post(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`, { + subscriptionResult: { + status: 'ok' + } + }) + .reply(201, (uri, requestBody) => { + hooksDataRequest = requestBody; + return requestBody; + }); + + // response for a subscription request, which performed inside of init method + nock('https://api.acme.com') + .post('/subscribe') + .reply(200, { + id: 'subscription_12345' + }) + .get('/customers') + .reply(200, customers); + + const promise = new Promise(resolve => + amqpHelper.on('data', ({ properties, body }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + expect(startupRegistrationRequest).to.deep.equal({ + data: 'startup' + }); + + expect(hooksDataRequest).to.deep.equal({ + subscriptionResult: { + status: 'ok' + } + }); + + expect(startupRegistrationNock.isDone()).to.be.ok; + expect(hooksDataNock.isDone()).to.be.ok; + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + + expect(properties.headers).to.eql({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + containerId: config.ELASTICIO_CONTAINER_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + messageId + }); + + expect(body).to.deep.equal({ + originalMsg: inputMessage, + customers: customers, + subscription: { + id: 'subscription_12345', + cfg: { + apiKey: 'secret' + } + } + }); + + resolve(); + }) + ); + + await app.start(); + amqpHelper.publishMessage(inputMessage); + await promise; + }); + }); + + describe('when hooks data already exists', () => { + it('should delete previous data and execute startup successfully', async () => { + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + let startupRegistrationRequest; + const startupRegistrationNock = nock('http://example.com/') + .post('/subscriptions/enable') + .reply(200, (uri, requestBody) => { + startupRegistrationRequest = requestBody; + return { + status: 'ok' + }; + }); + + helpers.mockApiTaskStepResponse(config); + + let hooksDataRequest1; + let hooksDataRequest2; + + // sailor persists startup data via sailor-support API + const hooksDataNock1 = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .post(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`, { + subscriptionResult: { + status: 'ok' + } + }) + .reply(409, (uri, requestBody) => { + hooksDataRequest1 = requestBody; + return { + error: 'Hooks data for the task already exist. Delete previous data first.', + status: 409, + title: 'ConflictError' + }; + }); + + // sailor removes data in order to resolve conflict + const hooksDataDeleteNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .delete(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`) + .reply(204); + + // sailor persists startup data via sailor-support API + const hooksDataNock2 = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .post(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`, { + subscriptionResult: { + status: 'ok' + } + }) + .reply(201, (uri, requestBody) => { + hooksDataRequest2 = requestBody; + return requestBody; + }); + + // response for a subscription request, which performed inside of init method + nock('https://api.acme.com') + .post('/subscribe') + .reply(200, { + id: 'subscription_12345' + }) + .get('/customers') + .reply(200, customers); + + const promise = new Promise(resolve => + amqpHelper.on('data', ({ properties, body }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + expect(startupRegistrationRequest).to.deep.equal({ + data: 'startup' + }); + + expect(startupRegistrationNock.isDone()).to.be.ok; + expect(hooksDataNock1.isDone()).to.be.ok; + expect(hooksDataNock2.isDone()).to.be.ok; + expect(hooksDataDeleteNock.isDone()).to.be.ok; + + expect(hooksDataRequest1).to.deep.equal({ + subscriptionResult: { + status: 'ok' + } + }); + + expect(hooksDataRequest2).to.deep.equal({ + subscriptionResult: { + status: 'ok' + } + }); + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + + expect(properties.headers).to.eql({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + containerId: config.ELASTICIO_CONTAINER_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + messageId + }); + + expect(body).to.deep.equal({ + originalMsg: inputMessage, + customers: customers, + subscription: { + id: 'subscription_12345', + cfg: { + apiKey: 'secret' + } + } + }); + resolve(); + }) + ); + + await app.start(); + + amqpHelper.publishMessage(inputMessage); + await promise; + }); + }); + + describe('when startup method returns empty data', () => { + it('should store an empty object as data and execute trigger successfully', async () => { + let startupRegistrationRequest; + + config.ELASTICIO_FUNCTION = 'startup_with_empty_data'; + Object.assign(process.env, config); + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + + const startupRegistrationNock = nock('http://example.com/') + .post('/subscriptions/enable') + .reply(200, (uri, requestBody) => { + startupRegistrationRequest = requestBody; + return { + status: 'ok' + }; + }); + + helpers.mockApiTaskStepResponse(config); + + // sailor persists startup data via sailor-support API + const hooksDataNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .post(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`, {}) + .reply(201); + + // sailor removes data in order to resolve conflict + const hooksDataDeleteNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .delete(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`) + .reply(400); + + // response for a subscription request, which performed inside of init method + nock('https://api.acme.com') + .post('/subscribe') + .reply(200, { + id: 'subscription_12345' + }) + .get('/customers') + .reply(200, customers); + + const promise = new Promise(resolve => + amqpHelper.on('data', ({ properties, body }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + expect(startupRegistrationRequest).to.deep.equal({ + data: 'startup' + }); + + expect(startupRegistrationNock.isDone()).to.be.ok; + expect(hooksDataNock.isDone()).to.be.ok; + expect(hooksDataDeleteNock.isDone()).to.not.be.ok; + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + + expect(properties.headers).to.eql({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + containerId: config.ELASTICIO_CONTAINER_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + messageId + }); + + expect(body).to.deep.equal({ + originalMsg: inputMessage, + customers: customers, + subscription: { + id: 'subscription_12345', + cfg: { + apiKey: 'secret' + } + } + }); + resolve(); + }) + ); + + await app.start(); + + amqpHelper.publishMessage(inputMessage); + await promise; + }); + }); + + describe('when startup method does not exist', () => { + it('should store an empty hooks data and run trigger successfully', async () => { + config.ELASTICIO_FUNCTION = 'trigger_with_no_hooks'; + Object.assign(process.env, config); + + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + helpers.mockApiTaskStepResponse(config); + + // sailor persists startup data via sailor-support API + const hooksDataNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .post(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`, {}) + .reply(201); + + // response for a subscription request, which performed inside of init method + nock('https://api.acme.com') + .get('/customers') + .reply(200, customers); + + const promise = new Promise( + resolve => amqpHelper.on('data', ({ properties, body }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepQueue); + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + + expect(properties.headers).to.eql({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + containerId: config.ELASTICIO_CONTAINER_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + messageId + }); + + expect(body).to.deep.equal({ + originalMsg: inputMessage, + customers: customers + }); + + expect(hooksDataNock.isDone()).to.be.ok; + + resolve(); + }) + ); + + await app.start(); + + amqpHelper.publishMessage(inputMessage); + await promise; + }); + }); + }); + + describe('when reply_to header is set', () => { + it('should send http reply successfully', async () => { + config = helpers.prepareEnv('step_1'); + config.ELASTICIO_FUNCTION = 'http_reply_action'; + Object.assign(process.env, config); + + helpers.mockApiTaskStepResponse(config); + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + + nock('https://api.acme.com') + .post('/subscribe') + .reply(200, { + id: 'subscription_12345' + }) + .get('/customers') + .reply(200, customers); + + const promise = new Promise(resolve => + amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { + expect(queueName).to.eql(amqpHelper.httpReplyQueueName); + + delete properties.headers.start; + delete properties.headers.end; + delete properties.headers.cid; + + if (singleMode) { + expect(properties.headers.containerId).to.equal(config.ELASTICIO_CONTAINER_ID); + } else { + // FIXME in sigle mode this should be config.ELASTICIO_CONTAINER_ID + expect(properties.headers.containerId).to.be.uuid('v4'); + } + delete properties.headers.containerId; + + expect(properties.headers.messageId).to.be.a('string'); + delete properties.headers.messageId; + + expect(properties.headers).to.eql({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION, + reply_to: amqpHelper.httpReplyQueueRoutingKey + }); + + expect(emittedMessage).to.eql({ + headers: { + 'content-type': 'text/plain' + }, + body: 'Ok', + statusCode: 200 + }); + + resolve(); + }) + ); + + await app.start(); + + amqpHelper.publishMessage(inputMessage, {}, { + reply_to: amqpHelper.httpReplyQueueRoutingKey + }); + await promise; + }); + }); + + describe('when sailor could not init the module', () => { + it('should publish init errors to RabbitMQ', async () => { + config = helpers.prepareEnv('step_1'); + // If component fails to start, then it calls process.exit + // and this crashes tests. Handle this. + config.ELASTICIO_FUNCTION = 'fails_to_init'; + Object.assign(process.env, config); + + helpers.mockApiTaskStepResponse(config); + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + + const promise = new Promise(resolve => + amqpHelper.on('data', ({ properties, emittedMessage }, queueName) => { + expect(queueName).to.eql(amqpHelper.nextStepErrorQueue); + + expect(JSON.parse(emittedMessage.error).message).to.equal('OMG. I cannot init'); + + if (singleMode) { + expect(properties.headers.containerId).to.equal(config.ELASTICIO_CONTAINER_ID); + } else { + // FIXME in sigle mode this should be config.ELASTICIO_CONTAINER_ID + expect(properties.headers.containerId).to.be.uuid('v4'); + } + delete properties.headers.containerId; + + expect(properties.headers).to.eql({ + execId: config.ELASTICIO_EXEC_ID, + taskId: config.ELASTICIO_FLOW_ID, + workspaceId: config.ELASTICIO_WORKSPACE_ID, + userId: config.ELASTICIO_USER_ID, + stepId: config.ELASTICIO_STEP_ID, + compId: config.ELASTICIO_COMP_ID, + function: config.ELASTICIO_FUNCTION + }); + + // FIXME this is different for multicluster and single + // signle sailor inits at start. Multisailor at first message + // Sailor should not kill itself + if (singleMode) { + expect(process.exit).to.have.been.calledOnce.and.calledWith(1); + } + resolve(); + }) + ); + await app.start(); + if (!singleMode) { + // FIXME this is different for multicluster and single + // signle sailor inits at start. Multisailor at first message + amqpHelper.publishMessage(inputMessage); + } + await promise; + }); + }); + }); + describe('when sailor is being invoked for shutdown', () => { + if (!singleMode) { + // FIXME can not work with "multi sailor mode" + return; + } + describe('when hooksdata is found', () => { + it('should execute shutdown successfully', async () => { + config = helpers.prepareEnv('step_1'); + config.ELASTICIO_HOOK_SHUTDOWN = '1'; + config.ELASTICIO_FUNCTION = 'init_trigger'; + Object.assign(process.env, config); + + amqpHelper = helpers.amqp(config); + await amqpHelper.prepare(); + + const subsriptionResponse = { + subId: '507' + }; + + let requestFromShutdownHook; + const requestFromShutdownNock = nock('http://example.com/') + .post('/subscriptions/disable') + .reply(200, (uri, requestBody) => { + requestFromShutdownHook = requestBody; + return { + status: 'ok' + }; + }); + + // sailor retrieves startup data via sailor-support API + const hooksDataGetNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .get(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`) + .reply(200, subsriptionResponse); + + // sailor removes startup data via sailor-support API + const hooksDataDeleteNock = nock(config.ELASTICIO_API_URI) + .matchHeader('Connection', 'Keep-Alive') + .delete(`/sailor-support/hooks/task/${config.ELASTICIO_FLOW_ID}/startup/data`) + .reply(204); + + helpers.mockApiTaskStepResponse(config); + const promise = new Promise(resolve => hooksDataDeleteNock.on('replied', () => setTimeout(() => { + expect(hooksDataGetNock.isDone()).to.be.ok; + + expect(requestFromShutdownHook).to.deep.equal({ + cfg: { + apiKey: 'secret' + }, + startupData: subsriptionResponse + }); + + expect(requestFromShutdownNock.isDone()).to.be.ok; + expect(hooksDataDeleteNock.isDone()).to.be.ok; + resolve(); + }, 50))); + + await app.start(); + await promise; + }); + }); + + describe('when request for hooksdata is failed with an error', () => { + // @todo + it('should not execute shutdown'); + }); + + describe('when shutdown hook method is not found', () => { + // @todo + it('should not thrown error and just finish process'); + }); + }); +}; diff --git a/spec/integration/run_as_service.spec.js b/spec/integration/run_as_service.spec.js new file mode 100644 index 00000000..bd297803 --- /dev/null +++ b/spec/integration/run_as_service.spec.js @@ -0,0 +1,5 @@ +const testPack = require('./integration_tests_pack.js'); +const MultiApp = require('../../lib/MultiApp.js'); +describe('Component as service integration tests', () => { + testPack(() => new MultiApp(), false); +}); diff --git a/spec/integration/run_as_single.spec.js b/spec/integration/run_as_single.spec.js new file mode 100644 index 00000000..c71a2472 --- /dev/null +++ b/spec/integration/run_as_single.spec.js @@ -0,0 +1,5 @@ +const testPack = require('./integration_tests_pack.js'); +const SingleApp = require('../../lib/SingleApp.js'); +describe('Single sailor mode integration tests', () => { + testPack(() => new SingleApp()); +}); diff --git a/spec/integration/service.spec.js b/spec/integration/service.spec.js new file mode 100644 index 00000000..60406c2c --- /dev/null +++ b/spec/integration/service.spec.js @@ -0,0 +1,380 @@ +const chai = require('chai'); +const { expect } = chai; +const nock = require('nock'); + +const service = require('../../lib/service'); +describe('Service', () => { + describe('execService', () => { + let oldEnv; + beforeEach(() => { + oldEnv = process.env; + }); + + afterEach(() => { + process.env = oldEnv; + }); + + function makeEnv(env) { + return Object.assign({ + ELASTICIO_API_URI: 'http://apihost.com', + ELASTICIO_CFG: '{}', + ELASTICIO_COMPONENT_PATH: '/spec/component', + ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456', + ELASTICIO_API_USERNAME: 'test@test.com', + ELASTICIO_API_KEY: '5559edd', + ELASTICIO_ACTION_OR_TRIGGER: 'do_smth', + ELASTICIO_GET_MODEL_METHOD: 'really get smth' + }, env); + } + + describe('error cases', () => { + beforeEach(() => { + nock('http://test.com:80') + .post('/123/456') + .reply(200, 'OK'); + }); + + it('should fail if no ELASTICIO_POST_RESULT_URL provided', async () => { + let caughtError; + try { + await service.processService('verifyCredentials', {}); + } catch (e) { + caughtError = e; + } + expect(caughtError).to.be.instanceof(Error); + expect(caughtError.message).to.equal('ELASTICIO_POST_RESULT_URL is missing'); + }); + + it('should throw an error when there is no such service method', async () => { + process.env = makeEnv({}); + const result = await service.processService('unknownMethod'); + expect(result.status).to.equal('error'); + expect(result.data.message).to.equal('Unknown service method "unknownMethod"'); + }); + + it('should send error response if no ELASTICIO_CFG provided', async () => { + process.env = makeEnv({ ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456' }); + delete process.env.ELASTICIO_CFG; + let caughtError; + try { + await service.processService('verifyCredentials'); + } catch (e) { + caughtError = e; + } + expect(caughtError).to.be.instanceof(Error); + expect(caughtError.message).to.equal('ELASTICIO_CFG is missing'); + }); + + it('should send error response if failed to parse ELASTICIO_CFG', async () => { + process.env = makeEnv({ + ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456', + ELASTICIO_CFG: 'test' + + }); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('error'); + expect(result.data.message).to.equal('Unable to parse CFG'); + }); + + it('should send error response if component is not found', async () => { + process.env = makeEnv({ + ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456', + ELASTICIO_CFG: '{"param1":"param2"}', + ELASTICIO_API_URI: 'http://example.com', + ELASTICIO_API_USERNAME: 'admin', + ELASTICIO_API_KEY: 'key' + }); + delete process.env.ELASTICIO_COMPONENT_PATH; + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('error'); + expect(result.data.message).to.include('Failed to load component.json'); + }); + + it('should throw an error when ELASTICIO_ACTION_OR_TRIGGER is not provided', async () => { + process.env = makeEnv({}); + delete process.env.ELASTICIO_ACTION_OR_TRIGGER; + let caughtError; + try { + await service.processService('getMetaModel'); + } catch (e) { + caughtError = e; + } + expect(caughtError).to.be.instanceof(Error); + expect(caughtError.message).to.equal('ELASTICIO_ACTION_OR_TRIGGER is missing'); + }); + + it('should throw an error when ELASTICIO_ACTION_OR_TRIGGER is not found', async () => { + process.env = makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'unknown' }); + const result = await service.processService('getMetaModel'); + expect(result.status).to.equal('error'); + expect(result.data.message).to.equal('Trigger or action "unknown" is not found in component.json!'); + }); + + it('should throw an error when ELASTICIO_GET_MODEL_METHOD is not provided', async () => { + process.env = makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update' }); + delete process.env.ELASTICIO_ACTION_OR_TRIGGER; + let caughtError; + try { + await service.processService('selectModel'); + } catch (e) { + caughtError = e; + } + expect(caughtError).to.be.instanceof(Error); + expect(caughtError.message).to.equal('ELASTICIO_ACTION_OR_TRIGGER is missing'); + }); + + it('should throw an error when ELASTICIO_GET_MODEL_METHOD is not found', async () => { + process.env = makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update', ELASTICIO_GET_MODEL_METHOD: 'unknown' }); + const result = await service.processService('selectModel'); + expect(result.status).to.equal('error'); + expect(result.data.message).to.equal('Method "unknown" is not found in "update" action or trigger'); + }); + }); + + describe('success cases', () => { + beforeEach(() => { + nock('http://test.com:80') + .post('/123/456') + .reply(200, 'OK'); + }); + + describe('verifyCredentials', () => { + it('should verify successfully when verifyCredentials.js is not available', async () => { + process.env = makeEnv({}); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ verified: true }); + }); + + it('should verify successfully when callback verified', async () => { + process.env = makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component2' }); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ verified: true }); + }); + + it('should NOT verify successfully when callback did not verify', async () => { + process.env = makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component3' }); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ verified: false }); + }); + + it('should verify successfully when promise resolves', async () => { + process.env = makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component4' }); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ verified: true }); + }); + + it('should NOT verify successfully when promise rejects', async () => { + process.env = makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component5' }); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + verified: false, + reason: 'Your API key is invalid' + }); + }); + + it('should NOT verify successfully when error thrown synchronously', async () => { + process.env = makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component6' }); + const result = await service.processService('verifyCredentials'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + verified: false, + reason: 'Ouch. This occurred during verification.' + }); + }); + }); + + describe('getMetaModel', () => { + it('should return callback based model successfully', async () => { + process.env = makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update' }); + const result = await service.processService('getMetaModel'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + in: { + type: 'object', + properties: { + name: { + type: 'string', + title: 'Name' + } + } + } + }); + }); + it('should return promise based model successfully', async () => { + process.env = makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update1' }); + const result = await service.processService('getMetaModel'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + in: { + type: 'object', + properties: { + email: { + type: 'string', + title: 'E-Mail' + } + } + } + }); + }); + it('should return error when promise rejects', async () => { + process.env = makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update2' }); + const result = await service.processService('getMetaModel'); + expect(result.status).to.equal('error'); + expect(result.data).to.deep.equal({ + message: 'Today no metamodels. Sorry!' + }); + }); + }); + + describe('selectModel', () => { + it('selectModel', async () => { + process.env = makeEnv({ + ELASTICIO_ACTION_OR_TRIGGER: 'update', + ELASTICIO_GET_MODEL_METHOD: 'getModel' + }); + const result = await service.processService('selectModel'); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + de: 'Germany', + us: 'USA', + ua: 'Ukraine' + }); + }); + + it('selectModel with updateKeys event', async () => { + process.env = makeEnv({ + ELASTICIO_ACTION_OR_TRIGGER: 'update', + ELASTICIO_GET_MODEL_METHOD: 'getModelWithKeysUpdate', + ELASTICIO_CFG: '{"_account":"1234567890"}', + ELASTICIO_API_URI: 'http://apihost.com', + ELASTICIO_API_USERNAME: 'test@test.com', + ELASTICIO_API_KEY: '5559edd' + }); + + const nockScope = nock('http://apihost.com:80') + .matchHeader('Connection', 'Keep-Alive') + .put('/v1/accounts/1234567890', { keys: { oauth: { access_token: 'newAccessToken' } } }) + .reply(200, 'Success'); + + const result = await service.processService('selectModel'); + expect(nockScope.isDone()).to.equal(true); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + 0: 'Mr', + 1: 'Mrs' + }); + }); + + it('selectModel with failed updateKeys event should return result anyway', async () => { + process.env = makeEnv({ + ELASTICIO_ACTION_OR_TRIGGER: 'update', + ELASTICIO_GET_MODEL_METHOD: 'getModelWithKeysUpdate', + ELASTICIO_CFG: '{"_account":"1234567890"}', + ELASTICIO_API_URI: 'http://apihost.com', + ELASTICIO_API_USERNAME: 'test@test.com', + ELASTICIO_API_KEY: '5559edd' + }); + + const nockScope = nock('http://apihost.com:80') + .matchHeader('Connection', 'Keep-Alive') + .put('/v1/accounts/1234567890', { keys: { oauth: { access_token: 'newAccessToken' } } }) + .reply(400, 'Success'); + + const result = await service.processService('selectModel'); + expect(nockScope.isDone()).to.equal(true); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + 0: 'Mr', + 1: 'Mrs' + }); + }); + + it('selectModel returns a promise that resolves successfully', async () => { + process.env = makeEnv({ + ELASTICIO_ACTION_OR_TRIGGER: 'update', + ELASTICIO_GET_MODEL_METHOD: 'promiseSelectModel', + ELASTICIO_CFG: '{"_account":"1234567890"}', + ELASTICIO_API_URI: 'http://apihost.com', + ELASTICIO_API_USERNAME: 'test@test.com', + ELASTICIO_API_KEY: '5559edd' + }); + + const result = await service.processService('selectModel'); + + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + de: 'de_DE', + at: 'de_AT' + }); + }); + + it('selectModel returns a promise that sends a request', async () => { + process.env = makeEnv({ + ELASTICIO_ACTION_OR_TRIGGER: 'update', + ELASTICIO_GET_MODEL_METHOD: 'promiseRequestSelectModel', + ELASTICIO_CFG: '{"_account":"1234567890"}', + ELASTICIO_API_URI: 'http://apihost.com', + ELASTICIO_API_USERNAME: 'test@test.com', + ELASTICIO_API_KEY: '5559edd' + }); + + const nockScope = nock('http://promise_target_url:80') + .get('/selectmodel') + .reply(200, { + a: 'x', + b: 'y' + }); + + const result = await service.processService('selectModel'); + expect(nockScope.isDone()).to.equal(true); + expect(result.status).to.equal('success'); + expect(result.data).to.deep.equal({ + a: 'x', + b: 'y' + }); + }); + + it('selectModel returns a promise that rejects', async () => { + process.env = makeEnv({ + ELASTICIO_ACTION_OR_TRIGGER: 'update', + ELASTICIO_GET_MODEL_METHOD: 'promiseSelectModelRejected', + ELASTICIO_CFG: '{"_account":"1234567890"}', + ELASTICIO_API_URI: 'http://apihost.com', + ELASTICIO_API_USERNAME: 'test@test.com', + ELASTICIO_API_KEY: '5559edd' + }); + + const result = await service.processService('selectModel'); + expect(result.status).to.equal('error'); + expect(result.data.message).to.equal('Ouch. This promise is rejected'); + }); + }); + }); + + describe('sending error', () => { + beforeEach(() => { + nock('http://test.com:80') + .post('/111/222') + .reply(404, 'Page not found'); + }); + + it('verifyCredentials', async () => { + process.env = makeEnv({ ELASTICIO_POST_RESULT_URL: 'http://test.com/111/222' }); + let caughtError; + try { + await service.processService('verifyCredentials'); + } catch (e) { + caughtError = e; + } + expect(caughtError).to.be.instanceof(Error); + expect(caughtError.message) + .to.equal('Failed to POST data to http://test.com/111/222 (404, Page not found)'); + }); + }); + }); +}); diff --git a/spec/sailor.spec.js b/spec/sailor.spec.js deleted file mode 100644 index e3c29e6d..00000000 --- a/spec/sailor.spec.js +++ /dev/null @@ -1,1052 +0,0 @@ -'use strict'; - -describe('Sailor', () => { - const envVars = {}; - - envVars.ELASTICIO_AMQP_URI = 'amqp://test2/test2'; - envVars.ELASTICIO_FLOW_ID = '5559edd38968ec0736000003'; - envVars.ELASTICIO_STEP_ID = 'step_1'; - envVars.ELASTICIO_EXEC_ID = 'some-exec-id'; - envVars.ELASTICIO_WORKSPACE_ID = '5559edd38968ec073600683'; - envVars.ELASTICIO_CONTAINER_ID = 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948'; - - envVars.ELASTICIO_USER_ID = '5559edd38968ec0736000002'; - envVars.ELASTICIO_COMP_ID = '5559edd38968ec0736000456'; - envVars.ELASTICIO_FUNCTION = 'list'; - - envVars.ELASTICIO_LISTEN_MESSAGES_ON = '5559edd38968ec0736000003:step_1:1432205514864:messages'; - envVars.ELASTICIO_PUBLISH_MESSAGES_TO = 'userexchange:5527f0ea43238e5d5f000001'; - envVars.ELASTICIO_DATA_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:message'; - envVars.ELASTICIO_ERROR_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:error'; - envVars.ELASTICIO_REBOUND_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:rebound'; - envVars.ELASTICIO_SNAPSHOT_ROUTING_KEY = '5559edd38968ec0736000003:step_1:1432205514864:snapshot'; - - envVars.ELASTICIO_COMPONENT_PATH = '/spec/component'; - envVars.ELASTICIO_DEBUG = 'sailor'; - - envVars.ELASTICIO_API_URI = 'http://apihost.com'; - envVars.ELASTICIO_API_USERNAME = 'test@test.com'; - envVars.ELASTICIO_API_KEY = '5559edd'; - - process.env.ELASTICIO_TIMEOUT = 3000; - - const amqp = require('../lib/amqp.js'); - let settings; - const encryptor = require('../lib/encryptor.js'); - const Sailor = require('../lib/sailor.js').Sailor; - const _ = require('lodash'); - const Q = require('q'); - - const payload = { param1: 'Value1' }; - - const message = { - fields: { - consumerTag: 'abcde', - deliveryTag: 12345, - exchange: 'test', - routingKey: 'test.hello' - }, - properties: { - contentType: 'application/json', - contentEncoding: 'utf8', - headers: { - taskId: '5559edd38968ec0736000003', - execId: 'some-exec-id', - userId: '5559edd38968ec0736000002', - workspaceId: '5559edd38968ec073600683' - }, - deliveryMode: undefined, - priority: undefined, - correlationId: undefined, - replyTo: undefined, - expiration: undefined, - messageId: undefined, - timestamp: undefined, - type: undefined, - userId: undefined, - appId: undefined, - mandatory: true, - clusterId: '' - }, - content: new Buffer(encryptor.encryptMessageContent(payload)) - }; - - beforeEach(() => { - settings = require('../lib/settings').readFrom(envVars); - }); - - describe('init', () => { - it('should init properly if developer returned a plain string in init', done => { - settings.FUNCTION = 'init_trigger_returns_string'; - - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Promise.resolve({ - config: { - _account: '1234567890' - } - }); - }); - - sailor.prepare() - .then(() => sailor.init({})) - .then((result) =>{ - expect(result).toEqual('this_is_a_string'); - done(); - }) - .catch(done); - }); - - it('should init properly if developer returned a promise', done => { - settings.FUNCTION = 'init_trigger'; - - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Promise.resolve({ - config: { - _account: '1234567890' - } - }); - }); - - sailor.prepare() - .then(() => sailor.init({})) - .then((result) =>{ - expect(result).toEqual({ - subscriptionId: '_subscription_123' - }); - done(); - }) - .catch(done); - }); - }); - - describe('prepare', () => { - let sailor; - - beforeEach(() => { - sailor = new Sailor(settings); - }); - - describe('when step data retrieved', () => { - let stepData; - - beforeEach(() => { - stepData = { - snapshot: {} - }; - }); - - describe(`when step data retreived`, () => { - beforeEach(() => { - spyOn(sailor.componentReader, 'init').andReturn(Promise.resolve()); - spyOn(sailor.apiClient.tasks, 'retrieveStep').andReturn(Promise.resolve(stepData)); - }); - - it('should init component', done => { - sailor.prepare() - .then(() => { - expect(sailor.stepData).toEqual(stepData); - expect(sailor.snapshot).toEqual(stepData.snapshot); - expect(sailor.apiClient.tasks.retrieveStep) - .toHaveBeenCalledWith(settings.FLOW_ID, settings.STEP_ID); - expect(sailor.componentReader.init).toHaveBeenCalledWith(settings.COMPONENT_PATH); - done(); - }) - .catch(done); - }); - }); - }); - - describe('when step data is not retrieved', () => { - beforeEach(() => { - spyOn(sailor.apiClient.tasks, 'retrieveStep') - .andReturn(Promise.reject(new Error('failed'))); - }); - - it('should fail', done => { - sailor.prepare() - .then(() => { - throw new Error('Error is expected'); - }) - .catch(err => { - expect(err.message).toEqual('failed'); - done(); - }); - }); - }); - }); - - describe('disconnection', () => { - it('should disconnect Mongo and RabbitMQ, and exit process', done => { - const fakeAMQPConnection = jasmine.createSpyObj('AMQPConnection', ['disconnect']); - fakeAMQPConnection.disconnect.andReturn(Q.resolve()); - - spyOn(amqp, 'Amqp').andReturn(fakeAMQPConnection); - spyOn(process, 'exit').andReturn(0); - - const sailor = new Sailor(settings); - - sailor.disconnect() - .then(() => { - expect(fakeAMQPConnection.disconnect).toHaveBeenCalled(); - done(); - }) - .catch(done); - }); - }); - - describe('processMessage', () => { - let fakeAMQPConnection; - - beforeEach(() => { - fakeAMQPConnection = jasmine.createSpyObj('AMQPConnection', [ - 'connect','sendData','sendError','sendRebound','ack','reject', - 'sendSnapshot', 'sendHttpReply' - ]); - - spyOn(amqp, 'Amqp').andReturn(fakeAMQPConnection); - }); - - it('should call sendData() and ack() if success', done => { - settings.FUNCTION = 'data_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.connect() - .then(() => sailor.prepare()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendData).toHaveBeenCalled(); - - const sendDataCalls = fakeAMQPConnection.sendData.calls; - - expect(sendDataCalls[0].args[0]).toEqual({ items: [1,2,3,4,5,6] }); - expect(sendDataCalls[0].args[1]).toEqual(jasmine.any(Object)); - - expect(sendDataCalls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'some-exec-id', - taskId: '5559edd38968ec0736000003', - userId: '5559edd38968ec0736000002', - workspaceId: '5559edd38968ec073600683', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'data_trigger', - start: jasmine.any(Number), - cid: 1, - end: jasmine.any(Number), - messageId: jasmine.any(String) - } - }); - - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); //todo: use done.fail after migration to Jasmine 2.x - }); - - it('should call sendData() and ack() only once', done => { - settings.FUNCTION = 'end_after_data_twice'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.connect() - .then(() => sailor.prepare()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendData).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendData.callCount).toEqual(1); - - expect(fakeAMQPConnection.reject).not.toHaveBeenCalled(); - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - done(); - }) - .catch(done); //todo: use done.fail after migration to Jasmine 2.x - }); - - it('should augment emitted message with passthrough data', done => { - settings.FUNCTION = 'passthrough'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({ is_passthrough: true }); - }); - - const psPayload = { - body: payload, - passthrough: { - step_0: { - body: { key: 'value' } - } - } - }; - - sailor.connect() - .then(() => sailor.prepare()) - .then(() => sailor.processMessage(psPayload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendData).toHaveBeenCalled(); - - const sendDataCalls = fakeAMQPConnection.sendData.calls; - - expect(sendDataCalls[0].args[0]).toEqual({ - body: { - param1: 'Value1' - }, - passthrough: { - step_0: { - body: { - key: 'value' - } - }, - step_1: { - body: { param1: 'Value1' } - } - } - }); - expect(sendDataCalls[0].args[1]).toEqual(jasmine.any(Object)); - expect(sendDataCalls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'some-exec-id', - taskId: '5559edd38968ec0736000003', - userId: '5559edd38968ec0736000002', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - workspaceId: '5559edd38968ec073600683', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'passthrough', - start: jasmine.any(Number), - cid: 1, - end: jasmine.any(Number), - messageId: jasmine.any(String) - } - }); - - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); //todo: use done.fail after migration to Jasmine 2.x - }); - - it('should send request to API server to update keys', done => { - settings.FUNCTION = 'keys_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({ - config: { - _account: '1234567890' - } - }); - }); - - spyOn(sailor.apiClient.accounts, 'update').andCallFake((accountId, keys) => { - expect(accountId).toEqual('1234567890'); - expect(keys).toEqual({ keys: { oauth: { access_token: 'newAccessToken' } } }); - return Q(); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - expect(sailor.apiClient.accounts.update).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); //todo: use done.fail after migration to Jasmine 2.x - }); - - it('should emit error if failed to update keys', done => { - settings.FUNCTION = 'keys_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Promise.resolve({ - config: { - _account: '1234567890' - } - }); - }); - - spyOn(sailor.apiClient.accounts, 'update').andCallFake((accountId, keys) => { - expect(accountId).toEqual('1234567890'); - expect(keys).toEqual({ keys: { oauth: { access_token: 'newAccessToken' } } }); - return Promise.reject(new Error('Update keys error')); - }); - - async function test() { - await sailor.prepare(); - await sailor.connect(); - await sailor.processMessage(payload, message); - // It will not throw an error because component - // process method is not `async` - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - expect(sailor.apiClient.accounts.update).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendError).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendError.calls[0].args[0].message).toEqual('Update keys error'); - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - } - test().then(done,done); - }); - - it('should call sendRebound() and ack()', done => { - settings.FUNCTION = 'rebound_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendRebound).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendRebound.calls[0].args[0].message).toEqual('Rebound reason'); - expect(fakeAMQPConnection.sendRebound.calls[0].args[1]).toEqual(message); - - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); - }); - - it('should call sendSnapshot() and ack() after a `snapshot` event', done => { - settings.FUNCTION = 'update'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => { - const payload = { - snapshot: { blabla: 'blablabla' } - }; - return sailor.processMessage(payload, message); - }) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - const expectedSnapshot = { blabla: 'blablabla' }; - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendSnapshot.callCount).toBe(1); - expect(fakeAMQPConnection.sendSnapshot.calls[0].args[0]).toEqual(expectedSnapshot); - expect(fakeAMQPConnection.sendSnapshot.calls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: '5559edd38968ec0736000003', - execId: 'some-exec-id', - userId: '5559edd38968ec0736000002', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - workspaceId: '5559edd38968ec073600683', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'update', - start: jasmine.any(Number), - cid: 1, - snapshotEvent: 'snapshot', - messageId: jasmine.any(String) - } - }); - expect(sailor.snapshot).toEqual(expectedSnapshot); - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); - }); - - it('should call sendSnapshot() and ack() after an `updateSnapshot` event', done => { - settings.FUNCTION = 'update'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({ - snapshot: { - someId: 'someData' - } - }); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => { - const payload = { - updateSnapshot: { updated: 'value' } - }; - return sailor.processMessage(payload, message); - }) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - const expectedSnapshot = { someId: 'someData', updated: 'value' }; - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendSnapshot.callCount).toBe(1); - expect(fakeAMQPConnection.sendSnapshot.calls[0].args[0]).toEqual({ updated: 'value' }); - expect(fakeAMQPConnection.sendSnapshot.calls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - taskId: '5559edd38968ec0736000003', - execId: 'some-exec-id', - userId: '5559edd38968ec0736000002', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - workspaceId: '5559edd38968ec073600683', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'update', - start: jasmine.any(Number), - cid: 1, - snapshotEvent: 'updateSnapshot', - messageId: jasmine.any(String) - } - }); - expect(sailor.snapshot).toEqual(expectedSnapshot); - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); - }); - - it('should send error if error happened', done => { - settings.FUNCTION = 'error_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendError).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendError.calls[0].args[0].message).toEqual('Some component error'); - expect(fakeAMQPConnection.sendError.calls[0].args[0].stack).not.toBeUndefined(); - expect(fakeAMQPConnection.sendError.calls[0].args[2]).toEqual(message.content); - - expect(fakeAMQPConnection.reject).toHaveBeenCalled(); - expect(fakeAMQPConnection.reject.callCount).toEqual(1); - expect(fakeAMQPConnection.reject.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); - }); - - it('should send error and reject only once()', done => { - settings.FUNCTION = 'end_after_error_twice'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendError).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendError.callCount).toEqual(1); - - expect(fakeAMQPConnection.ack).not.toHaveBeenCalled(); - expect(fakeAMQPConnection.reject).toHaveBeenCalled(); - expect(fakeAMQPConnection.reject.callCount).toEqual(1); - done(); - }) - .catch(done); - }); - - it('should reject message if trigger is missing', done => { - settings.FUNCTION = 'missing_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - expect(fakeAMQPConnection.sendError).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendError.calls[0].args[0].message).toMatch( - /* eslint-disable */ - /Failed to load file \'.\/triggers\/missing_trigger.js\': Cannot find module.+missing_trigger\.js/ - /* eslint-enable */ - ); - expect(fakeAMQPConnection.sendError.calls[0].args[0].stack).not.toBeUndefined(); - expect(fakeAMQPConnection.sendError.calls[0].args[2]).toEqual(message.content); - - expect(fakeAMQPConnection.reject).toHaveBeenCalled(); - expect(fakeAMQPConnection.reject.callCount).toEqual(1); - expect(fakeAMQPConnection.reject.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); - }); - - it('should not process message if taskId in header is not equal to task._id', done => { - const message2 = _.cloneDeep(message); - message2.properties.headers.taskId = 'othertaskid'; - - settings.FUNCTION = 'error_trigger'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message2)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - expect(fakeAMQPConnection.reject).toHaveBeenCalled(); - done(); - }) - .catch(done); - }); - - it('should catch all data calls and all error calls', done => { - settings.FUNCTION = 'datas_and_errors'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => { - expect(taskId).toEqual('5559edd38968ec0736000003'); - expect(stepId).toEqual('step_1'); - return Q({}); - }); - - sailor.prepare() - .then(() => sailor.connect()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep).toHaveBeenCalled(); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - - // data - expect(fakeAMQPConnection.sendData).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendData.calls.length).toEqual(3); - - // error - expect(fakeAMQPConnection.sendError).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendError.calls.length).toEqual(2); - - // ack - expect(fakeAMQPConnection.reject).toHaveBeenCalled(); - expect(fakeAMQPConnection.reject.callCount).toEqual(1); - expect(fakeAMQPConnection.reject.calls[0].args[0]).toEqual(message); - done(); - }) - .catch(done); - }); - - it('should handle errors in httpReply properly', done => { - settings.FUNCTION = 'http_reply'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => Promise.resolve({})); - - sailor.connect() - .then(() => sailor.prepare()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep) - .toHaveBeenCalledWith('5559edd38968ec0736000003', 'step_1'); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendHttpReply).toHaveBeenCalled(); - - const sendHttpReplyCalls = fakeAMQPConnection.sendHttpReply.calls; - - expect(sendHttpReplyCalls[0].args[0]).toEqual({ - statusCode: 200, - body: 'Ok', - headers: { - 'content-type': 'text/plain' - } - }); - expect(sendHttpReplyCalls[0].args[1]).toEqual(jasmine.any(Object)); - expect(sendHttpReplyCalls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'some-exec-id', - taskId: '5559edd38968ec0736000003', - userId: '5559edd38968ec0736000002', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - workspaceId: '5559edd38968ec073600683', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'http_reply', - start: jasmine.any(Number), - cid: 1, - messageId: jasmine.any(String) - } - }); - - expect(fakeAMQPConnection.sendData).toHaveBeenCalled(); - - const sendDataCalls = fakeAMQPConnection.sendData.calls; - - expect(sendDataCalls[0].args[0]).toEqual({ - body: {} - }); - expect(sendDataCalls[0].args[1]).toEqual(jasmine.any(Object)); - expect(sendDataCalls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'some-exec-id', - taskId: '5559edd38968ec0736000003', - userId: '5559edd38968ec0736000002', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - workspaceId: '5559edd38968ec073600683', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'http_reply', - start: jasmine.any(Number), - cid: 1, - end: jasmine.any(Number), - messageId: jasmine.any(String) - } - }); - - expect(fakeAMQPConnection.ack).toHaveBeenCalled(); - expect(fakeAMQPConnection.ack.callCount).toEqual(1); - expect(fakeAMQPConnection.ack.calls[0].args[0]).toEqual(message); - - done(); - }) - .catch(done); //todo: use done.fail after migration to Jasmine 2.x - }); - - it('should handle errors in httpReply properly', done => { - settings.FUNCTION = 'http_reply'; - const sailor = new Sailor(settings); - - spyOn(sailor.apiClient.tasks, 'retrieveStep').andCallFake((taskId, stepId) => Promise.resolve({})); - - fakeAMQPConnection.sendHttpReply.andCallFake(() => { - throw new Error('Failed to send HTTP reply'); - }); - - sailor.connect() - .then(() => sailor.prepare()) - .then(() => sailor.processMessage(payload, message)) - .then(() => { - expect(sailor.apiClient.tasks.retrieveStep) - .toHaveBeenCalledWith('5559edd38968ec0736000003', 'step_1'); - - expect(fakeAMQPConnection.connect).toHaveBeenCalled(); - expect(fakeAMQPConnection.sendHttpReply).toHaveBeenCalled(); - - const sendHttpReplyCalls = fakeAMQPConnection.sendHttpReply.calls; - - expect(sendHttpReplyCalls[0].args[0]).toEqual({ - statusCode: 200, - body: 'Ok', - headers: { - 'content-type': 'text/plain' - } - }); - expect(sendHttpReplyCalls[0].args[1]).toEqual(jasmine.any(Object)); - expect(sendHttpReplyCalls[0].args[1]).toEqual({ - contentType: 'application/json', - contentEncoding: 'utf8', - mandatory: true, - headers: { - execId: 'some-exec-id', - taskId: '5559edd38968ec0736000003', - userId: '5559edd38968ec0736000002', - containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', - workspaceId: '5559edd38968ec073600683', - stepId: 'step_1', - compId: '5559edd38968ec0736000456', - function: 'http_reply', - start: jasmine.any(Number), - cid: 1, - messageId: jasmine.any(String) - } - }); - - expect(fakeAMQPConnection.sendData).not.toHaveBeenCalled(); - expect(fakeAMQPConnection.ack).not.toHaveBeenCalled(); - - // error - expect(fakeAMQPConnection.sendError).toHaveBeenCalled(); - - const sendErrorCalls = fakeAMQPConnection.sendError.calls; - expect(sendErrorCalls[0].args[0].message).toEqual('Failed to send HTTP reply'); - - // ack - expect(fakeAMQPConnection.reject).toHaveBeenCalled(); - expect(fakeAMQPConnection.reject.callCount).toEqual(1); - expect(fakeAMQPConnection.reject.calls[0].args[0]).toEqual(message); - - done(); - }) - .catch(done); //todo: use done.fail after migration to Jasmine 2.x - }); - }); - - describe('readIncomingMessageHeaders', () => { - it('execId missing', () => { - const sailor = new Sailor(settings); - - try { - sailor.readIncomingMessageHeaders({ - properties: { - headers: {} - } - }); - throw new Error('Must not be reached'); - } catch (e) { - expect(e.message).toEqual('ExecId is missing in message header'); - } - }); - - it('taskId missing', () => { - const sailor = new Sailor(settings); - - try { - sailor.readIncomingMessageHeaders({ - properties: { - headers: { - execId: 'my_exec_123' - } - } - }); - throw new Error('Must not be reached'); - } catch (e) { - expect(e.message).toEqual('TaskId is missing in message header'); - } - }); - - it('userId missing', () => { - const sailor = new Sailor(settings); - - try { - sailor.readIncomingMessageHeaders({ - properties: { - headers: { - execId: 'my_exec_123', - taskId: 'my_task_123' - } - } - }); - throw new Error('Must not be reached'); - } catch (e) { - expect(e.message).toEqual('UserId is missing in message header'); - } - }); - - it('Message with wrong taskID arrived to the sailor', () => { - const sailor = new Sailor(settings); - - try { - sailor.readIncomingMessageHeaders({ - properties: { - headers: { - execId: 'my_exec_123', - taskId: 'my_task_123', - userId: 'my_user_123' - } - } - }); - throw new Error('Must not be reached'); - } catch (e) { - expect(e.message).toEqual('Message with wrong taskID arrived to the sailor'); - } - }); - - it('should copy standard headers', () => { - const sailor = new Sailor(settings); - - const headers = { - execId: 'my_exec_123', - taskId: settings.FLOW_ID, - userId: 'my_user_123' - }; - - const result = sailor.readIncomingMessageHeaders({ - properties: { - headers - } - }); - - expect(result).toEqual(headers); - }); - - it('should copy standard headers and parentMessageId', () => { - const sailor = new Sailor(settings); - - const messageId = 'parent_message_1234'; - - const headers = { - execId: 'my_exec_123', - taskId: settings.FLOW_ID, - userId: 'my_user_123', - messageId - }; - - const result = sailor.readIncomingMessageHeaders({ - properties: { - headers - } - }); - - expect(result).toEqual({ - execId: 'my_exec_123', - taskId: settings.FLOW_ID, - userId: 'my_user_123', - parentMessageId: messageId - }); - }); - - it('should copy standard headers and reply_to', () => { - const sailor = new Sailor(settings); - - const headers = { - execId: 'my_exec_123', - taskId: settings.FLOW_ID, - userId: 'my_user_123', - reply_to: 'my_reply_to_exchange' - }; - - const result = sailor.readIncomingMessageHeaders({ - properties: { - headers - } - }); - - expect(result).toEqual(headers); - }); - - it('should copy standard headers, reply_to and x-eio headers', () => { - const sailor = new Sailor(settings); - - const headers = { - 'execId': 'my_exec_123', - 'taskId': settings.FLOW_ID, - 'userId': 'my_user_123', - 'reply_to': 'my_reply_to_exchange', - 'x-eio-meta-lowercase': 'I am lowercase', - 'X-eio-meta-miXeDcAse': 'Eventually to become lowercase' - }; - - const result = sailor.readIncomingMessageHeaders({ - properties: { - headers - } - }); - - expect(result).toEqual({ - 'execId': 'my_exec_123', - 'taskId': settings.FLOW_ID, - 'userId': 'my_user_123', - 'reply_to': 'my_reply_to_exchange', - 'x-eio-meta-lowercase': 'I am lowercase', - 'x-eio-meta-mixedcase': 'Eventually to become lowercase' - }); - }); - }); -}); diff --git a/spec/service.spec.js b/spec/service.spec.js deleted file mode 100644 index 65a97280..00000000 --- a/spec/service.spec.js +++ /dev/null @@ -1,487 +0,0 @@ -describe('Service', () => { - var service = require('../lib/service'); - var nock = require('nock'); - - describe('execService', () => { - - beforeEach(() => { - process.env.ELASTICIO_API_URI = 'http://apihost.com'; - }); - - afterEach(() => { - delete process.env.ELASTICIO_API_URI; - }); - - function makeEnv(env) { - env.ELASTICIO_CFG = env.ELASTICIO_CFG || '{}'; - env.ELASTICIO_COMPONENT_PATH = env.ELASTICIO_COMPONENT_PATH || '/spec/component'; - env.ELASTICIO_POST_RESULT_URL = env.ELASTICIO_POST_RESULT_URL || 'http://test.com/123/456'; - env.ELASTICIO_API_URI = 'http://apihost.com'; - env.ELASTICIO_API_USERNAME = 'test@test.com'; - env.ELASTICIO_API_KEY = '5559edd'; - return env; - } - - describe('error cases', () => { - - beforeEach(() => { - nock('http://test.com:80') - .post('/123/456') - .reply(200, 'OK'); - }); - - it('should fail if no ELASTICIO_POST_RESULT_URL provided', done => { - - service.processService('verifyCredentials', {}) - .catch(checkError) - .done(done, done); - - function checkError(err) { - expect(err.message).toEqual('ELASTICIO_POST_RESULT_URL is not provided'); - } - }); - - it('should throw an error when there is no such service method', done => { - - service.processService('unknownMethod', makeEnv({})) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('Unknown service method "unknownMethod"'); - } - }); - - it('should send error response if no ELASTICIO_CFG provided', done => { - - service.processService('verifyCredentials', { ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456' }) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('ELASTICIO_CFG is not provided'); - } - }); - - it('should send error response if failed to parse ELASTICIO_CFG', done => { - - service.processService('verifyCredentials', makeEnv({ - ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456', - ELASTICIO_CFG: 'test' - - })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('Unable to parse CFG'); - } - }); - - it('should send error response if component is not found', done => { - - service.processService('verifyCredentials', { - ELASTICIO_POST_RESULT_URL: 'http://test.com/123/456', - ELASTICIO_CFG: '{"param1":"param2"}', - ELASTICIO_API_URI: 'http://example.com', - ELASTICIO_API_USERNAME: 'admin', - ELASTICIO_API_KEY: 'key' - }) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toMatch('Failed to load component.json'); - } - }); - - it('should throw an error when ELASTICIO_ACTION_OR_TRIGGER is not provided', done => { - - service.processService('getMetaModel', makeEnv({})) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('ELASTICIO_ACTION_OR_TRIGGER is not provided'); - } - }); - - it('should throw an error when ELASTICIO_ACTION_OR_TRIGGER is not found', done => { - - service.processService('getMetaModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'unknown' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('Trigger or action "unknown" is not found in component.json!'); - } - }); - - it('should throw an error when ELASTICIO_GET_MODEL_METHOD is not provided', done => { - - service.processService('selectModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('ELASTICIO_GET_MODEL_METHOD is not provided'); - } - }); - - it('should throw an error when ELASTICIO_GET_MODEL_METHOD is not found', done => { - - //eslint-disable-next-line max-len - service.processService('selectModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update', ELASTICIO_GET_MODEL_METHOD: 'unknown' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('Method "unknown" is not found in "update" action or trigger'); - } - }); - - }); - - describe('success cases', () => { - - beforeEach(() => { - nock('http://test.com:80') - .post('/123/456') - .reply(200, 'OK'); - }); - - describe('verifyCredentials', () => { - - it('should verify successfully when verifyCredentials.js is not available', done => { - - service.processService('verifyCredentials', makeEnv({})) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ verified: true }); - } - }); - - it('should verify successfully when callback verified', done => { - - //eslint-disable-next-line max-len - service.processService('verifyCredentials', makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component2' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ verified: true }); - } - }); - - it('should NOT verify successfully when callback did not verify', done => { - - //eslint-disable-next-line max-len - service.processService('verifyCredentials', makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component3' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ verified: false }); - } - }); - - it('should verify successfully when promise resolves', done => { - - //eslint-disable-next-line max-len - service.processService('verifyCredentials', makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component4' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ verified: true }); - } - }); - - it('should NOT verify successfully when promise rejects', done => { - - //eslint-disable-next-line max-len - service.processService('verifyCredentials', makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component5' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - verified: false, - reason: 'Your API key is invalid' - }); - } - }); - - it('should NOT verify successfully when error thrown synchronously', done => { - - //eslint-disable-next-line max-len - service.processService('verifyCredentials', makeEnv({ ELASTICIO_COMPONENT_PATH: '/spec/component6' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - verified: false, - reason: 'Ouch. This occurred during verification.' - }); - } - }); - - }); - - describe('getMetaModel', () => { - it('should return callback based model successfully', done => { - - service.processService('getMetaModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - in: { - type: 'object', - properties: { - name: { - type: 'string', - title: 'Name' - } - } - } - }); - } - }); - it('should return promise based model successfully', done => { - - service.processService('getMetaModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update1' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - in: { - type: 'object', - properties: { - email: { - type: 'string', - title: 'E-Mail' - } - } - } - }); - } - }); - it('should return error when promise rejects', done => { - - service.processService('getMetaModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update2' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data).toEqual({ - message: 'Today no metamodels. Sorry!' - }); - } - }); - }); - - describe('selectModel', () => { - - it('selectModel', done => { - - //eslint-disable-next-line max-len - service.processService('selectModel', makeEnv({ ELASTICIO_ACTION_OR_TRIGGER: 'update', ELASTICIO_GET_MODEL_METHOD: 'getModel' })) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - de: 'Germany', - us: 'USA', - ua: 'Ukraine' - }); - } - }); - - it('selectModel with updateKeys event', done => { - - var env = makeEnv({ - ELASTICIO_ACTION_OR_TRIGGER: 'update', - ELASTICIO_GET_MODEL_METHOD: 'getModelWithKeysUpdate', - ELASTICIO_CFG: '{"_account":"1234567890"}', - ELASTICIO_API_URI: 'http://apihost.com', - ELASTICIO_API_USERNAME: 'test@test.com', - ELASTICIO_API_KEY: '5559edd' - }); - - var nockScope = nock('http://apihost.com:80') - .matchHeader('Connection', 'Keep-Alive') - .put('/v1/accounts/1234567890', { keys: { oauth: { access_token: 'newAccessToken' } } }) - .reply(200, 'Success'); - - service.processService('selectModel', env) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(nockScope.isDone()).toEqual(true); - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - 0: 'Mr', - 1: 'Mrs' - }); - } - }); - - it('selectModel with failed updateKeys event should return result anyway', done => { - - var env = makeEnv({ - ELASTICIO_ACTION_OR_TRIGGER: 'update', - ELASTICIO_GET_MODEL_METHOD: 'getModelWithKeysUpdate', - ELASTICIO_CFG: '{"_account":"1234567890"}', - ELASTICIO_API_URI: 'http://apihost.com', - ELASTICIO_API_USERNAME: 'test@test.com', - ELASTICIO_API_KEY: '5559edd' - }); - - var nockScope = nock('http://apihost.com:80') - .matchHeader('Connection', 'Keep-Alive') - .put('/v1/accounts/1234567890', { keys: { oauth: { access_token: 'newAccessToken' } } }) - .reply(400, 'Success'); - - service.processService('selectModel', env) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(nockScope.isDone()).toEqual(true); - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - 0: 'Mr', - 1: 'Mrs' - }); - } - }); - - it('selectModel returns a promise that resolves successfully', done => { - - var env = makeEnv({ - ELASTICIO_ACTION_OR_TRIGGER: 'update', - ELASTICIO_GET_MODEL_METHOD: 'promiseSelectModel', - ELASTICIO_CFG: '{"_account":"1234567890"}', - ELASTICIO_API_URI: 'http://apihost.com', - ELASTICIO_API_USERNAME: 'test@test.com', - ELASTICIO_API_KEY: '5559edd' - }); - - service.processService('selectModel', env) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - de: 'de_DE', - at: 'de_AT' - }); - } - }); - - it('selectModel returns a promise that sends a request', done => { - - var env = makeEnv({ - ELASTICIO_ACTION_OR_TRIGGER: 'update', - ELASTICIO_GET_MODEL_METHOD: 'promiseRequestSelectModel', - ELASTICIO_CFG: '{"_account":"1234567890"}', - ELASTICIO_API_URI: 'http://apihost.com', - ELASTICIO_API_USERNAME: 'test@test.com', - ELASTICIO_API_KEY: '5559edd' - }); - - var nockScope = nock('http://promise_target_url:80') - .get('/selectmodel') - .reply(200, { - a: 'x', - b: 'y' - }); - - service.processService('selectModel', env) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(nockScope.isDone()).toEqual(true); - expect(result.status).toEqual('success'); - expect(result.data).toEqual({ - a: 'x', - b: 'y' - }); - } - }); - - it('selectModel returns a promise that rejects', done => { - - var env = makeEnv({ - ELASTICIO_ACTION_OR_TRIGGER: 'update', - ELASTICIO_GET_MODEL_METHOD: 'promiseSelectModelRejected', - ELASTICIO_CFG: '{"_account":"1234567890"}', - ELASTICIO_API_URI: 'http://apihost.com', - ELASTICIO_API_USERNAME: 'test@test.com', - ELASTICIO_API_KEY: '5559edd' - }); - - service.processService('selectModel', env) - .then(checkResult) - .done(done, done); - - function checkResult(result) { - expect(result.status).toEqual('error'); - expect(result.data.message).toEqual('Ouch. This promise is rejected'); - } - }); - }); - - }); - - describe('sending error', () => { - - beforeEach(() => { - nock('http://test.com:80') - .post('/111/222') - .reply(404, 'Page not found'); - }); - - it('verifyCredentials', done => { - - //eslint-disable-next-line max-len - service.processService('verifyCredentials', makeEnv({ ELASTICIO_POST_RESULT_URL: 'http://test.com/111/222' })) - .catch(checkError) - .done(done, done); - - function checkError(err) { - expect(err.message).toEqual('Failed to POST data to http://test.com/111/222 (404, Page not found)'); - } - }); - - }); - }); -}); diff --git a/spec/unit/AmqpCommunicationLayer.spec.js b/spec/unit/AmqpCommunicationLayer.spec.js new file mode 100644 index 00000000..2bebbafe --- /dev/null +++ b/spec/unit/AmqpCommunicationLayer.spec.js @@ -0,0 +1,656 @@ +const chai = require('chai'); +const sinon = require('sinon'); +chai.use(require('sinon-chai')); +const { expect } = chai; + +const AmqpCommunicationLayer = require('../..//lib/amqp.js').AmqpCommunicationLayer; +const AmqpConnWrapper = require('../../lib/AmqpConnWrapper.js'); +const encryptor = require('../../lib/encryptor.js'); +const _ = require('lodash'); + +async function waitsFor(cb, timeout) { + const start = Date.now(); + while (Date.now() - start < timeout) { + if (cb()) { + return; + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error('timeout waiting condition'); +} + +describe('AMQP', () => { + let config; + let cryptoSettings; + let message; + let sandbox; + let amqpConn; + let publishChannel; + let subscribeChannel; + let logger; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + config = { + MESSAGE_CRYPTO_PASSWORD: 'testCryptoPassword', + MESSAGE_CRYPTO_IV: 'iv=any16_symbols', + + LISTEN_MESSAGES_ON: '5559edd38968ec0736000003:step_1:1432205514864:messages', + PUBLISH_MESSAGES_TO: 'userexchange:5527f0ea43238e5d5f000001', + DATA_ROUTING_KEY: '5559edd38968ec0736000003:step_1:1432205514864:message', + ERROR_ROUTING_KEY: '5559edd38968ec0736000003:step_1:1432205514864:error', + REBOUND_ROUTING_KEY: '5559edd38968ec0736000003:step_1:1432205514864:rebound', + SNAPSHOT_ROUTING_KEY: '5559edd38968ec0736000003:step_1:1432205514864:snapshot', + + DATA_RATE_LIMIT: 1000, + ERROR_RATE_LIMIT: 1000, + SNAPSHOT_RATE_LIMIT: 1000, + RATE_INTERVAL: 1000, + + REBOUND_INITIAL_EXPIRATION: 15000, + REBOUND_LIMIT: 5, + + RABBITMQ_PREFETCH_SAILOR: 1 + }; + cryptoSettings = { + password: config.MESSAGE_CRYPTO_PASSWORD, + cryptoIV: config.MESSAGE_CRYPTO_IV + }; + message = { + fields: { + consumerTag: 'abcde', + deliveryTag: 12345, + exchange: 'test', + routingKey: 'test.hello' + }, + properties: { + contentType: 'application/json', + contentEncoding: 'utf8', + headers: { + taskId: 'task1234567890', + execId: 'exec1234567890', + reply_to: 'replyTo1234567890' + }, + deliveryMode: undefined, + priority: undefined, + correlationId: undefined, + replyTo: undefined, + expiration: undefined, + messageId: undefined, + timestamp: undefined, + type: undefined, + userId: undefined, + appId: undefined, + mandatory: true, + clusterId: '' + }, + content: encryptor.encryptMessageContent(cryptoSettings, { content: 'Message content' }) + }; + sandbox.spy(encryptor, 'decryptMessageContent'); + amqpConn = new AmqpConnWrapper(); + publishChannel = { + publish: sandbox.stub().returns(true), + waitForConfirms: sandbox.stub().resolves([null]), + on: sandbox.stub() + }; + subscribeChannel = { + ack: sandbox.stub(), + reject: sandbox.stub(), + consume: sandbox.stub().returns('tag'), + prefetch: sandbox.stub() + }; + sandbox.stub(amqpConn, 'getPublishChannel').returns(publishChannel); + sandbox.stub(amqpConn, 'getSubscribeChannel').returns(subscribeChannel); + logger = { + error: sandbox.stub(), + info: sandbox.stub(), + debug: sandbox.stub(), + trace: sandbox.stub() + }; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('Should send message to outgoing channel when process data', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }; + + await amqp.sendData({ + headers: { + 'some-other-header': 'headerValue' + }, + body: 'Message content' + }, props); + + expect(publishChannel.publish).to.have.been.calledOnce.and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.DATA_ROUTING_KEY, + sinon.match(arg => { + const payload = encryptor.decryptMessageContent(cryptoSettings, arg.toString()); + expect(payload).to.deep.equal({ + headers: { + 'some-other-header': 'headerValue' + }, + body: 'Message content' + }); + return true; + }), + props + ); + }); + + it('Should send message async to outgoing channel when process data', async () => { + config.DATA_RATE_LIMIT = 1; + config.RATE_INTERVAL = 500; + + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }; + const start = Date.now(); + for (let i = 0; i < 3; i++) { + await amqp.sendData({ + headers: { + 'some-other-header': 'headerValue' + }, + body: 'Message content' + }, props); + } + const duration = Math.round((Date.now() - start) / 1000); + // Total duration should be around 1 seconds, because + // first goes through + // second throttled for 500ms + // third throttled for another 500 ms + expect(duration).to.equal(1); + new Array(3).fill(null).reduce( + expector => expector.and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.DATA_ROUTING_KEY, + sinon.match(arg => { + const payload = encryptor.decryptMessageContent(cryptoSettings, arg.toString()); + expect(payload).to.deep.equal({ + headers: { + 'some-other-header': 'headerValue' + }, + body: 'Message content' + }); + return true; + }), + props + ), + expect(publishChannel.publish).to.have.been.calledThrice + ); + }); + + it('Should sendHttpReply to outgoing channel using routing key from headers when process data', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const msg = { + statusCode: 200, + headers: { + 'content-type': 'text/plain' + }, + body: 'OK' + }; + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456', + reply_to: 'my-special-routing-key' + } + }; + await amqp.sendHttpReply(msg, props); + expect(publishChannel.publish).to.have.been.calledOnce.and.calledWith( + config.PUBLISH_MESSAGES_TO, + 'my-special-routing-key', + sinon.match(arg => + expect(arg.toString()).to.equal(encryptor.encryptMessageContent(cryptoSettings, msg)) || true), + props + ); + }); + + it('Should throw error in sendHttpReply if reply_to header not found', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const msg = { + statusCode: 200, + headers: { + 'content-type': 'text/plain' + }, + body: 'OK' + }; + let caughtError; + try { + await amqp.sendHttpReply(msg, { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }); + } catch (e) { + caughtError = e; + } + expect(publishChannel.publish).not.to.have.been.called; + expect(caughtError).instanceof(Error); + }); + + it('Should send message to outgoing channel using routing key from headers when process data', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const msg = { + headers: { + 'X-EIO-Routing-Key': 'my-special-routing-key' + }, + body: { + content: 'Message content' + } + }; + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }; + + await amqp.sendData(msg, props); + + expect(publishChannel.publish).to.have.been.calledOnce.and.calledWith( + config.PUBLISH_MESSAGES_TO, + 'my-special-routing-key', + sinon.match(arg => { + const payload = encryptor.decryptMessageContent(cryptoSettings, arg.toString()); + expect(payload).to.deep.equal({ + headers: {}, + body: { + content: 'Message content' + } + }); + return true; + }), + props + ); + }); + + it('Should send message to errors when process error', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }; + const error = new Error('Test error'); + await amqp.sendError(error, props, message.content); + expect(publishChannel.publish).to.have.been.calledOnce.and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.ERROR_ROUTING_KEY, + sinon.match(arg => { + arg = JSON.parse(arg); + const payload = { + error: encryptor.decryptMessageContent(cryptoSettings, arg.error), + errorInput: encryptor.decryptMessageContent(cryptoSettings, arg.errorInput) + }; + expect(payload).to.deep.equal({ + error: { + name: 'Error', + message: 'Test error', + stack: error.stack + }, + errorInput: { + content: 'Message content' + } + }); + return true; + }), + props + ); + }); + + it('Should send message to errors using routing key from headers when process error', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456', + reply_to: 'my-special-routing-key' + } + }; + + const error = new Error('Test error'); + const expectedErrorPayload = { + error: { + name: 'Error', + message: 'Test error', + stack: error.stack + }, + errorInput: { + content: 'Message content' + } + }; + + await amqp.sendError(error, props, message.content); + expect(publishChannel.publish).to.have.been.calledTwice + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.ERROR_ROUTING_KEY, + sinon.match(arg => { + const parsed = JSON.parse(arg.toString()); + expect({ + error: encryptor.decryptMessageContent(cryptoSettings, parsed.error), + errorInput: encryptor.decryptMessageContent(cryptoSettings, parsed.errorInput) + }).to.deep.equal(expectedErrorPayload); + return true; + }), + props + ) + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + 'my-special-routing-key', + sinon.match(arg => { + expect(encryptor.decryptMessageContent(cryptoSettings, arg.toString())).to.deep.equal({ + name: error.name, + message: error.message, + stack: error.stack + }); + return true; + }), + { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + 'taskId': 'task1234567890', + 'stepId': 'step_456', + 'reply_to': 'my-special-routing-key', + 'x-eio-error-response': true + } + } + ); + }); + + it('Should not provide errorInput if errorInput was empty', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }; + const error = new Error('Test error'); + + amqp.sendError(error, props, ''); + await amqp.sendError(error, props, message.content); + expect(publishChannel.publish).to.have.been.calledTwice + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.ERROR_ROUTING_KEY, + sinon.match(arg => { + arg = JSON.parse(arg.toString()); + const payload = { + error: encryptor.decryptMessageContent(cryptoSettings, arg.error) + }; + expect(payload).to.deep.equal({ + error: { + name: 'Error', + message: 'Test error', + stack: error.stack + } + }); + return true; + }), + props + ); + }); + + it('Should not provide errorInput if errorInput was null', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: 'task1234567890', + stepId: 'step_456' + } + }; + + const error = new Error('Test error'); + await amqp.sendError(error, props, ''); + expect(publishChannel.publish).to.have.been.calledOnce + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.ERROR_ROUTING_KEY, + sinon.match(arg => { + arg = JSON.parse(arg.toString()); + const payload = { + error: encryptor.decryptMessageContent(cryptoSettings, arg.error) + }; + expect(payload).to.deep.equal({ + error: { + name: 'Error', + message: 'Test error', + stack: error.stack + } + }); + return true; + }), + props + ); + }); + + it('Should send message to rebounds when rebound happened', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'exec1234567890', + taskId: 'task1234567890', + stepId: 'step_1', + compId: 'comp1', + function: 'list', + start: '1432815685034' + } + }; + + const error = new Error('Rebound error'); + await amqp.sendRebound(error, message, props); + expect(publishChannel.publish).to.have.been.calledOnce + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.REBOUND_ROUTING_KEY, + sinon.match(arg => { + expect(encryptor.decryptMessageContent(cryptoSettings, arg.toString())).to.deep.equal({ + content: 'Message content' + }); + return true; + }), + { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + expiration: 15000, + headers: { + execId: 'exec1234567890', + taskId: 'task1234567890', + stepId: 'step_1', + compId: 'comp1', + function: 'list', + start: '1432815685034', + reboundIteration: 1 + } + } + ); + }); + + it('Should send message to rebounds with reboundIteration=3', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'exec1234567890', + taskId: 'task1234567890', + stepId: 'step_1', + compId: 'comp1', + function: 'list', + start: '1432815685034' + } + }; + + const clonedMessage = _.cloneDeep(message); + clonedMessage.properties.headers.reboundIteration = 2; + + const error = new Error('Rebound error'); + await amqp.sendRebound(error, clonedMessage, props); + expect(publishChannel.publish).to.have.been.calledOnce + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.REBOUND_ROUTING_KEY, + sinon.match(arg => { + expect(encryptor.decryptMessageContent(cryptoSettings, arg.toString())).to.deep.equal({ + content: 'Message content' + }); + return true; + }), + { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + expiration: 60000, + headers: { + execId: 'exec1234567890', + taskId: 'task1234567890', + stepId: 'step_1', + compId: 'comp1', + function: 'list', + start: '1432815685034', + reboundIteration: 3 + } + } + ); + }); + + it('Should send message to errors when rebound limit exceeded', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + const props = { + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'exec1234567890', + taskId: 'task1234567890', + stepId: 'step_1', + compId: 'comp1', + function: 'list', + start: '1432815685034' + } + }; + + const clonedMessage = _.cloneDeep(message); + clonedMessage.properties.headers.reboundIteration = 100; + const error = new Error('Rebound error'); + await amqp.sendRebound(error, clonedMessage, props); + expect(publishChannel.publish).to.have.been.calledOnce + .and.calledWith( + config.PUBLISH_MESSAGES_TO, + config.ERROR_ROUTING_KEY, + sinon.match(arg => { + const payload = JSON.parse(arg.toString()); + expect(encryptor.decryptMessageContent(cryptoSettings, payload.error)).to.contain({ + message: 'Rebound limit exceeded', + name: 'Error' + }); + expect(encryptor.decryptMessageContent(cryptoSettings, payload.errorInput)).to.deep.equal({ + content: 'Message content' + }); + return true; + }), + props + ); + }); + + it('Should ack message when confirmed', () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + + amqp.ack(message); + expect(subscribeChannel.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('Should reject message when ack is called with false', () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + amqp.reject(message); + + expect(subscribeChannel.reject).to.have.been.calledOnce.and.calledWith(message, false); + }); + + it('Should listen queue and pass decrypted message to client function', async () => { + const amqp = new AmqpCommunicationLayer(amqpConn, config, logger); + const clientFunction = sandbox.stub(); + subscribeChannel.consume.callsFake((queueName, callback) => { + callback(message); + }); + + const promise = waitsFor(() => clientFunction.callCount > 0, 1000); + await amqp.listenQueue(clientFunction); + await promise; + + expect(subscribeChannel.prefetch).to.have.been.calledOnce.and.calledWith(config.RABBITMQ_PREFETCH_SAILOR); + expect(clientFunction).to.have.been.calledOnce.and.calledWith( + { + headers: { + reply_to: 'replyTo1234567890' + }, + content: 'Message content' + }, + message + ); + expect(encryptor.decryptMessageContent).to.have.been.calledOnce + .and.calledWith(cryptoSettings, message.content); + }); +}); diff --git a/spec/unit/component_reader.spec.js b/spec/unit/component_reader.spec.js new file mode 100644 index 00000000..f8ebb972 --- /dev/null +++ b/spec/unit/component_reader.spec.js @@ -0,0 +1,97 @@ +const { expect } = require('chai'); +const ComponentReader = require('../../lib/component_reader.js').ComponentReader; +const { SingleAppLogger } = require('../../lib/logging.js'); + +describe('Component reader', () => { + let logger; + let reader; + beforeEach(() => { + logger = new SingleAppLogger({ get: () => undefined }); + reader = new ComponentReader(logger); + }); + it('Should find component located on the path', async () => { + await reader.init('/spec/component/'); + expect(reader.componentJson.title).to.equal('Client component'); + }); + + it('Should find component trigger', async () => { + await reader.init('/spec/component/'); + const filename = await reader._findTriggerOrAction('passthrough'); + + expect(reader.componentJson.title).to.equal('Client component'); + expect(filename).contains('triggers/passthrough.js'); + }); + + it('Should return error if trigger not found', async () => { + await reader.init('/spec/component/'); + let caughtError; + try { + await reader._findTriggerOrAction('some-missing-component'); + } catch (e) { + caughtError = e; + } + + expect(caughtError.message).to.equal( + 'Trigger or action "some-missing-component" is not found in component.json!' + ); + }); + + it('Should return appropriate error if trigger file is missing', async () => { + await reader.init('/spec/component/'); + + let caughtError; + try { + await reader.loadTriggerOrAction('missing_trigger'); + } catch (e) { + caughtError = e; + } + expect(caughtError.message).match( + // eslint-disable-next-line no-useless-escape + /Failed to load file \'.\/triggers\/missing_trigger.js\': Cannot find module.+missing_trigger\.js/ + ); + expect(caughtError.code).to.equal('MODULE_NOT_FOUND'); + }); + + it('Should return appropriate error if missing dependency is required by module', async () => { + await reader.init('/spec/component/'); + + let caughtError; + try { + await reader.loadTriggerOrAction('trigger_with_wrong_dependency'); + } catch (e) { + caughtError = e; + } + + expect(caughtError.message).to.equal( + 'Failed to load file \'./triggers/trigger_with_wrong_dependency.js\': ' + + 'Cannot find module \'../not-found-dependency\'' + ); + expect(caughtError.code).to.equal('MODULE_NOT_FOUND'); + }); + + it('Should return appropriate error if trigger file is presented, but contains syntax error', async () => { + await reader.init('/spec/component/'); + + let caughtError; + try { + await reader.loadTriggerOrAction('syntax_error_trigger'); + } catch (e) { + caughtError = e; + } + + expect(caughtError.message).to.equal( + "Trigger or action 'syntax_error_trigger' is found, but can not be loaded. " + + "Please check if the file './triggers/syntax_error_trigger.js' is correct." + ); + }); + + it('Should return error if trigger not initialized', async () => { + let caughtError; + try { + await reader._findTriggerOrAction('some-missing-component'); + } catch (err) { + caughtError = err; + } + expect(caughtError.message).to.equal('Component.json was not loaded'); + }); +}); diff --git a/spec/settings.spec.js b/spec/unit/config.spec.js similarity index 97% rename from spec/settings.spec.js rename to spec/unit/config.spec.js index b4b6fdfd..8170bbff 100644 --- a/spec/settings.spec.js +++ b/spec/unit/config.spec.js @@ -1,4 +1,7 @@ -describe('Settings', () => { +describe('Config', () => { + // FIXME implement me + /* + describe('Settings', () => { const settings = require('../lib/settings.js'); it('should throw error if no important settings provided', () => { @@ -70,3 +73,5 @@ describe('Settings', () => { expect(result.RABBITMQ_PREFETCH_SAILOR).toEqual(20); }); }); +*/ +}); diff --git a/spec/unit/encryptor.spec.js b/spec/unit/encryptor.spec.js new file mode 100644 index 00000000..819cb8fa --- /dev/null +++ b/spec/unit/encryptor.spec.js @@ -0,0 +1,90 @@ +const sinon = require('sinon'); +const chai = require('chai'); +const { expect } = chai; +chai.use(require('sinon-chai')); + +const encryptor = require('../../lib/encryptor.js'); +const cipher = require('../../lib/cipher.js'); + +describe('Cipher', () => { + const cryptoSettings = { + password: 'testCryptoPassword', + cryptoIV: 'iv=any16_symbols' + }; + + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.spy(global, 'decodeURIComponent'); + sandbox.spy(global, 'encodeURIComponent'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should encrypt & decrypt strings', () => { + const content = 'B2B_L größere Firmenkunden 25% Rabatt'; + const result = encryptor.encryptMessageContent(cryptoSettings, content); + const decryptedResult = encryptor.decryptMessageContent(cryptoSettings, result); + expect(decryptedResult.toString()).to.equal(content.toString()); + expect(global.decodeURIComponent).not.to.have.been.called; + expect(global.encodeURIComponent).not.to.have.been.called; + }); + + it('should encrypt & decrypt objects', () => { + const content = { property1: 'Hello world' }; + const result = encryptor.encryptMessageContent(cryptoSettings, content); + const decryptedResult = encryptor.decryptMessageContent(cryptoSettings, result); + expect(decryptedResult).to.deep.equal({ property1: 'Hello world' }); + }); + + it('should encrypt & decrypt buffer', () => { + const content = Buffer.from('Hello world'); + const result = encryptor.encryptMessageContent(cryptoSettings, content.toString()); + const decryptedResult = encryptor.decryptMessageContent(cryptoSettings, result); + expect(decryptedResult).to.equal('Hello world'); + }); + + it('should encrypt & decrypt message with buffers', () => { + const content = { + property1: Buffer.from('Hello world').toString() + }; + const result = encryptor.encryptMessageContent(cryptoSettings, content); + const decryptedResult = encryptor.decryptMessageContent(cryptoSettings, result); + expect(decryptedResult).to.deep.equal({ property1: 'Hello world' }); + }); + + it('should throw error if failed to decrypt', () => { + let caughtError; + try { + encryptor.decryptMessageContent(cryptoSettings, 'dsdasdsad'); + } catch (err) { + caughtError = err; + } + expect(caughtError).to.be.instanceof(Error); + expect(caughtError.message).to.contain('Failed to decrypt message'); + }); + + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should be compatible with Java-Sailor', () => { + // NOBODY knows why this test is skipped. I've moved it from jasmine + // eslint-disable-next-line max-len + const javaResult = 'wXTeSuonL1KvG7eKJ1Dk/hUHeLOhr7GMC1mGa7JyGQ9ZGg6AdjrKKn0ktoFMNVU77uB9dRd+tqqe0GNKlH8yuJrM2JWNdMbAWFHDLK5PvSRgL/negMTlmEnk/5/V5wharU8Qs9SW6rFI/E78Nkqlmqgwbd7ovHyzuOQIZj3kT4h6CW7S2fWJ559jpByhwXU1T8ZcGPOs4T+356AqYTXj8q2QgnkduKY7sNTrXNDsQUIZpm7tbBmMkoWuE6BXTitN/56TI2SVpo7TEQ/ef4c11fnrnCkpremZl4qPCCQcXD/47gMTSbSIydZCFQ584PE64pAwwn7UxloSen059tKKYF1BtGmBaqj97mHAL8izh3wsDoG8GuMRo2GhKopHnZTm'; + + const data = { + body: { + incomingProperty1: 'incomingValue1', + incomingProperty2: 'incomingValue2' + }, + attachments: { + incomingAttachment2: 'incomingAttachment2Content', + incomingAttachment1: 'incomingAttachment1Content' + } + }; + + expect(encryptor.decryptMessageContent(cryptoSettings, javaResult)).to.deep.equal(data); + }); + it('should properly return encryption identifier', () => { + expect(encryptor.getEncryptionId()).to.equal(cipher.id); + }); +}); diff --git a/spec/unit/executor.spec.js b/spec/unit/executor.spec.js new file mode 100644 index 00000000..fc2408e1 --- /dev/null +++ b/spec/unit/executor.spec.js @@ -0,0 +1,402 @@ +const chai = require('chai'); +const { expect } = chai; +const sinon = require('sinon'); +chai.use(require('sinon-chai')); +const nock = require('nock'); + +const TaskExec = require('../../lib/executor.js').TaskExec; +const { MessageLevelLogger } = require('../../lib/logging.js'); + +async function waitsFor(cb, timeout) { + const start = Date.now(); + while (Date.now() - start < timeout) { + if (cb()) { + return; + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error('timeout waiting condition'); +} + +describe('Executor', () => { + let logger; + let sandbox; + + const payload = { content: 'MessageContent' }; + const cfg = {}; + + beforeEach(() => { + logger = new MessageLevelLogger({}, { + threadId: 'threadId', + messageId: 'messageId', + parentMessageId: 'parentMessageId' + }); + sandbox = sinon.createSandbox(); + }); + afterEach(() => { + sandbox.restore(); + }); + describe('#constructor', () => { + it('should be constructable without arguments', () => { + const taskexec = new TaskExec({ }, logger); + expect(taskexec).to.be.instanceof(TaskExec); + }); + it('should properly store variables', () => { + const vars = { + var1: 'val1', + var2: 'val2' + }; + const taskexec = new TaskExec({ variables: vars }, logger); + expect(taskexec._variables).to.deep.equal(vars); + }); + }); + describe('getVariables', () => { + it('should return flow variables', () => { + const vars = { + var1: 'val1', + var2: 'val2' + }; + const taskexec = new TaskExec({ variables: vars }, logger); + expect(taskexec.getFlowVariables()).to.deep.equal(vars); + }); + }); + + it('Should execute passthrough trigger and emit all events - data, end', async () => { + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/passthrough.js'); + + taskexec.process(module, payload, cfg); + + await waitsFor(() => taskexec.emit.callCount > 1, 5000); + + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('data') + .and.calledWith('end'); + }); + + it('Should reject if module is missing', async () => { + const taskexec = new TaskExec({}, logger); + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + taskexec.process({}, payload, cfg); + + await waitsFor(() => taskexec.emit.callCount > 1, 5000); + + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('error', sinon.match((arg) => arg.message === 'Process function is not found')) + .and.calledWith('end'); + }); + + it('Should execute rebound_trigger and emit all events - rebound, end', async () => { + const taskexec = new TaskExec({ }, logger); + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/rebound_trigger.js'); + + taskexec.process(module, payload, cfg); + + await waitsFor(() => taskexec.emit.callCount > 1, 5000); + + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('rebound', sinon.match(arg => arg.message === 'Rebound reason')) + .and.calledWith('end') + .and.calledWith('end'); + }); + + it('Should execute complex trigger, and emit all 6 events', async () => { + const taskexec = new TaskExec({ }, logger); + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/datas_and_errors.js'); + + const promise = waitsFor(() => taskexec.emit.callCount >= 5, 5000); + taskexec.process(module, payload, cfg); + await promise; + + expect(taskexec.emit).to.have.been.callCount(5) + .and.calledWith('data', { content: 'Data 1' }) + .and.calledWith('error', sinon.match(arg => arg.message === 'Error 1')) + .and.calledWith('data', { content: 'Data 2' }) + .and.calledWith('error', sinon.match(arg => arg.message === 'Error 2')) + .and.calledWith('data', { content: 'Data 3' }); + }); + + it('Should execute a Promise trigger and emit all events - data, end', async () => { + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/promise_trigger.js'); + + const promise = waitsFor(() => taskexec.emit.callCount > 1, 5000); + taskexec.process(module, payload, cfg); + await promise; + + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('data', { body: 'I am a simple promise' }) + .and.calledWith('end'); + }); + it('Should execute test_trigger and emit all events - 3 data events, 3 errors, 3 rebounds, 1 end', async () => { + const taskexec = new TaskExec({ }, logger); + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/test_trigger.js'); + const promise = waitsFor(() => taskexec.emit.callCount >= 9, 5000); + taskexec.process(module, payload, cfg); + + await promise; + + expect(taskexec.emit).to.have.been.callCount(10) + .and.calledWith('data', 'Data 1') + .and.calledWith('data', { content: 'Data 2' }) + .and.calledWith('data') + .and.calledWith('error', 'Error 1') + .and.calledWith('error', sinon.match(arg => (arg instanceof Error) && arg.message === 'Error 2')) + .and.calledWith('error') + .and.calledWith('rebound', 'Rebound Error 1') + .and.calledWith('rebound', sinon.match(arg => (arg instanceof Error) && arg.message === 'Rebound Error 2')) + .and.calledWith('rebound') + .and.calledWith('end'); + }); + + describe('Promises', () => { + it('Should execute a Promise.resolve() trigger and emit end', async () => { + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/promise_resolve_no_data.js'); + + const promise = waitsFor(() => taskexec.emit.callCount > 0, 5000); + taskexec.process(module, payload, cfg); + await promise; + + expect(taskexec.emit).to.have.been.calledOnce + .and.calledWith('end'); + }); + }); + + describe('Request Promise', () => { + beforeEach(() => { + nock('http://promise_target_url:80') + .get('/foo/bar') + .reply(200, { + message: 'Life is good with promises' + }); + }); + + it('Should execute a Promise trigger and emit all events - data, end', async () => { + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/promise_request_trigger.js'); + + const promise = waitsFor(() => taskexec.emit.callCount > 1, 5000); + taskexec.process(module, payload, cfg); + await promise; + + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('data', { body: { message: 'Life is good with promises' } }) + .and.calledWith('end'); + }); + }); + + describe('Request Generators', () => { + it('Should execute a Promise trigger and emit all events - data, end', async () => { + nock('http://promise_target_url:80') + .get('/foo/bar') + .reply(200, { + message: 'Life is good with generators' + }); + + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/generator_request_trigger.js'); + + const promise = waitsFor(() => taskexec.emit.callCount > 1, 5000); + taskexec.process(module, payload, cfg); + await promise; + + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('data', { body: { message: 'Life is good with generators' } }) + .and.calledWith('end'); + }); + + it('Should execute a Promise trigger and emit all events - data, end', async () => { + nock('https://login.acme') + .post('/oauth2/v2.0/token', { + client_id: 'admin', + client_secret: 'secret' + }) + .reply(200, { + access_token: 'new_access_token' + }) + .get('/oauth2/v2.0/contacts') + .reply(200, { + result: [ + { + email: 'homer.simpson@acme.org' + }, + { + email: 'marge.simpson@acme.org' + } + ] + }); + + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/promise_emitting_events.js'); + + const promise = waitsFor(() => taskexec.emit.callCount > 1, 5000); + taskexec.process(module, payload, cfg); + await promise; + + expect(taskexec.emit).to.have.been.callCount(3) + .and.calledWith('updateKeys', { oauth: { access_token: 'new_access_token' } }) + .and.calledWith('data', { + body: { + result: [ + { + email: 'homer.simpson@acme.org' + }, + { + email: 'marge.simpson@acme.org' + } + ] + } + }) + .and.calledWith('end'); + }); + }); + describe('async process function', () => { + it('should work', async () => { + const taskexec = new TaskExec({ }, logger); + + // eslint-disable-next-line no-empty-function + taskexec.on('error', () => {}); + sandbox.spy(taskexec, 'emit'); + + const module = require('./../../spec/component/triggers/async_process_function'); + + const promise = waitsFor(() => taskexec.emit.callCount > 1, 1000); + taskexec.process(module, payload, cfg); + await promise; + expect(taskexec.emit).to.have.been.calledTwice + .and.calledWith('data', { some: 'data' }) + .and.calledWith('end'); + }); + }); + + describe('executor logger', () => { + function TestStream() { + this.lastRecord = ''; + } + + TestStream.prototype.write = function write(record) { + this.lastRecord = record; + }; + + let taskExec; + let testStream; + + beforeEach(() => { + testStream = new TestStream(); + const logger = new MessageLevelLogger( + {}, + { + threadId: 'threadId', + messageId: 'messageId', + parentMessageId: 'parentMessageId', + streams: [ + { + type: 'raw', + stream: testStream + } + ] + }, + true + ); + + taskExec = new TaskExec({ }, logger); + }); + + it('should check if level is enabled', () => { + expect(taskExec.logger.info()).to.be.ok; + }); + + it('should implicitly convert first argument to string', () => { + taskExec.logger.info(undefined); + expect(testStream.lastRecord.msg).equal('undefined'); + + taskExec.logger.info(null); + expect(testStream.lastRecord.msg).equal('null'); + + taskExec.logger.info({}); + expect(testStream.lastRecord.msg).equal('[object Object]'); + }); + + it('should format log message', () => { + taskExec.logger.info('hello %s', 'world'); + + expect(testStream.lastRecord.msg).equal('hello world'); + }); + + it('should log extra fields', () => { + const testStream = new TestStream(); + const logger = new MessageLevelLogger( + {}, + { + streams: [ + { + type: 'raw', + stream: testStream + } + ], + threadId: 'threadId', + messageId: 'messageId', + parentMessageId: 'parentMessageId' + + }, + true + ); + + taskExec = new TaskExec({ }, logger); + + taskExec.logger.info('info'); + + expect(testStream.lastRecord.name).to.equal('component'); + expect(testStream.lastRecord.threadId).to.equal('threadId'); + expect(testStream.lastRecord.messageId).to.equal('messageId'); + expect(testStream.lastRecord.parentMessageId).to.equal('parentMessageId'); + expect(testStream.lastRecord.msg).to.equal('info'); + }); + }); +}); diff --git a/spec/unit/sailor.spec.js b/spec/unit/sailor.spec.js new file mode 100644 index 00000000..9b639e14 --- /dev/null +++ b/spec/unit/sailor.spec.js @@ -0,0 +1,1023 @@ +const chai = require('chai'); +const sinon = require('sinon'); +chai.use(require('sinon-chai')); +chai.use(require('chai-uuid')); +const { expect } = chai; +const uuid = require('uuid'); +const _ = require('lodash'); + +const AmqpCommunicationLayer = require('../../lib/amqp.js').AmqpCommunicationLayer; +const AmqpConnWrapper = require('../../lib/AmqpConnWrapper.js'); +const Sailor = require('../../lib/sailor.js'); +const encryptor = require('../../lib/encryptor.js'); +const { SingleSailorConfig } = require('../../lib/settings.js'); + +const { prepareEnv } = require('../config.js'); + +describe('Sailor', () => { + let config; + let amqpCommunicationLayer; + let logger; + let sandbox; + let message; + let oldEnv; + + const payload = { param1: 'Value1' }; + beforeEach(() => { + sandbox = sinon.createSandbox(); + oldEnv = process.env; + process.env = prepareEnv('step_1'); + config = SingleSailorConfig.fromEnv(); + config.COMPONENT_PATH = '/spec/component'; + + const cryptoSettings = { + password: config.MESSAGE_CRYPTO_PASSWORD, + cryptoIV: config.MESSAGE_CRYPTO_IV + }; + message = { + fields: { + consumerTag: 'abcde', + deliveryTag: 12345, + exchange: 'test', + routingKey: 'test.hello' + }, + properties: { + contentType: 'application/json', + contentEncoding: 'utf8', + headers: { + taskId: config.FLOW_ID, + execId: config.EXEC_ID, + userId: config.USER_ID, + workspaceId: config.WORKSPACE_ID, + threadId: uuid.v4() + }, + deliveryMode: undefined, + priority: undefined, + correlationId: undefined, + replyTo: undefined, + expiration: undefined, + messageId: undefined, + timestamp: undefined, + type: undefined, + userId: undefined, + appId: undefined, + mandatory: true, + clusterId: '' + }, + content: Buffer.from(encryptor.encryptMessageContent(cryptoSettings, payload)) + }; + + const amqpConn = new AmqpConnWrapper(); + const publishChannel = { + publish: sandbox.stub().returns(true), + waitForConfirms: sandbox.stub().resolves([null]), + on: sandbox.stub() + }; + const subscribeChannel = { + ack: sandbox.stub(), + reject: sandbox.stub(), + consume: sandbox.stub().returns('tag'), + prefetch: sandbox.stub() + }; + sandbox.stub(amqpConn, 'getPublishChannel').returns(publishChannel); + sandbox.stub(amqpConn, 'getSubscribeChannel').returns(subscribeChannel); + logger = { + error: sandbox.stub(), + info: sandbox.stub(), + warn: sandbox.stub(), + debug: sandbox.stub(), + trace: sandbox.stub(), + child: sandbox.stub().callsFake(() => logger) + }; + + amqpCommunicationLayer = new AmqpCommunicationLayer(amqpConn, config, logger); + }); + afterEach(() => { + process.env = oldEnv; + sandbox.restore(); + }); + + describe('init', () => { + it('should init properly if developer returned a plain string in init', async () => { + config.FUNCTION = 'init_trigger_returns_string'; + + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { + config: { + _account: '1234567890' + } + }; + }); + + await sailor.prepare(); + const result = await sailor.init(); + expect(result).to.equal('this_is_a_string'); + }); + + it('should init properly if developer returned a promise', async () => { + config.FUNCTION = 'init_trigger'; + + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { + config: { + _account: '1234567890' + } + }; + }); + + await sailor.prepare(); + const result = await sailor.init(); + expect(result).to.deep.equal({ + subscriptionId: '_subscription_123' + }); + }); + }); + + describe('prepare', () => { + let sailor; + + beforeEach(() => { + sailor = new Sailor(amqpCommunicationLayer, config, logger); + }); + + describe('when step data retrieved', () => { + let stepData; + + beforeEach(() => { + stepData = { + snapshot: {} + }; + }); + + describe(`when step data retreived`, () => { + beforeEach(() => { + sandbox.stub(sailor.componentReader, 'init').resolves(); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').resolves(stepData); + }); + + it('should init component', async () => { + await sailor.prepare(); + expect(sailor.stepData).to.deep.equal(stepData); + expect(sailor.snapshot).to.deep.equal(stepData.snapshot); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce + .and.calledWith(config.FLOW_ID, config.STEP_ID); + expect(sailor.componentReader.init).to.have.been.calledOnce + .and.calledWith(config.COMPONENT_PATH); + }); + }); + }); + + describe('when step data is not retrieved', () => { + let error; + beforeEach(() => { + error = new Error('failed'); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').rejects(error); + }); + + it('should fail', async () => { + let caughtError; + try { + await sailor.prepare(); + } catch (e) { + caughtError = e; + } + expect(caughtError).to.equal(error); + }); + }); + }); + + describe('processMessage', () => { + it('should call sendData() and ack() if success', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'data_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + sandbox.stub(amqpCommunicationLayer, 'sendData'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + await sailor.prepare(); + await sailor.processMessage(payload, message); + + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce + .and.calledWith(config.FLOW_ID, config.STEP_ID); + + expect(amqpCommunicationLayer.sendData).to.have.been.calledOnce.and.calledWith( + { items: [1, 2, 3, 4, 5, 6] }, + sinon.match((arg) => { + expect(arg.headers.messageId).to.be.a.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'some-exec-id', + taskId: config.FLOW_ID, + userId: config.USER_ID, + workspaceId: config.WORKSPACE_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + stepId: 'step_1', + compId: config.COMP_ID, + threadId: message.properties.headers.threadId, + function: 'data_trigger', + start: 0, + cid: 1, + end: 0 + } + }); + return true; + }) + ); + + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should call sendData() and ack() only once', async () => { + config.FUNCTION = 'end_after_data_twice'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + sandbox.stub(amqpCommunicationLayer, 'sendData'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + sandbox.stub(amqpCommunicationLayer, 'reject'); + + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(amqpCommunicationLayer.sendData).to.have.been.calledOnce; + + expect(amqpCommunicationLayer.reject).not.to.have.been.called; + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce; + }); + + it('should augment emitted message with passthrough data', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'passthrough'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { is_passthrough: true }; + }); + + const psPayload = { + body: payload, + passthrough: { + step_0: { + body: { key: 'value' } + } + } + }; + + sandbox.stub(amqpCommunicationLayer, 'sendData'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + + await sailor.prepare(); + await sailor.processMessage(psPayload, message); + + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(amqpCommunicationLayer.sendData).to.have.been.calledOnce.and.calledWith( + { + body: { + param1: 'Value1' + }, + passthrough: { + step_0: { + body: { + key: 'value' + } + }, + step_1: { + body: { param1: 'Value1' } + } + } + }, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.a.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'some-exec-id', + taskId: config.FLOW_ID, + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'passthrough', + start: 0, + cid: 1, + end: 0, + threadId: message.properties.headers.threadId + } + }); + return true; + }) + ); + + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + it('should provide access to flow vairables', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'use_flow_variables'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { + is_passthrough: false, + variables: { + var1: 'val1', + var2: 'val2' + } + }; + }); + + const psPayload = { + body: payload + }; + + sandbox.stub(amqpCommunicationLayer, 'sendData'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + + await sailor.prepare(); + await sailor.processMessage(psPayload, message); + + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(amqpCommunicationLayer.sendData).to.have.been.calledOnce.and.calledWith( + { + body: { + var1: 'val1', + var2: 'val2' + } + }, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.a.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'some-exec-id', + taskId: config.FLOW_ID, + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'use_flow_variables', + start: 0, + cid: 1, + end: 0, + threadId: message.properties.headers.threadId + } + }); + return true; + }) + ); + + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should send request to API server to update keys', async () => { + config.FUNCTION = 'keys_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + const account = '1234567890'; + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { + config: { + _account: account + } + }; + }); + + sandbox.stub(sailor._apiClient.accounts, 'update').callsFake(async (accountId, keys) => { + expect(accountId).to.equal(account); + expect(keys).to.deep.equal({ keys: { oauth: { access_token: 'newAccessToken' } } }); + }); + sandbox.stub(amqpCommunicationLayer, 'ack'); + + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(sailor._apiClient.accounts.update).to.have.been.calledOnce; + + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should emit error if failed to update keys', async () => { + config.FUNCTION = 'keys_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + const account = '1234567890'; + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { + config: { + _account: account + } + }; + }); + const error = new Error('Update keys error'); + sandbox.stub(sailor._apiClient.accounts, 'update').callsFake(async (accountId, keys) => { + expect(accountId).to.equal(account); + expect(keys).to.deep.equal({ keys: { oauth: { access_token: 'newAccessToken' } } }); + throw error; + }); + sandbox.stub(amqpCommunicationLayer, 'ack'); + sandbox.stub(amqpCommunicationLayer, 'sendError'); + await sailor.prepare(); + await sailor.processMessage(payload, message); + // It will not throw an error because component + // process method is not `async` + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(sailor._apiClient.accounts.update).to.have.been.calledOnce; + + expect(amqpCommunicationLayer.sendError).to.have.been.calledOnce + .and.calledWith(sinon.match(arg => arg.message === error.message)); + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should call sendRebound() and ack()', async () => { + config.FUNCTION = 'rebound_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + sandbox.stub(amqpCommunicationLayer, 'sendRebound'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + + expect(amqpCommunicationLayer.sendRebound).to.have.been.calledOnce + .and.calledWith(sinon.match(arg => arg.message === 'Rebound reason')); + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should call sendSnapshot() and ack() after a `snapshot` event', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'update'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + sandbox.stub(amqpCommunicationLayer, 'sendSnapshot'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + await sailor.prepare(); + const payload = { + snapshot: { blabla: 'blablabla' } + }; + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + + const expectedSnapshot = { blabla: 'blablabla' }; + expect(amqpCommunicationLayer.sendSnapshot).to.have.been.calledOnce.and.calledWith( + expectedSnapshot, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: config.FLOW_ID, + execId: 'some-exec-id', + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'update', + start: 0, + cid: 1, + snapshotEvent: 'snapshot', + threadId: message.properties.headers.threadId + } + }); + return true; + }) + ); + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should call sendSnapshot() and ack() after an `updateSnapshot` event', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'update'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return { + snapshot: { + someId: 'someData' + } + }; + }); + + sandbox.stub(amqpCommunicationLayer, 'sendSnapshot'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + await sailor.prepare(); + const payload = { + updateSnapshot: { updated: 'value' } + }; + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.to.have.been.calledOnce; + + const expectedSnapshot = { someId: 'someData', updated: 'value' }; + + expect(amqpCommunicationLayer.sendSnapshot).to.have.been.calledOnce + .and.calledWith( + { updated: 'value' }, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + taskId: config.FLOW_ID, + execId: 'some-exec-id', + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'update', + start: 0, + cid: 1, + snapshotEvent: 'updateSnapshot', + threadId: message.properties.headers.threadId + } + }); + return true; + }) + ); + expect(sailor.snapshot).to.deep.equal(expectedSnapshot); + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should send error if error happened', async () => { + config.FUNCTION = 'error_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + sandbox.stub(amqpCommunicationLayer, 'sendError'); + sandbox.stub(amqpCommunicationLayer, 'reject'); + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(amqpCommunicationLayer.sendError).to.have.been.calledOnce + .and.calledWith( + sinon.match(arg => { + expect(arg.message).to.equal('Some component error'); + expect(arg.stack).to.be.ok; + return true; + }), + sinon.match(() => true), + message.content + ); + expect(amqpCommunicationLayer.reject).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should send error and reject only once()', async () => { + config.FUNCTION = 'end_after_error_twice'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + sandbox.stub(amqpCommunicationLayer, 'reject'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + sandbox.stub(amqpCommunicationLayer, 'sendError'); + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(amqpCommunicationLayer.sendError).to.have.been.calledOnce; + expect(amqpCommunicationLayer.ack).not.to.have.been.called; + expect(amqpCommunicationLayer.reject).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should reject message if trigger is missing', async () => { + config.FUNCTION = 'missing_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + + sandbox.stub(amqpCommunicationLayer, 'reject'); + sandbox.stub(amqpCommunicationLayer, 'sendError'); + + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + + expect(amqpCommunicationLayer.sendError).to.have.been.calledOnce + .and.calledWith( + sinon.match(arg => { + expect(arg.message).match( + /Failed to load file \'.\/triggers\/missing_trigger.js\': Cannot find module.+missing_trigger\.js/ // eslint-disable-line + ); + expect(arg.stack).to.be.ok; + return true; + }), + sinon.match(() => true), + message.content + ); + expect(amqpCommunicationLayer.reject).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should not process message if taskId in header is not equal to task._id', async () => { + const message2 = _.cloneDeep(message); + message2.properties.headers.taskId = 'othertaskid'; + + config.FUNCTION = 'error_trigger'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + + sandbox.stub(amqpCommunicationLayer, 'reject'); + + await sailor.prepare(); + await sailor.processMessage(payload, message2); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + expect(amqpCommunicationLayer.reject).to.have.been.calledOnce; + }); + + it('should catch all data calls and all error calls', async () => { + const clock = sandbox.useFakeTimers(); + // Notice this test relies on timeout's behavior of sailor + // e.g. if message is not processed during timeout + // (see ELASTICIO_TIMEOUT env var or TIMEOUT parameter in config) + // then end message is emitted anyway. + config.FUNCTION = 'datas_and_errors'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + + sandbox.stub(amqpCommunicationLayer, 'sendData'); + sandbox.stub(amqpCommunicationLayer, 'sendError'); + sandbox.stub(amqpCommunicationLayer, 'reject'); + + await sailor.prepare(); + // SORRY i was made to do this by angry aliens + process.nextTick(() => { + clock.tick(1000); + process.nextTick(() => clock.tick(2000)); + }); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce; + + expect(amqpCommunicationLayer.sendData).to.have.been.calledThrice; + expect(amqpCommunicationLayer.sendError).to.have.been.calledTwice; + expect(amqpCommunicationLayer.reject).to.have.been.calledOnce.and.calledWith(message); + }).timeout(10000); + + it('should handle errors in httpReply properly', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'http_reply'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + + sandbox.stub(amqpCommunicationLayer, 'sendHttpReply'); + sandbox.stub(amqpCommunicationLayer, 'ack'); + sandbox.stub(amqpCommunicationLayer, 'sendData'); + + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce + .and.calledWith(config.FLOW_ID, config.STEP_ID); + + expect(amqpCommunicationLayer.sendHttpReply).to.have.been.calledOnce.and.calledWith( + { + statusCode: 200, + body: 'Ok', + headers: { + 'content-type': 'text/plain' + } + }, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'some-exec-id', + taskId: config.FLOW_ID, + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'http_reply', + start: 0, + cid: 1, + threadId: message.properties.headers.threadId + } + }); + return true; + }) + ); + + expect(amqpCommunicationLayer.sendData).to.have.been.calledOnce.and.calledWith( + { body: {} }, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.uuid('v4'); + delete arg.headers.messageId; + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'some-exec-id', + taskId: config.FLOW_ID, + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'http_reply', + threadId: message.properties.headers.threadId, + start: 0, + cid: 1, + end: 0 + } + }); + return true; + }) + ); + expect(amqpCommunicationLayer.ack).to.have.been.calledOnce.and.calledWith(message); + }); + + it('should handle errors in httpReply properly', async () => { + sandbox.useFakeTimers(); + config.FUNCTION = 'http_reply'; + const sailor = new Sailor(amqpCommunicationLayer, config, logger); + sandbox.stub(sailor._apiClient.tasks, 'retrieveStep').callsFake(async (taskId, stepId) => { + expect(taskId).to.equal(config.FLOW_ID); + expect(stepId).to.equal(config.STEP_ID); + return {}; + }); + + sandbox.stub(amqpCommunicationLayer, 'sendHttpReply').rejects(new Error('Failed to send HTTP reply')); + sandbox.stub(amqpCommunicationLayer, 'ack'); + sandbox.stub(amqpCommunicationLayer, 'reject'); + sandbox.stub(amqpCommunicationLayer, 'sendError'); + sandbox.stub(amqpCommunicationLayer, 'sendData'); + + await sailor.prepare(); + await sailor.processMessage(payload, message); + expect(sailor._apiClient.tasks.retrieveStep).to.have.been.calledOnce + .and.calledWith(config.FLOW_ID, config.STEP_ID); + + expect(amqpCommunicationLayer.sendHttpReply).to.have.been.calledOnce + .and.calledWith( + { + statusCode: 200, + body: 'Ok', + headers: { + 'content-type': 'text/plain' + } + }, + sinon.match(arg => { + expect(arg.headers.messageId).to.be.uuid('v4'); + delete arg.headers.messageId; + + expect(arg).to.deep.equal({ + contentType: 'application/json', + contentEncoding: 'utf8', + mandatory: true, + headers: { + execId: 'some-exec-id', + taskId: config.FLOW_ID, + userId: config.USER_ID, + containerId: 'dc1c8c3f-f9cb-49e1-a6b8-716af9e15948', + workspaceId: config.WORKSPACE_ID, + stepId: 'step_1', + compId: config.COMP_ID, + function: 'http_reply', + threadId: message.properties.headers.threadId, + start: 0, + cid: 1 + } + }); + return true; + }) + ); + + expect(amqpCommunicationLayer.ack).not.to.have.been.called; + expect(amqpCommunicationLayer.sendData).not.to.have.been.called; + expect(amqpCommunicationLayer.reject).to.have.been.calledOnce.and.calledWith(message); + expect(amqpCommunicationLayer.sendError).to.have.been.calledOnce.and.calledWith(sinon.match(arg => { + expect(arg.message).to.equal('Failed to send HTTP reply'); + return true; + })); + }); + }); + + describe('_readIncomingMessageHeaders', () => { + let sailor; + beforeEach(() => { + sailor = new Sailor(amqpCommunicationLayer, config, logger); + }); + it('execId missing', () => { + try { + sailor._readIncomingMessageHeaders({ + properties: { + headers: {} + } + }); + throw new Error('Must not be reached'); + } catch (e) { + expect(e.message).to.equal('ExecId is missing in message header'); + } + }); + + it('taskId missing', () => { + try { + sailor._readIncomingMessageHeaders({ + properties: { + headers: { + execId: 'my_exec_123' + } + } + }); + throw new Error('Must not be reached'); + } catch (e) { + expect(e.message).to.equal('TaskId is missing in message header'); + } + }); + + it('userId missing', () => { + try { + sailor._readIncomingMessageHeaders({ + properties: { + headers: { + execId: 'my_exec_123', + taskId: 'my_task_123' + } + } + }); + throw new Error('Must not be reached'); + } catch (e) { + expect(e.message).to.equal('UserId is missing in message header'); + } + }); + + it('Message with wrong taskID arrived to the sailor', () => { + try { + sailor._readIncomingMessageHeaders({ + properties: { + headers: { + execId: 'my_exec_123', + taskId: 'my_task_123', + userId: 'my_user_123' + } + } + }); + throw new Error('Must not be reached'); + } catch (e) { + expect(e.message).to.equal('Message with wrong taskID arrived to the sailor'); + } + }); + + it('should copy standard headers', () => { + const headers = { + execId: 'my_exec_123', + taskId: config.FLOW_ID, + userId: 'my_user_123', + threadId: uuid.v4() + }; + + const result = sailor._readIncomingMessageHeaders({ + properties: { + headers + } + }); + + expect(result).to.deep.equal(headers); + }); + + it('should copy standard headers and parentMessageId', () => { + const messageId = 'parent_message_1234'; + + const headers = { + execId: 'my_exec_123', + taskId: config.FLOW_ID, + userId: 'my_user_123', + messageId, + threadId: uuid.v4() + }; + + const result = sailor._readIncomingMessageHeaders({ + properties: { + headers + } + }); + + expect(result).to.deep.equal({ + execId: 'my_exec_123', + taskId: config.FLOW_ID, + userId: 'my_user_123', + parentMessageId: messageId, + threadId: headers.threadId + }); + }); + + it('should copy standard headers and reply_to', () => { + const headers = { + execId: 'my_exec_123', + taskId: config.FLOW_ID, + userId: 'my_user_123', + reply_to: 'my_reply_to_exchange', + threadId: uuid.v4() + }; + + const result = sailor._readIncomingMessageHeaders({ + properties: { + headers + } + }); + + expect(result).to.deep.equal(headers); + }); + + it('should copy standard headers, reply_to and x-eio headers', () => { + const headers = { + 'execId': 'my_exec_123', + 'taskId': config.FLOW_ID, + 'userId': 'my_user_123', + 'reply_to': 'my_reply_to_exchange', + 'x-eio-meta-lowercase': 'I am lowercase', + 'X-eio-meta-miXeDcAse': 'Eventually to become lowercase', + 'threadId': uuid.v4() + }; + + const result = sailor._readIncomingMessageHeaders({ + properties: { + headers + } + }); + + expect(result).to.deep.equal({ + 'execId': 'my_exec_123', + 'taskId': config.FLOW_ID, + 'userId': 'my_user_123', + 'reply_to': 'my_reply_to_exchange', + 'x-eio-meta-lowercase': 'I am lowercase', + 'x-eio-meta-mixedcase': 'Eventually to become lowercase', + 'threadId': headers.threadId + }); + }); + }); +});