agenda
Advanced tools
Comparing version 0.6.28 to 0.7.0
@@ -0,8 +1,27 @@ | ||
/* Code forked from https://github.com/rschmukler/agenda | ||
* | ||
* Updates by Neville Franks neville.franks@gmail.com www.clibu.com | ||
* - Refactored MongoDB code to use the MongoDB Native Driver V2 instead of MongoSkin. | ||
* - Renamed _db to _collection because it is a collection, not a db. | ||
* - Moved code into Agenda.db_init() and call same for all initialization functions. | ||
* - Removed findJobsResultWrapper() and replaced with inline code. | ||
* - Removed db code from jobs.js | ||
* - Comments. | ||
* | ||
* TODO: | ||
* - Refactor remaining deprecated MongoDB Native Driver methods. findAndModify() | ||
* | ||
* Last change: NF 4/06/2015 2:06:12 PM | ||
*/ | ||
var Job = require('./job.js'), | ||
humanInterval = require('human-interval'), | ||
utils = require('util'), | ||
Emitter = require('events').EventEmitter, | ||
mongo = require('mongoskin'); | ||
Emitter = require('events').EventEmitter; | ||
var Agenda = module.exports = function(config) { | ||
var MongoClient = require('mongodb').MongoClient, | ||
Db = require('mongodb').Db; | ||
var Agenda = module.exports = function(config, cb) { | ||
if (!(this instanceof Agenda)) { | ||
@@ -21,20 +40,31 @@ return new Agenda(config); | ||
if (config.db) { | ||
this.database(config.db.address, config.db.collection, config.db.options); | ||
this.database(config.db.address, config.db.collection, config.db.options, function(err, coll) { | ||
if (cb) { | ||
cb(err, coll); | ||
} | ||
}); | ||
} else if (config.mongo) { | ||
this._db = config.mongo; | ||
this._mdb = config.mongo; | ||
this.db_init( config.db ? config.db.collection : undefined ); // NF 20/04/2015 | ||
} | ||
}; | ||
utils.inherits(Agenda, Emitter); | ||
utils.inherits(Agenda, Emitter); // Job uses emit() to fire job events client can use. | ||
// Configuration Methods | ||
Agenda.prototype.mongo = function(db) { | ||
this._db = db; | ||
Agenda.prototype.mongo = function( mdb ){ | ||
this._mdb = mdb; | ||
this.db_init(); // NF 20/04/2015 | ||
return this; | ||
}; | ||
Agenda.prototype.database = function(url, collection, options) { | ||
collection = collection || 'agendaJobs'; | ||
options = options || {w: 0}; | ||
/** Connect to the spec'd MongoDB server and database. | ||
* Notes: | ||
* - If `url` inludes auth details then `options` must specify: { 'uri_decode_auth': true }. This does Auth on the specified | ||
* database, not the Admin database. If you are using Auth on the Admin DB and not on the Agenda DB, then you need to | ||
* authenticate against the Admin DB and then pass the MongoDB instance in to the Constructor or use Agenda.mongo(). | ||
* - If your app already has a MongoDB connection then use that. ie. specify config.mongo in the Constructor or use Agenda.mongo(). | ||
*/ | ||
Agenda.prototype.database = function(url, collection, options, cb) { | ||
if (!url.match(/^mongodb:\/\/.*/)) { | ||
@@ -44,13 +74,34 @@ url = 'mongodb://' + url; | ||
this._db = mongo.db(url, options).collection(collection); | ||
var ignoreErrors = function() {}; | ||
this._db.ensureIndex("nextRunAt", ignoreErrors) | ||
.ensureIndex("lockedAt", ignoreErrors) | ||
.ensureIndex("name", ignoreErrors) | ||
.ensureIndex("priority", ignoreErrors); | ||
collection = collection || 'agendaJobs'; | ||
options = options || {}; | ||
var self = this; | ||
MongoClient.connect(url, options, function( error, db ){ | ||
if (error) throw error; // Auth failed etc. | ||
self._mdb = db; | ||
self.db_init( collection ); // NF 20/04/2015 | ||
if (cb) { | ||
cb(error, this._collection); | ||
} | ||
}); | ||
return this; | ||
}; | ||
/** Setup and initialize the collection used to manage Jobs. | ||
* @param collection collection name or undefined for default 'agendaJobs' | ||
* NF 20/04/2015 | ||
*/ | ||
Agenda.prototype.db_init = function( collection ){ | ||
this._collection = this._mdb.collection(collection || 'agendaJobs'); | ||
this._collection.createIndexes([{ | ||
"key": {"name" : 1, "priority" : -1, "lockedAt" : 1, "nextRunAt" : 1, "disabled" : 1}, | ||
"name": "findAndLockNextJobIndex1" | ||
}, { | ||
"key": {"name" : 1, "lockedAt" : 1, "priority" : -1, "nextRunAt" : 1, "disabled" : 1}, | ||
"name": "findAndLockNextJobIndex2" | ||
}], | ||
function( err, result ){ | ||
if (err) throw err; | ||
}); | ||
}; | ||
Agenda.prototype.name = function(name) { | ||
@@ -88,15 +139,21 @@ this._name = name; | ||
Agenda.prototype.jobs = function() { | ||
var args = Array.prototype.slice.call(arguments); | ||
if (typeof args[args.length - 1] === 'function') { | ||
args.push(findJobsResultWrapper(this, args.pop())); | ||
} | ||
return this._db.findItems.apply(this._db, args); | ||
/** Find all Jobs matching `query` and pass same back in cb(). | ||
* refactored. NF 21/04/2015 | ||
*/ | ||
Agenda.prototype.jobs = function( query, cb ){ | ||
var self = this; | ||
this._collection.find( query ).toArray( function( error, result ){ | ||
var jobs; | ||
if( !error ){ | ||
jobs = result.map( createJob.bind( null, self ) ); | ||
} | ||
cb( error, jobs ); | ||
}); | ||
}; | ||
Agenda.prototype.purge = function(cb) { | ||
var definedNames = Object.keys(this._definitions); | ||
this._db.remove({name: {$not: {$in: definedNames}}}, cb); | ||
this.cancel( {name: {$not: {$in: definedNames}}}, cb ); // NF refactored 21/04/2015 | ||
}; | ||
@@ -173,4 +230,15 @@ | ||
/** Cancels any jobs matching the passed mongodb query, and removes them from the database. | ||
* @param query mongo db query | ||
* @param cb callback( error, numRemoved ) | ||
* | ||
* @caller client code, Agenda.purge(), Job.remove() | ||
*/ | ||
Agenda.prototype.cancel = function(query, cb) { | ||
return this._db.remove(query, cb); | ||
// NF refactored 21/04/2015 | ||
this._collection.deleteMany( query, function( error, result ){ | ||
cb( error, result && result.result ? result.result.n : undefined ); | ||
}); | ||
}; | ||
@@ -197,3 +265,3 @@ | ||
if (id) { | ||
this._db.findAndModify({_id: id}, {}, update, {new: true}, processDbResult); | ||
this._collection.findAndModify({_id: id}, {}, update, {new: true}, processDbResult ); | ||
} else if (props.type == 'single') { | ||
@@ -208,24 +276,27 @@ if (props.nextRunAt && props.nextRunAt <= now) { | ||
// Try an upsert. | ||
this._db.findAndModify({name: props.name, type: 'single'}, {}, update, {upsert: true, new: true}, processDbResult); | ||
this._collection.findAndModify({name: props.name, type: 'single'}, {}, update, {upsert: true, new: true}, processDbResult); | ||
} else if (unique) { | ||
var query = job.attrs.unique; | ||
query.name = props.name; | ||
this._db.findAndModify(query, {}, update, {upsert: true, new: true}, processDbResult); | ||
this._collection.findAndModify(query, {}, update, {upsert: true, new: true}, processDbResult); | ||
} else { | ||
this._db.insert(props, processDbResult); | ||
this._collection.insertOne(props, processDbResult); // NF updated 22/04/2015 | ||
} | ||
function processDbResult(err, res) { | ||
function processDbResult(err, result) { | ||
if (err) { | ||
throw(err); | ||
} else if (res) { | ||
if (Array.isArray(res)) { | ||
res = res[0]; | ||
} | ||
} else if (result) { | ||
var res = result.ops ? result.ops : result.value; // result is different for findAndModify() vs. insertOne(). NF 20/04/2015 | ||
if ( res ){ | ||
if (Array.isArray(res)) { | ||
res = res[0]; | ||
} | ||
job.attrs._id = res._id; | ||
job.attrs.nextRunAt = res.nextRunAt; | ||
job.attrs._id = res._id; | ||
job.attrs.nextRunAt = res.nextRunAt; | ||
if (job.attrs.nextRunAt && job.attrs.nextRunAt < self._nextScanAt) { | ||
processJobs.call(self, job); | ||
if (job.attrs.nextRunAt && job.attrs.nextRunAt < self._nextScanAt) { | ||
processJobs.call(self, job); | ||
} | ||
} | ||
@@ -253,3 +324,3 @@ } | ||
this._processInterval = undefined; | ||
unlockJobs.call(this, cb); | ||
this._unlockJobs( cb ); | ||
}; | ||
@@ -262,47 +333,37 @@ | ||
* @protected | ||
* @caller jobQueueFilling() only | ||
*/ | ||
Agenda.prototype._findAndLockNextJob = function(jobName, definition, cb) { | ||
var now = new Date(), | ||
var self = this, | ||
now = new Date(), | ||
lockDeadline = new Date(Date.now().valueOf() - definition.lockLifetime); | ||
this._db.findAndModify( | ||
{ | ||
nextRunAt: {$lte: this._nextScanAt}, | ||
disabled: { $ne: true }, | ||
$or: [ | ||
{lockedAt: null}, | ||
{lockedAt: {$exists: false}}, | ||
{lockedAt: {$lte: lockDeadline}} | ||
], | ||
name: jobName | ||
}, | ||
{'priority': -1}, | ||
{$set: {lockedAt: now}}, | ||
{'new': true}, | ||
findJobsResultWrapper(this, cb) | ||
); | ||
// Don't try and access Mongo Db if we've lost connection to it. Also see clibu_automation.js db.on.close code. NF 29/04/2015 | ||
// Trying to resolve crash on Dev PC when it resumes from sleep. | ||
if ( this._mdb.s.topology.connections().length === 0 ) { | ||
cb( new MongoError( 'No MongoDB Connection') ); | ||
} else { | ||
this._collection.findAndModify( | ||
{ | ||
$or: [ | ||
{name: jobName, lockedAt: null, nextRunAt: {$lte: this._nextScanAt}, disabled: { $ne: true }}, | ||
{name: jobName, lockedAt: {$exists: false}, nextRunAt: {$lte: this._nextScanAt}, disabled: { $ne: true }}, | ||
{name: jobName, lockedAt: {$lte: lockDeadline}, nextRunAt: {$lte: this._nextScanAt}, disabled: { $ne: true }} | ||
] | ||
}, | ||
{'priority': -1}, // sort | ||
{$set: {lockedAt: now}}, // Doc | ||
{'new': true}, // options | ||
function( error, result ){ | ||
var jobs; | ||
if ( !error && result.value ){ | ||
jobs = createJob( self, result.value ); | ||
} | ||
cb( error, jobs ); | ||
} | ||
); | ||
} | ||
}; | ||
/** | ||
* | ||
* @param agenda | ||
* @param cb | ||
* @return {Function} | ||
* @private | ||
*/ | ||
function findJobsResultWrapper(agenda, cb) { | ||
return function (err, jobs) { | ||
if (jobs) { | ||
//query result can be array or one record | ||
if (Array.isArray(jobs)) { | ||
jobs = jobs.map(createJob.bind(null, agenda)); | ||
} else { | ||
jobs = createJob(agenda, jobs); | ||
} | ||
} | ||
cb(err, jobs); | ||
}; | ||
} | ||
/** | ||
@@ -320,3 +381,5 @@ * Create Job object from data | ||
function unlockJobs(done) { | ||
// Refactored to Agenda method. NF 22/04/2015 | ||
// @caller Agenda.stop() only. Could be moved into stop(). NF | ||
Agenda.prototype._unlockJobs = function(done) { | ||
function getJobId(j) { | ||
@@ -328,5 +391,6 @@ return j.attrs._id; | ||
.concat(this._runningJobs.map(getJobId)); | ||
this._db.update({_id: { $in: jobIds } }, { $set: { lockedAt: null } }, {multi: true}, done); | ||
} | ||
this._collection.updateMany({_id: { $in: jobIds } }, { $set: { lockedAt: null } }, done); // NF refactored .update() 22/04/2015 | ||
}; | ||
function processJobs(extraJob) { | ||
@@ -349,4 +413,4 @@ if (!this._processInterval) { | ||
var now = new Date(); | ||
self._db.findAndModify({ _id: extraJob.attrs._id, lockedAt: null }, {}, { $set: { lockedAt: now } }, function(err, resp) { | ||
if (resp) { | ||
self._collection.findAndModify({ _id: extraJob.attrs._id, lockedAt: null, disabled: { $ne: true } }, {}, { $set: { lockedAt: now } }, function(err, resp) { | ||
if ( resp.value ){ // NF 20/04/2015 | ||
jobQueue.unshift(extraJob); | ||
@@ -360,3 +424,3 @@ jobProcessing(); | ||
var now = new Date(); | ||
self._nextScanAt = new Date(now.valueOf() + self._processEvery), | ||
self._nextScanAt = new Date(now.valueOf() + self._processEvery); | ||
self._findAndLockNextJob(name, definitions[name], function (err, job) { | ||
@@ -363,0 +427,0 @@ if (err) { |
@@ -0,1 +1,10 @@ | ||
/** @package | ||
job.js | ||
21/04/2015 NF Removed database code. ie. From Job.remove() | ||
Last change: NF 21/04/2015 1:20:47 PM | ||
*/ | ||
var humanInterval = require('human-interval'), | ||
@@ -172,7 +181,16 @@ CronTime = require('cron').CronTime, | ||
self.attrs.lockedAt = null; | ||
self.save(function(saveErr, job) { | ||
cb && cb(err || saveErr, job); | ||
if (self.attrs.nextRunAt) { | ||
self.save(postCommit); | ||
} else { | ||
self.remove(function(removeErr) { | ||
postCommit(removeErr, self); | ||
}); | ||
} | ||
function postCommit(commitErr, job) { | ||
cb && cb(err || commitErr, job); | ||
agenda.emit('complete', self); | ||
agenda.emit('complete:' + self.attrs.name, self); | ||
}); | ||
} | ||
}; | ||
@@ -214,2 +232,5 @@ | ||
Job.prototype.remove = function(cb) { | ||
// refactored NF 21/04/2015 | ||
this.agenda.cancel( {_id: this.attrs._id}, cb ); | ||
/* | ||
var self = this; | ||
@@ -222,2 +243,3 @@ this.agenda._db.remove({_id: this.attrs._id}, function(err, count) { | ||
}); | ||
*/ | ||
}; | ||
@@ -224,0 +246,0 @@ |
{ | ||
"name": "agenda", | ||
"version": "0.6.28", | ||
"version": "0.7.0", | ||
"description": "Light weight job scheduler for Node.js", | ||
@@ -15,3 +15,3 @@ "main": "index.js", | ||
"type": "git", | ||
"url": "git://github.com/rschmukler/agenda" | ||
"url": "git://github.com/classdojo/agenda" | ||
}, | ||
@@ -34,13 +34,13 @@ "keywords": [ | ||
"date.js": "~0.2.0", | ||
"mongoskin": "~1.4.1", | ||
"mongodb": "2.0.34", | ||
"cron": "~1.0.1" | ||
}, | ||
"devDependencies": { | ||
"mocha": "~1.13.0", | ||
"expect.js": "~0.2.0", | ||
"mocha-lcov-reporter": "0.0.1", | ||
"coveralls": "~2.3.0", | ||
"blanket": "~1.1.5", | ||
"q": "~1.0.0" | ||
"blanket": "1.1.5", | ||
"coveralls": "~2.11.4", | ||
"expect.js": "~0.3.1", | ||
"mocha": "~2.3.2", | ||
"mocha-lcov-reporter": "0.0.2", | ||
"q": "~1.4.1" | ||
} | ||
} |
# Agenda | ||
[![Build Status](https://api.travis-ci.org/rschmukler/agenda.svg)](http://travis-ci.org/rschmukler/agenda) | ||
[![Code Climate](https://d3s6mut3hikguw.cloudfront.net/github/rschmukler/agenda.png)](https://codeclimate.com/github/rschmukler/agenda/badges) | ||
[![Coverage Status](https://coveralls.io/repos/rschmukler/agenda/badge.png)](https://coveralls.io/r/rschmukler/agenda) | ||
[![Dependency Status](https://david-dm.org/rschmukler/agenda.svg)](https://david-dm.org/rschmukler/agenda) | ||
[![devDependency Status](https://david-dm.org/rschmukler/agenda/dev-status.svg)](https://david-dm.org/rschmukler/agenda#info=devDependencies) | ||
[![Code Climate](https://d3s6mut3hikguw.cloudfront.net/github/rschmukler/agenda.svg)](https://codeclimate.com/github/rschmukler/agenda/badges) | ||
[![Coverage Status](https://coveralls.io/repos/rschmukler/agenda/badge.svg)](https://coveralls.io/r/rschmukler/agenda) | ||
@@ -17,2 +19,3 @@ Agenda is a light-weight job scheduling library for Node.js. | ||
# Installation | ||
@@ -24,3 +27,3 @@ | ||
You will also need a working [mongo](http://www.mongodb.org/) database (2.4+) to point it to. | ||
You will also need a working [mongo](http://www.mongodb.org/) database (2.6+) to point it to. | ||
@@ -30,4 +33,16 @@ # Example Usage | ||
```js | ||
var agenda = new Agenda({db: { address: 'localhost:27017/agenda-example'}}); | ||
var mongoConnectionString = "mongodb://127.0.0.1/agenda"; | ||
var agenda = new Agenda({db: {address: mongoConnectionString}}); | ||
// or override the default collection name: | ||
// var agenda = new Agenda({db: {address: mongoConnectionString, collection: "jobCollectionName"}}); | ||
// or pass additional connection options: | ||
// var agenda = new Agenda({db: {address: mongoConnectionString, collection: "jobCollectionName", options: {server:{auto_reconnect:true}}}); | ||
// or pass in an existing mongodb-native MongoClient instance | ||
// var agenda = new Agenda({mongo: myMongoClient}); | ||
agenda.define('delete old users', function(job, done) { | ||
@@ -40,6 +55,6 @@ User.remove({lastLogIn: { $lt: twoDaysAgo }}, done); | ||
// Alternatively, you could also do: | ||
agenda.every('*/3 * * * *', 'delete old users'); | ||
agenda.start(); | ||
``` | ||
@@ -63,3 +78,3 @@ | ||
```js | ||
var weeklyReport = agenda.schedule('Saturday at noon', 'send email report', {to: 'another-guy@example.com'}); | ||
var weeklyReport = agenda.create('send email report', {to: 'another-guy@example.com'}) | ||
weeklyReport.repeatEvery('1 week').save(); | ||
@@ -127,5 +142,5 @@ agenda.start(); | ||
### mongo(mongoSkinInstance) | ||
### mongo(mongoClientInstance) | ||
Use an existing mongoskin instance. This can help consolidate connections to a | ||
Use an existing mongodb-native MongoClient instance. This can help consolidate connections to a | ||
database. You can instead use `.database` to have agenda handle connecting for | ||
@@ -136,3 +151,3 @@ you. | ||
Please note that this must be a *collection*. Also, you will want to run the following | ||
Please note that this must be a *collection*. Also, you will want to run the following | ||
afterwards to ensure the database has the proper indexes: | ||
@@ -154,3 +169,3 @@ | ||
```js | ||
var agenda = new Agenda({mongo: mongoSkinInstance}); | ||
var agenda = new Agenda({mongo: mongoClientInstance}); | ||
``` | ||
@@ -388,6 +403,6 @@ | ||
### jobs(mongoskin query) | ||
### jobs(mongodb-native query) | ||
Lets you query all of the jobs in the agenda job's database. This is a full [mongoskin](https://github.com/kissjs/node-mongoskin) | ||
`find` query. See mongoskin's documentation for details. | ||
Lets you query all of the jobs in the agenda job's database. This is a full [mongodb-native](https://github.com/mongodb/node-mongodb-native) | ||
`find` query. See mongodb-native's documentation for details. | ||
@@ -400,5 +415,5 @@ ```js | ||
### cancel(mongoskin query, cb) | ||
### cancel(mongodb-native query, cb) | ||
Cancels any jobs matching the passed mongoskin query, and removes them from the database. | ||
Cancels any jobs matching the passed mongodb-native query, and removes them from the database. | ||
@@ -827,3 +842,3 @@ ```js | ||
``` | ||
node server.js | ||
node server.js | ||
``` | ||
@@ -858,2 +873,3 @@ Fire up an instance with no `JOB_TYPES`, giving you the ability to process jobs, | ||
- [@nwkeeley](http://github.com/nwkeeley) | ||
- [@liamdon](http://github.com/liamdon) | ||
@@ -860,0 +876,0 @@ |
1742
test/agenda.js
/* globals before, describe, it, beforeEach, after, afterEach */ | ||
var mongoHost = process.env.MONGODB_HOST || 'localhost', | ||
mongoPort = process.env.MONGODB_PORT || '27017', | ||
mongoCfg = 'mongodb://' + mongoHost + ':' + mongoPort + '/agenda-test'; | ||
var mongoCfg = 'localhost:27017/agenda-test', | ||
expect = require('expect.js'), | ||
var expect = require('expect.js'), | ||
path = require('path'), | ||
cp = require('child_process'), | ||
mongo = require('mongoskin').db('mongodb://' + mongoCfg, {w: 0}), | ||
Agenda = require( path.join('..', 'index.js') ), | ||
jobs = new Agenda({ | ||
defaultConcurrency: 5, | ||
db: { | ||
address: mongoCfg | ||
} | ||
}), | ||
Job = require( path.join('..', 'lib', 'job.js') ); | ||
var MongoClient = require('mongodb').MongoClient; | ||
var mongo = null; | ||
// create agenda instances | ||
var jobs = null; | ||
function clearJobs(done) { | ||
@@ -22,283 +23,315 @@ mongo.collection('agendaJobs').remove({}, done); | ||
// Slow timeouts for travis | ||
var jobTimeout = process.env.TRAVIS ? 1000 : 300; | ||
var jobTimeout = process.env.TRAVIS ? 1500 : 300; | ||
before(clearJobs); | ||
var jobProcessor = function(job) { }; | ||
before(function() { | ||
jobs.define('someJob', jobProcessor); | ||
jobs.define('send email', jobProcessor); | ||
jobs.define('some job', jobProcessor); | ||
}); | ||
var jobType = 'do work'; | ||
var jobProcessor = function(job) { }; | ||
describe('Agenda', function() { | ||
it('sets a default processEvery', function() { | ||
expect(jobs._processEvery).to.be(5000); | ||
}); | ||
describe('configuration methods', function() { | ||
describe('database', function() { | ||
it('sets the database', function() { | ||
jobs.database('localhost:27017/agenda-test-new'); | ||
expect(jobs._db._skin_db._connect_args[0]).to.contain('agenda-test-new'); | ||
}); | ||
it('sets the collection', function() { | ||
jobs.database(mongoCfg, 'myJobs'); | ||
expect(jobs._db._collection_args[0]).to.be('myJobs'); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.database(mongoCfg)).to.be(jobs); | ||
}); | ||
}); | ||
function failOnError(err) { | ||
if (err) { | ||
throw err; | ||
} | ||
} | ||
describe('mongo', function() { | ||
it('sets the _db directly', function() { | ||
var agenda = new Agenda(); | ||
agenda.mongo({hello: 'world'}); | ||
expect(agenda._db).to.have.property('hello', 'world'); | ||
describe("agenda", function() { | ||
before(function(done) { | ||
jobs = new Agenda({ | ||
db: { | ||
address: mongoCfg | ||
} | ||
}, function(err) { | ||
MongoClient.connect(mongoCfg, function( error, db ){ | ||
mongo = db; | ||
setTimeout(function() { | ||
clearJobs(function() { | ||
jobs.define('someJob', jobProcessor); | ||
jobs.define('send email', jobProcessor); | ||
jobs.define('some job', jobProcessor); | ||
jobs.define(jobType, jobProcessor); | ||
done(); | ||
}); | ||
}, 50); | ||
}); | ||
it('returns itself', function() { | ||
var agenda = new Agenda(); | ||
expect(agenda.mongo({hello: 'world'})).to.be(agenda); | ||
}); | ||
}); | ||
}); | ||
describe('name', function() { | ||
it('sets the agenda name', function() { | ||
jobs.name('test queue'); | ||
expect(jobs._name).to.be('test queue'); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.name('test queue')).to.be(jobs); | ||
}); | ||
after(function(done) { | ||
setTimeout(function() { | ||
mongo.close(done); | ||
}, 50); | ||
}); | ||
describe('Agenda', function() { | ||
it('sets a default processEvery', function() { | ||
expect(jobs._processEvery).to.be(5000); | ||
}); | ||
describe('processEvery', function() { | ||
it('sets the processEvery time', function() { | ||
jobs.processEvery('3 minutes'); | ||
expect(jobs._processEvery).to.be(180000); | ||
describe('configuration methods', function() { | ||
it('sets the _db directly when passed as an option', function() { | ||
var agenda = new Agenda({mongo: mongo}); | ||
expect(agenda._mdb.databaseName).to.equal('agenda-test'); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.processEvery('3 minutes')).to.be(jobs); | ||
}); | ||
}); | ||
describe('maxConcurrency', function() { | ||
it('sets the maxConcurrency', function() { | ||
jobs.maxConcurrency(10); | ||
expect(jobs._maxConcurrency).to.be(10); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.maxConcurrency(10)).to.be(jobs); | ||
}); | ||
}); | ||
describe('defaultConcurrency', function() { | ||
it('sets the defaultConcurrency', function() { | ||
jobs.defaultConcurrency(1); | ||
expect(jobs._defaultConcurrency).to.be(1); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.defaultConcurrency(5)).to.be(jobs); | ||
}); | ||
}); | ||
describe('defaultLockLifetime', function(){ | ||
it('returns itself', function() { | ||
expect(jobs.defaultLockLifetime(1000)).to.be(jobs); | ||
}); | ||
it('sets the default lock lifetime', function(){ | ||
jobs.defaultLockLifetime(9999); | ||
expect(jobs._defaultLockLifetime).to.be(9999); | ||
}); | ||
it('is inherited by jobs', function(){ | ||
jobs.defaultLockLifetime(7777); | ||
jobs.define('testDefaultLockLifetime', function(job, done){}); | ||
expect(jobs._definitions.testDefaultLockLifetime.lockLifetime).to.be(7777); | ||
}); | ||
}); | ||
}); | ||
describe('job methods', function() { | ||
describe('configuration methods', function() { | ||
describe('create', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = jobs.create('sendEmail', {to: 'some guy'}); | ||
describe('mongo', function() { | ||
it('sets the _db directly', function() { | ||
var agenda = new Agenda(); | ||
agenda.mongo(mongo); | ||
expect(agenda._mdb.databaseName).to.equal('agenda-test'); | ||
}); | ||
it('returns itself', function() { | ||
var agenda = new Agenda(); | ||
expect(agenda.mongo(mongo)).to.be(agenda); | ||
}); | ||
}); | ||
it('returns a job', function() { | ||
expect(job).to.be.a(Job); | ||
describe('name', function() { | ||
it('sets the agenda name', function() { | ||
jobs.name('test queue'); | ||
expect(jobs._name).to.be('test queue'); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.name('test queue')).to.be(jobs); | ||
}); | ||
}); | ||
it('sets the name', function() { | ||
expect(job.attrs.name).to.be('sendEmail'); | ||
describe('processEvery', function() { | ||
it('sets the processEvery time', function() { | ||
jobs.processEvery('3 minutes'); | ||
expect(jobs._processEvery).to.be(180000); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.processEvery('3 minutes')).to.be(jobs); | ||
}); | ||
}); | ||
it('sets the type', function() { | ||
expect(job.attrs.type).to.be('normal'); | ||
describe('maxConcurrency', function() { | ||
it('sets the maxConcurrency', function() { | ||
jobs.maxConcurrency(10); | ||
expect(jobs._maxConcurrency).to.be(10); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.maxConcurrency(10)).to.be(jobs); | ||
}); | ||
}); | ||
it('sets the agenda', function() { | ||
expect(job.agenda).to.be(jobs); | ||
describe('defaultConcurrency', function() { | ||
it('sets the defaultConcurrency', function() { | ||
jobs.defaultConcurrency(1); | ||
expect(jobs._defaultConcurrency).to.be(1); | ||
}); | ||
it('returns itself', function() { | ||
expect(jobs.defaultConcurrency(5)).to.be(jobs); | ||
}); | ||
}); | ||
it('sets the data', function() { | ||
expect(job.attrs.data).to.have.property('to', 'some guy'); | ||
describe('defaultLockLifetime', function(){ | ||
it('returns itself', function() { | ||
expect(jobs.defaultLockLifetime(1000)).to.be(jobs); | ||
}); | ||
it('sets the default lock lifetime', function(){ | ||
jobs.defaultLockLifetime(9999); | ||
expect(jobs._defaultLockLifetime).to.be(9999); | ||
}); | ||
it('is inherited by jobs', function(){ | ||
jobs.defaultLockLifetime(7777); | ||
jobs.define('testDefaultLockLifetime', function(job, done){}); | ||
expect(jobs._definitions.testDefaultLockLifetime.lockLifetime).to.be(7777); | ||
}); | ||
}); | ||
}); | ||
describe('define', function() { | ||
describe('job methods', function() { | ||
it('stores the definition for the job', function() { | ||
expect(jobs._definitions.someJob).to.have.property('fn', jobProcessor); | ||
}); | ||
describe('create', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = jobs.create('sendEmail', {to: 'some guy'}); | ||
}); | ||
it('sets the default concurrency for the job', function() { | ||
expect(jobs._definitions.someJob).to.have.property('concurrency', 5); | ||
}); | ||
it('sets the default priority for the job', function() { | ||
expect(jobs._definitions.someJob).to.have.property('priority', 0); | ||
}); | ||
it('takes concurrency option for the job', function() { | ||
jobs.define('highPriority', {priority: 10}, jobProcessor); | ||
expect(jobs._definitions.highPriority).to.have.property('priority', 10); | ||
}); | ||
}); | ||
describe('every', function() { | ||
describe('with a job name specified', function() { | ||
it('returns a job', function() { | ||
expect(jobs.every('5 minutes', 'send email')).to.be.a(Job); | ||
expect(job).to.be.a(Job); | ||
}); | ||
it('sets the repeatEvery', function() { | ||
expect(jobs.every('5 seconds', 'send email').attrs.repeatInterval).to.be('5 seconds'); | ||
it('sets the name', function() { | ||
expect(job.attrs.name).to.be('sendEmail'); | ||
}); | ||
it('sets the type', function() { | ||
expect(job.attrs.type).to.be('normal'); | ||
}); | ||
it('sets the agenda', function() { | ||
expect(jobs.every('5 seconds', 'send email').agenda).to.be(jobs); | ||
expect(job.agenda).to.be(jobs); | ||
}); | ||
it('should update a job that was previously scheduled with `every`', function(done) { | ||
jobs.every(10, 'shouldBeSingleJob'); | ||
setTimeout(function() { | ||
jobs.every(20, 'shouldBeSingleJob'); | ||
}, 10); | ||
it('sets the data', function() { | ||
expect(job.attrs.data).to.have.property('to', 'some guy'); | ||
}); | ||
}); | ||
// Give the saves a little time to propagate | ||
setTimeout(function() { | ||
jobs.jobs({name: 'shouldBeSingleJob'}, function(err, res) { | ||
expect(res).to.have.length(1); | ||
done(); | ||
}); | ||
}, jobTimeout); | ||
describe('define', function() { | ||
it('stores the definition for the job', function() { | ||
expect(jobs._definitions.someJob).to.have.property('fn', jobProcessor); | ||
}); | ||
}); | ||
describe('with array of names specified', function () { | ||
it('returns array of jobs', function () { | ||
expect(jobs.every('5 minutes', ['send email', 'some job'])).to.be.an('array'); | ||
it('sets the default concurrency for the job', function() { | ||
expect(jobs._definitions.someJob).to.have.property('concurrency', 5); | ||
}); | ||
}); | ||
after(clearJobs); | ||
}); | ||
describe('schedule', function() { | ||
describe('with a job name specified', function() { | ||
it('returns a job', function() { | ||
expect(jobs.schedule('in 5 minutes', 'send email')).to.be.a(Job); | ||
it('sets the default priority for the job', function() { | ||
expect(jobs._definitions.someJob).to.have.property('priority', 0); | ||
}); | ||
it('sets the schedule', function() { | ||
var fiveish = (new Date()).valueOf() + 250000; | ||
expect(jobs.schedule('in 5 minutes', 'send email').attrs.nextRunAt.valueOf()).to.be.greaterThan(fiveish); | ||
it('takes concurrency option for the job', function() { | ||
jobs.define('highPriority', {priority: 10}, jobProcessor); | ||
expect(jobs._definitions.highPriority).to.have.property('priority', 10); | ||
}); | ||
}); | ||
describe('with array of names specified', function () { | ||
it('returns array of jobs', function () { | ||
expect(jobs.schedule('5 minutes', ['send email', 'some job'])).to.be.an('array'); | ||
}); | ||
}); | ||
after(clearJobs); | ||
}); | ||
describe('unique', function() { | ||
describe('every', function() { | ||
describe('with a job name specified', function() { | ||
it('returns a job', function() { | ||
expect(jobs.every('5 minutes', 'send email')).to.be.a(Job); | ||
}); | ||
it('sets the repeatEvery', function() { | ||
expect(jobs.every('5 seconds', 'send email').attrs.repeatInterval).to.be('5 seconds'); | ||
}); | ||
it('sets the agenda', function() { | ||
expect(jobs.every('5 seconds', 'send email').agenda).to.be(jobs); | ||
}); | ||
it('should update a job that was previously scheduled with `every`', function(done) { | ||
jobs.every(10, 'shouldBeSingleJob'); | ||
setTimeout(function() { | ||
jobs.every(20, 'shouldBeSingleJob'); | ||
}, 10); | ||
describe('should demonstrate unique contraint', function(done) { | ||
it('should create one job when unique matches', function(done) { | ||
var time = new Date(); | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': true}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time}).schedule(time).save(function(err, job) { | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': false}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time}).schedule(time).save(function(err, job) { | ||
mongo.collection('agendaJobs').find({name: 'unique job'}).toArray(function(err, j) { | ||
expect(j).to.have.length(1); | ||
// Give the saves a little time to propagate | ||
setTimeout(function() { | ||
jobs.jobs({name: 'shouldBeSingleJob'}, function(err, res) { | ||
expect(res).to.have.length(1); | ||
done(); | ||
}); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
}); | ||
describe('with array of names specified', function () { | ||
it('returns array of jobs', function () { | ||
expect(jobs.every('5 minutes', ['send email', 'some job'])).to.be.an('array'); | ||
}); | ||
}); | ||
after(clearJobs); | ||
}); | ||
describe('should demonstrate non-unique contraint', function(done) { | ||
it('should create two jobs when unique doesn\t match', function(done) { | ||
var time = new Date(Date.now() + 1000*60*3); | ||
var time2 = new Date(Date.now() + 1000*60*4); | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': true}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time}).schedule(time).save(function(err, job) { | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': false}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time2}).schedule(time).save(function(err, job) { | ||
mongo.collection('agendaJobs').find({name: 'unique job'}).toArray(function(err, j) { | ||
expect(j).to.have.length(2); | ||
done(); | ||
}); | ||
}); | ||
describe('schedule', function() { | ||
describe('with a job name specified', function() { | ||
it('returns a job', function() { | ||
expect(jobs.schedule('in 5 minutes', 'send email')).to.be.a(Job); | ||
}); | ||
it('sets the schedule', function() { | ||
var fiveish = (new Date()).valueOf() + 250000; | ||
expect(jobs.schedule('in 5 minutes', 'send email').attrs.nextRunAt.valueOf()).to.be.greaterThan(fiveish); | ||
}); | ||
}); | ||
describe('with array of names specified', function () { | ||
it('returns array of jobs', function () { | ||
expect(jobs.schedule('5 minutes', ['send email', 'some job'])).to.be.an('array'); | ||
}); | ||
}); | ||
after(clearJobs); | ||
}); | ||
describe('unique', function() { | ||
describe('should demonstrate unique contraint', function(done) { | ||
it('should create one job when unique matches', function(done) { | ||
var time = new Date(); | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': true}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time}).schedule(time).save(function(err, job) { | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': false}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time}).schedule(time).save(function(err, job) { | ||
mongo.collection('agendaJobs').find({name: 'unique job'}).toArray(function(err, j) { | ||
expect(j).to.have.length(1); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
after(clearJobs); | ||
}); | ||
after(clearJobs); | ||
}); | ||
}); | ||
describe('now', function() { | ||
it('returns a job', function() { | ||
expect(jobs.now('send email')).to.be.a(Job); | ||
describe('should demonstrate non-unique contraint', function(done) { | ||
it('should create two jobs when unique doesn\t match', function(done) { | ||
var time = new Date(Date.now() + 1000*60*3); | ||
var time2 = new Date(Date.now() + 1000*60*4); | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': true}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time}).schedule(time).save(function(err, job) { | ||
jobs.create('unique job', {type: 'active', userId: '123', 'other': false}).unique({'data.type': 'active', 'data.userId': '123', nextRunAt: time2}).schedule(time).save(function(err, job) { | ||
mongo.collection('agendaJobs').find({name: 'unique job'}).toArray(function(err, j) { | ||
expect(j).to.have.length(2); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
after(clearJobs); | ||
}); | ||
}); | ||
it('sets the schedule', function() { | ||
var now = new Date(); | ||
expect(jobs.now('send email').attrs.nextRunAt.valueOf()).to.be.greaterThan(now.valueOf() - 1); | ||
}); | ||
it('runs the job immediately', function(done) { | ||
jobs.define('immediateJob', function(job) { | ||
jobs.stop(done); | ||
describe('now', function() { | ||
it('returns a job', function() { | ||
expect(jobs.now('send email')).to.be.a(Job); | ||
}); | ||
jobs.now('immediateJob'); | ||
jobs.start(); | ||
it('sets the schedule', function() { | ||
var now = new Date(); | ||
expect(jobs.now('send email').attrs.nextRunAt.valueOf()).to.be.greaterThan(now.valueOf() - 1); | ||
}); | ||
it('runs the job immediately', function(done) { | ||
jobs.define('immediateJob', function(job) { | ||
expect(job.isRunning()).to.be(true); | ||
jobs.stop(done); | ||
}); | ||
jobs.now('immediateJob'); | ||
jobs.start(); | ||
}); | ||
after(clearJobs); | ||
}); | ||
after(clearJobs); | ||
}); | ||
describe('jobs', function() { | ||
it('returns jobs', function(done) { | ||
var job = jobs.create('test'); | ||
job.save(function() { | ||
jobs.jobs({}, function(err, c) { | ||
expect(c.length).to.not.be(0); | ||
expect(c[0]).to.be.a(Job); | ||
clearJobs(done); | ||
describe('jobs', function() { | ||
it('returns jobs', function(done) { | ||
var job = jobs.create('test'); | ||
job.save(function() { | ||
jobs.jobs({}, function(err, c) { | ||
expect(c.length).to.not.be(0); | ||
expect(c[0]).to.be.a(Job); | ||
clearJobs(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('purge', function() { | ||
it('removes all jobs without definitions', function(done) { | ||
var job = jobs.create('no definition'); | ||
jobs.stop(function() { | ||
job.save(function() { | ||
jobs.jobs({name: 'no definition'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(1); | ||
jobs.purge(function(err) { | ||
describe('purge', function() { | ||
it('removes all jobs without definitions', function(done) { | ||
var job = jobs.create('no definition'); | ||
jobs.stop(function() { | ||
job.save(function() { | ||
jobs.jobs({name: 'no definition'}, function(err, j) { | ||
if(err) return done(err); | ||
jobs.jobs({name: 'no definition'}, function(err, j) { | ||
expect(j).to.have.length(1); | ||
jobs.purge(function(err) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
jobs.jobs({name: 'no definition'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
}); | ||
}); | ||
@@ -310,80 +343,80 @@ }); | ||
}); | ||
}); | ||
describe('saveJob', function() { | ||
it('persists job to the database', function(done) { | ||
var job = jobs.create('someJob', {}); | ||
job.save(function(err, job) { | ||
expect(job.attrs._id).to.be.ok(); | ||
clearJobs(done); | ||
describe('saveJob', function() { | ||
it('persists job to the database', function(done) { | ||
var job = jobs.create('someJob', {}); | ||
job.save(function(err, job) { | ||
expect(job.attrs._id).to.be.ok(); | ||
clearJobs(done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('cancel', function() { | ||
beforeEach(function(done) { | ||
var remaining = 3; | ||
var checkDone = function(err) { | ||
if(err) return done(err); | ||
remaining--; | ||
if(!remaining) { | ||
describe('cancel', function() { | ||
beforeEach(function(done) { | ||
var remaining = 3; | ||
var checkDone = function(err) { | ||
if(err) return done(err); | ||
remaining--; | ||
if(!remaining) { | ||
done(); | ||
} | ||
}; | ||
jobs.create('jobA').save(checkDone); | ||
jobs.create('jobA', 'someData').save(checkDone); | ||
jobs.create('jobB').save(checkDone); | ||
}); | ||
afterEach(function(done) { | ||
jobs._collection.remove({name: {$in: ['jobA', 'jobB']}}, function(err) { | ||
if(err) return done(err); | ||
done(); | ||
} | ||
}; | ||
jobs.create('jobA').save(checkDone); | ||
jobs.create('jobA', 'someData').save(checkDone); | ||
jobs.create('jobB').save(checkDone); | ||
}); | ||
afterEach(function(done) { | ||
jobs._db.remove({name: {$in: ['jobA', 'jobB']}}, function(err) { | ||
if(err) return done(err); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('should cancel a job', function(done) { | ||
jobs.jobs({name: 'jobA'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(2); | ||
jobs.cancel({name: 'jobA'}, function(err) { | ||
it('should cancel a job', function(done) { | ||
jobs.jobs({name: 'jobA'}, function(err, j) { | ||
if(err) return done(err); | ||
jobs.jobs({name: 'jobA'}, function(err, j) { | ||
expect(j).to.have.length(2); | ||
jobs.cancel({name: 'jobA'}, function(err) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
jobs.jobs({name: 'jobA'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should cancel multiple jobs', function(done) { | ||
jobs.jobs({name: {$in: ['jobA', 'jobB']}}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(3); | ||
jobs.cancel({name: {$in: ['jobA', 'jobB']}}, function(err) { | ||
it('should cancel multiple jobs', function(done) { | ||
jobs.jobs({name: {$in: ['jobA', 'jobB']}}, function(err, j) { | ||
if(err) return done(err); | ||
jobs.jobs({name: {$in: ['jobA', 'jobB']}}, function(err, j) { | ||
expect(j).to.have.length(3); | ||
jobs.cancel({name: {$in: ['jobA', 'jobB']}}, function(err) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
jobs.jobs({name: {$in: ['jobA', 'jobB']}}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should cancel jobs only if the data matches', function(done){ | ||
jobs.jobs({name: 'jobA', data: 'someData'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(1); | ||
jobs.cancel({name: 'jobA', data: 'someData'}, function(err) { | ||
it('should cancel jobs only if the data matches', function(done){ | ||
jobs.jobs({name: 'jobA', data: 'someData'}, function(err, j) { | ||
if(err) return done(err); | ||
jobs.jobs({name: 'jobA', data: 'someData'}, function(err, j) { | ||
expect(j).to.have.length(1); | ||
jobs.cancel({name: 'jobA', data: 'someData'}, function(err) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
jobs.jobs({name: 'jobA'}, function(err, j) { | ||
jobs.jobs({name: 'jobA', data: 'someData'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(1); | ||
done(); | ||
expect(j).to.have.length(0); | ||
jobs.jobs({name: 'jobA'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(1); | ||
done(); | ||
}); | ||
}); | ||
@@ -395,175 +428,175 @@ }); | ||
}); | ||
}); | ||
describe('Job', function() { | ||
describe('repeatAt', function() { | ||
var job = new Job(); | ||
it('sets the repeat at', function() { | ||
job.repeatAt('3:30pm'); | ||
expect(job.attrs.repeatAt).to.be('3:30pm'); | ||
describe('Job', function() { | ||
describe('repeatAt', function() { | ||
var job = new Job(); | ||
it('sets the repeat at', function() { | ||
job.repeatAt('3:30pm'); | ||
expect(job.attrs.repeatAt).to.be('3:30pm'); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.repeatAt('3:30pm')).to.be(job); | ||
}); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.repeatAt('3:30pm')).to.be(job); | ||
}); | ||
}); | ||
describe('unique', function() { | ||
var job = new Job(); | ||
it('sets the unique property', function() { | ||
job.unique({'data.type': 'active', 'data.userId': '123'}); | ||
expect(JSON.stringify(job.attrs.unique)).to.be(JSON.stringify({'data.type': 'active', 'data.userId': '123'})); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.unique({'data.type': 'active', 'data.userId': '123'})).to.be(job); | ||
}); | ||
}); | ||
describe('repeatEvery', function() { | ||
var job = new Job(); | ||
it('sets the repeat interval', function() { | ||
job.repeatEvery(5000); | ||
expect(job.attrs.repeatInterval).to.be(5000); | ||
describe('unique', function() { | ||
var job = new Job(); | ||
it('sets the unique property', function() { | ||
job.unique({'data.type': 'active', 'data.userId': '123'}); | ||
expect(JSON.stringify(job.attrs.unique)).to.be(JSON.stringify({'data.type': 'active', 'data.userId': '123'})); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.unique({'data.type': 'active', 'data.userId': '123'})).to.be(job); | ||
}); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.repeatEvery('one second')).to.be(job); | ||
}); | ||
}); | ||
describe('schedule', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = new Job(); | ||
describe('repeatEvery', function() { | ||
var job = new Job(); | ||
it('sets the repeat interval', function() { | ||
job.repeatEvery(5000); | ||
expect(job.attrs.repeatInterval).to.be(5000); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.repeatEvery('one second')).to.be(job); | ||
}); | ||
}); | ||
it('sets the next run time', function() { | ||
job.schedule('in 5 minutes'); | ||
expect(job.attrs.nextRunAt).to.be.a(Date); | ||
}); | ||
it('sets the next run time Date object', function() { | ||
var when = new Date(Date.now() + 1000*60*3); | ||
job.schedule(when); | ||
expect(job.attrs.nextRunAt).to.be.a(Date); | ||
expect(job.attrs.nextRunAt.getTime()).to.eql(when.getTime()); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.schedule('tomorrow at noon')).to.be(job); | ||
}); | ||
}); | ||
describe('priority', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = new Job(); | ||
describe('schedule', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = new Job(); | ||
}); | ||
it('sets the next run time', function() { | ||
job.schedule('in 5 minutes'); | ||
expect(job.attrs.nextRunAt).to.be.a(Date); | ||
}); | ||
it('sets the next run time Date object', function() { | ||
var when = new Date(Date.now() + 1000*60*3); | ||
job.schedule(when); | ||
expect(job.attrs.nextRunAt).to.be.a(Date); | ||
expect(job.attrs.nextRunAt.getTime()).to.eql(when.getTime()); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.schedule('tomorrow at noon')).to.be(job); | ||
}); | ||
}); | ||
it('sets the priority to a number', function() { | ||
job.priority(10); | ||
expect(job.attrs.priority).to.be(10); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.priority(50)).to.be(job); | ||
}); | ||
it('parses written priorities', function() { | ||
job.priority('high'); | ||
expect(job.attrs.priority).to.be(10); | ||
}); | ||
}); | ||
describe('computeNextRunAt', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = new Job(); | ||
describe('priority', function() { | ||
var job; | ||
beforeEach(function() { | ||
job = new Job(); | ||
}); | ||
it('sets the priority to a number', function() { | ||
job.priority(10); | ||
expect(job.attrs.priority).to.be(10); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.priority(50)).to.be(job); | ||
}); | ||
it('parses written priorities', function() { | ||
job.priority('high'); | ||
expect(job.attrs.priority).to.be(10); | ||
}); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.computeNextRunAt()).to.be(job); | ||
}); | ||
describe('computeNextRunAt', function() { | ||
var job; | ||
it('sets to undefined if no repeat at', function() { | ||
job.attrs.repeatAt = null; | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
}); | ||
beforeEach(function() { | ||
job = new Job(); | ||
}); | ||
it('it understands repeatAt times', function() { | ||
var d = new Date(); | ||
d.setHours(23); | ||
d.setMinutes(59); | ||
d.setSeconds(0); | ||
job.attrs.repeatAt = '11:59pm'; | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt.getHours()).to.be(d.getHours()); | ||
expect(job.attrs.nextRunAt.getMinutes()).to.be(d.getMinutes()); | ||
}); | ||
it('returns the job', function() { | ||
expect(job.computeNextRunAt()).to.be(job); | ||
}); | ||
it('sets to undefined if no repeat interval', function() { | ||
job.attrs.repeatInterval = null; | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
}); | ||
it('sets to undefined if no repeat at', function() { | ||
job.attrs.repeatAt = null; | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
}); | ||
it('it understands human intervals', function() { | ||
var now = new Date(); | ||
job.attrs.lastRunAt = now; | ||
job.repeatEvery('2 minutes'); | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt).to.be(now.valueOf() + 120000); | ||
}); | ||
it('understands cron intervals', function() { | ||
var now = new Date(); | ||
now.setMinutes(1); | ||
now.setMilliseconds(0); | ||
now.setSeconds(0); | ||
job.attrs.lastRunAt = now; | ||
job.repeatEvery('*/2 * * * *'); | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt.valueOf()).to.be(now.valueOf() + 60000); | ||
}); | ||
describe('when repeat at time is invalid', function () { | ||
beforeEach(function () { | ||
try { | ||
job.attrs.repeatAt = 'foo'; | ||
job.computeNextRunAt(); | ||
} catch(e) {} | ||
it('it understands repeatAt times', function() { | ||
var d = new Date(); | ||
d.setHours(23); | ||
d.setMinutes(59); | ||
d.setSeconds(0); | ||
job.attrs.repeatAt = '11:59pm'; | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt.getHours()).to.be(d.getHours()); | ||
expect(job.attrs.nextRunAt.getMinutes()).to.be(d.getMinutes()); | ||
}); | ||
it('sets nextRunAt to undefined', function () { | ||
it('sets to undefined if no repeat interval', function() { | ||
job.attrs.repeatInterval = null; | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
}); | ||
it('fails the job', function () { | ||
expect(job.attrs.failReason).to.equal('failed to calculate repeatAt time due to invalid format'); | ||
it('it understands human intervals', function() { | ||
var now = new Date(); | ||
job.attrs.lastRunAt = now; | ||
job.repeatEvery('2 minutes'); | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt).to.be(now.valueOf() + 120000); | ||
}); | ||
}); | ||
describe('when repeat interval is invalid', function () { | ||
beforeEach(function () { | ||
try { | ||
job.attrs.repeatInterval = 'asd'; | ||
job.computeNextRunAt(); | ||
} catch(e) {} | ||
it('understands cron intervals', function() { | ||
var now = new Date(); | ||
now.setMinutes(1); | ||
now.setMilliseconds(0); | ||
now.setSeconds(0); | ||
job.attrs.lastRunAt = now; | ||
job.repeatEvery('*/2 * * * *'); | ||
job.computeNextRunAt(); | ||
expect(job.attrs.nextRunAt.valueOf()).to.be(now.valueOf() + 60000); | ||
}); | ||
it('sets nextRunAt to undefined', function () { | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
describe('when repeat at time is invalid', function () { | ||
beforeEach(function () { | ||
try { | ||
job.attrs.repeatAt = 'foo'; | ||
job.computeNextRunAt(); | ||
} catch(e) {} | ||
}); | ||
it('sets nextRunAt to undefined', function () { | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
}); | ||
it('fails the job', function () { | ||
expect(job.attrs.failReason).to.equal('failed to calculate repeatAt time due to invalid format'); | ||
}); | ||
}); | ||
it('fails the job', function () { | ||
expect(job.attrs.failReason).to.equal('failed to calculate nextRunAt due to invalid repeat interval'); | ||
describe('when repeat interval is invalid', function () { | ||
beforeEach(function () { | ||
try { | ||
job.attrs.repeatInterval = 'asd'; | ||
job.computeNextRunAt(); | ||
} catch(e) {} | ||
}); | ||
it('sets nextRunAt to undefined', function () { | ||
expect(job.attrs.nextRunAt).to.be(undefined); | ||
}); | ||
it('fails the job', function () { | ||
expect(job.attrs.failReason).to.equal('failed to calculate nextRunAt due to invalid repeat interval'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('remove', function() { | ||
it('removes the job', function(done) { | ||
var job = new Job({agenda: jobs, name: 'removed job'}); | ||
job.save(function(err) { | ||
if(err) return done(err); | ||
job.remove(function(err) { | ||
describe('remove', function() { | ||
it('removes the job', function(done) { | ||
var job = new Job({agenda: jobs, name: 'removed job'}); | ||
job.save(function(err) { | ||
if(err) return done(err); | ||
mongo.collection('agendaJobs').find({_id: job.attrs._id}).toArray(function(err, j) { | ||
expect(j).to.have.length(0); | ||
done(); | ||
job.remove(function(err) { | ||
if(err) return done(err); | ||
mongo.collection('agendaJobs').find({_id: job.attrs._id}).toArray(function(err, j) { | ||
expect(j).to.have.length(0); | ||
done(); | ||
}); | ||
}); | ||
@@ -573,176 +606,177 @@ }); | ||
}); | ||
}); | ||
describe('run', function() { | ||
var job, | ||
definitions = jobs._definitions; | ||
describe('run', function() { | ||
var job; | ||
jobs.define('testRun', function(job, done) { | ||
setTimeout(function() { | ||
done(); | ||
}, 100); | ||
}); | ||
before(function() { | ||
jobs.define('testRun', function(job, done) { | ||
setTimeout(function() { | ||
done(); | ||
}, 100); | ||
}); | ||
}); | ||
beforeEach(function() { | ||
job = new Job({agenda: jobs, name: 'testRun'}); | ||
}); | ||
beforeEach(function() { | ||
job = new Job({agenda: jobs, name: 'testRun'}); | ||
}); | ||
it('updates lastRunAt', function(done) { | ||
var now = new Date(); | ||
setTimeout(function() { | ||
job.run(function() { | ||
expect(job.attrs.lastRunAt.valueOf()).to.be.greaterThan(now.valueOf()); | ||
done(); | ||
}); | ||
}, 5); | ||
}); | ||
it('updates lastRunAt', function(done) { | ||
var now = new Date(); | ||
setTimeout(function() { | ||
job.run(function() { | ||
expect(job.attrs.lastRunAt.valueOf()).to.be.greaterThan(now.valueOf()); | ||
done(); | ||
}); | ||
}, 5); | ||
}); | ||
it('fails if job is undefined', function(done) { | ||
job = new Job({agenda: jobs, name: 'not defined'}); | ||
job.run(function() { | ||
expect(job.attrs.failedAt).to.be.ok(); | ||
expect(job.attrs.failReason).to.be('Undefined job'); | ||
done(); | ||
}); | ||
}); | ||
it('updates nextRunAt', function(done) { | ||
var now = new Date(); | ||
job.repeatEvery('10 minutes'); | ||
setTimeout(function() { | ||
it('fails if job is undefined', function(done) { | ||
job = new Job({agenda: jobs, name: 'not defined'}); | ||
job.run(function() { | ||
expect(job.attrs.nextRunAt.valueOf()).to.be.greaterThan(now.valueOf() + 59999); | ||
expect(job.attrs.failedAt).to.be.ok(); | ||
expect(job.attrs.failReason).to.be('Undefined job'); | ||
done(); | ||
}); | ||
}, 5); | ||
}); | ||
it('handles errors', function(done) { | ||
job.attrs.name = 'failBoat'; | ||
jobs.define('failBoat', function(job, cb) { | ||
throw(new Error("Zomg fail")); | ||
}); | ||
job.run(function(err) { | ||
expect(err).to.be.ok(); | ||
done(); | ||
it('updates nextRunAt', function(done) { | ||
var now = new Date(); | ||
job.repeatEvery('10 minutes'); | ||
setTimeout(function() { | ||
job.run(function() { | ||
expect(job.attrs.nextRunAt.valueOf()).to.be.greaterThan(now.valueOf() + 59999); | ||
done(); | ||
}); | ||
}, 5); | ||
}); | ||
}); | ||
it('handles errors with q promises', function(done) { | ||
job.attrs.name = 'failBoat2'; | ||
jobs.define('failBoat2', function(job, cb) { | ||
var Q = require('q'); | ||
Q.delay(100).then(function(){ | ||
it('handles errors', function(done) { | ||
job.attrs.name = 'failBoat'; | ||
jobs.define('failBoat', function(job, cb) { | ||
throw(new Error("Zomg fail")); | ||
}).fail(cb).done(); | ||
}); | ||
job.run(function(err) { | ||
expect(err).to.be.ok(); | ||
done(); | ||
}); | ||
}); | ||
job.run(function(err) { | ||
expect(err).to.be.ok(); | ||
done(); | ||
it('handles errors with q promises', function(done) { | ||
job.attrs.name = 'failBoat2'; | ||
jobs.define('failBoat2', function(job, cb) { | ||
var Q = require('q'); | ||
Q.delay(100).then(function(){ | ||
throw(new Error("Zomg fail")); | ||
}).fail(cb).done(); | ||
}); | ||
job.run(function(err) { | ||
expect(err).to.be.ok(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('doesn\'t allow a stale job to be saved', function(done) { | ||
var flag = false; | ||
job.attrs.name = 'failBoat3'; | ||
job.save(function(err) { | ||
if(err) return done(err); | ||
jobs.define('failBoat3', function(job, cb) { | ||
// Explicitly find the job again, | ||
// so we have a new job object | ||
jobs.jobs({name: 'failBoat3'}, function(err, j) { | ||
if(err) return done(err); | ||
j[0].remove(function(err) { | ||
it('doesn\'t allow a stale job to be saved', function(done) { | ||
var flag = false; | ||
job.attrs.name = 'failBoat3'; | ||
job.save(function(err) { | ||
if(err) return done(err); | ||
jobs.define('failBoat3', function(job, cb) { | ||
// Explicitly find the job again, | ||
// so we have a new job object | ||
jobs.jobs({name: 'failBoat3'}, function(err, j) { | ||
if(err) return done(err); | ||
cb(); | ||
j[0].remove(function(err) { | ||
if(err) return done(err); | ||
cb(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
job.run(function(err) { | ||
// Expect the deleted job to not exist in the database | ||
jobs.jobs({name: 'failBoat3'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
job.run(function(err) { | ||
// Expect the deleted job to not exist in the database | ||
jobs.jobs({name: 'failBoat3'}, function(err, j) { | ||
if(err) return done(err); | ||
expect(j).to.have.length(0); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('touch', function(done) { | ||
it('extends the lock lifetime', function(done) { | ||
var lockedAt = new Date(); | ||
var job = new Job({agenda: jobs, name: 'some job', lockedAt: lockedAt}); | ||
job.save = function(cb) { cb(); }; | ||
setTimeout(function() { | ||
job.touch(function() { | ||
expect(job.attrs.lockedAt).to.be.greaterThan(lockedAt); | ||
done(); | ||
}); | ||
}, 2); | ||
}); | ||
}); | ||
describe('fail', function() { | ||
var job = new Job(); | ||
it('takes a string', function() { | ||
job.fail('test'); | ||
expect(job.attrs.failReason).to.be('test'); | ||
describe('touch', function(done) { | ||
it('extends the lock lifetime', function(done) { | ||
var lockedAt = new Date(); | ||
var job = new Job({agenda: jobs, name: 'some job', lockedAt: lockedAt}); | ||
job.save = function(cb) { cb(); }; | ||
setTimeout(function() { | ||
job.touch(function() { | ||
expect(job.attrs.lockedAt).to.be.greaterThan(lockedAt); | ||
done(); | ||
}); | ||
}, 2); | ||
}); | ||
}); | ||
it('takes an error object', function() { | ||
job.fail(new Error('test')); | ||
expect(job.attrs.failReason).to.be('test'); | ||
}); | ||
it('sets the failedAt time', function() { | ||
job.fail('test'); | ||
expect(job.attrs.failedAt).to.be.a(Date); | ||
}); | ||
}); | ||
describe('enable', function() { | ||
it('sets disabled to false on the job', function() { | ||
var job = new Job({disabled: true}); | ||
job.enable(); | ||
expect(job.attrs.disabled).to.be(false); | ||
describe('fail', function() { | ||
var job = new Job(); | ||
it('takes a string', function() { | ||
job.fail('test'); | ||
expect(job.attrs.failReason).to.be('test'); | ||
}); | ||
it('takes an error object', function() { | ||
job.fail(new Error('test')); | ||
expect(job.attrs.failReason).to.be('test'); | ||
}); | ||
it('sets the failedAt time', function() { | ||
job.fail('test'); | ||
expect(job.attrs.failedAt).to.be.a(Date); | ||
}); | ||
}); | ||
it('returns the job', function() { | ||
var job = new Job({disabled: true}); | ||
expect(job.enable()).to.be(job); | ||
}); | ||
}); | ||
describe('enable', function() { | ||
it('sets disabled to false on the job', function() { | ||
var job = new Job({disabled: true}); | ||
job.enable(); | ||
expect(job.attrs.disabled).to.be(false); | ||
}); | ||
describe('disable', function() { | ||
it('sets disabled to true on the job', function() { | ||
var job = new Job(); | ||
job.disable(); | ||
expect(job.attrs.disabled).to.be(true); | ||
it('returns the job', function() { | ||
var job = new Job({disabled: true}); | ||
expect(job.enable()).to.be(job); | ||
}); | ||
}); | ||
it('returns the job', function() { | ||
var job = new Job(); | ||
expect(job.disable()).to.be(job); | ||
}); | ||
}); | ||
describe('save', function() { | ||
it('calls saveJob on the agenda', function(done) { | ||
var oldSaveJob = jobs.saveJob; | ||
jobs.saveJob = function() { | ||
jobs.saveJob = oldSaveJob; | ||
done(); | ||
}; | ||
var job = jobs.create('some job', { wee: 1}); | ||
job.save(); | ||
describe('disable', function() { | ||
it('sets disabled to true on the job', function() { | ||
var job = new Job(); | ||
job.disable(); | ||
expect(job.attrs.disabled).to.be(true); | ||
}); | ||
it('returns the job', function() { | ||
var job = new Job(); | ||
expect(job.disable()).to.be(job); | ||
}); | ||
}); | ||
it('doesnt save the job if its been removed', function(done) { | ||
var job = jobs.create('another job'); | ||
// Save, then remove, then try and save again. | ||
// The second save should fail. | ||
job.save(function(err, j) { | ||
j.remove(function() { | ||
j.save(function(err, res) { | ||
jobs.jobs({name: 'another job'}, function(err, res) { | ||
expect(res).to.have.length(0); | ||
done(); | ||
describe('save', function() { | ||
it('calls saveJob on the agenda', function(done) { | ||
var oldSaveJob = jobs.saveJob; | ||
jobs.saveJob = function() { | ||
jobs.saveJob = oldSaveJob; | ||
done(); | ||
}; | ||
var job = jobs.create('some job', { wee: 1}); | ||
job.save(); | ||
}); | ||
it('doesnt save the job if its been removed', function(done) { | ||
var job = jobs.create('another job'); | ||
// Save, then remove, then try and save again. | ||
// The second save should fail. | ||
job.save(function(err, j) { | ||
j.remove(function() { | ||
j.save(function(err, res) { | ||
jobs.jobs({name: 'another job'}, function(err, res) { | ||
expect(res).to.have.length(0); | ||
done(); | ||
}); | ||
}); | ||
@@ -752,370 +786,396 @@ }); | ||
}); | ||
}); | ||
it('returns the job', function() { | ||
var job = jobs.create('some job', { wee: 1}); | ||
expect(job.save()).to.be(job); | ||
it('returns the job', function() { | ||
var job = jobs.create('some job', { wee: 1}); | ||
expect(job.save()).to.be(job); | ||
}); | ||
}); | ||
}); | ||
describe("start/stop", function() { | ||
it("starts/stops the job queue", function(done) { | ||
jobs.define('jobQueueTest', function jobQueueTest(job, cb) { | ||
jobs.stop(function() { | ||
clearJobs(function() { | ||
cb(); | ||
jobs.define('jobQueueTest', function(job, cb) { | ||
describe("start/stop", function() { | ||
it("starts/stops the job queue", function(done) { | ||
jobs.define('jobQueueTest', function jobQueueTest(job, cb) { | ||
jobs.stop(function() { | ||
clearJobs(function() { | ||
cb(); | ||
jobs.define('jobQueueTest', function(job, cb) { | ||
cb(); | ||
}); | ||
done(); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
jobs.every('1 second', 'jobQueueTest'); | ||
jobs.processEvery('1 second'); | ||
jobs.start(); | ||
}); | ||
jobs.every('1 second', 'jobQueueTest'); | ||
jobs.processEvery('1 second'); | ||
jobs.start(); | ||
}); | ||
it('does not run disabled jobs', function(done) { | ||
var ran = false; | ||
jobs.define('disabledJob', function() { | ||
ran = true; | ||
it('does not run disabled jobs', function(done) { | ||
var ran = false; | ||
jobs.define('disabledJob', function() { | ||
ran = true; | ||
}); | ||
var job = jobs.create('disabledJob').disable().schedule('now'); | ||
job.save(function(err) { | ||
if (err) return done(err); | ||
jobs.start(); | ||
setTimeout(function() { | ||
expect(ran).to.be(false); | ||
jobs.stop(done); | ||
}, jobTimeout); | ||
}); | ||
}); | ||
var job = jobs.create('disabledJob').disable().schedule('now'); | ||
job.save(function(err) { | ||
if (err) return done(err); | ||
it('clears locks on stop', function(done) { | ||
jobs.define('longRunningJob', function(job, cb) { | ||
//Job never finishes | ||
}); | ||
jobs.every('10 seconds', 'longRunningJob'); | ||
jobs.processEvery('1 second'); | ||
jobs.start(); | ||
setTimeout(function() { | ||
expect(ran).to.be(false); | ||
jobs.stop(done); | ||
jobs.stop(function(err, res) { | ||
jobs._collection.findOne({name: 'longRunningJob'}, function(err, job) { | ||
expect(job.lockedAt).to.be(null); | ||
done(); | ||
}); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
}); | ||
it('clears locks on stop', function(done) { | ||
jobs.define('longRunningJob', function(job, cb) { | ||
//Job never finishes | ||
}); | ||
jobs.every('10 seconds', 'longRunningJob'); | ||
jobs.processEvery('1 second'); | ||
jobs.start(); | ||
setTimeout(function() { | ||
jobs.stop(function(err, res) { | ||
jobs._db.findOne({name: 'longRunningJob'}, function(err, job) { | ||
expect(job.lockedAt).to.be(null); | ||
describe('events', function() { | ||
beforeEach(clearJobs); | ||
it('emits start event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('start', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
describe('events', function() { | ||
beforeEach(clearJobs); | ||
it('emits start event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('start', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
it('emits start:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('start:jobQueueTest', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits start:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('start:jobQueueTest', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
it('emits complete event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('complete', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits complete event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('complete', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
it('emits complete:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('complete:jobQueueTest', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits complete:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('complete:jobQueueTest', function(j) { | ||
expect(j).to.be(job); | ||
done(); | ||
it('emits success event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('success', function(j) { | ||
expect(j).to.be.ok(); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits success event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('success', function(j) { | ||
expect(j).to.be.ok(); | ||
done(); | ||
it('emits success:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('success:jobQueueTest', function(j) { | ||
expect(j).to.be.ok(); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits success:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'jobQueueTest'}); | ||
jobs.once('success:jobQueueTest', function(j) { | ||
expect(j).to.be.ok(); | ||
done(); | ||
it('emits fail event', function(done){ | ||
var job = new Job({agenda: jobs, name: 'failBoat'}); | ||
jobs.once('fail', function(err, j) { | ||
expect(err.message).to.be('Zomg fail'); | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits fail event', function(done){ | ||
var job = new Job({agenda: jobs, name: 'failBoat'}); | ||
jobs.once('fail', function(err, j) { | ||
expect(err.message).to.be('Zomg fail'); | ||
expect(j).to.be(job); | ||
done(); | ||
it('emits fail:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'failBoat'}); | ||
jobs.once('fail:failBoat', function(err, j) { | ||
expect(err.message).to.be('Zomg fail'); | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
job.run(); | ||
}); | ||
it('emits fail:job name event', function(done) { | ||
var job = new Job({agenda: jobs, name: 'failBoat'}); | ||
jobs.once('fail:failBoat', function(err, j) { | ||
expect(err.message).to.be('Zomg fail'); | ||
expect(j).to.be(job); | ||
done(); | ||
}); | ||
job.run(); | ||
}); | ||
}); | ||
}); | ||
describe("job lock", function(){ | ||
describe("job lock", function(){ | ||
it("runs job after a lock has expired", function(done) { | ||
var startCounter = 0; | ||
it("runs job after a lock has expired", function(done) { | ||
var startCounter = 0; | ||
jobs.define("lock job", {lockLifetime: 50}, function(job, cb){ | ||
startCounter++; | ||
jobs.define("lock job", {lockLifetime: 50}, function(job, cb){ | ||
startCounter++; | ||
if(startCounter != 1) { | ||
expect(startCounter).to.be(2); | ||
jobs.stop(done); | ||
} | ||
if(startCounter != 1) { | ||
expect(startCounter).to.be(2); | ||
jobs.stop(done); | ||
} | ||
}); | ||
expect(jobs._definitions["lock job"].lockLifetime).to.be(50); | ||
jobs.defaultConcurrency(100); | ||
jobs.processEvery(10); | ||
jobs.every('0.02 seconds', 'lock job'); | ||
jobs.stop(); | ||
jobs.start(); | ||
}); | ||
expect(jobs._definitions["lock job"].lockLifetime).to.be(50); | ||
jobs.defaultConcurrency(100); | ||
jobs.processEvery(10); | ||
jobs.every('0.02 seconds', 'lock job'); | ||
jobs.stop(); | ||
jobs.start(); | ||
}); | ||
}); | ||
describe("every running", function() { | ||
before(function(done) { | ||
jobs.defaultConcurrency(1); | ||
jobs.processEvery(5); | ||
describe("every running", function() { | ||
before(function(done) { | ||
jobs.defaultConcurrency(1); | ||
jobs.processEvery(5); | ||
jobs.stop(done); | ||
jobs.stop(done); | ||
}); | ||
it('should run the same job multiple times', function(done) { | ||
var counter = 0; | ||
}); | ||
it('should run the same job multiple times', function(done) { | ||
var counter = 0; | ||
jobs.define('everyRunTest1', function(job, cb) { | ||
if(counter < 2) { | ||
counter++; | ||
} | ||
cb(); | ||
}); | ||
jobs.define('everyRunTest1', function(job, cb) { | ||
if(counter < 2) { | ||
counter++; | ||
} | ||
cb(); | ||
jobs.every(10, 'everyRunTest1'); | ||
jobs.start(); | ||
setTimeout(function() { | ||
jobs.jobs({name: 'everyRunTest1'}, function(err, res) { | ||
expect(counter).to.be(2); | ||
jobs.stop(done); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
jobs.every(10, 'everyRunTest1'); | ||
it('should reuse the same job on multiple runs', function(done) { | ||
var counter = 0; | ||
jobs.start(); | ||
setTimeout(function() { | ||
jobs.jobs({name: 'everyRunTest1'}, function(err, res) { | ||
expect(counter).to.be(2); | ||
jobs.stop(done); | ||
jobs.define('everyRunTest2', function(job, cb) { | ||
if(counter < 2) { | ||
counter++; | ||
} | ||
cb(); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
jobs.every(10, 'everyRunTest2'); | ||
it('should reuse the same job on multiple runs', function(done) { | ||
var counter = 0; | ||
jobs.start(); | ||
jobs.define('everyRunTest2', function(job, cb) { | ||
if(counter < 2) { | ||
counter++; | ||
} | ||
cb(); | ||
setTimeout(function() { | ||
jobs.jobs({name: 'everyRunTest2'}, function(err, res) { | ||
expect(res).to.have.length(1); | ||
jobs.stop(done); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
jobs.every(10, 'everyRunTest2'); | ||
}); | ||
jobs.start(); | ||
describe("Integration Tests", function() { | ||
setTimeout(function() { | ||
jobs.jobs({name: 'everyRunTest2'}, function(err, res) { | ||
expect(res).to.have.length(1); | ||
jobs.stop(done); | ||
describe('.every()', function() { | ||
it('Should not rerun completed jobs after restart', function(done) { | ||
var i = 0; | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == "ran" ) { | ||
expect(i).to.be(0); | ||
i += 1; | ||
startService(); | ||
} else if( msg == 'notRan' ) { | ||
expect(i).to.be(1); | ||
done(); | ||
} else return done( new Error('Unexpected response returned!') ); | ||
}; | ||
var startService = function() { | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'daily' ] ); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}; | ||
startService(); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
}); | ||
describe("Integration Tests", function() { | ||
it('Should properly run jobs when defined via an array', function(done) { | ||
var ran1 = false, ran2 = true, doneCalled = false; | ||
describe('.every()', function() { | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == "test1-ran" ) { | ||
ran1 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else if( msg == "test2-ran") { | ||
ran2 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else return done( new Error('Jobs did not run!') ); | ||
}; | ||
it('Should not rerun completed jobs after restart', function(done) { | ||
var i = 0; | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == "ran" ) { | ||
expect(i).to.be(0); | ||
i += 1; | ||
startService(); | ||
} else if( msg == 'notRan' ) { | ||
expect(i).to.be(1); | ||
done(); | ||
} else return done( new Error('Unexpected response returned!') ); | ||
}; | ||
var startService = function() { | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'daily' ] ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'daily-array' ] ); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}; | ||
}); | ||
startService(); | ||
}); | ||
it('should not run if job is disabled', function(done) { | ||
var counter = 0; | ||
it('Should properly run jobs when defined via an array', function(done) { | ||
var ran1 = false, ran2 = true, doneCalled = false; | ||
jobs.define('everyDisabledTest', function(job, cb) { | ||
counter++; | ||
cb(); | ||
}); | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == "test1-ran" ) { | ||
ran1 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else if( msg == "test2-ran") { | ||
ran2 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else return done( new Error('Jobs did not run!') ); | ||
}; | ||
var job = jobs.every(10, 'everyDisabledTest'); | ||
jobs.start(); | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'daily-array' ] ); | ||
job.disable(); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
job.save(); | ||
setTimeout(function() { | ||
jobs.jobs({name: 'everyDisabledTest'}, function(err, res) { | ||
expect(counter).to.be(0); | ||
jobs.stop(done); | ||
}); | ||
}, jobTimeout); | ||
}); | ||
}); | ||
}); | ||
describe('schedule()', function() { | ||
describe('schedule()', function() { | ||
it('Should not run jobs scheduled in the future', function(done) { | ||
var i = 0; | ||
it('Should not run jobs scheduled in the future', function(done) { | ||
var i = 0; | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == 'notRan' ) { | ||
if( i < 5 ) return done(); | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == 'notRan' ) { | ||
if( i < 5 ) return done(); | ||
i += 1; | ||
startService(); | ||
} else return done( new Error('Job scheduled in future was ran!') ); | ||
}; | ||
i += 1; | ||
startService(); | ||
} else return done( new Error('Job scheduled in future was ran!') ); | ||
}; | ||
var startService = function() { | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'define-future-job' ] ); | ||
var startService = function() { | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'define-future-job' ] ); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}; | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}; | ||
startService(); | ||
}); | ||
startService(); | ||
}); | ||
it('Should run past due jobs when process starts', function(done) { | ||
it('Should run past due jobs when process starts', function(done) { | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == 'ran' ) { | ||
done(); | ||
} else return done( new Error('Past due job did not run!') ); | ||
}; | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == 'ran' ) { | ||
done(); | ||
} else return done( new Error('Past due job did not run!') ); | ||
}; | ||
var startService = function() { | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'define-past-due-job' ] ); | ||
var startService = function() { | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'define-past-due-job' ] ); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}; | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}; | ||
startService(); | ||
}); | ||
startService(); | ||
}); | ||
it('Should schedule using array of names', function(done) { | ||
var ran1 = false, ran2 = false, doneCalled = false; | ||
it('Should schedule using array of names', function(done) { | ||
var ran1 = false, ran2 = false, doneCalled = false; | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == "test1-ran" ) { | ||
ran1 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else if( msg == "test2-ran") { | ||
ran2 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else return done( new Error('Jobs did not run!') ); | ||
}; | ||
if( msg == "test1-ran" ) { | ||
ran1 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else if( msg == "test2-ran") { | ||
ran2 = true; | ||
if( !!ran1 && !!ran2 && !doneCalled) { | ||
doneCalled = true; | ||
done(); | ||
return n.send('exit'); | ||
} | ||
} else return done( new Error('Jobs did not run!') ); | ||
}; | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'schedule-array' ] ); | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'schedule-array' ] ); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}); | ||
}); | ||
describe('now()', function() { | ||
describe('now()', function() { | ||
it('Should immediately run the job', function(done) { | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == 'ran' ) { | ||
return done(); | ||
} else return done( new Error("Job did not immediately run!") ); | ||
}; | ||
it('Should immediately run the job', function(done) { | ||
var serviceError = function(e) { done(e); }; | ||
var receiveMessage = function(msg) { | ||
if( msg == 'ran' ) { | ||
return done(); | ||
} else return done( new Error("Job did not immediately run!") ); | ||
}; | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'now' ] ); | ||
var serverPath = path.join( __dirname, 'fixtures', 'agenda-instance.js' ); | ||
var n = cp.fork( serverPath, [ mongoCfg, 'now' ] ); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
n.on('message', receiveMessage); | ||
n.on('error', serviceError); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -8,19 +8,22 @@ var connStr = process.argv[2]; | ||
var agenda = new Agenda({ db: { address: connStr } }); | ||
var agenda = new Agenda({ db: { address: connStr } }, function(err, collection) { | ||
tests.forEach(function(test) { | ||
addTests[test](agenda); | ||
}); | ||
tests.forEach(function(test) { | ||
addTests[test](agenda); | ||
}); | ||
agenda.start(); | ||
agenda.start(); | ||
// Ensure we can shut down the process from tests | ||
process.on('message', function(msg) { | ||
if( msg == 'exit' ) process.exit(0); | ||
// Ensure we can shut down the process from tests | ||
process.on('message', function(msg) { | ||
if( msg == 'exit' ) process.exit(0); | ||
}); | ||
// Send default message of "notRan" after 400ms | ||
setTimeout(function() { | ||
process.send('notRan'); | ||
process.exit(0); | ||
}, 400); | ||
}); | ||
// Send default message of "notRan" after 200ms | ||
setTimeout(function() { | ||
process.send('notRan'); | ||
process.exit(0); | ||
}, 200); |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
91975
12
1726
886
7
+ Addedmongodb@2.0.34
+ Addedbson@0.4.23(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedkerberos@0.0.24(transitive)
+ Addedmongodb@2.0.34(transitive)
+ Addedmongodb-core@1.2.0(transitive)
+ Addednan@2.10.0(transitive)
+ Addedreadable-stream@1.0.31(transitive)
+ Addedstring_decoder@0.10.31(transitive)
- Removedmongoskin@~1.4.1
- Removedabort-controller@3.0.0(transitive)
- Removedbase64-js@1.5.1(transitive)
- Removedbson@0.2.22(transitive)
- Removedbuffer@6.0.3(transitive)
- Removedevent-target-shim@5.0.1(transitive)
- Removedevents@3.3.0(transitive)
- Removedieee754@1.2.1(transitive)
- Removedkerberos@0.0.11(transitive)
- Removedmongodb@1.4.40(transitive)
- Removedmongoskin@1.4.13(transitive)
- Removednan@1.8.4(transitive)
- Removedprocess@0.11.10(transitive)
- Removedreadable-stream@4.5.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedstring_decoder@1.3.0(transitive)