node-resque
Version:
an opinionated implementation of resque in node
298 lines (249 loc) • 9.22 kB
JavaScript
var specHelper = require(__dirname + '/../_specHelper.js').specHelper;
var should = require('should');
describe('worker', function(){
var os = require('os');
var worker;
var queue;
var jobs = {
'add': {
perform: function(a, b, callback){
var answer = a + b;
callback(null, answer);
}
},
'badAdd': {
perform: function(a, b, callback){
callback(new Error('Blue Smoke'));
}
},
'messWithData': {
perform: function(a, callback){
a.data = 'new thing';
callback(null, a);
}
},
'doubleCaller':{
perform: function(callback){
callback(null, 'a');
setTimeout(function(){ callback(null, 'b'); }, 500);
setTimeout(function(){ callback(null, 'c'); }, 1000);
}
},
'quickDefine': function(callback){
setTimeout(callback.bind(null, null, 'ok'));
},
};
it('can connect', function(done){
worker = new specHelper.NR.worker({connection: specHelper.connectionDetails, queues: specHelper.queue});
worker.connect(function(){
should.exist(worker);
worker.end();
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' || process.env.FAKEREDIS === true){
return done();
}
var connectionDetails = {
pkg: specHelper.connectionDetails.pkg,
host: 'wronghostname',
password: specHelper.connectionDetails.password,
port: specHelper.connectionDetails.port,
database: specHelper.connectionDetails.database,
namespace: specHelper.connectionDetails.namespace,
};
worker = new specHelper.NR.worker({connection: connectionDetails, timeout: specHelper.timeout, queues: specHelper.queue});
worker.connect(function(){
throw new Error('should not get here');
});
worker.on('error', function(q, job, error){
error.message.should.match(/getaddrinfo ENOTFOUND/);
worker.end(done);
});
});
describe('performInline', function(){
before(function(done){
worker = new specHelper.NR.worker({connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: specHelper.queue}, jobs);
done();
});
it('can run a successful job', function(done){
worker.performInline('add', [1, 2], function(error, result){
should.not.exist(error);
result.should.equal(3);
done();
});
});
it('can run a failing job', function(done){
worker.performInline('badAdd', [1, 2], function(error, result){
String(error).should.equal('Error: Blue Smoke');
should.not.exist(result);
done();
});
});
it('handles double callbacks properly', function(done){
var count = 0;
worker.performInline('doubleCaller', [], function(error, result){
if(count === 0){
should.not.exist(error);
}
if(count === 1){
String(error).should.equal('Error: refusing to continue with job, multiple callbacks detected');
should.not.exist(result);
}
if(count === 2){
String(error).should.equal('Error: refusing to continue with job, multiple callbacks detected');
should.not.exist(result);
done();
}
count++;
});
});
});
describe('[with connection]', function(){
before(function(done){
specHelper.connect(function(){
queue = new specHelper.NR.queue({connection: specHelper.connectionDetails});
queue.connect(function(){
done();
});
});
});
after(function(done){
specHelper.cleanup(function(){
done();
});
});
it('can boot and stop', function(done){
this.timeout(specHelper.timeout * 3);
worker = new specHelper.NR.worker({connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: specHelper.queue}, jobs);
worker.connect(function(){
worker.start();
worker.end(function(){
done();
});
});
});
describe('crashing workers', function(){
it('can clear previously crashed workers from the same host', function(done){
var name1 = os.hostname() + ':' + '0'; // fake pid
var name2 = os.hostname() + ':' + process.pid; // real pid
var worker1 = new specHelper.NR.worker({connection: specHelper.connectionDetails, timeout: specHelper.timeout, name: name1}, jobs);
worker1.connect(function(){
worker1.init(function(){
worker1.running = false;
setTimeout(function(){
var worker2 = new specHelper.NR.worker({connection: specHelper.connectionDetails, timeout: specHelper.timeout, name: name2}, jobs);
worker2.connect(function(){
worker2.on('cleaning_worker', function(worker, pid){
worker.should.equal(name1 + ':*');
pid.should.equal(0);
done();
});
worker2.workerCleanup();
});
}, 500);
});
});
});
});
describe('integration', function(){
beforeEach(function(done){
worker = new specHelper.NR.worker({connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: specHelper.queue}, jobs);
worker.connect(done);
});
afterEach(function(done){
worker.end(done);
});
it('will mark a job as failed', function(done){
var listener = worker.on('failure', function(q, job, failire){
q.should.equal(specHelper.queue);
job['class'].should.equal('badAdd');
failire.message.should.equal('Blue Smoke');
worker.removeAllListeners('failire');
done();
});
queue.enqueue(specHelper.queue, 'badAdd', [1, 2]);
worker.start();
});
it('can work a job and return succesful things', function(done){
var listener = worker.on('success', function(q, job, result){
q.should.equal(specHelper.queue);
job['class'].should.equal('add');
result.should.equal(3);
worker.result.should.equal(result);
worker.removeAllListeners('success');
done();
});
queue.enqueue(specHelper.queue, 'add', [1, 2]);
worker.start();
});
it('job arguments are immutable', function(done){
var listener = worker.on('success', function(q, job, result){
result.a.should.equal('starting value');
worker.result.should.equal(result);
worker.removeAllListeners('success');
done();
});
queue.enqueue(specHelper.queue, 'messWithData', {a: 'starting value'});
worker.start();
});
it('can accept jobs that are simple functions', function(done){
var listener = worker.on('success', function(q, job, result){
result.should.equal('ok');
worker.removeAllListeners('success');
done();
});
queue.enqueue(specHelper.queue, 'quickDefine', []);
worker.start();
});
it('will not work jobs that are not defined', function(done){
var listener = worker.on('failure', function(q, job, failure){
q.should.equal(specHelper.queue);
String(failure).should.equal('Error: No job defined for class "somethingFake"');
worker.removeAllListeners('failure');
done();
});
queue.enqueue(specHelper.queue, 'somethingFake', []);
worker.start();
});
it('will place failed jobs in the failed queue', function(done){
specHelper.redis.rpop(specHelper.namespace + ':' + 'failed', function(err, data){
data = JSON.parse(data);
data.queue.should.equal(specHelper.queue);
data.exception.should.equal('Error');
data.error.should.equal('No job defined for class "somethingFake"');
done();
});
});
it('will not double-work with a baddly defined job', function(done){
var callbackCounts = 0;
var expected = 3;
var errorCounter = 0;
var successCounter = 0;
var errorListener = worker.on('failure', function(q, job, err){
String(err).should.equal('Error: refusing to continue with job, multiple callbacks detected');
callbackCounts++;
errorCounter++;
if(callbackCounts === expected){ complete(); }
});
var successListener = worker.on('success', function(q, job, result){
result.should.equal('a');
successCounter++;
callbackCounts++;
if(callbackCounts === expected){ complete(); }
});
var complete = function(){
errorCounter.should.equal(2);
successCounter.should.equal(1);
worker.removeAllListeners('success');
worker.removeAllListeners('failure');
done();
};
queue.enqueue(specHelper.queue, 'doubleCaller', []);
worker.start();
});
});
});
});