node-resque
Version:
an opinionated implementation of resque in node
487 lines (431 loc) • 16.4 kB
JavaScript
var specHelper = require(__dirname + "/../_specHelper.js").specHelper;
var should = require('should');
describe('queue', function(){
var queue;
it("can connect", function(done){
queue = new specHelper.NR.queue({connection: specHelper.connectionDetails, queue: specHelper.queue}, function(){
should.exist(queue);
done();
});
});
it("can provide an error if connection failed", function(done) {
// Only run this test if this is using real redis
if(process.env.FAKEREDIS == 'true') {
return done();
}
// Make a copy of the connectionDetails so we don't overwrite the original one
var connectionDetails = {
package: specHelper.connectionDetails.package,
host: "wronghostname",
password: specHelper.connectionDetails.password,
port: "wrongport",
database: specHelper.connectionDetails.database,
namespace: specHelper.connectionDetails.namespace,
};
resolved = false;
queue = new specHelper.NR.queue({connection: connectionDetails, queue: specHelper.queue}, function(err){
if(resolved === false){ // new versions of redis will keep retrying in node v0.11x...
should.exist(err);
resolved = true;
done();
}
});
});
describe('[with connection]', function(){
before(function(done){
specHelper.connect(function(){
queue = new specHelper.NR.queue({connection: specHelper.connectionDetails, queue: specHelper.queue}, function(){
done();
});
});
});
beforeEach(function(done){
specHelper.cleanup(function(){
done();
});
});
after(function(done){
specHelper.cleanup(function(){
done();
});
});
it('can add a normal job', function(done){
queue.enqueue(specHelper.queue, 'someJob', [1,2,3], function(){
specHelper.popFromQueue(function(err, obj){
should.exist(obj);
obj = JSON.parse(obj);
obj['class'].should.equal('someJob');
obj['args'].should.eql([1,2,3]);
done();
});
});
});
it('can add delayed job (enqueueAt)', function(done){
queue.enqueueAt(10000, specHelper.queue, 'someJob', [1,2,3], function(){
specHelper.redis.zscore(specHelper.namespace + ":delayed_queue_schedule", "10", function(err, score){
String(score).should.equal("10");
specHelper.redis.lpop(specHelper.namespace + ":delayed:" + "10", function(err, obj){
should.exist(obj);
obj = JSON.parse(obj);
obj['class'].should.equal('someJob');
obj['args'].should.eql([1,2,3]);
done();
});
});
});
});
it('can add delayed job (enqueueIn)', function(done){
var now = Math.round( new Date().getTime() / 1000 ) + 5;
queue.enqueueIn(5 * 1000, specHelper.queue, 'someJob', [1,2,3], function(){
specHelper.redis.zscore(specHelper.namespace + ":delayed_queue_schedule", now, function(err, score){
String(score).should.equal(String(now));
specHelper.redis.lpop(specHelper.namespace + ":delayed:" + now, function(err, obj){
should.exist(obj);
obj = JSON.parse(obj);
obj['class'].should.equal('someJob');
obj['args'].should.eql([1,2,3]);
done();
});
});
});
});
it('can get the number of jobs currently enqueued', function(done){
queue.enqueue(specHelper.queue, 'someJob', [1,2,3], function(){
queue.enqueue(specHelper.queue, 'someJob', [1,2,3], function(){
queue.length(specHelper.queue, function(err, len){
len.should.equal(2);
done();
});
});
});
});
it('can find previously scheduled jobs', function(done){
queue.enqueueAt(10000, specHelper.queue, 'someJob', [1,2,3], function(){
queue.scheduledAt(specHelper.queue, 'someJob', [1,2,3], function(err, timestamps){
timestamps.length.should.equal(1);
timestamps[0].should.equal('10');
done();
});
});
});
it('will not match previously scheduled jobs with differnt args', function(done){
queue.enqueueAt(10000, specHelper.queue, 'someJob', [1,2,3], function(){
queue.scheduledAt(specHelper.queue, 'someJob', [3,2,1], function(err, timestamps){
timestamps.length.should.equal(0);
done();
});
});
});
it('can deleted an enqued job', function(done){
queue.enqueue(specHelper.queue, 'someJob', [1,2,3], function(){
queue.length(specHelper.queue, function(err, len){
len.should.equal(1);
queue.del(specHelper.queue, 'someJob', [1,2,3], function(){
queue.length(specHelper.queue, function(err, len){
len.should.equal(0);
done();
});
});
});
});
});
it('can deleted a delayed job', function(done){
queue.enqueueAt(10000, specHelper.queue, 'someJob', [1,2,3], function(){
queue.delDelayed(specHelper.queue, 'someJob', [1,2,3], function(err, timestamps){
timestamps.length.should.equal(1);
timestamps[0].should.equal('10');
done();
});
});
});
it('can handle single arguments without explicit array', function(done){
queue.enqueue(specHelper.queue, 'someJob', 1, function(){
specHelper.popFromQueue(function(err, obj){
JSON.parse(obj)['args'].should.eql([1]);
done();
});
});
});
it('allows omitting arguments when enqueuing', function(done){
queue.enqueue(specHelper.queue, 'noParams'); // no callback here, but in practice will finish before next enqueue calls back
queue.enqueue(specHelper.queue, 'noParams', function(){
queue.length(specHelper.queue, function(err, len){
len.should.equal(2);
specHelper.popFromQueue(function(err, obj){
obj = JSON.parse(obj);
obj['class'].should.equal('noParams');
obj['args'].should.be.empty;
specHelper.popFromQueue(function(err, obj){
obj = JSON.parse(obj);
obj['class'].should.equal('noParams');
obj['args'].should.be.empty;
done();
});
});
});
});
});
it('allows omitting arguments when deleting', function(done){
queue.enqueue(specHelper.queue, 'noParams', [], function(){
queue.enqueue(specHelper.queue, 'noParams', [], function(){
queue.length(specHelper.queue, function(err, len){
len.should.equal(2);
queue.del(specHelper.queue, 'noParams');
queue.del(specHelper.queue, 'noParams', function(err, len){
queue.length(specHelper.queue, function(err, len){
len.should.equal(0);
done();
});
});
});
});
});
});
it('allows omitting arguments when adding delayed job', function(done){
queue.allDelayed(function(err, hash){
hash.should.be.empty;
queue.enqueueAt(10000, specHelper.queue, 'noParams');
queue.enqueueIn(11000, specHelper.queue, 'noParams');
queue.enqueueAt(12000, specHelper.queue, 'noParams', function(){
queue.enqueueIn(13000, specHelper.queue, 'noParams', function(){
queue.scheduledAt(specHelper.queue, 'noParams', function(err, timestamps){
timestamps.length.should.equal(4);
queue.allDelayed(function(err, hash){
Object.keys(hash).length.should.equal(4);
for(var key in hash){
hash[key][0].args.should.be.empty;
}
done();
});
});
});
});
});
});
it('allows omitting arguments when deleting a delayed job', function(done){
queue.allDelayed(function(err, hash){
hash.should.be.empty;
queue.enqueueAt(10000, specHelper.queue, 'noParams');
queue.enqueueAt(12000, specHelper.queue, 'noParams', function(){
queue.allDelayed(function(err, hash){
Object.keys(hash).length.should.equal(2);
queue.delDelayed(specHelper.queue, 'noParams');
queue.delDelayed(specHelper.queue, 'noParams', function(){
queue.allDelayed(function(err, hash){
hash.should.be.empty;
done();
});
});
});
});
});
});
describe('failed job managment', function(){
beforeEach(function(done){
var errorPayload = function(id){
return JSON.stringify({
worker: 'busted-worker-' + id,
queue: 'busted-queue',
payload: {
"class": 'busted_job',
queue: 'busted-queue',
args: [1,2,3]
},
exception: 'ERROR_NAME',
error: 'I broke',
failed_at: (new Date()).toString()
});
};
queue.connection.redis.rpush(queue.connection.key('failed'), errorPayload(1), function(){
queue.connection.redis.rpush(queue.connection.key('failed'), errorPayload(2), function(){
queue.connection.redis.rpush(queue.connection.key('failed'), errorPayload(3), function(){
done();
});
});
});
});
it('can list how many failed jobs there are', function(done){
queue.failedCount(function(err, failedCount){
should.not.exist(err);
failedCount.should.equal(3);
done();
});
});
it('can get the body content for a collection of failed jobs', function(done){
queue.failed(1,2, function(err, failedJobs){
should.not.exist(err);
failedJobs.length.should.equal(2);
failedJobs[0].worker.should.equal('busted-worker-2');
failedJobs[0].queue.should.equal('busted-queue');
failedJobs[0].exception.should.equal('ERROR_NAME');
failedJobs[0].error.should.equal('I broke');
failedJobs[0].payload.args.should.eql([1,2,3]);
failedJobs[1].worker.should.equal('busted-worker-3');
failedJobs[1].queue.should.equal('busted-queue');
failedJobs[1].exception.should.equal('ERROR_NAME');
failedJobs[1].error.should.equal('I broke');
failedJobs[1].payload.args.should.eql([1,2,3]);
done();
});
});
it('can remove a failed job by payload', function(done){
queue.failed(1,1, function(err, failedJobs){
failedJobs.length.should.equal(1);
queue.removeFailed(failedJobs[0], function(err, removedJobs){
should.not.exist(err);
removedJobs.should.equal(1);
queue.failedCount(function(err, failedCount){
failedCount.should.equal(2);
done();
});
});
});
});
it('can re-enqueue a specific job, removing it from the failed queue', function(done){
queue.failed(0,999, function(err, failedJobs){
failedJobs.length.should.equal(3);
failedJobs[2].worker.should.equal('busted-worker-3');
queue.retryAndRemoveFailed(failedJobs[2], function(err, retriedJob){
should.not.exist(err);
queue.failed(0,999, function(err, failedJobs){
failedJobs.length.should.equal(2);
failedJobs[0].worker.should.equal('busted-worker-1');
failedJobs[1].worker.should.equal('busted-worker-2');
done();
});
});
});
});
it('will return an error when trying to retry a job not in the failed queue', function(done){
queue.failed(0,999, function(err, failedJobs){
failedJobs.length.should.equal(3);
var failedJob = failedJobs[2];
failedJob.worker = 'a-fake-worker';
queue.retryAndRemoveFailed(failedJob, function(err, retriedJob){
String(err).should.eql('Error: This job is not in failed queue');
queue.failed(0,999, function(err, failedJobs){
failedJobs.length.should.equal(3);
done();
});
});
});
});
});
describe('delayed status', function(){
beforeEach(function(done){
queue.enqueueAt(10000, specHelper.queue, 'job1', [1,2,3], function(){
queue.enqueueAt(10000, specHelper.queue, 'job2', [1,2,3], function(){
queue.enqueueAt(20000, specHelper.queue, 'job3', [1,2,3], function(){
done();
});
});
});
});
it('can list the timestamps that exist', function(done){
queue.timestamps(function(err, timestamps){
should.not.exist(err);
timestamps.length.should.equal(2);
timestamps[0].should.equal(10000);
timestamps[1].should.equal(20000);
done();
});
});
it('can list the jobs delayed at a timestamp', function(done){
queue.delayedAt(10000, function(err, tasks_a){
should.not.exist(err);
tasks_a.length.should.equal(2);
tasks_a[0].class.should.equal('job1');
tasks_a[1].class.should.equal('job2');
queue.delayedAt(20000, function(err, tasks_b){
should.not.exist(err);
tasks_b.length.should.equal(1);
tasks_b[0].class.should.equal('job3');
done();
});
});
});
it('can also return a hash with all delayed tasks', function(done){
queue.allDelayed(function(err, hash){
Object.keys(hash).length.should.equal(2);
Object.keys(hash)[0].should.equal('10000');
Object.keys(hash)[1].should.equal('20000');
hash['10000'].length.should.equal(2);
hash['20000'].length.should.equal(1);
done();
});
});
});
describe('worker status', function(){
var workerA;
var workerB;
var timeout = 100;
var jobs = {
"slowJob": {
perform: function(callback){
setTimeout(function(){
callback(null);
}, timeout)
}
}
};
beforeEach(function(done){
workerA = new specHelper.NR.worker({
connection: specHelper.connectionDetails,
timeout: specHelper.timeout,
queues: specHelper.queue,
name: 'workerA'
}, jobs, function(){
workerB = new specHelper.NR.worker({
connection: specHelper.connectionDetails,
timeout: specHelper.timeout,
queues: specHelper.queue,
name: 'workerB'
}, jobs, function(){
workerA.init(function(){
workerB.init(function(){
done();
});
});
}); });
});
afterEach(function(done){
workerA.end(function(){
workerB.end(function(){
done();
});
});
});
it('can list running workers', function(done){
queue.workers(function(err, workers){
should.not.exist(err);
workers['workerA'].should.equal('test_queue');
workers['workerB'].should.equal('test_queue');
done();
});
});
it('we can see what workers are working on (idle)', function(done){
queue.allWorkingOn(function(err, data){
should.not.exist(err);
data.should.containEql({'workerA': 'started'});
data.should.containEql({'workerB': 'started'});
done();
});
});
it('we can see what workers are working on (active)', function(done){
var listener = workerA.on('job', function(q, job, failure){
workerA.removeAllListeners('job');
queue.allWorkingOn(function(err, data){
should.not.exist(err);
data.should.containEql({'workerB': 'started'});
var paylaod = data['workerA'].payload;
paylaod.queue.should.equal('test_queue');
paylaod.class.should.equal('slowJob');
done();
});
});
queue.enqueue(specHelper.queue, "slowJob");
workerA.start();
});
});
});
});