UNPKG

fivebeans

Version:

beanstalkd client & worker daemon for node.

413 lines (356 loc) 9.23 kB
/*global describe:true, it:true, before:true, after:true, beforeEach:true, afterEach:true */ var demand = require('must'), events = require('events'), fivebeans = require('../index'), util = require('util') ; //------------------------------------------------------------- // some job handlers for testing var asyncHandler = require('./fixtures/async')(); function TestHandler() { events.EventEmitter.call(this); this.type = 'reverse'; } util.inherits(TestHandler, events.EventEmitter); TestHandler.prototype.work = function(payload, callback) { this.emit('result', this.reverseWords(payload.words)); callback(payload.trigger || 'success', 0); }; TestHandler.prototype.reverseWords = function(input) { var words = input.split(' '); words.reverse(); return words.join(' '); }; //------------------------------------------------------------- var host = '127.0.0.1'; var port = 11300; var tube = 'testtube'; var testopts = { id: 'testworker', host: host, port: port, ignoreDefault: true, handlers: { reverse: new TestHandler(), longasync: asyncHandler, }, timeout: 1 }; //------------------------------------------------------------- describe('FiveBeansWorker', function() { this.timeout(5000); var producer; before(function(done) { producer = new fivebeans.client(host, port); producer.once('connect', function() { producer.use(tube, function(err, resp) { demand(err).not.exist(); done(); }); }); producer.connect(); }); describe('constructor', function() { it('creates a worker with the passed-in options', function() { var opts = { id: 'testworker', host: 'example.com', port: 3000 }; var w = new fivebeans.worker(opts); w.id.must.equal(opts.id); w.host.must.equal(opts.host); w.port.must.equal(opts.port); }); it('inherits from EventEmitter', function() { var w = new fivebeans.worker({ id: 'testworker' }); w.must.have.property('on'); w.on.must.be.a.function(); }); it('respects the timeout option', function() { var opts = { id: 'testworker', host: 'example.com', port: 3000, timeout: 20 }; var w = new fivebeans.worker(opts); w.timeout.must.equal(20); }); }); describe('starting & stopping', function() { var w; it('emits the error event on failure', function(done) { w = new fivebeans.worker({id: 'fail', port: 5000}); w.on('error', function(err) { err.must.exist(); err.must.have.property('errno'); err.errno.must.equal('ECONNREFUSED'); done(); }); w.start(); }); it('emits the started event on success', function(done) { w = new fivebeans.worker(testopts); w.once('started', function() { done(); }).on('error', function(err) { throw(err); }); w.start(); }); it('stops and cleans up when stopped', function(done) { w.on('stopped', function() { w.stopped.must.equal(true); done(); }); w.stop(); }); it('watches tubes on start', function(done) { var worker = new fivebeans.worker(testopts); // worker.on('info', function(obj) { console.log(obj); }) // worker.on('warning', function(obj) { console.error(util.inspect(obj)); }) function handleStart() { worker.client.list_tubes_watched(function(err, response) { demand(err).not.exist(); response.must.be.an.array(); response.length.must.equal(2); response.indexOf(tube).must.be.above(-1); worker.removeListener('started', handleStart); worker.stop(); }); } worker.on('started', handleStart); worker.on('stopped', done); worker.start([tube, 'unused']); }); }); describe('job processing', function() { var worker; before(function(done) { worker = new fivebeans.worker(testopts); worker.on('started', done); worker.start([tube, 'unused']); }); it('deletes jobs with bad formats', function(done) { var job = { format: 'bad'}; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); function detectReady() { producer.peek_ready(function(err, jobid, payload) { err.must.exist(); err.must.equal('NOT_FOUND'); done(); }); } setTimeout(detectReady, 500); }); }); it('buries jobs with bad json', function(done) { function handleBuried(jobid) { producer.peek_buried(function(err, buriedID, payload) { demand(err).not.exist(); buriedID.must.equal(jobid); producer.destroy(buriedID, function(err) { demand(err).not.exist(); done(); }); }); } worker.once('job.buried', handleBuried); producer.put(0, 0, 60, '{ I am invalid JSON', function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('buries jobs for which it has no handler', function(done) { function handleBuried(jobid) { producer.peek_buried(function(err, buriedID, payload) { demand(err).not.exist(); buriedID.must.equal(jobid); producer.destroy(buriedID, function(err) { demand(err).not.exist(); done(); }); }); } worker.once('job.buried', handleBuried); var job = { type: 'unknown', payload: 'extremely important!'}; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('passes good jobs to handlers', function(done) { function verifyResult(item) { item.must.exist(); item.must.be.a.string(); item.must.equal('yo success'); done(); } testopts.handlers.reverse.once('result', verifyResult); var job = { type: 'reverse', payload: {words: 'success yo', trigger: 'success' }}; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('handles jobs that contain arrays (for ruby compatibility)', function(done) { worker.once('job.deleted', function(result) { done(); }); var job = ['stalker', { type: 'reverse', payload: {words: 'not important', trigger: 'success'}}]; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('buries jobs when the handler responds with "bury"', function(done) { function detectBuried(jobid) { producer.peek_buried(function(err, buriedID, payload) { demand(err).not.exist(); buriedID.must.equal(jobid); producer.destroy(buriedID, function(err) { demand(err).not.exist(); done(); }); }); } worker.once('job.buried', detectBuried); var job = { type: 'reverse', payload: { words: 'bury', trigger: 'bury' }}; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('successfully handles jobs with non-ascii characters', function(done) { testopts.handlers.reverse.once('result', function(result) { result.must.equal('brûlée crèmes'); done(); }); var job = { type: 'reverse', payload: { words: 'crèmes brûlée', trigger: 'success' }}; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('can call touch() on jobs in progress', function(done) { this.timeout(15000); var jobid, timeleft; function getInfo() { worker.client.stats_job(jobid, function(err, info) { demand(err).not.exist(); timeleft = info['time-left']; timeleft.must.be.below(27); // 30 seconds minus the 2 second wait worker.client.touch(jobid, function(err) { demand(err).not.exist(); worker.client.stats_job(jobid, function(err, info2) { // now test that the wait has been reset demand(err).not.exist(); info2['time-left'].must.be.above(timeleft); }); }); }); } function handleReserved(id) { jobid = id; worker.once('job.handled', function() { done(); }); setTimeout(getInfo, 3000); } worker.once('job.reserved', handleReserved); worker.on('warning', console.log); var job = { type: 'longasync', payload: { words: 'ignored', trigger: 'ignored' }}; producer.put(0, 0, 30, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); it('releases jobs when the handler responds with "release"', function(done) { function detectReleased(jobid) { worker.stop(); producer.peek_ready(function(err, releasedID, payload) { demand(err).not.exist(); releasedID.must.equal(jobid); producer.destroy(releasedID, function(err) { demand(err).not.exist(); done(); }); }); } worker.once('job.released', detectReleased); var job = { type: 'reverse', payload: { words: 'release', trigger: 'release' }}; producer.put(0, 0, 60, JSON.stringify(job), function(err, jobid) { demand(err).not.exist(); jobid.must.exist(); }); }); }); describe('log events', function() { it('have tests'); }); });