diff --git a/README.md b/README.md index 2bc7c6b..7460f1c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Dispo is a job and cronjob scheduler for Node. -It uses [Kue](https://github.com/Automattic/kue) as its job queue and [Redis](http://redis.io/) to store job data. Definition of recurring jobs is done using crontab syntax, one-off jobs can be queued using [ZeroMQ](http://zeromq.org/) requests. +It uses [Kue](https://github.com/Automattic/kue) as its job queue and [Redis](http://redis.io/) to store job data. Definition of recurring jobs is done using crontab syntax, one-off jobs can be queued using [ZeroMQ](http://zeromq.org/) requests. [nodemailer](https://github.com/nodemailer/nodemailer) and [nodemailer-sendmail-transport](https://github.com/andris9/nodemailer-sendmail-transport) are being used to send emails. All Jobs, regardless if running automatically or queued on demand for single runs, have to be defined in a configuration file. Jobs defined as cronjobs with a recurring interval are scheduled and run automatically. @@ -46,6 +46,8 @@ The following configuration example defines a job called `logRandomNumber` that The `attempts` property on the database cleanup job defines that the job is only attempted to run once. When the property is not explicitely set, it defaults to 3 so a job is retried twice on failure. When a recurringly scheduled job/cronjob reaches fails on each of its attempts, it is not automatically rescheduled. +The `recipients` property will override the default set in [mailer.js](src/mailer.js). You can also use it to disable sending an email for a job when it is enabled globally. You can set the global configuration in [index.js](src/index.js). + ```json { "jobs": { @@ -55,12 +57,17 @@ The `attempts` property on the database cleanup job defines that the job is only "databaseCleanup": { "file": "jobs/databaseCleanup.js", "cron": "0 1 * * * *", - "attempts": 1 + "attempts": 1, + "recipients": "example@email.com" } } } ``` +#### Send an email on job failure + +Dispo supports sending an email when a job fails after all available retries. You can toggle the feature in [index.js](src/index.js) and configure your mail server in [mailer.js](src/mailer.js). By default, Dispo wil use sendmail (through [nodemailer-sendmail-transport](https://github.com/andris9/nodemailer-sendmail-transport)). Per job configuration of the recipients are available through the [configuration file](https://github.com/gonsfx/dispo#job-configuration). + #### Attempts with delay and backoff for failing jobs Jobs that sometimes fail to execute correctly (or any job in general to be precise) can be configured to restart with a delay after they fail. You can use this feature via the `backoff` property. diff --git a/dist/index.js b/dist/index.js index 38e198e..25d96c0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -53,6 +53,10 @@ var _logger = require('./logger'); var _logger2 = _interopRequireDefault(_logger); +var _mailer = require('./mailer'); + +var _mailer2 = _interopRequireDefault(_mailer); + var _util = require('./util'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -67,7 +71,8 @@ var defaultConfig = { jobs: [], options: { port: 5555, - logging: { path: 'log' } + logging: { path: 'log' }, + mailer: { enabled: false } } }; @@ -122,70 +127,75 @@ var Dispo = function () { this._logger = new _logger2.default(this.config.options.logging); this._logger.init(); + if (this.config.options.mailer && this.config.options.mailer.enabled) { + this._mailer = new _mailer2.default(this.config.options.mailer); + this._mailer.init(); + } + this._initSocket(); this._initQueue(this.config.options.queue); _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; - _context.prev = 7; + _context.prev = 8; _iterator = (0, _getIterator3.default)(this.config.jobs); - case 9: + case 10: if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { - _context.next = 16; + _context.next = 17; break; } job = _step.value; - _context.next = 13; + _context.next = 14; return this.defineJob(job); - case 13: + case 14: _iteratorNormalCompletion = true; - _context.next = 9; + _context.next = 10; break; - case 16: - _context.next = 22; + case 17: + _context.next = 23; break; - case 18: - _context.prev = 18; - _context.t0 = _context['catch'](7); + case 19: + _context.prev = 19; + _context.t0 = _context['catch'](8); _didIteratorError = true; _iteratorError = _context.t0; - case 22: - _context.prev = 22; + case 23: _context.prev = 23; + _context.prev = 24; if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } - case 25: - _context.prev = 25; + case 26: + _context.prev = 26; if (!_didIteratorError) { - _context.next = 28; + _context.next = 29; break; } throw _iteratorError; - case 28: - return _context.finish(25); - case 29: - return _context.finish(22); + return _context.finish(26); case 30: + return _context.finish(23); + + case 31: case 'end': return _context.stop(); } } - }, _callee, this, [[7, 18, 22, 30], [23,, 25, 29]]); + }, _callee, this, [[8, 19, 23, 31], [24,, 26, 30]]); })); function init() { @@ -216,6 +226,7 @@ var Dispo = function () { var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(_ref3) { var attempts = _ref3.attempts; var cron = _ref3.cron; + var recipients = _ref3.recipients; var fn = _ref3.fn; var name = _ref3.name; var backoff = _ref3.backoff; @@ -232,16 +243,20 @@ var Dispo = function () { return fn(job).then(done, done); }); + if (recipients) { + options.recipients = recipients; + } + if (!cron) { - _context2.next = 7; + _context2.next = 8; break; } options.cron = cron; - _context2.next = 7; + _context2.next = 8; return this._queueJob(name, options); - case 7: + case 8: case 'end': return _context2.stop(); } @@ -284,7 +299,7 @@ var Dispo = function () { switch (_context3.prev = _context3.next) { case 0: _context3.next = 2; - return _this._logger.logStart(id); + return _this._handleStart(id); case 2: return _context3.abrupt('return', _context3.sent); @@ -308,7 +323,7 @@ var Dispo = function () { switch (_context4.prev = _context4.next) { case 0: _context4.next = 2; - return _this._logger.logFailedAttempt(id, msg); + return _this._handleFailedAttempt(id, msg); case 2: return _context4.abrupt('return', _context4.sent); @@ -332,7 +347,7 @@ var Dispo = function () { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; - return _this._logger.logFailure(id, msg); + return _this._handleFailed(id, msg); case 2: return _context5.abrupt('return', _context5.sent); @@ -356,7 +371,7 @@ var Dispo = function () { switch (_context6.prev = _context6.next) { case 0: _context6.next = 2; - return _this._logger.logComplete(id); + return _this._handleComplete(id); case 2: return _context6.abrupt('return', _context6.sent); @@ -377,26 +392,17 @@ var Dispo = function () { this._queue.on('job complete', function () { var _ref8 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee7(id) { - var job; return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: _context7.next = 2; - return getJob(id); + return _this.__handleCompleteAlways(id); case 2: - job = _context7.sent; + return _context7.abrupt('return', _context7.sent); - if (!job.data.cron) { - _context7.next = 6; - break; - } - - _context7.next = 6; - return _this._queueJob(job.data.name, job.data); - - case 6: + case 3: case 'end': return _context7.stop(); } @@ -409,6 +415,152 @@ var Dispo = function () { }; }()); } + }, { + key: '_handleStart', + value: function () { + var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(id) { + return _regenerator2.default.wrap(function _callee8$(_context8) { + while (1) { + switch (_context8.prev = _context8.next) { + case 0: + _context8.next = 2; + return this._logger.logStart(id); + + case 2: + case 'end': + return _context8.stop(); + } + } + }, _callee8, this); + })); + + function _handleStart(_x11) { + return _ref9.apply(this, arguments); + } + + return _handleStart; + }() + }, { + key: '_handleFailedAttempt', + value: function () { + var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(id, msg) { + return _regenerator2.default.wrap(function _callee9$(_context9) { + while (1) { + switch (_context9.prev = _context9.next) { + case 0: + _context9.next = 2; + return this._logger.logFailedAttempt(id, msg); + + case 2: + case 'end': + return _context9.stop(); + } + } + }, _callee9, this); + })); + + function _handleFailedAttempt(_x12, _x13) { + return _ref10.apply(this, arguments); + } + + return _handleFailedAttempt; + }() + }, { + key: '_handleFailed', + value: function () { + var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(id, msg) { + return _regenerator2.default.wrap(function _callee10$(_context10) { + while (1) { + switch (_context10.prev = _context10.next) { + case 0: + _context10.next = 2; + return this._logger.logFailure(id, msg); + + case 2: + if (!this._mailer) { + _context10.next = 5; + break; + } + + _context10.next = 5; + return this._mailer.sendMail(id); + + case 5: + case 'end': + return _context10.stop(); + } + } + }, _callee10, this); + })); + + function _handleFailed(_x14, _x15) { + return _ref11.apply(this, arguments); + } + + return _handleFailed; + }() + }, { + key: '_handleComplete', + value: function () { + var _ref12 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee11(id) { + return _regenerator2.default.wrap(function _callee11$(_context11) { + while (1) { + switch (_context11.prev = _context11.next) { + case 0: + _context11.next = 2; + return this._logger.logComplete(id); + + case 2: + case 'end': + return _context11.stop(); + } + } + }, _callee11, this); + })); + + function _handleComplete(_x16) { + return _ref12.apply(this, arguments); + } + + return _handleComplete; + }() + }, { + key: '_handleCompleteAlways', + value: function () { + var _ref13 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee12(id) { + var job; + return _regenerator2.default.wrap(function _callee12$(_context12) { + while (1) { + switch (_context12.prev = _context12.next) { + case 0: + _context12.next = 2; + return getJob(id); + + case 2: + job = _context12.sent; + + if (!job.data.cron) { + _context12.next = 6; + break; + } + + _context12.next = 6; + return this._queueJob(job.data.name, job.data); + + case 6: + case 'end': + return _context12.stop(); + } + } + }, _callee12, this); + })); + + function _handleCompleteAlways(_x17) { + return _ref13.apply(this, arguments); + } + + return _handleCompleteAlways; + }() /** * Initialize ØMQ reply socket @@ -430,11 +582,11 @@ var Dispo = function () { var responder = _zmqPrebuilt2.default.socket('rep'); responder.on('message', function () { - var _ref9 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee8(message) { + var _ref14 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee13(message) { var payload, job, data; - return _regenerator2.default.wrap(function _callee8$(_context8) { + return _regenerator2.default.wrap(function _callee13$(_context13) { while (1) { - switch (_context8.prev = _context8.next) { + switch (_context13.prev = _context13.next) { case 0: payload = JSON.parse(message.toString()); job = _this2.config.jobs.filter(function (job) { @@ -442,12 +594,12 @@ var Dispo = function () { }).shift(); if (!job) { - _context8.next = 7; + _context13.next = 7; break; } data = (0, _lodash.omit)((0, _assign2.default)(payload, job), 'fn', 'name'); - _context8.next = 6; + _context13.next = 6; return _this2._queueJob(job.name, data); case 6: @@ -455,14 +607,14 @@ var Dispo = function () { case 7: case 'end': - return _context8.stop(); + return _context13.stop(); } } - }, _callee8, _this2); + }, _callee13, _this2); })); - return function (_x12) { - return _ref9.apply(this, arguments); + return function (_x19) { + return _ref14.apply(this, arguments); }; }()); @@ -486,32 +638,32 @@ var Dispo = function () { }, { key: '_isCronScheduled', value: function () { - var _ref10 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee9(name) { + var _ref15 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee14(name) { var jobsByType, cronjobsByType; - return _regenerator2.default.wrap(function _callee9$(_context9) { + return _regenerator2.default.wrap(function _callee14$(_context14) { while (1) { - switch (_context9.prev = _context9.next) { + switch (_context14.prev = _context14.next) { case 0: - _context9.next = 2; + _context14.next = 2; return getJobsByType(name, 'delayed', 0, 10000, 'desc'); case 2: - jobsByType = _context9.sent; + jobsByType = _context14.sent; cronjobsByType = jobsByType.filter(function (job) { return !!job.data.cron; }); - return _context9.abrupt('return', cronjobsByType.length > 0); + return _context14.abrupt('return', cronjobsByType.length > 0); case 5: case 'end': - return _context9.stop(); + return _context14.stop(); } } - }, _callee9, this); + }, _callee14, this); })); - function _isCronScheduled(_x13) { - return _ref10.apply(this, arguments); + function _isCronScheduled(_x20) { + return _ref15.apply(this, arguments); } return _isCronScheduled; @@ -536,13 +688,13 @@ var Dispo = function () { }, { key: '_queueJob', value: function () { - var _ref11 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee10(name, options) { + var _ref16 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee15(name, options) { var _this3 = this; var attempts, cron, delay, backoff, isScheduled; - return _regenerator2.default.wrap(function _callee10$(_context10) { + return _regenerator2.default.wrap(function _callee15$(_context15) { while (1) { - switch (_context10.prev = _context10.next) { + switch (_context15.prev = _context15.next) { case 0: attempts = options.attempts; cron = options.cron; @@ -551,11 +703,11 @@ var Dispo = function () { (0, _assert2.default)(!!cron || !!delay, 'To queue a job, either `cron` or `delay` needs to be defined'); - _context10.next = 7; + _context15.next = 7; return this._isCronScheduled(name); case 7: - isScheduled = _context10.sent; + isScheduled = _context15.sent; if (!cron || !isScheduled) { @@ -578,14 +730,14 @@ var Dispo = function () { case 9: case 'end': - return _context10.stop(); + return _context15.stop(); } } - }, _callee10, this); + }, _callee15, this); })); - function _queueJob(_x14, _x15) { - return _ref11.apply(this, arguments); + function _queueJob(_x21, _x22) { + return _ref16.apply(this, arguments); } return _queueJob; diff --git a/dist/mailer.js b/dist/mailer.js new file mode 100644 index 0000000..266eef3 --- /dev/null +++ b/dist/mailer.js @@ -0,0 +1,143 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _regenerator = require('babel-runtime/regenerator'); + +var _regenerator2 = _interopRequireDefault(_regenerator); + +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +var _assign = require('babel-runtime/core-js/object/assign'); + +var _assign2 = _interopRequireDefault(_assign); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _nodemailer = require('nodemailer'); + +var _nodemailer2 = _interopRequireDefault(_nodemailer); + +var _nodemailerSendmailTransport = require('nodemailer-sendmail-transport'); + +var _nodemailerSendmailTransport2 = _interopRequireDefault(_nodemailerSendmailTransport); + +var _ = require('.'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Default logger options + * @type {Object} + */ +var defaults = { + nodemailerConfig: { + transportOptions: null, + mailOptions: { + from: 'info@dispo-cheduler.com', + to: '', // list of receivers, override this through the jobs config file + subject: 'Dispo - job and cronjob scheduler for Node', + text: '' + } + } +}; + +/** + * Mailer + */ + +var Mailer = function () { + function Mailer(options) { + (0, _classCallCheck3.default)(this, Mailer); + + this.config = (0, _assign2.default)({}, defaults, options); + } + + (0, _createClass3.default)(Mailer, [{ + key: 'init', + value: function init() { + this._mailer = _nodemailer2.default.createTransport(this.config.nodemailerConfig.transportOptions || (0, _nodemailerSendmailTransport2.default)()); + } + }, { + key: 'sendMail', + value: function () { + var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(job) { + var mailOptions, _ref2, data, id; + + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + mailOptions = this.config.nodemailerConfig.mailOptions; + _context.next = 3; + return (0, _.getJob)(job); + + case 3: + _ref2 = _context.sent; + data = _ref2.data; + id = _ref2.id; + + // set recipients to empty string to disable mail in a per job basis + + if ('recipients' in data) { + mailOptions.to = data.recipients; + } + + if ('enabled' in this.config) { + _context.next = 9; + break; + } + + return _context.abrupt('return'); + + case 9: + if (!(this.config.enabled === false)) { + _context.next = 11; + break; + } + + return _context.abrupt('return'); + + case 11: + if (!(mailOptions.to === '')) { + _context.next = 13; + break; + } + + return _context.abrupt('return'); + + case 13: + + mailOptions.text = 'Job ' + id + ' - ' + data.name + ': Failed on all ' + data.attempts + ' attempts.'; + + return _context.abrupt('return', this._mailer.sendMail(mailOptions)); + + case 15: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + + function sendMail(_x) { + return _ref.apply(this, arguments); + } + + return sendMail; + }() + }]); + return Mailer; +}(); + +exports.default = Mailer; \ No newline at end of file diff --git a/dist/util.js b/dist/util.js index 3cbdaf0..b270317 100644 --- a/dist/util.js +++ b/dist/util.js @@ -45,6 +45,7 @@ var parseJobs = exports.parseJobs = function parseJobs(jobs, basedir) { var cron = _jobs$name.cron; var attempts = _jobs$name.attempts; var backoff = _jobs$name.backoff; + var recipients = _jobs$name.recipients; if (!file) { @@ -64,6 +65,9 @@ var parseJobs = exports.parseJobs = function parseJobs(jobs, basedir) { if (backoff) { jobOptions.backoff = backoff; } + if (recipients) { + jobOptions.recipients = recipients; + } res.push(jobOptions); return res; }, []); diff --git a/package.json b/package.json index cf196b3..4d4b6fd 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "kue": "^0.11.1", "later": "^1.2.0", "lodash": "^4.16.1", + "nodemailer": "^2.6.4", + "nodemailer-sendmail-transport": "^1.0.0", "pretty-ms": "^2.1.0", "winston": "^2.2.0", "zmq-prebuilt": "^2.0.0" diff --git a/src/index.js b/src/index.js index ede4195..7c4f9a7 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ import zmq from 'zmq-prebuilt' import { promisify } from 'bluebird' import { merge, omit } from 'lodash' import Logger from './logger' +import Mailer from './mailer' import { parseBackoff } from './util' const { NODE_ENV } = process.env @@ -16,7 +17,8 @@ const defaultConfig = { jobs: [], options: { port: 5555, - logging: { path: 'log' } + logging: { path: 'log' }, + mailer: { enabled: false } } } @@ -56,6 +58,11 @@ export default class Dispo { this._logger = new Logger(this.config.options.logging) this._logger.init() + if (this.config.options.mailer && this.config.options.mailer.enabled) { + this._mailer = new Mailer(this.config.options.mailer) + this._mailer.init() + } + this._initSocket() this._initQueue(this.config.options.queue) @@ -78,12 +85,16 @@ export default class Dispo { * @param {DefineJobOptions} options - Job options * @return {Promise} */ - async defineJob ({ attempts, cron, fn, name, backoff }) { + async defineJob ({ attempts, cron, recipients, fn, name, backoff }) { assert(name, 'Job must have a name') const options = { attempts, backoff } this._queue.process(name, (job, done) => fn(job).then(done, done)) + if (recipients) { + options.recipients = recipients + } + if (cron) { options.cron = cron await this._queueJob(name, options) @@ -104,18 +115,39 @@ export default class Dispo { this._queue.watchStuckJobs(5e3) if (NODE_ENV !== 'test') { - this._queue.on('job start', async (id) => await this._logger.logStart(id)) - this._queue.on('job failed attempt', async (id, msg) => await this._logger.logFailedAttempt(id, msg)) - this._queue.on('job failed', async (id, msg) => await this._logger.logFailure(id, msg)) - this._queue.on('job complete', async (id) => await this._logger.logComplete(id)) + this._queue.on('job start', async (id) => await this._handleStart(id)) + this._queue.on('job failed attempt', async (id, msg) => await this._handleFailedAttempt(id, msg)) + this._queue.on('job failed', async (id, msg) => await this._handleFailed(id, msg)) + this._queue.on('job complete', async (id) => await this._handleComplete(id)) } - this._queue.on('job complete', async (id) => { - const job = await getJob(id) - if (job.data.cron) { - await this._queueJob(job.data.name, job.data) - } - }) + this._queue.on('job complete', async (id) => await this.__handleCompleteAlways(id)) + } + + async _handleStart (id) { + await this._logger.logStart(id) + } + + async _handleFailedAttempt (id, msg) { + await this._logger.logFailedAttempt(id, msg) + } + + async _handleFailed (id, msg) { + await this._logger.logFailure(id, msg) + if (this._mailer) { + await this._mailer.sendMail(id) + } + } + + async _handleComplete (id) { + await this._logger.logComplete(id) + } + + async _handleCompleteAlways (id) { + const job = await getJob(id) + if (job.data.cron) { + await this._queueJob(job.data.name, job.data) + } } /** diff --git a/src/mailer.js b/src/mailer.js new file mode 100644 index 0000000..2f7b5c2 --- /dev/null +++ b/src/mailer.js @@ -0,0 +1,56 @@ +import nodemailer from 'nodemailer' +import sendmailTransport from 'nodemailer-sendmail-transport' +import { getJob } from '.' + +/** + * Default logger options + * @type {Object} + */ +const defaults = { + nodemailerConfig: { + transportOptions: null, + mailOptions: { + from: 'info@dispo-cheduler.com', + to: '', // list of receivers, override this through the jobs config file + subject: 'Dispo - job and cronjob scheduler for Node', + text: '' + } + } +} + +/** + * Mailer + */ +export default class Mailer { + constructor (options) { + this.config = Object.assign({}, defaults, options) + } + + init () { + this._mailer = nodemailer.createTransport(this.config.nodemailerConfig.transportOptions || sendmailTransport()) + } + + async sendMail (job) { + let mailOptions = this.config.nodemailerConfig.mailOptions + const { data, id } = await getJob(job) + + // set recipients to empty string to disable mail in a per job basis + if ('recipients' in data) { + mailOptions.to = data.recipients + } + + if (!('enabled' in this.config)) { + return + } + if (this.config.enabled === false) { + return + } + if (mailOptions.to === '') { + return + } + + mailOptions.text = `Job ${id} - ${data.name}: Failed on all ${data.attempts} attempts.` + + return this._mailer.sendMail(mailOptions) + } +} diff --git a/src/util.js b/src/util.js index 63fd9d1..d63871d 100644 --- a/src/util.js +++ b/src/util.js @@ -20,7 +20,7 @@ export const parseJobs = (jobs, basedir) => { return Object .keys(jobs) .reduce((res, name) => { - const { file, cron, attempts, backoff } = jobs[name] + const { file, cron, attempts, backoff, recipients } = jobs[name] if (!file) { throw new Error(`no file defined for job "${name}"`) @@ -39,6 +39,9 @@ export const parseJobs = (jobs, basedir) => { if (backoff) { jobOptions.backoff = backoff } + if (recipients) { + jobOptions.recipients = recipients + } res.push(jobOptions) return res }, []) diff --git a/test/index.js b/test/index.js index a0c94db..4ffb692 100644 --- a/test/index.js +++ b/test/index.js @@ -5,6 +5,7 @@ import chai, { expect } from 'chai' import path, { resolve } from 'path' import Scheduler from '../src' import Logger from '../src/logger' +import Mailer from '../src/mailer' import { getAbsolutePath, @@ -61,14 +62,14 @@ describe('Utility methods', () => { const fn = require(`../${file}`) const result = parseJobs({ - random: { file, attempts: 2 }, + random: { file, attempts: 2, recipients: 'example@email.com' }, alsoRandom: { file, cron: '*/2 * * * *' }, backoffFixed: { file, attempts: 2, backoff: { delay: 3000, type: 'fixed' } }, backoffExponentially: { file, attempts: 2, backoff: { delay: 3000, type: 'exponential' } } }, base) expect(result).to.deep.equal([ - { name: 'random', attempts: 2, fn }, + { name: 'random', attempts: 2, fn, recipients: 'example@email.com' }, { name: 'alsoRandom', attempts: 3, fn, cron: '*/2 * * * *' }, { name: 'backoffFixed', attempts: 2, fn, backoff: { delay: 3000, type: 'fixed' } }, { name: 'backoffExponentially', attempts: 2, fn, backoff: { delay: 3000, type: 'exponential' } } @@ -107,5 +108,17 @@ describe('Scheduler', () => { scheduler.init() expect(scheduler._logger).to.be.instanceof(Logger) }) + + it('initializes a mailer when switch is on', () => { + const scheduler = new Scheduler({options: {mailer: {enabled: true}}}) + scheduler.init() + expect(scheduler._mailer).to.be.instanceof(Mailer) + }) + + it('doesn\'t initializes a mailer by default', () => { + const scheduler = new Scheduler() + scheduler.init() + expect(scheduler._mailer).to.be.undefined + }) }) })