Explorar o código

Merge branch 'dev' of http://192.168.1.220:10080/Amoy/im.doctor into dev

lyr %!s(int64=8) %!d(string=hai) anos
pai
achega
9c552b2c94
Modificáronse 30 ficheiros con 5248 adicións e 10 borrados
  1. 2 1
      src/doctor/endpoints/chats.endpoint.js
  2. 9 5
      src/doctor/models/doctor.js
  3. 7 1
      src/doctor/models/group.js
  4. 21 0
      src/doctor/node_modules/cron-builder/bower.json
  5. 287 0
      src/doctor/node_modules/cron-builder/cron-builder.js
  6. 57 0
      src/doctor/node_modules/cron-builder/package.json
  7. 200 0
      src/doctor/node_modules/cron-builder/test/test.js
  8. 18 0
      src/doctor/node_modules/node-schedule/fff.js
  9. 586 0
      src/doctor/node_modules/node-schedule/lib/schedule.js
  10. 11 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/component.json
  11. 79 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/lib/date.js
  12. 611 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/lib/expression.js
  13. 103 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/lib/parser.js
  14. 108 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/package.json
  15. 16 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/test/31_of_month.js
  16. 787 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/test/expression.js
  17. 47 0
      src/doctor/node_modules/node-schedule/node_modules/cron-parser/test/parser.js
  18. 10 0
      src/doctor/node_modules/node-schedule/node_modules/long-timeout/example.js
  19. 33 0
      src/doctor/node_modules/node-schedule/node_modules/long-timeout/index.js
  20. 51 0
      src/doctor/node_modules/node-schedule/node_modules/long-timeout/package.json
  21. 83 0
      src/doctor/node_modules/node-schedule/package.json
  22. 713 0
      src/doctor/node_modules/node-schedule/test/convenience-method-test.js
  23. 59 0
      src/doctor/node_modules/node-schedule/test/date-convenience-methods-test.js
  24. 33 0
      src/doctor/node_modules/node-schedule/test/es6/job-test.js
  25. 494 0
      src/doctor/node_modules/node-schedule/test/job-test.js
  26. 77 0
      src/doctor/node_modules/node-schedule/test/range-test.js
  27. 294 0
      src/doctor/node_modules/node-schedule/test/recurrence-rule-test.js
  28. 136 0
      src/doctor/node_modules/node-schedule/test/schedule-cron-jobs.js
  29. 312 0
      src/doctor/node_modules/node-schedule/test/start-end-test.js
  30. 4 3
      src/doctor/package.json

+ 2 - 1
src/doctor/endpoints/chats.endpoint.js

@ -39,7 +39,8 @@ const DEFAULT_PAGE_SIZE = require('../include/commons').DEFAULT_PAGE_SIZE;
 *      title: "System Message",
 *      summary: "You have new job",
 *      contentType: "1",
 *      content: "The patient has been followed in the scheduler, please make new follow plan as soon as possible."
 *      content: "The patient has been followed in the scheduler, please make new follow plan as soon as possible.",
 *      delay: ""
 *  }
 *
 * @param message

+ 9 - 5
src/doctor/models/doctor.js

@ -86,7 +86,11 @@ class Doctor extends BaseModel {
                // 先结束网络连接,再推送给客户端
                modelUtil.emitData(self.eventEmitter, {});
                Doctor.pushMessage(message, 'system_msg');
                if (message.delay) {
                    //todo
                } else {
                    Doctor.pushMessage(message, 'system_msg');
                }
            });
    }
@ -96,7 +100,7 @@ class Doctor extends BaseModel {
     * @param message
     * @param channel
     */
    static pushMessage(message, channel){
    static pushMessage(message, channel) {
        doctorRepo.getUserStatus(message.to, function (err, result) {
            if (err) {
                log.error('Lookup notify message receiver failed: ' + message.to);
@ -113,8 +117,8 @@ class Doctor extends BaseModel {
            // 构建通知消息
            let notifyMessage = {type: channel, data: message.content};
            if(message.from) notifyMessage.from_uid = message.from;
            if(message.gid) notifyMessage.gid = message.gid;
            if (message.from) notifyMessage.from_uid = message.from;
            if (message.gid) notifyMessage.gid = message.gid;
            let title = '新消息';
            let content = message.content;
@ -122,7 +126,7 @@ class Doctor extends BaseModel {
                content = '[图片]';
            } else if (message.contentType === CONTENT_TYPES.Audio) {
                content = '[语音]';
            } else if(message.contentType > 3) {
            } else if (message.contentType > 3) {
                content = '您有一条新消息';
            }

+ 7 - 1
src/doctor/models/group.js

@ -81,7 +81,13 @@ class GroupMessage extends BaseModel {
                            (function (userId) {
                                Patient.isPatientCode(userId,
                                    function () {
                                        Patient.sendMessage(userId, 2, feedback);
                                        let message = {
                                            from: message.from,
                                            to: userId,
                                            contentType: message.contentType,
                                            content: message.content
                                        };
                                        Patient.sendMessage(message);
                                    },
                                    function () {
                                        Doctor.pushMessage(message, 'group_msg');

+ 21 - 0
src/doctor/node_modules/cron-builder/bower.json

@ -0,0 +1,21 @@
{
  "name": "cron-builder",
  "main": "cron-builder.js",
  "version": "0.1.0",
  "homepage": "https://github.com/srcclr/cron-builder",
  "authors": [
    "waneka <twwaneka@gmail.com>"
  ],
  "description": "simple API for building cron expressions",
  "keywords": [
    "cron"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}

+ 287 - 0
src/doctor/node_modules/cron-builder/cron-builder.js

@ -0,0 +1,287 @@
var DEFAULT_INTERVAL = ['*'];
var CronValidator = (function() {
    /**
     * Contains the position-to-name mapping of the cron expression
     * @type {Object}
     * @const
     */
    var MeasureOfTimeMap = {
            0: 'minute',
            1: 'hour',
            2: 'dayOfTheMonth',
            3: 'month',
            4: 'dayOfTheWeek'
        },
        /**
         * contains every permissible 'measureOfTime' string constant
         * @const
         * @type {Array}
         */
        MeasureOfTimeValues = Object.keys(MeasureOfTimeMap).map(function (key) {
            return MeasureOfTimeMap[key];
        });
    /**
     * validates a given cron expression (object) for length, then calls validateValue on each value
     * @param {!{
        minute: Array.string,
        hour: Array.string,
        dayOfTheMonth: Array.string,
        month: Array.string,
        dayOfTheWeek: Array.string,
     * }} expression - rich object containing the state of the cron expression
     * @throws {Error} if expression contains more than 5 keys
     */
    var validateExpression = function(expression) {
        // don't care if it's less than 5, we'll just set those to the default '*'
        if (Object.keys(expression).length > 5) {
            throw new Error('Invalid cron expression; limited to 5 values.');
        }
        for (var measureOfTime in expression) {
            if (expression.hasOwnProperty(measureOfTime)) {
                this.validateValue(measureOfTime, expression[measureOfTime]);
            }
        }
    },
    /**
     * validates a given cron expression (string) for length, then calls validateValue on each value
     * @param {!String} expression - an optionally empty string containing at most 5 space delimited expressions.
     * @throws {Error} if the string contains more than 5 space delimited parts.
     */
    validateString = function(expression) {
        var splitExpression = expression.split(' ');
        if (splitExpression.length > 5) {
            throw new Error('Invalid cron expression; limited to 5 values.');
        }
        for (var i = 0; i < splitExpression.length; i++) {
            this.validateValue(MeasureOfTimeMap[i], splitExpression[i]);
        }
    },
    /**
     * validates any given measureOfTime and corresponding value
     * @param {!String} measureOfTime - as expected
     * @param {!String} value - the cron-ish interval specifier
     * @throws {Error} if measureOfTime is bogus
     * @throws {Error} if value contains an unsupported character
     */
    validateValue = function(measureOfTime, value) {
        var validatorObj = {
                minute:        {min: 0, max: 59},
                hour:          {min: 0, max: 23},
                dayOfTheMonth: {min: 1, max: 31},
                month:         {min: 1, max: 12},
                dayOfTheWeek:  {min: 1, max: 7}
            },
            range,
            validChars = /^[0-9*-]/;
        if (!validatorObj[measureOfTime]) {
            throw new Error('Invalid measureOfTime; Valid options are: ' + MeasureOfTimeValues.join(', '));
        }
        if (!validChars.test(value)) {
            throw new Error('Invalid value; Only numbers 0-9, "-", and "*" chars are allowed');
        }
        if (value !== '*') {
            // check to see if value is within range if value is not '*'
            if (value.indexOf('-') >= 0) {
                // value is a range and must be split into high and low
                range = value.split('-');
                if (!range[0] || range[0] < validatorObj[measureOfTime].min) {
                    throw new Error('Invalid value; bottom of range is not valid for "' + measureOfTime + '". Limit is ' + validatorObj[measureOfTime].min + '.');
                }
                if (!range[1] || range[1] > validatorObj[measureOfTime].max) {
                    throw new Error('Invalid value; top of range is not valid for "' + measureOfTime + '". Limit is ' + validatorObj[measureOfTime].max + '.');
                }
            } else {
                if (parseInt(value) < validatorObj[measureOfTime].min) {
                    throw new Error('Invalid value; given value is not valid for "' + measureOfTime + '". Minimum value is "' + validatorObj[measureOfTime].min + '".');
                }
                if (parseInt(value) > validatorObj[measureOfTime].max) {
                    throw new Error('Invalid value; given value is not valid for "' + measureOfTime + '". Maximum value is "' + validatorObj[measureOfTime].max + '".');
                }
            }
        }
    };
    return {
        measureOfTimeValues: MeasureOfTimeValues,
        validateExpression: validateExpression,
        validateString: validateString,
        validateValue: validateValue
    }
}());
/**
 * Initializes a CronBuilder with an optional initial cron expression.
 * @param {String=} initialExpression - if provided, it must be up to 5 space delimited parts
 * @throws {Error} if the initialExpression is bogus
 * @constructor
 */
var CronBuilder = (function() {
    function CronBuilder(initialExpression) {
        var splitExpression,
            expression;
        if (initialExpression) {
            CronValidator.validateString(initialExpression);
            splitExpression = initialExpression.split(' ');
            // check to see if initial expression is valid
            expression = {
                minute:        splitExpression[0] ? [splitExpression[0]] : DEFAULT_INTERVAL,
                hour:          splitExpression[1] ? [splitExpression[1]] : DEFAULT_INTERVAL,
                dayOfTheMonth: splitExpression[2] ? [splitExpression[2]] : DEFAULT_INTERVAL,
                month:         splitExpression[3] ? [splitExpression[3]] : DEFAULT_INTERVAL,
                dayOfTheWeek:  splitExpression[4] ? [splitExpression[4]] : DEFAULT_INTERVAL,
            };
        } else {
            expression = {
                minute: DEFAULT_INTERVAL,
                hour: DEFAULT_INTERVAL,
                dayOfTheMonth: DEFAULT_INTERVAL,
                month: DEFAULT_INTERVAL,
                dayOfTheWeek: DEFAULT_INTERVAL,
            };
        }
        /**
         * builds a working cron expression based on the state of the cron object
         * @returns {string} - working cron expression
         */
        this.build = function () {
            return [
                expression.minute.join(','),
                expression.hour.join(','),
                expression.dayOfTheMonth.join(','),
                expression.month.join(','),
                expression.dayOfTheWeek.join(','),
            ].join(' ');
        };
        /**
         * adds a value to what exists currently (builds)
         * @param {!String} measureOfTime
         * @param {!Number} value
         * @throws {Error} if measureOfTime or value fail validation
         */
        this.addValue = function (measureOfTime, value) {
            CronValidator.validateValue(measureOfTime, value);
            if (expression[measureOfTime].length === 1 && expression[measureOfTime][0] === '*') {
                expression[measureOfTime] = [value];
            } else {
                if (expression[measureOfTime].indexOf(value) < 0) {
                    expression[measureOfTime].push(value);
                }
            }
        };
        /**
         * removes a single explicit value (subtracts)
         * @param {!String} measureOfTime - as you might guess
         * @param {!String} value - the offensive value
         * @throws {Error} if measureOfTime is bogus.
         */
        this.removeValue = function (measureOfTime, value) {
            if (!expression[measureOfTime]) {
                throw new Error('Invalid measureOfTime: Valid options are: ' + CronValidator.measureOfTimeValues.join(', '));
            }
            if (expression[measureOfTime].length === 1 && expression[measureOfTime][0] === '*') {
                return 'The value for "' + measureOfTime + '" is already at the default value of "*" - this is a no-op.';
            }
            expression[measureOfTime] = expression[measureOfTime].filter(function (timeValue) {
               return value !== timeValue;
            });
            if (!expression[measureOfTime].length) {
                expression[measureOfTime] = DEFAULT_INTERVAL;
            }
        };
        /**
         * returns the current state of a given measureOfTime
         * @param {!String} measureOfTime one of "minute", "hour", etc
         * @returns {!String} comma separated blah blah
         * @throws {Error} if the measureOfTime is not one of the permitted values.
         */
        this.get = function (measureOfTime) {
            if (!expression[measureOfTime]) {
                throw new Error('Invalid measureOfTime: Valid options are: ' + CronValidator.measureOfTimeValues.join(', '));
            }
            return expression[measureOfTime].join(',');
        };
        /**
         * sets the state of a given measureOfTime
         * @param {!String} measureOfTime - yup
         * @param {!Array.<String>} value - the 5 tuple array of values to set
         * @returns {!String} the comma separated version of the value that you passed in
         * @throws {Error} if your "value" is not an Array&lt;String&gt;
         * @throws {Error} when any item in your value isn't a legal cron-ish descriptor
         */
        this.set = function (measureOfTime, value) {
            if (!Array.isArray(value)) {
                throw new Error('Invalid value; Value must be in the form of an Array.');
            }
            for(var i = 0; i < value.length; i++) {
                CronValidator.validateValue(measureOfTime, value[i]);
            }
            expression[measureOfTime] = value;
            return expression[measureOfTime].join(',');
        };
        /**
         * Returns a rich object that describes the current state of the cron expression.
         * @returns {!{
            minute: Array.string,
            hour: Array.string,
            dayOfTheMonth: Array.string,
            month: Array.string,
            dayOfTheWeek: Array.string,
         * }}
         */
        this.getAll = function () {
            return expression;
        };
        /**
         * sets the state for the entire cron expression
         * @param {!{
            minute: Array.string,
            hour: Array.string,
            dayOfTheMonth: Array.string,
            month: Array.string,
            dayOfTheWeek: Array.string,
         * }} expToSet - the entirety of the cron expression.
         * @throws {Error} as usual
         */
        this.setAll = function (expToSet) {
            CronValidator.validateExpression(expToSet);
            expression = expToSet;
        };
    }
    return CronBuilder;
}());
module.exports = CronBuilder;

+ 57 - 0
src/doctor/node_modules/cron-builder/package.json

@ -0,0 +1,57 @@
{
  "name": "cron-builder",
  "description": "simple API for building cron expressions",
  "author": {
    "name": "Tyler Waneka",
    "email": "twwaneka@gmail.com",
    "url": "http://tylerwaneka.com"
  },
  "version": "0.3.0",
  "main": "cron-builder.js",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/srcclr/cron-builder.git"
  },
  "devDependencies": {
    "chai": "^3.4.1",
    "mocha": "^3.1.2"
  },
  "scripts": {
    "test": "mocha"
  },
  "gitHead": "d0c070bc64ddbad6ecc87ff74d5f138c3cd06c18",
  "bugs": {
    "url": "https://github.com/srcclr/cron-builder/issues"
  },
  "homepage": "https://github.com/srcclr/cron-builder#readme",
  "_id": "cron-builder@0.3.0",
  "_shasum": "6f121cd98e82db521bbd849bb115e3ccf9b21f9e",
  "_from": "cron-builder@latest",
  "_npmVersion": "3.10.3",
  "_nodeVersion": "6.7.0",
  "_npmUser": {
    "name": "sourceclear",
    "email": "npm@srcclr.com"
  },
  "dist": {
    "shasum": "6f121cd98e82db521bbd849bb115e3ccf9b21f9e",
    "size": 6326,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/cron-builder/download/cron-builder-0.3.0.tgz"
  },
  "maintainers": [
    {
      "name": "sourceclear",
      "email": "npm@srcclr.com"
    }
  ],
  "_npmOperationalInternal": {
    "host": "packages-18-east.internal.npmjs.com",
    "tmp": "tmp/cron-builder-0.3.0.tgz_1478126488456_0.05626012128777802"
  },
  "directories": {},
  "publish_time": 1478126490345,
  "_cnpm_publish_time": 1478126490345,
  "_resolved": "https://registry.npm.taobao.org/cron-builder/download/cron-builder-0.3.0.tgz"
}

+ 200 - 0
src/doctor/node_modules/cron-builder/test/test.js

@ -0,0 +1,200 @@
var chai = require('chai'),
    expect = chai.expect,
    cb = require('../cron-builder.js');
describe('cron-builder', function () {
    var cron;
    it('defaults to "* * * * *" when initialized without arguments', function () {
        cron = new cb();
        expect(cron.get('minute')).to.equal('*');
        expect(cron.get('hour')).to.equal('*');
        expect(cron.get('dayOfTheMonth')).to.equal('*');
        expect(cron.get('month')).to.equal('*');
        expect(cron.get('dayOfTheWeek')).to.equal('*');
    });
    it('protects against the user accessing the expression directly', function () {
        cron = new cb();
        expect(cron.minute).to.not.eql(['*']);
        expect(cron.hour).to.be.undefined;
    });
    it('protects against the user manipulating the expression directly', function () {
        cron = new cb();
        cron.minute = ['5'];
        expect(cron.get('minute')).to.not.equal('5');
        expect(cron.get('minute')).to.equal('*');
    });
    it('returns a working cron expression when calling .build()', function () {
        expect(cron.build()).to.equal('* * * * *');
    });
    it('sets a single value', function () {
        cron = new cb();
        expect(cron.set('hour', ['5'])).to.equal('5');
        expect(cron.build()).to.equal('* 5 * * *');
    });
    it('sets multiple values at once', function () {
        cron = new cb();
        expect(cron.set('minute', ['0', '10', '20', '30', '40', '50'])).to.equal('0,10,20,30,40,50');
        expect(cron.build()).to.equal('0,10,20,30,40,50 * * * *');
    });
    it('sets a range', function () {
        cron = new cb();
        expect(cron.set('dayOfTheWeek', ['5-7'])).to.equal('5-7');
        expect(cron.build()).to.equal('* * * * 5-7');
    });
    it('multiple sets build the cron string accurately', function () {
        cron = new cb();
        cron.set('minute', ['10', '30', '50']);
        cron.set('hour', ['6', '18']);
        cron.set('dayOfTheMonth', ['1', '15']);
        cron.set('dayOfTheWeek', ['1-5']);
        expect(cron.build()).to.equal('10,30,50 6,18 1,15 * 1-5');
    });
    it('validates against setting an incorrect measureOfTime', function () {
        cron = new cb();
        expect(function () { cron.set(['5'], 'minutes') }).to.throw(Error);
    });
    it('validates against setting a value that is not an Array', function () {
        cron = new cb();
        expect(function () { cron.set('10', 'hour') }).to.throw(Error);
    });
    it('validates against setting a value that is not a number or range of numbers', function () {
        cron = new cb();
        expect(function () { cron.set(['!'], 'hour') }).to.throw(Error);
    });
    describe('validates against setting values outside the valid range', function () {
        it('validates against values too low', function () {
            cron = new cb();
            expect(function () { cron.set(['0'], 'dayOfTheWeek') }).to.throw(Error);
        });
        it('validates against values too high', function () {
            cron = new cb();
            expect(function () { cron.set(['100'], 'hour') }).to.throw(Error);
        });
        it('validates against setting a range that is out of bounds', function () {
            cron = new cb();
            expect(function () { cron.set(['20-60'], 'minute') }).to.throw(Error);
            expect(function () { cron.set(['12', '22-26', '15'], 'hour') }).to.throw(Error);
        });
    });
    it('gets a single value', function () {
        cron = new cb();
        cron.set('minute', ['30']);
        expect(cron.get('minute')).to.equal('30');
    });
    it('validates against getting with an invalid measureOfTime', function () {
        cron = new cb();
        expect(function () { cron.get('hours'); }).to.throw(Error);
    });
    it('returns the entire expression object when getAll is called', function () {
        cron = new cb();
        var getAllResponse = cron.getAll();
        expect(getAllResponse).to.be.an('object');
        expect(getAllResponse).to.have.property('minute').that.is.an('array').with.deep.property('[0]').that.deep.equals('*');
        expect(getAllResponse).to.have.property('hour').that.is.an('array').with.deep.property('[0]').that.deep.equals('*');
        expect(getAllResponse).to.have.property('dayOfTheMonth').that.is.an('array').with.deep.property('[0]').that.deep.equals('*');
        expect(getAllResponse).to.have.property('month').that.is.an('array').with.deep.property('[0]').that.deep.equals('*');
        expect(getAllResponse).to.have.property('dayOfTheWeek').that.is.an('array').with.deep.property('[0]').that.deep.equals('*');
    });
    it('sets the entire object when setAll is called', function () {
        cron = new cb();
        var getAllResponse = cron.getAll();
        getAllResponse.hour = ['13'];
        getAllResponse.month = ['1-6'];
        getAllResponse.dayOfTheWeek = ['1,3,5,7'];
        cron.setAll(getAllResponse);
        expect(cron.build()).to.equal('* 13 * 1-6 1,3,5,7');
    });
    it('validates setting all with too many keys in the expression object', function () {
        cron = new cb();
        var getAllResponse = cron.getAll();
        getAllResponse.tooManyMeasuresOfTime = ['13'];
        expect(function () { cron.setAll(getAllResponse) }).to.throw(Error);
    });
    it('validates setting with an incorrect value with setAll', function () {
        cron = new cb();
        var getAllResponse = cron.getAll();
        getAllResponse.hour = ['28'];
        expect(function () { cron.setAll(getAllResponse) }).to.throw(Error);
    });
    it('adds a value to a measureOfTime that is set to "*"', function () {
        cron = new cb();
        cron.addValue('minute', '5');
        expect(cron.get('minute')).to.equal('5');
        expect(cron.build()).to.equal('5 * * * *');
    });
    it('adds a value to a measure of time that has been set to a number', function () {
        cron = new cb();
        cron.addValue('hour', '5');
        cron.addValue('hour', '10');
        expect(cron.get('hour')).to.equal('5,10');
    });
    it('validates duplicate values', function () {
        cron = new cb();
        cron.addValue('dayOfTheMonth', '5');
        cron.addValue('dayOfTheMonth', '15');
        cron.addValue('dayOfTheMonth', '5');
        expect(cron.get('dayOfTheMonth')).to.equal('5,15');
    });
    it('validates an invalid value when adding', function () {
        cron = new cb();
        expect(function () { cron.addValue('62', 'minute') }).to.throw(Error);
    });
    it('removes a value that exists with other values', function () {
        cron = new cb();
        cron.set('dayOfTheWeek', ['2', '4']);
        cron.removeValue('dayOfTheWeek', '4');
        expect(cron.get('dayOfTheWeek')).to.equal('2');
    });
    it('resets the value to the default "*" when removing the only value', function () {
        cron = new cb();
        cron.set('minute', ['7']);
        expect(cron.get('minute')).to.equal('7');
        cron.removeValue('minute', '7');
        expect(cron.get('minute')).to.equal('*');
    });
    it('validates an invalid measure of time when removing a value', function () {
        cron = new cb();
        expect(function () { cron.removeValue('ninute') }).to.throw(Error);
    });
    it('accepts a cron expression when instantiating', function () {
        cron = new cb('30 0-6 * * 1-5');
        expect(cron.build()).to.equal('30 0-6 * * 1-5');
    });
    it('validates bad values when instantiating with an explicit expression', function () {
        expect(function () { cron = new cb('30 0-6 * * 1-10') }).to.throw(Error);
    });
    it('validates an expression that is too long when instantiating with an explicit expression', function () {
        expect(function () { cron = new cb('30 0-6 * * 1-5 * *') }).to.throw(Error);
    });
});

+ 18 - 0
src/doctor/node_modules/node-schedule/fff.js

@ -0,0 +1,18 @@
var schedule = require('./');
// Create new cron for each iteration (creating 1-10 mins)
//for (var x = 1; x<10; x++){
//      var rule = new schedule.RecurrenceRule();
//      rule.minute = new schedule.Range(0, 59, x);
//      schedule.scheduleJob(rule, function() {
//     // Can I access the RULE which is being executed NOW somehow?!
//        console.log(this);
//     });
//}
var job = schedule.scheduleJob('* * * * * *', function() {
  console.log(this.spec);
});
job.spec = '* * * * * *';

+ 586 - 0
src/doctor/node_modules/node-schedule/lib/schedule.js

@ -0,0 +1,586 @@
'use strict';
/*
  node-schedule
  A cron-like and not-cron-like job scheduler for Node.
*/
var events = require('events'),
  util = require('util'),
  cronParser = require('cron-parser'),
  CronDate = require('cron-parser/lib/date'),
  lt = require('long-timeout');
/* Job object */
var anonJobCounter = 0;
function isValidDate(date) {
  // Taken from http://stackoverflow.com/a/12372720/1562178
  // If getTime() returns NaN it'll return false anyway
  return date.getTime() === date.getTime();
}
function Job(name, job, callback) {
  // setup a private pendingInvocations variable
  var pendingInvocations = [];
  //setup a private number of invocations variable
  var triggeredJobs = 0;
  // Set scope vars
  var jobName = name && typeof name === 'string' ? name : '<Anonymous Job ' + (++anonJobCounter) + '>';
  this.job = name && typeof name === 'function' ? name : job;
  // Make sure callback is actually a callback
  if (this.job === name) {
    // Name wasn't provided and maybe a callback is there
    this.callback = typeof job === 'function' ? job : false;
  } else {
    // Name was provided, and maybe a callback is there
    this.callback = typeof callback === 'function' ? callback : false;
  }
  // Check for generator
  if (typeof this.job === 'function' &&
      this.job.prototype &&
      this.job.prototype.next) {
    this.job = function() {
      return this.next().value;
    }.bind(this.job.call(this));
  }
  // define properties
  Object.defineProperty(this, 'name', {
    value: jobName,
    writable: false,
    enumerable: true
  });
  // method that require private access
  this.trackInvocation = function(invocation) {
    // add to our invocation list
    pendingInvocations.push(invocation);
    // and sort
    pendingInvocations.sort(sorter);
    return true;
  };
  this.stopTrackingInvocation = function(invocation) {
    var invIdx = pendingInvocations.indexOf(invocation);
    if (invIdx > -1) {
      pendingInvocations.splice(invIdx, 1);
      return true;
    }
    return false;
  };
  this.triggeredJobs = function() {
    return triggeredJobs;
  };
  this.setTriggeredJobs = function(triggeredJob) {
    triggeredJobs = triggeredJob;
  };
  this.cancel = function(reschedule) {
    reschedule = (typeof reschedule == 'boolean') ? reschedule : false;
    var inv, newInv;
    var newInvs = [];
    for (var j = 0; j < pendingInvocations.length; j++) {
      inv = pendingInvocations[j];
      cancelInvocation(inv);
      if (reschedule && inv.recurrenceRule.recurs) {
        newInv = scheduleNextRecurrence(inv.recurrenceRule, this, inv.fireDate, inv.endDate);
        if (newInv !== null) {
          newInvs.push(newInv);
        }
      }
    }
    pendingInvocations = [];
    for (var k = 0; k < newInvs.length; k++) {
      this.trackInvocation(newInvs[k]);
    }
    // remove from scheduledJobs if reschedule === false
    if (!reschedule) {
      if (this.name) {
        delete scheduledJobs[this.name];
      }
    }
    return true;
  };
  this.cancelNext = function(reschedule) {
    reschedule = (typeof reschedule == 'boolean') ? reschedule : true;
    if (!pendingInvocations.length) {
      return false;
    }
    var newInv;
    var nextInv = pendingInvocations.shift();
    cancelInvocation(nextInv);
    if (reschedule && nextInv.recurrenceRule.recurs) {
      newInv = scheduleNextRecurrence(nextInv.recurrenceRule, this, nextInv.fireDate, nextInv.endDate);
      if (newInv !== null) {
        this.trackInvocation(newInv);
      }
    }
    return true;
  };
  this.reschedule = function(spec) {
    var inv;
    var cInvs = pendingInvocations.slice();
    for (var j = 0; j < cInvs.length; j++) {
      inv = cInvs[j];
      cancelInvocation(inv);
    }
    pendingInvocations = [];
    if (this.schedule(spec)) {
      this.setTriggeredJobs(0);
      return true;
    } else {
      pendingInvocations = cInvs;
      return false;
    }
  };
  this.nextInvocation = function() {
    if (!pendingInvocations.length) {
      return null;
    }
    return pendingInvocations[0].fireDate;
  };
  this.pendingInvocations = function() {
    return pendingInvocations;
  };
}
util.inherits(Job, events.EventEmitter);
Job.prototype.invoke = function() {
  if (typeof this.job == 'function') {
    this.setTriggeredJobs(this.triggeredJobs() + 1);
    this.job();
  } else {
    this.job.execute();
  }
};
Job.prototype.runOnDate = function(date) {
  return this.schedule(date);
};
Job.prototype.schedule = function(spec) {
  var self = this;
  var success = false;
  var inv;
  var start;
  var end;
  if (typeof spec === 'object' && spec.rule) {
    start = spec.start || null;
    end = spec.end || null;
    spec = spec.rule;
    if (start != null) {
      if (!(start instanceof Date)) {
        start = new Date(start);
      }
      if (!isValidDate(start) || start.getTime() < Date.now()) {
        start = null;
      }
    }
    if (end != null && !(end instanceof Date) && !isValidDate(end = new Date(end))) {
      end = null;
    }
  }
  try {
    var res = cronParser.parseExpression(spec, { currentDate: start });
    inv = scheduleNextRecurrence(res, self, start, end);
    if (inv !== null) {
      success = self.trackInvocation(inv);
    }
  } catch (err) {
    var type = typeof spec;
    if ((type === 'string') || (type === 'number')) {
      spec = new Date(spec);
    }
    if ((spec instanceof Date) && (isValidDate(spec))) {
      if (spec.getTime() >= Date.now()) {
        inv = new Invocation(self, spec);
        scheduleInvocation(inv);
        success = self.trackInvocation(inv);
      }
    } else if (type === 'object') {
      if (!(spec instanceof RecurrenceRule)) {
        var r = new RecurrenceRule();
        if ('year' in spec) {
          r.year = spec.year;
        }
        if ('month' in spec) {
          r.month = spec.month;
        }
        if ('date' in spec) {
          r.date = spec.date;
        }
        if ('dayOfWeek' in spec) {
          r.dayOfWeek = spec.dayOfWeek;
        }
        if ('hour' in spec) {
          r.hour = spec.hour;
        }
        if ('minute' in spec) {
          r.minute = spec.minute;
        }
        if ('second' in spec) {
          r.second = spec.second;
        }
        spec = r;
      }
      inv = scheduleNextRecurrence(spec, self, start, end);
      if (inv !== null) {
        success = self.trackInvocation(inv);
      }
    }
  }
  scheduledJobs[this.name] = this;
  return success;
};
/* API
  invoke()
  runOnDate(date)
  schedule(date || recurrenceRule || cronstring)
  cancel(reschedule = false)
  cancelNext(reschedule = true)
   Property constraints
  name: readonly
  job: readwrite
*/
/* DoesntRecur rule */
var DoesntRecur = new RecurrenceRule();
DoesntRecur.recurs = false;
/* Invocation object */
function Invocation(job, fireDate, recurrenceRule, endDate) {
  this.job = job;
  this.fireDate = fireDate;
  this.endDate = endDate;
  this.recurrenceRule = recurrenceRule || DoesntRecur;
  this.timerID = null;
}
function sorter(a, b) {
  return (a.fireDate.getTime() - b.fireDate.getTime());
}
/* Range object */
function Range(start, end, step) {
  this.start = start || 0;
  this.end = end || 60;
  this.step = step || 1;
}
Range.prototype.contains = function(val) {
  if (this.step === null || this.step === 1) {
    return (val >= this.start && val <= this.end);
  } else {
    for (var i = this.start; i < this.end; i += this.step) {
      if (i === val) {
        return true;
      }
    }
    return false;
  }
};
/* RecurrenceRule object */
/*
  Interpreting each property:
  null - any value is valid
  number - fixed value
  Range - value must fall in range
  array - value must validate against any item in list
  NOTE: Cron months are 1-based, but RecurrenceRule months are 0-based.
*/
function RecurrenceRule(year, month, date, dayOfWeek, hour, minute, second) {
  this.recurs = true;
  this.year = (year == null) ? null : year;
  this.month = (month == null) ? null : month;
  this.date = (date == null) ? null : date;
  this.dayOfWeek = (dayOfWeek == null) ? null : dayOfWeek;
  this.hour = (hour == null) ? null : hour;
  this.minute = (minute == null) ? null : minute;
  this.second = (second == null) ? 0 : second;
}
RecurrenceRule.prototype.nextInvocationDate = function(base) {
  base = (base instanceof Date) ? base : (new Date());
  if (!this.recurs) {
    return null;
  }
  var now = new Date();
  var fullYear = now.getFullYear();
  if ((this.year !== null) &&
      (typeof this.year == 'number') &&
      (this.year < fullYear)) {
    return null;
  }
  var next = new CronDate(base.getTime());
  next.addSecond();
  while (true) {
    if (this.year !== null) {
      fullYear = next.getFullYear();
      if ((typeof this.year == 'number') && (this.year < fullYear)) {
        next = null;
        break;
      }
      if (!recurMatch(fullYear, this.year)) {
        next.addYear();
        next.setMonth(0);
        next.setDate(1);
        next.setHours(0);
        next.setMinutes(0);
        next.setSeconds(0);
        continue;
      }
    }
    if (this.month != null && !recurMatch(next.getMonth(), this.month)) {
      next.addMonth();
      continue;
    }
    if (this.date != null && !recurMatch(next.getDate(), this.date)) {
      next.addDay();
      continue;
    }
    if (this.dayOfWeek != null && !recurMatch(next.getDay(), this.dayOfWeek)) {
      next.addDay();
      continue;
    }
    if (this.hour != null && !recurMatch(next.getHours(), this.hour)) {
      next.addHour();
      continue;
    }
    if (this.minute != null && !recurMatch(next.getMinutes(), this.minute)) {
      next.addMinute();
      continue;
    }
    if (this.second != null && !recurMatch(next.getSeconds(), this.second)) {
      next.addSecond();
      continue;
    }
    break;
  }
  return next;
};
function recurMatch(val, matcher) {
  if (matcher == null) {
    return true;
  }
  if (typeof matcher === 'number' || typeof matcher === 'string') {
    return (val === matcher);
  } else if (matcher instanceof Range) {
    return matcher.contains(val);
  } else if (Array.isArray(matcher) || (matcher instanceof Array)) {
    for (var i = 0; i < matcher.length; i++) {
      if (recurMatch(val, matcher[i])) {
        return true;
      }
    }
  }
  return false;
}
/* Date-based scheduler */
function runOnDate(date, job) {
  var now = (new Date()).getTime();
  var then = date.getTime();
  if (then < now) {
    setImmediate(job);
    return null;
  }
  return lt.setTimeout(job, (then - now));
}
var invocations = [];
var currentInvocation = null;
function scheduleInvocation(invocation) {
  invocations.push(invocation);
  invocations.sort(sorter);
  prepareNextInvocation();
  invocation.job.emit('scheduled', invocation.fireDate);
}
function prepareNextInvocation() {
  if (invocations.length > 0 && currentInvocation !== invocations[0]) {
    if (currentInvocation !== null) {
      lt.clearTimeout(currentInvocation.timerID);
      currentInvocation.timerID = null;
      currentInvocation = null;
    }
    currentInvocation = invocations[0];
    var job = currentInvocation.job;
    var cinv = currentInvocation;
    currentInvocation.timerID = runOnDate(currentInvocation.fireDate, function() {
      currentInvocationFinished();
      if (job.callback) {
        job.callback();
      }
      if (cinv.recurrenceRule.recurs || cinv.recurrenceRule._endDate === null) {
        var inv = scheduleNextRecurrence(cinv.recurrenceRule, cinv.job, cinv.fireDate, cinv.endDate);
        if (inv !== null) {
          inv.job.trackInvocation(inv);
        }
      }
      job.stopTrackingInvocation(cinv);
      job.invoke();
      job.emit('run');
    });
  }
}
function currentInvocationFinished() {
  invocations.shift();
  currentInvocation = null;
  prepareNextInvocation();
}
function cancelInvocation(invocation) {
  var idx = invocations.indexOf(invocation);
  if (idx > -1) {
    invocations.splice(idx, 1);
    if (invocation.timerID !== null) {
      lt.clearTimeout(invocation.timerID);
    }
    if (currentInvocation === invocation) {
      currentInvocation = null;
    }
    invocation.job.emit('canceled', invocation.fireDate);
    prepareNextInvocation();
  }
}
/* Recurrence scheduler */
function scheduleNextRecurrence(rule, job, prevDate, endDate) {
  prevDate = (prevDate instanceof Date) ? prevDate : (new Date());
  var date = (rule instanceof RecurrenceRule) ? rule.nextInvocationDate(prevDate) : rule.next();
  if (date === null) {
    return null;
  }
  if ((endDate instanceof Date) && date.getTime() > endDate.getTime()) {
    return null;
  }
  var inv = new Invocation(job, date, rule, endDate);
  scheduleInvocation(inv);
  return inv;
}
/* Convenience methods */
var scheduledJobs = {};
function scheduleJob() {
  if (arguments.length < 2) {
    return null;
  }
  var name = (arguments.length >= 3 && typeof arguments[0] === 'string') ? arguments[0] : null;
  var spec = name ? arguments[1] : arguments[0];
  var method = name ? arguments[2] : arguments[1];
  var callback = name ? arguments[3] : arguments[2];
  var job = new Job(name, method, callback);
  if (job.schedule(spec)) {
    return job;
  }
  return null;
}
function rescheduleJob(job, spec) {
  if (job instanceof Job) {
    if (job.reschedule(spec)) {
      return job;
    }
  } else if (typeof job == 'string' || job instanceof String) {
    if (job in scheduledJobs && scheduledJobs.hasOwnProperty(job)) {
      if (scheduledJobs[job].reschedule(spec)) {
        return scheduledJobs[job];
      }
    }
  }
  return null;
}
function cancelJob(job) {
  var success = false;
  if (job instanceof Job) {
    success = job.cancel();
  } else if (typeof job == 'string' || job instanceof String) {
    if (job in scheduledJobs && scheduledJobs.hasOwnProperty(job)) {
      success = scheduledJobs[job].cancel();
    }
  }
  return success;
}
/* Public API */
module.exports.Job = Job;
module.exports.Range = Range;
module.exports.RecurrenceRule = RecurrenceRule;
module.exports.Invocation = Invocation;
module.exports.scheduleJob = scheduleJob;
module.exports.rescheduleJob = rescheduleJob;
module.exports.scheduledJobs = scheduledJobs;
module.exports.cancelJob = cancelJob;

+ 11 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/component.json

@ -0,0 +1,11 @@
{
  "name": "cron-parser",
  "repo": "harrisiirak/cron-parser",
  "description": "Node.js library for parsing crontab instructions",
  "version": "1.1.0",
  "keywords": ["cron", "crontab", "parser"],
  "dependencies": {},
  "development": {},
  "main": "lib/parser.js",
  "scripts": [ "lib/parser.js", "lib/expression.js", "lib/date.js" ]
}

+ 79 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/lib/date.js

@ -0,0 +1,79 @@
'use strict';
/**
 * Date class extension methods
 */
var extensions = {
  addYear: function addYear() {
    this.setFullYear(this.getFullYear() + 1);
  },
  addMonth: function addMonth() {
    this.setDate(1);
    this.setHours(0);
    this.setMinutes(0);
    this.setSeconds(0);
    this.setMonth(this.getMonth() + 1);
  },
  addDay: function addDay() {
    var day = this.getDate();
    this.setDate(day + 1);
    this.setHours(0);
    this.setMinutes(0);
    this.setSeconds(0);
    if (this.getDate() === day) {
      this.setDate(day + 2);
    }
  },
  addHour: function addHour() {
    var hours = this.getHours();
    this.setHours(hours + 1);
    if (this.getHours() === hours) {
      this.setHours(hours + 2);
    }
    this.setMinutes(0);
    this.setSeconds(0);
  },
  addMinute: function addMinute() {
    this.setMinutes(this.getMinutes() + 1);
    this.setSeconds(0);
  },
  addSecond: function addSecond() {
    this.setSeconds(this.getSeconds() + 1);
  },
  toUTC: function toUTC() {
    var to = new CronDate(this);
    var ms = to.getTime() + (to.getTimezoneOffset() * 60000);
    to.setTime(ms);
    return to;
  }
};
/**
 * Extends Javascript Date class by adding
 * utility methods for basic date incrementation
 */
function CronDate (timestamp) {
  var date = timestamp ? new Date(timestamp) : new Date();
  // Attach extensions
  var methods = Object.keys(extensions);
  for (var i = 0, c = methods.length; i < c; i++) {
    var method = methods[i];
    date[method] = extensions[method].bind(date);
  }
  return date;
}
module.exports = CronDate;

+ 611 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/lib/expression.js

@ -0,0 +1,611 @@
'use strict';
// Load Date class extensions
var CronDate = require('./date');
/**
 * Construct a new expression parser
 *
 * Options:
 *   currentDate: iterator start date
 *   endDate: iterator end date
 *
 * @constructor
 * @private
 * @param {Object} fields  Expression fields parsed values
 * @param {Object} options Parser options
 */
function CronExpression (fields, options) {
  this._options = options;
  this._currentDate = new CronDate(options.currentDate);
  this._endDate = options.endDate ? new CronDate(options.endDate) : null;
  this._fields = {};
  this._isIterator = options.iterator || false;
  this._hasIterated = false;
  this._utc = options.utc || false;
  // Map fields
  for (var i = 0, c = CronExpression.map.length; i < c; i++) {
    var key = CronExpression.map[i];
    this._fields[key] = fields[i];
  }
}
/**
 * Field mappings
 * @type {Array}
 */
CronExpression.map = [ 'second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek' ];
/**
 * Prefined intervals
 * @type {Object}
 */
CronExpression.predefined = {
  '@yearly': '0 0 1 1 *',
  '@monthly': '0 0 1 * *',
  '@weekly': '0 0 * * 0',
  '@daily': '0 0 * * *',
  '@hourly': '0 * * * *'
};
/**
 * Fields constraints
 * @type {Array}
 */
CronExpression.constraints = [
  [ 0, 59 ], // Second
  [ 0, 59 ], // Minute
  [ 0, 23 ], // Hour
  [ 1, 31 ], // Day of month
  [ 1, 12 ], // Month
  [ 0, 7 ] // Day of week
];
/**
 * Days in month
 * @type {number[]}
 */
CronExpression.daysInMonth = [
  31,
  28,
  31,
  30,
  31,
  30,
  31,
  31,
  30,
  31,
  30,
  31
];
/**
 * Field aliases
 * @type {Object}
 */
CronExpression.aliases = {
  month: {
    jan: 1,
    feb: 2,
    mar: 3,
    apr: 4,
    may: 5,
    jun: 6,
    jul: 7,
    aug: 8,
    sep: 9,
    oct: 10,
    nov: 11,
    dec: 12
  },
  dayOfWeek: {
    sun: 0,
    mon: 1,
    tue: 2,
    wed: 3,
    thu: 4,
    fri: 5,
    sat: 6
  }
};
/**
 * Field defaults
 * @type {Array}
 */
CronExpression.parseDefaults = [ '0', '*', '*', '*', '*', '*' ];
/**
 * Parse input interval
 *
 * @param {String} field Field symbolic name
 * @param {String} value Field value
 * @param {Array} constraints Range upper and lower constraints
 * @return {Array} Sequence of sorted values
 * @private
 */
CronExpression._parseField = function _parseField (field, value, constraints) {
  // Replace aliases
  switch (field) {
    case 'month':
    case 'dayOfWeek':
      var aliases = CronExpression.aliases[field];
      value = value.replace(/[a-z]{1,3}/gi, function(match) {
        match = match.toLowerCase();
        if (typeof aliases[match] !== undefined) {
          return aliases[match];
        } else {
          throw new Error('Cannot resolve alias "' + match + '"')
        }
      });
      break;
  }
  // Check for valid characters.
  if (!(/^[\d|/|*|\-|,]+$/.test(value))) {
    throw new Error('Invalid characters, got value: ' + value)
  }
  // Replace '*'
  if (value.indexOf('*') !== -1) {
    value = value.replace(/\*/g, constraints.join('-'));
  }
  //
  // Inline parsing functions
  //
  // Parser path:
  //  - parseSequence
  //    - parseRepeat
  //      - parseRange
  /**
   * Parse sequence
   *
   * @param {String} val
   * @return {Array}
   * @private
   */
  function parseSequence (val) {
    var stack = [];
    function handleResult (result) {
      var max = stack.length > 0 ? Math.max.apply(Math, stack) : -1;
      if (result instanceof Array) { // Make sequence linear
        for (var i = 0, c = result.length; i < c; i++) {
          var value = result[i];
          // Check constraints
          if (value < constraints[0] || value > constraints[1]) {
            throw new Error(
                'Constraint error, got value ' + value + ' expected range ' +
                constraints[0] + '-' + constraints[1]
            );
          }
          if (value > max) {
            stack.push(value);
          }
          max = Math.max.apply(Math, stack);
        }
      } else { // Scalar value
        result = +result;
        // Check constraints
        if (result < constraints[0] || result > constraints[1]) {
          throw new Error(
            'Constraint error, got value ' + result + ' expected range ' +
            constraints[0] + '-' + constraints[1]
          );
        }
        if (field == 'dayOfWeek') {
          result = result % 7;
        }
        if (result > max) {
          stack.push(result);
        }
      }
    }
    var atoms = val.split(',');
    if (atoms.length > 1) {
      for (var i = 0, c = atoms.length; i < c; i++) {
        handleResult(parseRepeat(atoms[i]));
      }
    } else {
      handleResult(parseRepeat(val));
    }
    return stack;
  }
  /**
   * Parse repetition interval
   *
   * @param {String} val
   * @return {Array}
   */
  function parseRepeat (val) {
    var repeatInterval = 1;
    var atoms = val.split('/');
    if (atoms.length > 1) {
      return parseRange(atoms[0], atoms[atoms.length - 1]);
    }
    return parseRange(val, repeatInterval);
  }
  /**
   * Parse range
   *
   * @param {String} val
   * @param {Number} repeatInterval Repetition interval
   * @return {Array}
   * @private
   */
  function parseRange (val, repeatInterval) {
    var stack = [];
    var atoms = val.split('-');
    if (atoms.length > 1 ) {
      // Invalid range, return value
      if (atoms.length < 2 || !atoms[0].length) {
        return +val;
      }
      // Validate range
      var min = +atoms[0];
      var max = +atoms[1];
      if (Number.isNaN(min) || Number.isNaN(max) ||
          min < constraints[0] || max > constraints[1]) {
        throw new Error(
          'Constraint error, got range ' +
          min + '-' + max +
          ' expected range ' +
          constraints[0] + '-' + constraints[1]
        );
      } else if (min >= max) {
        throw new Error('Invalid range: ' + val);
      }
      // Create range
      var repeatIndex = +repeatInterval;
      if (Number.isNaN(repeatIndex) || repeatIndex <= 0) {
        throw new Error('Constraint error, cannot repeat at every ' + repeatIndex + ' time.');
      }
      for (var index = min, count = max; index <= count; index++) {
        if (repeatIndex > 0 && (repeatIndex % repeatInterval) === 0) {
          repeatIndex = 1;
          stack.push(index);
        } else {
          repeatIndex++;
        }
      }
      return stack;
    }
    return +val;
  }
  return parseSequence(value);
};
/**
 * Find next matching schedule date
 *
 * @return {CronDate}
 * @private
 */
CronExpression.prototype._findSchedule = function _findSchedule () {
  /**
   * Match field value
   *
   * @param {String} value
   * @param {Array} sequence
   * @return {Boolean}
   * @private
   */
  function matchSchedule (value, sequence) {
    for (var i = 0, c = sequence.length; i < c; i++) {
      if (sequence[i] >= value) {
        return sequence[i] === value;
      }
    }
    return sequence[0] === value;
  }
  /**
   * Detect if input range fully matches constraint bounds
   * @param {Array} range Input range
   * @param {Array} constraints Input constraints
   * @returns {Boolean}
   * @private
   */
  function isWildcardRange (range, constraints) {
    if (range instanceof Array && !range.length) {
      return false;
    }
    if (constraints.length !== 2) {
      return false;
    }
    return range.length === (constraints[1] - (constraints[0] < 1 ? - 1 : 0));
  }
  
  var method = function(name) {
    return !this._utc ? name : ('getUTC' + name.slice(3));
  }.bind(this);
  var currentDate = new CronDate(this._currentDate);
  var endDate = this._endDate;
  // TODO: Improve this part
  // Always increment second value when second part is present
  if (this._fields.second.length > 1 && !this._hasIterated) {
    currentDate.addSecond();
  }
  // Find matching schedule
  while (true) {
    // Validate timespan
    if (endDate && (endDate.getTime() - currentDate.getTime()) < 0) {
      throw new Error('Out of the timespan range');
    }
    // Day of month and week matching:
    //
    // "The day of a command's execution can be specified by two fields --
    // day of month, and day of week.  If  both	 fields	 are  restricted  (ie,
    // aren't  *),  the command will be run when either field matches the cur-
    // rent time.  For example, "30 4 1,15 * 5" would cause a command to be
    // run at 4:30 am on the  1st and 15th of each month, plus every Friday."
    //
    // http://unixhelp.ed.ac.uk/CGI/man-cgi?crontab+5
    //
    var dayOfMonthMatch = matchSchedule(currentDate[method('getDate')](), this._fields.dayOfMonth);
    var dayOfWeekMatch = matchSchedule(currentDate[method('getDay')](), this._fields.dayOfWeek);
    var isDayOfMonthWildcardMatch = isWildcardRange(this._fields.dayOfMonth, CronExpression.constraints[3]);
    var isMonthWildcardMatch = isWildcardRange(this._fields.month, CronExpression.constraints[4]);
    var isDayOfWeekWildcardMatch = isWildcardRange(this._fields.dayOfWeek, CronExpression.constraints[5]);
    // Validate days in month if explicit value is given
    if (!isMonthWildcardMatch) {
      var currentYear = currentDate[method('getFullYear')]();
      var currentMonth = currentDate[method('getMonth')]() + 1;
      var previousMonth = currentMonth === 1 ? 11 : currentMonth - 1;
      var daysInPreviousMonth = CronExpression.daysInMonth[previousMonth - 1];
      var daysOfMontRangeMax = this._fields.dayOfMonth[this._fields.dayOfMonth.length - 1];
      var _daysInPreviousMonth = daysInPreviousMonth;
      var _daysOfMontRangeMax = daysOfMontRangeMax;
      // Handle leap year
      var isLeap = !((currentYear % 4) || (!(currentYear % 100) && (currentYear % 400)));
      if (isLeap) {
        _daysInPreviousMonth = 29;
        _daysOfMontRangeMax = 29;
      }
      if (this._fields.month[0] === previousMonth && _daysInPreviousMonth < _daysOfMontRangeMax) {
        throw new Error('Invalid explicit day of month definition');
      }
    }
    // Add day if select day not match with month (according to calendar)
    if (!dayOfMonthMatch || !dayOfWeekMatch) {
      currentDate.addDay();
      continue;
    }
    // Add day if not day of month is set (and no match) and day of week is wildcard
    if (!isDayOfMonthWildcardMatch && isDayOfWeekWildcardMatch && !dayOfMonthMatch) {
      currentDate.addDay();
      continue;
    }
    // Add day if not day of week is set (and no match) and day of month is wildcard
    if (isDayOfMonthWildcardMatch && !isDayOfWeekWildcardMatch && !dayOfWeekMatch) {
      currentDate.addDay();
      continue;
    }
    // Add day if day of mont and week are non-wildcard values and both doesn't match
    if (!(isDayOfMonthWildcardMatch && isDayOfWeekWildcardMatch) &&
        !dayOfMonthMatch && !dayOfWeekMatch) {
      currentDate.addDay();
      continue;
    }
    // Match month
    if (!matchSchedule(currentDate[method('getMonth')]() + 1, this._fields.month)) {
      currentDate.addMonth();
      continue;
    }
    // Match hour
    if (!matchSchedule(currentDate[method('getHours')](), this._fields.hour)) {
      currentDate.addHour();
      continue;
    }
    // Match minute
    if (!matchSchedule(currentDate[method('getMinutes')](), this._fields.minute)) {
      currentDate.addMinute();
      continue;
    }
    // Match second
    if (!matchSchedule(currentDate[method('getSeconds')](), this._fields.second)) {
      currentDate.addSecond();
      continue;
    }
    break;
  }
  // When internal date is not mutated, append one second as a padding
  var nextDate = new CronDate(currentDate);
  if (this._currentDate !== currentDate) {
    nextDate.addSecond();
  }
  this._currentDate = nextDate;
  this._hasIterated = true;
  return currentDate;
};
/**
 * Find next suitable date
 *
 * @public
 * @return {CronDate|Object}
 */
CronExpression.prototype.next = function next () {
  var schedule = this._findSchedule();
  // Try to return ES6 compatible iterator
  if (this._isIterator) {
    return {
      value: schedule,
      done: !this.hasNext()
    };
  }
  return schedule;
};
/**
 * Check if next suitable date exists
 *
 * @public
 * @return {Boolean}
 */
CronExpression.prototype.hasNext = function() {
  var current = this._currentDate;
  try {
    this.next();
    return true;
  } catch (err) {
    return false;
  } finally {
    this._currentDate = current;
  }
};
/**
 * Iterate over expression iterator
 *
 * @public
 * @param {Number} steps Numbers of steps to iterate
 * @param {Function} callback Optional callback
 * @return {Array} Array of the iterated results
 */
CronExpression.prototype.iterate = function iterate (steps, callback) {
  var dates = [];
  for (var i = 0, c = steps; i < c; i++) {
    try {
      var item = this.next();
      dates.push(item);
      // Fire the callback
      if (callback) {
        callback(item, i);
      }
    } catch (err) {
      break;
    }
  }
  return dates;
};
/**
 * Reset expression iterator state
 *
 * @public
 */
CronExpression.prototype.reset = function reset () {
  this._currentDate = new CronDate(this._options.currentDate);
};
/**
 * Parse input expression (async)
 *
 * @public
 * @param {String} expression Input expression
 * @param {Object} [options] Parsing options
 * @param {Function} [callback]
 */
CronExpression.parse = function parse (expression, options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }
  function parse (expression, options) {
    if (!options) {
      options = {};
    }
    if (!options.currentDate) {
      options.currentDate = new CronDate();
    }
    // Is input expression predefined?
    if (CronExpression.predefined[expression]) {
      expression = CronExpression.predefined[expression];
    }
    // Split fields
    var fields = [];
    var atoms = expression.split(' ');
    // Resolve fields
    var start = (CronExpression.map.length - atoms.length);
    for (var i = 0, c = CronExpression.map.length; i < c; ++i) {
      var field = CronExpression.map[i]; // Field name
      var value = atoms[atoms.length > c ? i : i - start]; // Field value
      if (i < start || !value) {
        fields.push(CronExpression._parseField(
          field,
          CronExpression.parseDefaults[i],
          CronExpression.constraints[i])
        );
      } else { // Use default value
        fields.push(CronExpression._parseField(
          field,
          value,
          CronExpression.constraints[i])
        );
      }
    }
    return new CronExpression(fields, options);
  }
  return parse(expression, options);
};
module.exports = CronExpression;

+ 103 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/lib/parser.js

@ -0,0 +1,103 @@
'use strict';
var CronExpression = require('./expression');
function CronParser() {}
/**
 * Parse crontab entry
 *
 * @private
 * @param {String} entry Crontab file entry/line
 */
CronParser._parseEntry = function _parseEntry (entry) {
  var atoms = entry.split(' ');
  if (atoms.length === 6) {
    return {
      interval: CronExpression.parse(entry)
    };
  } else if (atoms.length > 6) {
    return {
      interval: CronExpression.parse(entry),
      command: atoms.slice(6, atoms.length)
    };
  } else {
    throw new Error('Invalid entry: ' + entry);
  }
};
/**
 * Wrapper for CronExpression.parser method
 *
 * @public
 * @param {String} expression Input expression
 * @param {Object} [options] Parsing options
 * @return {Object}
 */
CronParser.parseExpression = function parseExpression (expression, options, callback) {
  return CronExpression.parse(expression, options, callback);
};
/**
 * Parse content string
 *
 * @public
 * @param {String} data Crontab content
 * @return {Object}
 */
CronParser.parseString = function parseString (data) {
  var self = this;
  var blocks = data.split('\n');
  var response = {
    variables: {},
    expressions: [],
    errors: {}
  };
  for (var i = 0, c = blocks.length; i < c; i++) {
    var block = blocks[i];
    var matches = null;
    var entry = block.replace(/^\s+|\s+$/g, ''); // Remove surrounding spaces
    if (entry.length > 0) {
      if (entry.match(/^#/)) { // Comment
        continue;
      } else if ((matches = entry.match(/^(.*)=(.*)$/))) { // Variable
        response.variables[matches[1]] = matches[2];
      } else { // Expression?
        var result = null;
        try {
          result = self._parseEntry('0 ' + entry);
          response.expressions.push(result.interval);
        } catch (err) {
          response.errors[entry] = err;
        }
      }
    }
  }
  return response;
};
/**
 * Parse crontab file
 *
 * @public
 * @param {String} filePath Path to file
 * @param {Function} callback
 */
CronParser.parseFile = function parseFile (filePath, callback) {
  require('fs').readFile(filePath, function(err, data) {
    if (err) {
      callback(err);
      return;
    }
    return callback(null, CronParser.parseString(data.toString()));
  });
};
module.exports = CronParser;

+ 108 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/package.json

@ -0,0 +1,108 @@
{
  "name": "cron-parser",
  "version": "1.1.0",
  "description": "Node.js library for parsing crontab instructions",
  "main": "lib/parser.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "tap ./test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/harrisiirak/cron-parser.git"
  },
  "keywords": [
    "cron",
    "crontab",
    "parser"
  ],
  "author": {
    "name": "Harri Siirak"
  },
  "contributors": [
    {
      "name": "Daniel Prentis",
      "email": "daniel@salsitasoft.com"
    },
    {
      "name": "Renault John Lecoultre"
    },
    {
      "name": "Richard Astbury",
      "email": "richard.astbury@gmail.com"
    },
    {
      "name": "Meaglin Wasabi",
      "email": "Meaglin.wasabi@gmail.com"
    },
    {
      "name": "Mike Kusold",
      "email": "hello@mikekusold.com"
    },
    {
      "name": "Alex Kit",
      "email": "alex.kit@atmajs.com"
    },
    {
      "name": "Santiago Gimeno",
      "email": "santiago.gimeno@gmail.com"
    },
    {
      "name": "Daniel",
      "email": "darc.tec@gmail.com"
    },
    {
      "name": "Christian Steininger",
      "email": "christian.steininger.cs@gmail.com"
    },
    {
      "name": "Mykola Piskovyi",
      "email": "m.piskovyi@gmail.com"
    },
    {
      "name": "Brian Vaughn",
      "email": "brian.david.vaughn@gmail.com"
    }
  ],
  "license": "MIT",
  "devDependencies": {
    "tap": "^0.5.0"
  },
  "engines": {
    "node": ">=0.8"
  },
  "browser": {
    "fs": false
  },
  "gitHead": "7e3364bf134d61d2ac77445b6b8193f9d449b29d",
  "bugs": {
    "url": "https://github.com/harrisiirak/cron-parser/issues"
  },
  "homepage": "https://github.com/harrisiirak/cron-parser#readme",
  "_id": "cron-parser@1.1.0",
  "_shasum": "075b84c459c155e8c482ab4d56aff99dae58352e",
  "_from": "cron-parser@1.1.0",
  "_npmVersion": "3.3.12",
  "_nodeVersion": "5.2.0",
  "_npmUser": {
    "name": "harrisiirak",
    "email": "harri.siirak@gmail.com"
  },
  "maintainers": [
    {
      "name": "harrisiirak",
      "email": "harri.siirak@gmail.com"
    }
  ],
  "dist": {
    "shasum": "075b84c459c155e8c482ab4d56aff99dae58352e",
    "size": 13032,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/cron-parser/download/cron-parser-1.1.0.tgz"
  },
  "publish_time": 1450176878007,
  "_cnpm_publish_time": 1450176878007,
  "_resolved": "https://registry.npm.taobao.org/cron-parser/download/cron-parser-1.1.0.tgz"
}

+ 16 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/test/31_of_month.js

@ -0,0 +1,16 @@
var util = require('util');
var test = require('tap').test;
var expression = require('../lib/expression');
test('expression 31 of month', function(t) {
  try {
    var interval = expression.parse('0 0 31 * *');
    var i;
    for (i = 0; i < 100; ++i) {
      interval.next();
    }
    t.end();
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
});

+ 787 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/test/expression.js

@ -0,0 +1,787 @@
var util = require('util');
var test = require('tap').test;
var CronExpression = require('../lib/expression');
var CronDate = require('../lib/date');
test('empty expression test', function(t) {
  try {
    var interval = CronExpression.parse('');
    t.ok(interval, 'Interval parsed');
    var date = new CronDate();
    date.addMinute();
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
    t.end();
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
});
test('default expression test', function(t) {
  try {
    var interval = CronExpression.parse('* * * * *');
    t.ok(interval, 'Interval parsed');
    var date = new CronDate();
    date.addMinute();
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getMinutes(), date.getMinutes(), 'Schedule matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('second value out of the range', function(t) {
  try {
    CronExpression.parse('61 * * * * *');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got value 61 expected range 0-59');
  }
  t.end();
});
test('second value out of the range', function(t) {
  try {
    CronExpression.parse('-1 * * * * *');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got value -1 expected range 0-59');
  }
  t.end();
});
test('minute value out of the range', function(t) {
  try {
    CronExpression.parse('* 32,72 * * * *');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got value 72 expected range 0-59');
  }
  t.end();
});
test('hour value out of the range', function(t) {
  try {
    CronExpression.parse('* * 12-36 * * *');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got range 12-36 expected range 0-23');
  }
  t.end();
});
test('day of the month value out of the range', function(t) {
  try {
    CronExpression.parse('* * * 10-15,40 * *');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got value 40 expected range 1-31');
  }
  t.end();
});
test('month value out of the range', function(t) {
  try {
    CronExpression.parse('* * * * */10,12-13 *');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got range 12-13 expected range 1-12');
  }
  t.end();
});
test('day of the week value out of the range', function(t) {
  try {
    CronExpression.parse('* * * * * 9');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, got value 9 expected range 0-7');
  }
  t.end();
});
test('incremental minutes expression test', function(t) {
  try {
    var interval = CronExpression.parse('*/3 * * * *');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getMinutes() % 3, 0, 'Schedule matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('fixed expression test', function(t) {
  try {
    var interval = CronExpression.parse('10 2 12 8 0');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    t.equal(next.getHours(), 2, 'Hour matches');
    t.equal(next.getMinutes(), 10, 'Minute matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('invalid characters test - symbol', function(t) {
  try {
    CronExpression.parse('10 ! 12 8 0');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Invalid characters, got value: !');
  }
  t.end();
});
test('invalid characters test - letter', function(t) {
  try {
    CronExpression.parse('10 x 12 8 0');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Invalid characters, got value: x');
  }
  t.end();
});
test('invalid characters test - parentheses', function(t) {
  try {
    CronExpression.parse('10 ) 12 8 0');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Invalid characters, got value: )');
  }
  t.end();
});
test('interval with invalid characters test', function(t) {
  try {
    CronExpression.parse('10 */A 12 8 0');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Invalid characters, got value: */A');
  }
  t.end();
});
test('range with invalid characters test', function(t) {
  try {
    CronExpression.parse('10 0-z 12 8 0');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Invalid characters, got value: 0-z');
  }
  t.end();
});
test('group with invalid characters test', function(t) {
  try {
    CronExpression.parse('10 0,1,z 12 8 0');
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Invalid characters, got value: 0,1,z');
  }
  t.end();
});
test('range test with iterator', function(t) {
  try {
    var interval = CronExpression.parse('10-30 2 12 8 0');
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(20);
    t.ok(intervals, 'Found intervals');
    for (var i = 0, c = intervals.length; i < c; i++) {
      var next = intervals[i];
      t.ok(next, 'Found next scheduled interval');
      t.equal(next.getDay(), 0, 'Day matches');
      t.equal(next.getMonth(), 7, 'Month matches');
      t.equal(next.getDate(), 12, 'Day of month matches');
      t.equal(next.getHours(), 2, 'Hour matches');
      t.equal(next.getMinutes(), 10 + i, 'Minute matches');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('incremental range test with iterator', function(t) {
  try {
    var interval = CronExpression.parse('10-30/2 2 12 8 0');
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(10);
    t.ok(intervals, 'Found intervals');
    for (var i = 0, c = intervals.length; i < c; i++) {
      var next = intervals[i];
      t.ok(next, 'Found next scheduled interval');
      t.equal(next.getDay(), 0, 'Day matches');
      t.equal(next.getMonth(), 7, 'Month matches');
      t.equal(next.getDate(), 12, 'Day of month matches');
      t.equal(next.getHours(), 2, 'Hour matches');
      t.equal(next.getMinutes(), 10 + (i * 2), 'Minute matches');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('predefined expression', function(t) {
  try {
    var interval = CronExpression.parse('@yearly');
    t.ok(interval, 'Interval parsed');
    var date = new CronDate();
    date.addYear();
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getFullYear(), date.getFullYear(), 'Year matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('expression limited with start and end date', function(t) {
  try {
    var options = {
      currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53'),
      endDate: new CronDate('Wed, 26 Dec 2012 16:40:00')
    };
    var interval = CronExpression.parse('*/20 * * * *', options);
    t.ok(interval, 'Interval parsed');
    var dates = interval.iterate(10);
    t.equal(dates.length, 7, 'Dates count matches');
    interval.reset(); // Reset
    var next = interval.next();
    t.equal(next.getHours(), 14, 'Hour matches');
    t.equal(next.getMinutes(), 40, 'Minute matches');
    next = interval.next();
    t.equal(next.getHours(), 15, 'Hour matches');
    t.equal(next.getMinutes(), 0, 'Minute matches');
    next = interval.next();
    t.equal(next.getHours(), 15, 'Hour matches');
    t.equal(next.getMinutes(), 20, 'Minute matches');
    next = interval.next();
    t.equal(next.getHours(), 15, 'Hour matches');
    t.equal(next.getMinutes(), 40, 'Minute matches');
    next = interval.next();
    t.equal(next.getHours(), 16, 'Hour matches');
    t.equal(next.getMinutes(), 0, 'Minute matches');
    next = interval.next();
    t.equal(next.getHours(), 16, 'Hour matches');
    t.equal(next.getMinutes(), 20, 'Minute matches');
    next = interval.next();
    t.equal(next.getHours(), 16, 'Hour matches');
    t.equal(next.getMinutes(), 40, 'Minute matches');
    try {
      interval.next();
      t.ok(false, 'Should fail');
    } catch (e) {
      t.ok(true, 'Failed as expected');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('parse expression as UTC', function(t) {
  try {
    var options = {
      utc: true
    };
    var interval = CronExpression.parse('0 0 10 * * *', options);
    var date = interval.next();
    t.equal(date.getUTCHours(), 10, 'Correct UTC hour value');
    interval = CronExpression.parse('0 */5 * * * *', options);
    var date = interval.next(), now = new Date();
    now.setMinutes(now.getMinutes() + 5 - (now.getMinutes() % 5));
    t.equal(date.getHours(), now.getHours(), 'Correct local time for 5 minute interval');
  } catch (err) {
    t.ifError(err, 'UTC parse error');
  }
  t.end();
});
test('expression using days of week strings', function(t) {
  try {
    var interval = CronExpression.parse('15 10 * * MON-TUE');
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(8);
    t.ok(intervals, 'Found intervals');
    for (var i = 0, c = intervals.length; i < c; i++) {
      var next = intervals[i];
      var day = next.getDay();
      t.ok(next, 'Found next scheduled interval');
      t.ok(day == 1 || day == 2, 'Day matches')
      t.equal(next.getHours(), 10, 'Hour matches');
      t.equal(next.getMinutes(), 15, 'Minute matches');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('expression using mixed days of week strings', function(t) {
  try {
    var options = {
      currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53')
    };
    var interval = CronExpression.parse('15 10 * jAn-FeB mOn-tUE', options);
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(8);
    t.ok(intervals, 'Found intervals');
    for (var i = 0, c = intervals.length; i < c; i++) {
      var next = intervals[i];
      var day = next.getDay();
      var month = next.getMonth();
      t.ok(next, 'Found next scheduled interval');
      t.ok(month == 0 || month == 2, 'Month Matches');
      t.ok(day == 1 || day == 2, 'Day matches');
      t.equal(next.getHours(), 10, 'Hour matches');
      t.equal(next.getMinutes(), 15, 'Minute matches');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('expression using non-standard second field (wildcard)', function(t) {
  try {
    var options = {
      currentDate: new CronDate('Wed, 26 Dec 2012 14:38:00'),
      endDate: new CronDate('Wed, 26 Dec 2012 15:40:00')
    };
    var interval = CronExpression.parse('* * * * * *', options);
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(10);
    t.ok(intervals, 'Found intervals');
    for (var i = 0, c = intervals.length; i < c; i++) {
      var next = intervals[i];
      t.ok(next, 'Found next scheduled interval');
      t.equal(next.getSeconds(), i + 1, 'Second matches');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('expression using non-standard second field (step)', function(t) {
  try {
    var options = {
      currentDate: new CronDate('Wed, 26 Dec 2012 14:38:00'),
      endDate: new CronDate('Wed, 26 Dec 2012 15:40:00')
    };
    var interval = CronExpression.parse('*/20 * * * * *', options);
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(3);
    t.ok(intervals, 'Found intervals');
    t.equal(intervals[0].getSeconds(), 20, 'Second matches');
    t.equal(intervals[1].getSeconds(), 40, 'Second matches');
    t.equal(intervals[2].getSeconds(), 0, 'Second matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('expression using non-standard second field (range)', function(t) {
  try {
    var options = {
      currentDate: new CronDate('Wed, 26 Dec 2012 14:38:00'),
      endDate: new CronDate('Wed, 26 Dec 2012 15:40:00')
    };
    var interval = CronExpression.parse('20-40/10 * * * * *', options);
    t.ok(interval, 'Interval parsed');
    var intervals = interval.iterate(3);
    t.ok(intervals, 'Found intervals');
    for (var i = 0, c = intervals.length; i < c; i++) {
      var next = intervals[i];
      t.ok(next, 'Found next scheduled interval');
      t.equal(next.getSeconds(), 20 + (i * 10), 'Second matches');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('day of month and week are both set', function(t) {
  try {
    var interval = CronExpression.parse('10 2 12 8 0');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('Summertime bug test', function(t) {
  try {
    var month = new CronDate().getMonth() + 1;
    var interval = CronExpression.parse('0 0 0 1 '+month+' *');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    // Before fix the bug it was getting a timeout error if you are
    // in a timezone that changes the DST to ST in the hour 00:00h.
    t.ok(next, 'Found next scheduled interval');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('day of month and week are both set and dow is 7', function(t) {
  try {
    var interval = CronExpression.parse('10 2 12 8 7');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 0, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('day of month and week are both set and dow is 6,0', function(t) {
  try {
    var interval = CronExpression.parse('10 2 12 8 6,0');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    // next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('day of month and week are both set and dow is 6-7', function(t) {
  try {
    var interval = CronExpression.parse('10 2 12 8 6-7');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
    // next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getDay(), 6, 'Day matches');
    t.equal(next.getMonth(), 7, 'Month matches');
    t.equal(next.getDate(), 12, 'Day of month matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('day and date in week should matches', function(t){
  try {
    var interval = CronExpression.parse('0 1 1 1 * 1');
    t.ok(interval, 'Interval parsed');
    var next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getHours(), 1, 'Hours matches');
    t.equal(next.getDay(), 1, 'Day matches');
    t.equal(next.getDate(), 1, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getHours(), 1, 'Hours matches');
    t.equal(next.getDay(), 1, 'Day matches');
    t.equal(next.getDate(), 1, 'Day of month matches');
    next = interval.next();
    t.ok(next, 'Found next scheduled interval');
    t.equal(next.getHours(), 1, 'Hours matches');
    t.equal(next.getDay(), 1, 'Day matches');
    t.equal(next.getDate(), 1, 'Day of month matches');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('day of month value can\'t be larger than days in month maximum value if it\'s defined explicitly', function(t) {
  try {
    var interval = CronExpression.parse('0 4 30 2 *');
    t.ok(interval, 'Interval parsed');
    try {
      interval.next();
      t.ok(false, 'Should fail');
    } catch (e) {
      t.ok(true, 'Failed as expected');
    }
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('valid ES6 iterator should be returned if iterator options is set to true', function(t) {
  try {
    var options = {
      currentDate: new CronDate('Wed, 26 Dec 2012 14:38:53'),
      endDate: new CronDate('Wed, 26 Dec 2012 15:40:00'),
      iterator: true
    };
    var val = null;
    var interval = CronExpression.parse('*/25 * * * *', options);
    t.ok(interval, 'Interval parsed');
    val = interval.next();
    t.ok(val, 'Next iteration resolved');
    t.ok(val.value, 'Iterator value is set');
    t.notOk(val.done, 'Iterator is not finished');
    val = interval.next();
    t.ok(val, 'Next iteration resolved');
    t.ok(val.value, 'Iterator value is set');
    t.notOk(val.done, 'Iterator is not finished');
    val = interval.next();
    t.ok(val, 'Next iteration resolved');
    t.ok(val.value, 'Iterator value is set');
    t.ok(val.done, 'Iterator is finished');
  } catch (err) {
    t.ifError(err, 'Interval parse error');
  }
  t.end();
});
test('Must not parse an expression which has repeat 0 times', function(t) {
  try {
    var expression = CronExpression.parse('0 */0 * * *');
    var val = expression.next();
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, cannot repeat at every 0 time.');
  }
  t.end();
});
test('Must not parse an expression which has repeat negative number times', function(t) {
  try {
    var expression = CronExpression.parse('0 */-5 * * *');
    var val = expression.next();
  } catch (err) {
    t.ok(err, 'Error expected');
    t.equal(err.message, 'Constraint error, cannot repeat at every -5 time.');
  }
  t.end();
});

+ 47 - 0
src/doctor/node_modules/node-schedule/node_modules/cron-parser/test/parser.js

@ -0,0 +1,47 @@
var util = require('util');
var test = require('tap').test;
var CronParser = require('../lib/parser');
// Globals
test('load crontab file', function(t) {
  CronParser.parseFile('./crontab.example', function(err, result) {
    t.ifError(err, 'File read error');
    t.ok(result, 'Crontab parsed parsed');
    t.equal(Object.keys(result.variables).length, 2, 'variables length matches');
    t.equal(Object.keys(result.errors).length, 0, 'errors length matches');
    t.equal(result.expressions.length, 3, 'expressions length matches');
    // Parse expressions
    var next = null;
    t.equal(result.expressions[0].hasNext(), true);
    next = result.expressions[0].next();
    t.ok(next, 'first date');
    next = result.expressions[1].next();
    t.ok(next, 'second date');
    next = result.expressions[2].next();
    t.ok(next, 'third date');
    t.end();
  });
});
test('no next date', function(t) {
  var options = {
    currentDate: new Date(2014, 0, 1),
    endDate: new Date(2014, 0, 1)
  };
  try {
    var interval = CronParser.parseExpression('* * 2 * *', options);
    t.equal(interval.hasNext(), false);
  } catch (err) {
    t.ifError(err, 'Parse read error');
  }
  t.end();
});

+ 10 - 0
src/doctor/node_modules/node-schedule/node_modules/long-timeout/example.js

@ -0,0 +1,10 @@
var lt = require('./')
lt.setTimeout(function() {
  console.log('in a long time')
}, Number.MAX_VALUE)
lt.setTimeout(function() {
  console.log('2 seconds')
}, 2000)

+ 33 - 0
src/doctor/node_modules/node-schedule/node_modules/long-timeout/index.js

@ -0,0 +1,33 @@
var TIMEOUT_MAX = 2147483647; // 2^31-1
exports.setTimeout = function(listener, after) {
  return new Timeout(listener, after)
}
exports.clearTimeout = function(timer) {
  if (timer) timer.close()
}
exports.Timeout = Timeout
function Timeout(listener, after) {
  this.listener = listener
  this.after = after
  this.start()
}
Timeout.prototype.start = function() {
  if (this.after <= TIMEOUT_MAX) {
    this.timeout = setTimeout(this.listener, this.after)
  } else {
    var self = this
    this.timeout = setTimeout(function() {
      self.after -= TIMEOUT_MAX
      self.start()
    }, TIMEOUT_MAX)
  }
}
Timeout.prototype.close = function() {
  clearTimeout(this.timeout)
}

+ 51 - 0
src/doctor/node_modules/node-schedule/node_modules/long-timeout/package.json

@ -0,0 +1,51 @@
{
  "name": "long-timeout",
  "version": "0.0.2",
  "description": "Long timeout makes it possible to have a timeout that is longer than 24.8 days (2^31-1 milliseconds).",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/tellnes/long-timeout.git"
  },
  "author": {
    "name": "Christian Tellnes",
    "email": "christian@tellnes.no",
    "url": "http://christian.tellnes.com/"
  },
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/tellnes/long-timeout/issues"
  },
  "homepage": "https://github.com/tellnes/long-timeout",
  "publishConfig": {
    "registry": "https://registry.npmjs.org/"
  },
  "gitHead": "41ee160fd3a1f8d34fbde2cb96617f79e217b31d",
  "_id": "long-timeout@0.0.2",
  "_shasum": "f36449ba89629d13a7a2b2523a4db9dd66e3ff68",
  "_from": "long-timeout@0.0.2",
  "_npmVersion": "1.4.28",
  "_npmUser": {
    "name": "tellnes",
    "email": "christian@tellnes.no"
  },
  "maintainers": [
    {
      "name": "tellnes",
      "email": "christian@tellnes.no"
    }
  ],
  "dist": {
    "shasum": "f36449ba89629d13a7a2b2523a4db9dd66e3ff68",
    "size": 989,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/long-timeout/download/long-timeout-0.0.2.tgz"
  },
  "directories": {},
  "publish_time": 1426706272473,
  "_cnpm_publish_time": 1426706272473,
  "_resolved": "https://registry.npm.taobao.org/long-timeout/download/long-timeout-0.0.2.tgz"
}

+ 83 - 0
src/doctor/node_modules/node-schedule/package.json

@ -0,0 +1,83 @@
{
  "name": "node-schedule",
  "version": "1.2.0",
  "description": "A cron-like and not-cron-like job scheduler for Node.",
  "keywords": [
    "schedule",
    "task",
    "job",
    "cron"
  ],
  "license": "MIT",
  "main": "./lib/schedule.js",
  "scripts": {
    "test": "nodeunit",
    "lint": "eslint lib"
  },
  "author": {
    "name": "Matt Patenaude",
    "email": "matt@mattpatenaude.com",
    "url": "http://mattpatenaude.com"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/node-schedule/node-schedule.git"
  },
  "dependencies": {
    "cron-parser": "1.1.0",
    "long-timeout": "0.0.2"
  },
  "devDependencies": {
    "coveralls": "^2.11.2",
    "eslint": "^0.15.1",
    "istanbul": "^0.3.8",
    "nodeunit": "^0.9.1",
    "sinon": "^1.14.1"
  },
  "gitHead": "5ae891abc454a52ba05ac49ff7a652d64a393fd8",
  "bugs": {
    "url": "https://github.com/node-schedule/node-schedule/issues"
  },
  "homepage": "https://github.com/node-schedule/node-schedule#readme",
  "_id": "node-schedule@1.2.0",
  "_shasum": "f03d4ebe71b0573e1708ddae0aa45d1658450cf1",
  "_from": "node-schedule@latest",
  "_npmVersion": "3.10.8",
  "_nodeVersion": "6.8.1",
  "_npmUser": {
    "name": "sgimeno",
    "email": "santiago.gimeno@gmail.com"
  },
  "maintainers": [
    {
      "name": "jonhester",
      "email": "jon.d.hester@gmail.com"
    },
    {
      "name": "mattpat",
      "email": "matt@mattpatenaude.com"
    },
    {
      "name": "sgimeno",
      "email": "santiago.gimeno@gmail.com"
    },
    {
      "name": "tejasmanohar",
      "email": "me@tejasmanohar.com"
    }
  ],
  "dist": {
    "shasum": "f03d4ebe71b0573e1708ddae0aa45d1658450cf1",
    "size": 13827,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/node-schedule/download/node-schedule-1.2.0.tgz"
  },
  "_npmOperationalInternal": {
    "host": "packages-12-west.internal.npmjs.com",
    "tmp": "tmp/node-schedule-1.2.0.tgz_1476876571612_0.6606993437744677"
  },
  "directories": {},
  "publish_time": 1476876573438,
  "_cnpm_publish_time": 1476876573438,
  "_resolved": "https://registry.npm.taobao.org/node-schedule/download/node-schedule-1.2.0.tgz"
}

+ 713 - 0
src/doctor/node_modules/node-schedule/test/convenience-method-test.js

@ -0,0 +1,713 @@
'use strict';
var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);
var clock;
module.exports = {
  setUp: function(cb) {
    clock = sinon.useFakeTimers();
    cb();
  },
  ".scheduleJob": {
    "Returns Job instance": function(test) {
      var job = schedule.scheduleJob(new Date(Date.now() + 1000), function() {});
      test.ok(job instanceof schedule.Job);
      job.cancel();
      test.done();
    }
  },
  ".scheduleJob(Date, fn)": {
    "Runs job once at some date": function(test) {
      test.expect(1);
      schedule.scheduleJob(new Date(Date.now() + 3000), function() {
        test.ok(true);
      });
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job doesn't emit initial 'scheduled' event": function(test) {
        var job = schedule.scheduleJob(new Date(Date.now() + 1000), function() {});
        job.on('scheduled', function() {
          test.ok(false);
        });
        setTimeout(function() {
          test.done();
        }, 1250);
        clock.tick(1250);
      },
      "Won't run job if scheduled in the past": function(test) {
        test.expect(1);
        var job = schedule.scheduleJob(new Date(Date.now() - 3000), function() {
          test.ok(false);
        });
        test.equal(job, null);
        setTimeout(function() {
          test.done();
        }, 1000);
        clock.tick(1000);
      }
  },
  ".scheduleJob(RecurrenceRule, fn)": {
    "Runs job at interval based on recur rule, repeating indefinitely": function(test) {
      test.expect(3);
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job doesn't emit initial 'scheduled' event": function(test) {
        /*
         * If this was Job#schedule it'd fire 4 times.
         */
        test.expect(3);
        var rule = new schedule.RecurrenceRule();
        rule.second = null; // fire every second
        var job = new schedule.scheduleJob(rule, function() {});
        job.on('scheduled', function(runOnDate) {
          test.ok(true);
        });
        setTimeout(function() {
          job.cancel();
          test.done();
        }, 3250);
        clock.tick(3250);
      },
      "Doesn't invoke job if recur rule schedules it in the past": function(test) {
        test.expect(1);
        var rule = new schedule.RecurrenceRule();
        rule.year = 1960;
        var job = schedule.scheduleJob(rule, function() {
          test.ok(false);
        });
        
        test.equal(job, null);
        setTimeout(function() {
          test.done();
        }, 1000);
        clock.tick(1000);
      }
  },
  ".scheduleJob({...}, fn)": {
    "Runs job at interval based on object, repeating indefinitely": function(test) {
      test.expect(3);
      var job = new schedule.scheduleJob({
        second: null // Fire every second
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job doesn't emit initial 'scheduled' event": function(test) {
        /*
         * With Job#schedule this would be 3:
         *  scheduled at time 0
         *  scheduled at time 1000
         *  scheduled at time 2000
         */
        test.expect(2);
        var job = schedule.scheduleJob({
          second: null // fire every second
        }, function() {});
        job.on('scheduled', function() {
          test.ok(true);
        });
        setTimeout(function() {
          job.cancel();
          test.done();
        }, 2250);
        clock.tick(2250);
      },
      "Doesn't invoke job if object schedules it in the past": function(test) {
        test.expect(1);
      
        var job = schedule.scheduleJob({
          year: 1960
        }, function() {
          test.ok(false);
        });
        
        test.equal(job, null);
        setTimeout(function() {
          test.done();
        }, 1000);
        clock.tick(1000);
      }
  },
  ".scheduleJob({...}, {...}, fn)": {
    "Callback called for each job if callback is provided": function(test) {
      test.expect(3);
      var job = new schedule.scheduleJob({
        second: null // Fire every second
      }, function() {}, function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  ".rescheduleJob(job, {...})": {
    "Reschedule jobs from object based to object based": function(test) {
      test.expect(3);
      var job = new schedule.scheduleJob({
        second: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job, {
          minute: null
        });
      }, 3250);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 5000);
      clock.tick(5000);
    },
    "Reschedule jobs from every minutes to every second": function(test) {
      test.expect(3);
      var timeout = 60 * 1000;
      var job = new schedule.scheduleJob({
        minute: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job, {
          second: null
        });
      }, timeout);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout + 2250);
      clock.tick(timeout + 2250);
    }
  },
  ".rescheduleJob(job, Date)": {
    "Reschedule jobs from Date to Date": function(test) {
      test.expect(1);
      var job = new schedule.scheduleJob(new Date(Date.now() + 3000), function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job, new Date(Date.now() + 5000));
      }, 1000);
      setTimeout(function() {
        test.done();
      }, 6150);
      clock.tick(6150);
    },
    "Reschedule jobs that has been executed": function(test) {
      test.expect(2);
      var job = new schedule.scheduleJob(new Date(Date.now() + 1000), function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job, new Date(Date.now() + 2000));
      }, 2000);
      setTimeout(function() {
        test.done();
      }, 5150);
      clock.tick(5150);
    }
  },
  ".rescheduleJob(job, RecurrenceRule)": {
    "Reschedule jobs from RecurrenceRule to RecurrenceRule": function(test) {
      test.expect(3);
      var timeout = 60 * 1000;
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      var newRule = new schedule.RecurrenceRule();
      newRule.minute = null;
      setTimeout(function() {
        schedule.rescheduleJob(job, newRule);
      }, 2250);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout + 2250);
      clock.tick(timeout + 2250);
    },
    "Reschedule jobs from RecurrenceRule to Date": function(test) {
      test.expect(3);
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job, new Date(Date.now() + 2000));
      }, 2150);
      setTimeout(function() {
        test.done();
      }, 4250);
      clock.tick(4250);
    },
    "Reschedule jobs from RecurrenceRule to {...}": function(test) {
      test.expect(3);
      var timeout = 60 * 1000;
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job, {
          minute: null
        });
      }, 2150);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout + 2150);
      clock.tick(timeout + 2150);
    },
    "Reschedule jobs that is not available": function(test) {
      test.expect(4);
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(null, new Date(Date.now() + 2000));
      }, 2150);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 4250);
      clock.tick(4250);
    }
  },
  '.rescheduleJob("job name", {...})': {
    "Reschedule jobs from object based to object based": function(test) {
      test.expect(3);
      var job = new schedule.scheduleJob({
        second: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job.name, {
          minute: null
        });
      }, 3250);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 5000);
      clock.tick(5000);
    },
    "Reschedule jobs from every minutes to every second": function(test) {
      test.expect(3);
      var timeout = 60 * 1000;
      var job = new schedule.scheduleJob({
        minute: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job.name, {
          second: null
        });
      }, timeout);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout + 2250);
      clock.tick(timeout + 2250);
    }
  },
  '.rescheduleJob("job name", Date)': {
    "Reschedule jobs from Date to Date": function(test) {
      test.expect(1);
      var job = new schedule.scheduleJob(new Date(Date.now() + 3000), function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job.name, new Date(Date.now() + 5000));
      }, 1000);
      setTimeout(function() {
        test.done();
      }, 6150);
      clock.tick(6150);
    },
    "Reschedule jobs that has been executed": function(test) {
      test.expect(2);
      var job = new schedule.scheduleJob(new Date(Date.now() + 1000), function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job.name, new Date(Date.now() + 2000));
      }, 2000);
      setTimeout(function() {
        test.done();
      }, 5150);
      clock.tick(5150);
    }
  },
  '.rescheduleJob("job name", RecurrenceRule)': {
    "Reschedule jobs from RecurrenceRule to RecurrenceRule": function(test) {
      test.expect(3);
      var timeout = 60 * 1000;
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      var newRule = new schedule.RecurrenceRule();
      newRule.minute = null;
      setTimeout(function() {
        schedule.rescheduleJob(job.name, newRule);
      }, 2250);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout + 2250);
      clock.tick(timeout + 2250);
    },
    "Reschedule jobs from RecurrenceRule to Date": function(test) {
      test.expect(3);
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job.name, new Date(Date.now() + 2000));
      }, 2150);
      setTimeout(function() {
        test.done();
      }, 4250);
      clock.tick(4250);
    },
    "Reschedule jobs from RecurrenceRule to {...}": function(test) {
      test.expect(3);
      var timeout = 60 * 1000;
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob(job.name, {
          minute: null
        });
      }, 2150);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout + 2150);
      clock.tick(timeout + 2150);
    },
    "Reschedule jobs that is not available": function(test) {
      test.expect(4);
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      var job = schedule.scheduleJob(rule, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.rescheduleJob("Blah", new Date(Date.now() + 2000));
      }, 2150);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 4250);
      clock.tick(4250);
    }
  },
  ".cancelJob(Job)": {
    "Prevents all future invocations of Job passed in": function(test) {
      test.expect(2);
      var job = schedule.scheduleJob({
        second: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.cancelJob(job);
      }, 2250);
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Can cancel Jobs scheduled with Job#schedule": function(test) {
      test.expect(2);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.schedule({
        second: null
      });
      setTimeout(function() {
        schedule.cancelJob(job);
      }, 2250);
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job emits 'canceled' event": function(test) {
      test.expect(1);
      var job = schedule.scheduleJob({
        second: null
      }, function() {});
      job.on('canceled', function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.cancelJob(job);
        test.done();
      }, 1250);
      clock.tick(1250);
    }
  },
  '.cancelJob("job name")': {
    "Prevents all future invocations of Job identified by name": function(test) {
      test.expect(2);
      var job = schedule.scheduleJob({
        second: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.cancelJob(job.name);
      }, 2250);
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    /*
    "Can cancel Jobs scheduled with Job#schedule": function(test) {
      test.expect(2);
      var job = new schedule.Job(function() {
      test.ok(true);
      });
      job.schedule({
      second: null
      });
      setTimeout(function() {
      schedule.cancelJob(job.name);
      }, 2250);
      setTimeout(function() {
      test.done();
      }, 3250);
    },*/
    "Job emits 'canceled' event": function(test) {
      test.expect(1);
      var job = schedule.scheduleJob({
        second: null
      }, function() {});
      job.on('canceled', function() {
        test.ok(true);
      });
      setTimeout(function() {
        schedule.cancelJob(job.name);
        test.done();
      }, 1250);
      clock.tick(1250);
    },
    "Does nothing if no job found by that name": function(test) {
      test.expect(3);
      var job = schedule.scheduleJob({
        second: null
      }, function() {
        test.ok(true);
      });
      setTimeout(function() {
        // This cancel should not affect anything
        schedule.cancelJob('blah');
      }, 2250);
      setTimeout(function() {
        job.cancel(); // prevent tests from hanging
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  '.pendingInvocations()': {
    "Retrieves pendingInvocations of the job": function(test) {
      var job = schedule.scheduleJob(new Date(Date.now() + 1000), function() {});
      test.ok(job instanceof schedule.Job);
      test.ok(job.pendingInvocations()[0].job);
      job.cancel();
      test.done();
    }
  },
  tearDown: function(cb) {
    clock.restore();
    cb();
  }
};

+ 59 - 0
src/doctor/node_modules/node-schedule/test/date-convenience-methods-test.js

@ -0,0 +1,59 @@
'use strict';
var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);
var clock;
module.exports = {
  setUp: function(cb) {
    clock = sinon.useFakeTimers();
    cb();
  },
  "Date string": {
    "Should accept a valid date string": function(test) {
      test.expect(1);
      schedule.scheduleJob(new Date(Date.now() + 1000).toString(), function() {
        test.ok(true);
      });
      setTimeout(function() {
        test.done();
      }, 1250);
      clock.tick(1250);
    },
    "Should not accept invalid string as valid date": function(test) {
      test.expect(1);
      var job = schedule.scheduleJob("hello!!", function() {
      });
      test.equal(job, null);
      test.done();
    }
  },
  "UTC": {
     "Should accept a valid UTC date in milliseconds": function(test) {
      test.expect(1);
      schedule.scheduleJob(new Date(Date.now() + 1000).getTime(), function() {
        test.ok(true);
      });
      setTimeout(function() {
        test.done();
      }, 1250);
      clock.tick(1250);
    }
  },
  tearDown: function(cb) {
    clock.restore();
    cb();
  }
};

+ 33 - 0
src/doctor/node_modules/node-schedule/test/es6/job-test.js

@ -0,0 +1,33 @@
'use strict';
module.exports = function(schedule) {
  return {
    jobInGenerator: function(test) {
      test.expect(1);
      var job = new schedule.Job(function*() {
        test.ok(true);
      });
      job.runOnDate(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
    },
    jobContextInGenerator: function(test) {
      test.expect(1);
      var job = new schedule.Job('name of job', function*() {
        test.ok(this.name === 'name of job');
      });
      job.runOnDate(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
    }
  }
}

+ 494 - 0
src/doctor/node_modules/node-schedule/test/job-test.js

@ -0,0 +1,494 @@
'use strict';
var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);
var es6;
try {
  eval('(function* () {})()');
  es6 = require('./es6/job-test')(schedule);
} catch (e) {}
var clock;
module.exports = {
  setUp: function(cb) {
    clock = sinon.useFakeTimers();
    cb();
  },
  "Job constructor": {
    "Accepts Job name and function to run": function(test) {
      var job = new schedule.Job('the job', function() {});
      test.equal(job.name, 'the job');
      test.done();
    },
    "Job name is optional and will be auto-generated": function(test) {
      var job = new schedule.Job();
      test.ok(job.name);
      test.done();
    },
    "Uses unique names across auto-generated Job names": function(test) {
      var job1 = new schedule.Job();
      var job2 = new schedule.Job();
      test.notEqual(job1.name, job2.name);
      test.done();
    }
  },
  "#schedule(Date)": {
    "Runs job once at some date": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.schedule(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Cancel next job before it runs": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.schedule(new Date(Date.now() + 1500));
      job.schedule(new Date(Date.now() + 3000));
      job.cancelNext();
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Run job on specified date": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.runOnDate(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Run job in generator": function(test) {
      if (!es6) {
        test.expect(0);
        test.done();
        return;
      }
      es6.jobInGenerator(test);
      clock.tick(3250);
    },
    "Context is passed into generator correctly": function(test) {
      if (!es6) {
        test.expect(0);
        test.done();
        return;
      }
      es6.jobContextInGenerator(test);
      clock.tick(3250);
    },
    "Won't run job if scheduled in the past": function(test) {
      test.expect(0);
      var job = new schedule.Job(function() {
        test.ok(false);
      });
      job.schedule(new Date(Date.now() - 3000));
      setTimeout(function() {
        test.done();
      }, 1000);
      clock.tick(1000);
    },
    "Jobs still run after scheduling a Job in the past": function(test) {
      test.expect(1);
      var pastJob = new schedule.Job(function() {
      // Should not run, blow up if it does
        test.ok(false);
      });
      pastJob.schedule(new Date(Date.now() - 3000));
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.schedule(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job emits 'scheduled' event with 'run at' Date": function(test) {
      test.expect(1);
      var date = new Date(Date.now() + 3000);
      var job = new schedule.Job(function() {
        test.done();
      });
      job.on('scheduled', function(runAtDate) {
        test.equal(runAtDate, date);
      });
      job.schedule(date);
      clock.tick(3250);
    }
  },
  "#schedule(Date, fn)": {
    "Runs job once at some date, calls callback when done": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {}, function() {
        test.ok(true);
      });
      job.schedule(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  "#schedule(RecurrenceRule)": {
    "Runs job at interval based on recur rule, repeating indefinitely": function(test) {
      test.expect(3);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second
      job.schedule(rule);
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job emits 'scheduled' event for every next invocation": function(test) {
        // Job will run 3 times but be scheduled 4 times, 4th run never happens
        // due to cancel.
        test.expect(4);
        var job = new schedule.Job(function() {});
        job.on('scheduled', function(runOnDate) {
          test.ok(true);
        });
        var rule = new schedule.RecurrenceRule();
        rule.second = null; // fire every second
        job.schedule(rule);
        setTimeout(function() {
          job.cancel();
          test.done();
        }, 3250);
        clock.tick(3250);
      },
      "Doesn't invoke job if recur rule schedules it in the past": function(test) {
        test.expect(0);
        var job = new schedule.Job(function() {
          test.ok(false);
        });
        var rule = new schedule.RecurrenceRule();
        rule.year = 2000;
        job.schedule(rule);
        setTimeout(function() {
          job.cancel();
          test.done();
        }, 1000);
        clock.tick(1000);
      }
  },
  "#schedule({...})": {
    "Runs job at interval based on object, repeating indefinitely": function(test) {
      test.expect(3);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job emits 'scheduled' event for every next invocation": function(test) {
        // Job will run 3 times but be scheduled 4 times, 4th run never happens
        // due to cancel.
        test.expect(4);
        var job = new schedule.Job(function() {});
        job.on('scheduled', function(runOnDate) {
          test.ok(true);
        });
        job.schedule({
          second: null // Fire every second
        });
        setTimeout(function() {
          job.cancel();
          test.done();
        }, 3250);
        clock.tick(3250);
      },
      "Doesn't invoke job if object schedules it in the past": function(test) {
        test.expect(0);
        var job = new schedule.Job(function() {
          test.ok(false);
        });
        job.schedule({
          year: 2000
        });
        setTimeout(function() {
          job.cancel();
          test.done();
        }, 1000);
        clock.tick(1000);
      }
  },
  "#schedule('jobName', {...})": {
    "Runs job with a custom name input": function(test) {
      test.expect(3);
      var job = new schedule.Job('jobName', function() {
        test.equal(job.name, 'jobName');
      });
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  "#schedule({...}, {...})": {
    "Runs job and run callback when job is done if callback is provided": function(test) {
      test.expect(3);
      var job = new schedule.Job(function() {}, function() {
        test.ok(true);
      });
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Runs job with a custom name input and run callback when job is done": function(test) {
      test.expect(3);
      var job = new schedule.Job('MyJob', function() {}, function() {
        test.equal(job.name, 'MyJob');
      });
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  "#cancel": {
    "Prevents all future invocations": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {
        test.ok(true);
      });
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
      }, 1250);
      setTimeout(function() {
        test.done();
      }, 2250);
      clock.tick(2250);
    },
    "Job emits 'canceled' event": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {});
      job.on('canceled', function() {
        test.ok(true);
      });
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
      }, 1250);
      setTimeout(function() {
        test.done();
      }, 2250);
      clock.tick(2250);
    },
    "Job is added to scheduledJobs when created and removed when cancelled": function(test) {
      test.expect(4);
      var job1 = new schedule.Job('cancelJob', function() {});
      job1.schedule({
        second: null // fire every second
      });
      var job2 = schedule.scheduleJob('second',
                                      { second: null },
                                      function() {},
                                      function() {});
      test.strictEqual(schedule.scheduledJobs.cancelJob, job1);
      test.strictEqual(schedule.scheduledJobs.second, job2);
      setTimeout(function() {
        job1.cancel();
        job2.cancel();
        test.strictEqual(schedule.scheduledJobs.cancelJob, undefined);
        test.strictEqual(schedule.scheduledJobs.second, undefined);
        test.done();
      }, 1250);
      clock.tick(1250);
    }
  },
  "#reschedule": {
    "When rescheduled counter will be reset to zero": function(test) {
      var job = new schedule.scheduleJob({
        second: null
      }, function() {});
      setTimeout(function() {
        test.equal(job.triggeredJobs(), 3);
        schedule.rescheduleJob(job, {
          minute: null
        });
      }, 3250);
      setTimeout(function() {
        job.cancel();
        test.equal(job.triggeredJobs(), 0);
        test.done();
      }, 5000);
      clock.tick(5000);
    }
  },
  "When invoked": {
    "Job emits 'run' event": function(test) {
      test.expect(1);
      var job = new schedule.Job(function() {});
      job.on('run', function() {
        test.ok(true);
      });
      job.schedule(new Date(Date.now() + 3000));
      setTimeout(function() {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    "Job counter increase properly": function(test) {
      var job = new schedule.Job(function() {});
      job.schedule({
        second: null // fire every second
      });
      setTimeout(function() {
        job.cancel();
        test.equal(job.triggeredJobs(), 2);
        test.done();
      }, 2250);
      clock.tick(2250);
    }
  },
  tearDown: function(cb) {
    clock.restore();
    cb();
  }
};

+ 77 - 0
src/doctor/node_modules/node-schedule/test/range-test.js

@ -0,0 +1,77 @@
'use strict';
var main = require('../package.json').main;
var schedule = require('../' + main);
module.exports = {
  "step defaults to 1": function(test) {
    var range = new schedule.Range(2, 6);
    test.equals(1, range.step);
    test.done();
  },
  "when step is 1": {
    "setUp": function(done) {
      this.range = new schedule.Range(2, 6, 1);
      done();
    },
    "includes start value": function(test) {
      test.ok(this.range.contains(2));
      test.done();
    },
    "includes end value": function(test) {
      test.ok(this.range.contains(6));
      test.done();
    },
    "includes value between start and end": function(test) {
      test.ok(this.range.contains(3));
      test.done();
    },
    "excludes values outside of start and end": function(test) {
      test.ok(!this.range.contains(1));
      test.ok(!this.range.contains(7));
      test.done();
    }
  },
  "when step > 1": {
    "setUp": function(done) {
      this.range = new schedule.Range(2, 6, 2);
      done();
    },
    "includes start value": function(test) {
      test.ok(this.range.contains(2));
      test.done();
    },
    "excludes end value": function(test) {
      test.ok(!this.range.contains(6));
      test.done();
    },
    "includes value between start and end that is evenly divisible by step": function(test) {
      test.ok(this.range.contains(4));
      test.done();
    },
    "excludes value between start and end that is not evenly divisible by step": function(test) {
      test.ok(!this.range.contains(5));
      test.done();
    },
    "excludes values outside of start and end": function(test) {
      test.ok(!this.range.contains(1));
      test.ok(!this.range.contains(7));
      test.done();
    }
  }
};

+ 294 - 0
src/doctor/node_modules/node-schedule/test/recurrence-rule-test.js

@ -0,0 +1,294 @@
'use strict';
var main = require('../package.json').main;
var schedule = require('../' + main);
var sinon = require('sinon');
var clock;
// 12:30:15 pm Thursday 29 April 2010 in the timezone this code is being run in
var base = new Date(2010, 3, 29, 12, 30, 15, 0);
var baseMs = base.getTime();
module.exports = {
  "setUp": function(cb) {
    clock = sinon.useFakeTimers(baseMs);
    cb();
  },
  "tearDown": function(cb) {
    clock.restore();
    cb();
  },
  "#nextInvocationDate(Date)": {
    "next second": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.second = null;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 12, 30, 16, 0), next);
      test.done();
    },
    "next 25th second": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.second = 25;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 12, 30, 25, 0), next);
      test.done();
    },
    "next 5th second (minutes incremented)": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.second = 5;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 12, 31, 5, 0), next);
      test.done();
    },
    "next 40th minute": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.minute = 40;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 12, 40, 0, 0), next);
      test.done();
    },
    "next 1st minute (hours incremented)": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.minute = 1;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 13, 1, 0, 0), next);
      test.done();
    },
    "next 23rd hour": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.hour = 23;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 23, 0, 0, 0), next);
      test.done();
    },
    "next 3rd hour (days incremented)": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.hour = 3;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 30, 3, 0, 0, 0), next);
      test.done();
    },
    "next Friday": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.dayOfWeek = 5;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 30, 0, 0, 0, 0), next);
      test.done();
    },
    "next Monday (months incremented)": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.dayOfWeek = 1;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 4, 3, 0, 0, 0, 0), next);
      test.done();
    },
    "next 30th date": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.date = 30;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 30, 0, 0, 0, 0), next);
      test.done();
    },
    "next 5th date (months incremented)": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.date = 5;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 4, 5, 0, 0, 0, 0), next);
      test.done();
    },
    "next October": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.month = 9;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 9, 1, 0, 0, 0, 0), next);
      test.done();
    },
    "next February (years incremented)": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.month = 1;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2011, 1, 1, 0, 0, 0, 0), next);
      test.done();
    },
    "in the year 2040": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.year = 2040;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2040, 0, 1, 0, 0, 0, 0), next);
      test.done();
    },
    "using past year": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.year = 2000;
      var next = rule.nextInvocationDate(base);
      test.equal(null, next);
      test.done();
    },
    "using mixed time components": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.second = 50;
      rule.minute = 5;
      rule.hour = 10;
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 30, 10, 5, 50, 0), next);
      test.done();
    },
    /*
    "using date and dayOfWeek together": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.dayOfWeek = 4; // This is Thursday April 1st
      rule.date = 10;   // This is Saturday April 10th
      var next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 1, 0, 0, 0, 0), next);
      test.done();
    }*/
    "returns null when no invocations left": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.year = 2000;
      var next = rule.nextInvocationDate(base);
      test.strictEqual(null, next);
      test.done();
    },
    "specify span of components using Range": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.minute = new schedule.Range(4, 6);
      var next;
      next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 13, 4, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 5, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 6, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 14, 4, 0, 0), next);
      test.done();
    },
    "specify intervals within span of components using Range with step": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.minute = new schedule.Range(4, 8, 2);
      var next;
      next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 13, 4, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 6, 0, 0), next);
      /* Should Range stay inclusive on both ends when step > 1
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 8, 0, 0), next);
      */
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 14, 4, 0, 0), next);
      test.done();
    },
    "specify span and explicit components using Array of Ranges and Numbers": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.minute = [2, new schedule.Range(4, 6)];
      var next;
      next = rule.nextInvocationDate(base);
      test.deepEqual(new Date(2010, 3, 29, 13, 2, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 4, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 5, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 13, 6, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2010, 3, 29, 14, 2, 0, 0), next);
      test.done();
    },
    "From 31th May schedule the 1st of every June": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.second = 0;
      rule.minute = 0;
      rule.hour = 0;
      rule.date = 1;
      rule.month = 5;
      var next;
      var base1 = new Date(2010, 4, 31, 12, 30, 15, 0);
      next = rule.nextInvocationDate(base1);
      test.deepEqual(new Date(2010, 5, 1, 0, 0, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.deepEqual(new Date(2011, 5, 1, 0, 0, 0, 0), next);
      test.done();
    },
    "With the year set should not loop indefinetely": function(test) {
      var rule = new schedule.RecurrenceRule();
      rule.second = 0;
      rule.minute = 0;
      rule.hour = 0;
      rule.date = 1;
      rule.month = 5;
      rule.year = 2010;
      var next;
      var base1 = new Date(2010, 4, 31, 12, 30, 15, 0);
      next = rule.nextInvocationDate(base1);
      test.deepEqual(new Date(2010, 5, 1, 0, 0, 0, 0), next);
      next = rule.nextInvocationDate(next);
      test.equal(next, null);
      test.done();
    }
  }
};

+ 136 - 0
src/doctor/node_modules/node-schedule/test/schedule-cron-jobs.js

@ -0,0 +1,136 @@
'use strict';
var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);
var clock;
module.exports = {
  ".scheduleJob(cron_expr, fn)": {
    setUp: function(cb) {
      var now = Date.now();
      clock = sinon.useFakeTimers();
      clock.tick(now);
      cb();
    },
    "Runs job every second": function(test) {
      test.expect(3);
      var timeout = 3 * 1000 + 150;
      var job = schedule.scheduleJob('* * * * * *', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    "Runs job every minute": function(test) {
      test.expect(3);
      var timeout = 3 * 60 * 1000;
      var job = schedule.scheduleJob('0 * * * * *', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    "Runs job every hour": function(test) {
      test.expect(3);
      var timeout = 3 * 60 * 60 * 1000;
      var job = schedule.scheduleJob('0 0 * * * *', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    "Runs job every day": function(test) {
      test.expect(3);
      var timeout = 3 * 24 * 60 * 60 * 1000;
      var job = schedule.scheduleJob('0 0 0 * * *', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    "Runs job every week": function(test) {
      test.expect(3);
      var timeout = 3 * 7 * 24 * 60 * 60 * 1000;
      var job = schedule.scheduleJob('0 0 0 * * 1', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    "Runs job every month": function(test) {
      test.expect(48);
      var timeout = 4 * 365.25 * 24 * 60 * 60 * 1000;
      var job = schedule.scheduleJob('0 0 0 1 * *', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    "Runs job every year": function(test) {
      test.expect(4);
      var timeout = 4 * 365.25 * 24 * 60 * 60 * 1000;
      var job = schedule.scheduleJob('0 0 0 1 1 *', function() {
        test.ok(true);
      });
      setTimeout(function() {
        job.cancel();
        test.done();
      }, timeout);
      clock.tick(timeout);
    },
    tearDown: function(cb) {
      clock.restore();
      cb();
    }
  }
};

+ 312 - 0
src/doctor/node_modules/node-schedule/test/start-end-test.js

@ -0,0 +1,312 @@
'use strict';
var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);
var clock;
module.exports = {
  setUp: function (cb) {
    clock = sinon.useFakeTimers();
    cb();
  },
  'RecurrenceRule': {
    'no endTime , startTime less than now': function (test) {
      test.expect(3);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // every second
      job.schedule({
        start: new Date(Date.now() - 2000),
        rule: rule
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no endTime , startTime greater than now': function (test) {
      test.expect(1);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // every second
      job.schedule({
        start: new Date(Date.now() + 2000),
        rule: rule
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no startTime , endTime less than now': function (test) {
      test.expect(0);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // every second
      job.schedule({
        end: new Date(Date.now() - 2000),
        rule: rule
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no startTime , endTime greater than now': function (test) {
      test.expect(2);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // every second
      job.schedule({
        end: new Date(Date.now() + 2000),
        rule: rule
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'has startTime and endTime': function (test) {
      test.expect(1);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      var rule = new schedule.RecurrenceRule();
      rule.second = null; // every second
      job.schedule({
        start: new Date(Date.now() + 1000),
        end: new Date(Date.now() + 2000),
        rule: rule
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  'Object Literal': {
    'no endTime , startTime less than now': function (test) {
      test.expect(3);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        start: new Date(Date.now() - 2000),
        rule: { second: null }
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no endTime , startTime greater than now': function (test) {
      test.expect(1);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        start: new Date(Date.now() + 2000),
        rule: { second: null }
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no startTime , endTime less than now': function (test) {
      test.expect(0);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        end: new Date(Date.now() - 2000),
        rule: { second: null }
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no startTime , endTime greater than now': function (test) {
      test.expect(2);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        end: new Date(Date.now() + 2000),
        rule: { second: null }
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'has startTime and endTime': function (test) {
      test.expect(1);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        start: new Date(Date.now() + 1000),
        end: new Date(Date.now() + 2000),
        rule: { second: null }
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  'cron-style': {
    'no endTime , startTime less than now': function (test) {
      test.expect(3);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        start: new Date(Date.now() - 2000),
        rule: '*/1 * * * * *'
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no endTime , startTime greater than now': function (test) {
      test.expect(1);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        start: new Date(Date.now() + 2000),
        rule: '*/1 * * * * *'
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no startTime , endTime less than now': function (test) {
      test.expect(0);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        end: new Date(Date.now() - 2000),
        rule: '*/1 * * * * *'
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'no startTime , endTime greater than now': function (test) {
      test.expect(2);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        end: new Date(Date.now() + 2000),
        rule: '*/1 * * * * *'
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    },
    'has startTime and endTime': function (test) {
      test.expect(1);
      var job = new schedule.Job(function () {
        test.ok(true);
      });
      job.schedule({
        start: new Date(Date.now() + 1000),
        end: new Date(Date.now() + 2000),
        rule: '*/1 * * * * *'
      });
      setTimeout(function () {
        test.done();
      }, 3250);
      clock.tick(3250);
    }
  },
  tearDown: function (cb) {
    clock.restore();
    cb();
  }
};

+ 4 - 3
src/doctor/package.json

@ -6,11 +6,11 @@
  "scripts": {
    "start": "node ./app.armour.js"
  },
  "dependencies": {
    "async": "~2.0.1",
    "body-parser": "~1.12.4",
    "cookie-parser": "~1.3.5",
    "cron-builder": "^0.3.0",
    "debug": "~2.2.0",
    "enum": "~2.3.0",
    "express": "~4.12.4",
@ -18,9 +18,10 @@
    "mocha": "~3.1.2",
    "morgan": "~1.5.3",
    "mysql": "~2.5.3",
    "swagger-node-express": "~2.0",
    "node-schedule": "^1.2.0",
    "serve-favicon": "~2.2.1",
    "socket.io": "~1.5.1",
    "swagger-node-express": "~2.0",
    "underscore": "~1.8.3"
  }
}
}