bull
Version:
Job manager
428 lines (384 loc) • 13.4 kB
JavaScript
/*eslint-env node */
;
var Job = require('../lib/job');
var Queue = require('../lib/queue');
var expect = require('expect.js');
var redis = require('redis');
var Promise = require('bluebird');
var uuid = require('node-uuid');
Promise.promisifyAll(redis.RedisClient.prototype);
Promise.promisifyAll(redis.Multi.prototype);
describe('Job', function(){
var queue;
beforeEach(function(){
var client = redis.createClient();
return client.flushdbAsync();
});
beforeEach(function(){
queue = new Queue('test-' + uuid(), 6379, '127.0.0.1');
});
afterEach(function(){
return queue.close();
});
describe('.create', function () {
var job;
var data;
var opts;
beforeEach(function () {
data = {foo: 'bar'};
opts = {testOpt: 'enabled'};
return Job.create(queue, data, opts).then(function(createdJob){
job = createdJob;
});
});
it('returns a promise for the job', function () {
expect(job).to.have.property('jobId');
expect(job).to.have.property('data');
});
it('saves the job in redis', function () {
return Job.fromId(queue, job.jobId).then(function(storedJob){
expect(storedJob).to.have.property('jobId');
expect(storedJob).to.have.property('data');
expect(storedJob.data.foo).to.be.equal('bar');
expect(storedJob.opts).to.be.a(Object);
expect(storedJob.opts.testOpt).to.be('enabled');
});
});
});
describe('.remove', function () {
it('removes the job from redis', function(){
return Job.create(queue, {foo: 'bar'})
.tap(function(job){
return job.remove();
})
.then(function(job){
return Job.fromId(queue, job.jobId);
})
.then(function(storedJob){
expect(storedJob).to.be(null);
});
});
it('fails to remove a locked job', function() {
var token = uuid();
return Job.create(queue, 1, {foo: 'bar'}).then(function(job) {
return job.takeLock(token).then(function(lock) {
expect(lock).to.be(true);
}).then(function() {
return job.remove(token);
}).then(function() {
throw new Error('Should not be able to remove a locked job');
}).catch(function(err) {
expect(err.message).to.equal('Could not get lock for job: ' + job.jobId + '. Cannot remove job.');
});
});
});
it('removes any job from active set', function() {
return queue.add({ foo: 'bar' }).then(function(job) {
// Simulate a job in active state but not locked
return queue.moveJob('wait', 'active').then(function() {
return job.isActive().then(function(isActive) {
expect(isActive).to.be(true);
return job.remove();
});
}).then(function() {
return Job.fromId(queue, job.jobId);
}).then(function(stored) {
expect(stored).to.be(null);
return job.getState();
}).then(function(state) {
// This check is a bit of a hack. A job that is not found in any list will return the state
// stuck.
expect(state).to.equal('stuck');
});
});
});
it('emits removed event', function (cb) {
queue.once('removed', function (job) {
expect(job.data.foo).to.be.equal('bar');
cb();
});
Job.create(queue, {foo: 'bar'}).then(function(job){
job.remove();
});
});
it('a succesful job should be removable', function(done) {
queue.process(function () {
return Promise.resolve();
});
queue.add({ foo: 'bar' });
queue.on('completed', function(job) {
job.remove().then(done).catch(done);
});
});
it('a failed job should be removable', function(done) {
queue.process(function () {
throw new Error();
});
queue.add({ foo: 'bar' });
queue.on('failed', function(job) {
job.remove().then(done).catch(done);
});
});
});
describe('.retry', function () {
it('emits waiting event', function (cb) {
queue.add({foo: 'bar'});
queue.process(function (job, done) {
done(new Error('the job failed'));
});
queue.once('failed', function (job) {
queue.once('waiting', function (job2) {
expect(job2.data.foo).to.be.equal('bar');
cb();
});
job.retry();
});
});
});
describe('Locking', function(){
var id = 1000;
var job;
beforeEach(function () {
id++;
return Job.create(queue, {foo: 'bar'}).then(function(createdJob){
job = createdJob;
});
});
it('can take a lock', function(){
return job.takeLock('423').then(function(lockTaken){
expect(lockTaken).to.be(true);
}).then(function(){
return job.releaseLock('321').then(function(lockReleased){
expect(lockReleased).to.be(false);
});
});
});
it('cannot take an already taken lock', function(){
return job.takeLock('1234').then(function(lockTaken){
expect(lockTaken).to.be(true);
}).then(function(){
return job.takeLock('1234').then(function(lockTaken){
expect(lockTaken).to.be(false);
});
});
});
it('can renew a previously taken lock', function(){
return job.takeLock('1235').then(function(lockTaken){
expect(lockTaken).to.be(true);
}).then(function(){
return job.renewLock('1235').then(function(lockRenewed){
expect(lockRenewed).to.be(true);
});
});
});
it('can release a lock', function(){
return job.takeLock('1237').then(function(lockTaken){
expect(lockTaken).to.be(true);
}).then(function(){
return job.releaseLock('321').then(function(lockReleased){
expect(lockReleased).to.be(false);
});
}).then(function(){
return job.releaseLock('1237').then(function(lockReleased){
expect(lockReleased).to.be(true);
});
});
});
});
describe('.progress', function () {
it('can set and get progress', function () {
return Job.create(queue, {foo: 'bar'}).then(function(job){
return job.progress(42).then(function(){
return Job.fromId(queue, job.jobId).then(function(storedJob){
expect(storedJob.progress()).to.be(42);
});
});
});
});
});
describe('.moveToCompleted', function () {
it('marks the job as completed', function(){
return Job.create(queue, {foo: 'bar'}).then(function(job){
return job.isCompleted().then(function(isCompleted){
expect(isCompleted).to.be(false);
}).then(function(){
return job.moveToCompleted('succeeded');
}).then(function(){
return job.isCompleted().then(function(isCompleted){
expect(isCompleted).to.be(true);
expect(job.returnvalue).to.be('succeeded');
});
});
});
});
});
describe('.moveToFailed', function () {
it('marks the job as failed', function(){
return Job.create(queue, {foo: 'bar'}).then(function(job){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(false);
}).then(function(){
return job.moveToFailed(new Error('test error'));
}).then(function(){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(true);
expect(job.stacktrace).not.be(null);
expect(job.stacktrace.length).to.be(1);
});
});
});
});
it('moves the job to wait for retry if attempts are given', function() {
return Job.create(queue, {foo: 'bar'}, {attempts: 3}).then(function(job){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(false);
}).then(function(){
return job.moveToFailed(new Error('test error'));
}).then(function(){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(false);
expect(job.stacktrace).not.be(null);
expect(job.stacktrace.length).to.be(1);
return job.isWaiting().then(function(isWaiting){
expect(isWaiting).to.be(true);
});
});
});
});
});
it('marks the job as failed when attempts made equal to attempts given', function() {
return Job.create(queue, {foo: 'bar'}, {attempts: 1}).then(function(job){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(false);
}).then(function(){
return job.moveToFailed(new Error('test error'));
}).then(function(){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(true);
expect(job.stacktrace).not.be(null);
expect(job.stacktrace.length).to.be(1);
});
});
});
});
it('moves the job to delayed for retry if attempts are given and backoff is non zero', function() {
return Job.create(queue, {foo: 'bar'}, {attempts: 3, backoff: 300}).then(function(job){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(false);
}).then(function(){
return job.moveToFailed(new Error('test error'));
}).then(function(){
return job.isFailed().then(function(isFailed){
expect(isFailed).to.be(false);
expect(job.stacktrace).not.be(null);
expect(job.stacktrace.length).to.be(1);
return job.isDelayed().then(function(isDelayed){
expect(isDelayed).to.be(true);
});
});
});
});
});
});
describe('.promote', function() {
it('can promote a delayed job to be executed immediately', function() {
return Job.create(queue, {foo: 'bar'}, {delay: 1500}).then(function(job){
return job.isDelayed().then(function(isDelayed) {
expect(isDelayed).to.be(true);
}).then(function() {
return job.promote();
}).then(function() {
return job.isDelayed().then(function(isDelayed) {
expect(isDelayed).to.be(false);
return job.isWaiting().then(function(isWaiting) {
expect(isWaiting).to.be(true);
return;
});
});
});
});
});
it('should not promote a job that is not delayed', function() {
return Job.create(queue, {foo: 'bar'}).then(function(job){
return job.isDelayed().then(function(isDelayed) {
expect(isDelayed).to.be(false);
}).then(function() {
return job.promote();
}).then(function() {
throw new Error('Job should not be promoted!');
}).catch(function(err) {
expect(err).to.be.ok();
});
});
});
});
// TODO:
// Divide into several tests
//
it('get job status', function() {
this.timeout(12000);
var client = Promise.promisifyAll(redis.createClient());
return Job.create(queue, {foo: 'baz'}).then(function(job) {
return job.isStuck().then(function(yes) {
expect(yes).to.be(false);
return job.getState();
}).then(function(state) {
expect(state).to.be('waiting');
return job.moveToCompleted();
}).then(function (){
return job.isCompleted();
}).then(function (yes) {
expect(yes).to.be(true);
return job.getState();
}).then(function(state) {
expect(state).to.be('completed');
return client.sremAsync(queue.toKey('completed'), job.jobId);
}).then(function(){
return job.moveToDelayed(Date.now() + 10000);
}).then(function (){
return job.isDelayed();
}).then(function (yes) {
expect(yes).to.be(true);
return job.getState();
}).then(function(state) {
expect(state).to.be('delayed');
return client.zremAsync(queue.toKey('delayed'), job.jobId);
}).then(function() {
return job.moveToFailed(new Error('test'));
}).then(function (){
return job.isFailed();
}).then(function (yes) {
expect(yes).to.be(true);
return job.getState();
}).then(function(state) {
expect(state).to.be('failed');
return client.sremAsync(queue.toKey('failed'), job.jobId);
}).then(function(res) {
expect(res).to.be(1);
return job.getState();
}).then(function(state) {
expect(state).to.be('waiting');
return client.rpopAsync(queue.toKey('wait'));
}).then(function(){
return client.lpushAsync(queue.toKey('paused'), job.jobId);
}).then(function() {
return job.isPaused();
}).then(function (yes) {
expect(yes).to.be(true);
return job.getState();
}).then(function(state) {
expect(state).to.be('paused');
return client.rpopAsync(queue.toKey('paused'));
}).then(function() {
return client.lpushAsync(queue.toKey('wait'), job.jobId);
}).then(function() {
return job.isWaiting();
}).then(function (yes) {
expect(yes).to.be(true);
return job.getState();
}).then(function(state) {
expect(state).to.be('waiting');
});
});
});
});