qlobber-pg
Version:
PostgreSQL-based pub-sub and workqueues. Supports AMQP-like wildcard topics.
1,365 lines (1,193 loc) • 87.3 kB
JavaScript
'use strict';
const path = require('path');
const { Writable } = require('stream');
const { randomBytes, createHash } = require('crypto');
const { writeFile, createReadStream } = require('fs');
const { parallel, timesSeries, eachSeries, each } = require('async');
const { QlobberPG } = require('..');
const wu = require('wu');
const config = require('config');
const iferr = require('iferr');
const orig_it = it;
it = function(s, f) { // eslint-disable-line no-global-assign
return orig_it(s, function (done) {
f.call(this, err => {
if (err) {
console.error('DONE', err); // eslint-disable-line no-console
}
done(err);
});
});
};
it.only = orig_it.only.bind(orig_it);
function read_all(s, cb) {
const bufs = [];
s.on('end', () => cb(Buffer.concat(bufs)));
s.on('readable', function () {
while (true) { // eslint-disable-line no-constant-condition
const data = this.read();
if (data === null) {
break;
}
bufs.push(data);
}
});
}
function test(gopts) {
describe(`qlobber-pq options=${JSON.stringify(gopts)}`, function () {
let expect;
before(async () => {
({ expect } = await import('chai'));
});
let qpg;
let random_hash;
let random_path = path.join(__dirname, 'fixtures', 'random');
function t(topic) {
if (gopts) {
if (gopts.separator) {
topic = topic.split('.').join(gopts.separator);
}
if (gopts.wildcard_one) {
topic = topic.split('*').join(gopts.wildcard_one);
}
if (gopts.wildcard_some) {
topic = topic.split('#').join(gopts.wildcard_some);
}
}
return topic;
}
before(function (cb) {
const buf = randomBytes(1024 * 1024);
const hash = createHash('sha256');
hash.update(buf);
random_hash = hash.digest();
writeFile(random_path, buf, cb);
});
function make_qpg(cb, options) {
const qpg = new QlobberPG(Object.assign({
name: 'test1'
}, config, gopts, options));
qpg.on('warning', console.error); // eslint-disable-line no-console
if (cb) {
return qpg.on('start', () => cb(null, qpg));
}
return qpg;
}
function before_each(cb, options) {
make_qpg(iferr(cb, the_qpg => {
qpg = the_qpg;
cb();
}), options);
}
beforeEach(cb => {
before_each(iferr(cb, () => {
qpg._queue.push(cb => {
qpg._client.query('DELETE FROM messages', cb);
}, cb);
}));
});
function after_each(cb) {
if (this && this.timeout) {
this.timeout(5000);
}
if (qpg) {
// We need to wait for tasks to complete otherwise we may
// get 'stopped' or 'Connection terminated' errors.
// Note there are separate tests for these cases.
if ((qpg._queue.running() > 0) || (qpg._queue.length() > 0)) {
return setTimeout(() => after_each.call(this, cb), 500);
}
qpg.stop(cb);
} else {
cb();
}
}
afterEach(after_each);
function exists(id, cb) {
qpg._queue.push(cb => qpg._client.query('SELECT EXISTS(SELECT 1 FROM messages WHERE id = $1)', [
id
], cb), iferr(cb, r => cb(null, r.rows[0].exists)));
}
function count(qpg, cb) {
qpg._queue.push(
cb => qpg._client.query('SELECT id FROM messages', cb),
iferr(cb, r => cb(null, r.rows.length)));
}
it('should subscribe and publish to a simple topic', function (done) {
let pub_info, sub_info;
function check() {
if (pub_info && sub_info) {
pub_info.id = sub_info.id;
pub_info.data = sub_info.data;
expect(pub_info).to.eql(sub_info);
done();
}
}
qpg.subscribe('foo', (data, info, cb) => {
expect(info.topic).to.equal('foo');
expect(info.single).to.be.false;
expect(data.toString()).to.equal('bar');
expect(cb.num_handlers).to.equal(1);
sub_info = info;
check();
}, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, info => {
pub_info = info;
check();
}));
}));
});
it('should construct received data only once', function (done) {
const the_data = { foo: 0.435, bar: 'hello' };
let called1 = false;
let called2 = false;
let received_data;
qpg.subscribe('test', (data, info, cb) => {
expect(info.topic).to.equal('test');
expect(JSON.parse(data)).to.eql(the_data);
if (received_data) {
expect(data === received_data).to.be.true;
} else {
received_data = data;
}
called1 = true;
if (called1 && called2) {
cb(null, done);
} else {
cb();
}
}, iferr(done, () => {
qpg.subscribe('test', (data, info, cb) => {
expect(info.topic).to.equal('test');
expect(JSON.parse(data)).to.eql(the_data);
if (received_data) {
expect(data === received_data).to.be.true;
} else {
received_data = data;
}
called2 = true;
if (called1 && called2) {
cb(null, done);
} else {
cb();
}
}, iferr(done, () => {
qpg.publish('test', JSON.stringify(the_data), iferr(done, () => {}));
}));
}));
});
it('should support more than 10 subscribers', function (done) {
const the_data = { foo: 0.435, bar: 'hello' };
let counter = 11;
let received_data;
let a = [];
function subscribe(cb) {
qpg.subscribe('test', (data, info, cb) => {
expect(info.topic).to.equal('test');
expect(JSON.parse(data)).to.eql(the_data);
if (received_data) {
expect(data == received_data).to.be.true;
} else {
received_data = data;
}
if (--counter === 0) {
cb(null, done);
} else {
cb();
}
}, cb);
}
for (let i = counter; i > 0; --i) {
a.push(subscribe);
}
parallel(a, iferr(done, () => {
qpg.publish('test', JSON.stringify(the_data), iferr(done, () => {}));
}));
});
it('should support more than 10 subscribers with same handler', function (done) {
after_each(iferr(done, () => {
before_each(iferr(done, () => {
const the_data = { foo: 0.435, bar: 'hello' };
let counter = 11;
let received_data;
let a = [];
function handler(data, info, cb) {
expect(info.topic).to.equal('test');
expect(JSON.parse(data)).to.eql(the_data);
if (received_data) {
expect(data === received_data).to.be.true;
} else {
received_data = data;
}
if (--counter === 0) {
cb(null, done);
} else {
cb();
}
}
function subscribe(cb) {
qpg.subscribe('test', handler, cb);
}
for (let i = counter; i > 0; --i) {
a.push(subscribe);
}
parallel(a, iferr(done, () => {
qpg.publish('test', JSON.stringify(the_data), iferr(done, () => {}));
}));
}), {
dedup: false
});
}));
});
it('should subscribe to wildcards', function (done) {
let count = 0;
function received() {
if (++count === 2) {
done();
}
}
qpg.subscribe(t('*'), function (data, info) {
expect(info.topic).to.equal('foo');
expect(data.toString()).to.equal('bar');
received();
}, iferr(done, () => {
qpg.subscribe(t('#'), function (data, info) {
expect(info.topic).to.equal('foo');
expect(data.toString()).to.equal('bar');
received();
}, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should subscribe to wildcards with separator', function (done) {
let count = 0;
function received() {
if (++count === 2) {
done();
}
}
qpg.subscribe(t('foo.*'), function (data, info) {
expect(info.topic).to.equal(t('foo.bar'));
expect(data.toString()).to.equal('bar');
received();
}, iferr(done, () => {
qpg.subscribe(t('#'), function (data, info) {
expect(info.topic).to.equal(t('foo.bar'));
expect(data.toString()).to.equal('bar');
received();
}, iferr(done, () => {
qpg.publish(t('foo.bar'), 'bar', iferr(done, () => {}));
}));
}));
});
it('should only call each handler once', function (done) {
const handler = function (data, info) {
expect(info.topic).to.equal('foo');
expect(data.toString()).to.equal('bar');
done();
};
qpg.subscribe(t('*'), handler, iferr(done, () => {
qpg.subscribe(t('#'), handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should be able to disable handler dedup', function (done) {
after_each(iferr(done, () => {
before_each(iferr(done, () => {
let count_multi = 0;
let count_single = 0;
function handler(data, info, cb) {
expect(info.topic).to.equal('foo');
expect(data.toString()).to.equal('bar');
cb(null, err => {
if (info.single) {
++count_single;
} else {
++count_multi;
}
if ((count_single === 1) && (count_multi === 2)) {
return done(err);
}
if ((count_single > 1) || (count_multi > 2)) {
done(new Error('called too many times'));
}
});
}
qpg.subscribe(t('*'), handler, iferr(done, () => {
qpg.subscribe(t('#'), handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
qpg.publish('foo', 'bar', {
single: true
}, iferr(done, () => {}));
}));
}));
}), {
dedup: false
});
}));
});
it('should call all handlers on a topic for pubsub', function (done) {
let count = 0;
function received() {
if (++count === 2) {
done();
}
}
function handler(data, info) {
expect(info.topic).to.equal('foo');
expect(data.toString()).to.equal('bar');
received();
}
qpg.subscribe('foo', (...args) => handler(...args), iferr(done, () => {
qpg.subscribe('foo', (...args) => handler(...args), iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should support a work queue', function (done) {
qpg.subscribe('foo', function (data, info, cb) {
expect(info.topic).to.equal('foo');
expect(info.single).to.be.true;
cb(null, done);
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
});
it('should guard against calling subscribe callback twice', function (done) {
qpg.subscribe('foo', function (data, info, cb) {
expect(info.single).to.be.true;
cb(null, iferr(done, () => {
setTimeout(() => cb(null, done), 1000);
}));
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
});
it('should only give work to one worker', function (done) {
this.timeout(5000);
make_qpg(iferr(done, qpg2 => {
let called = false;
function handler(data, info, cb) {
expect(called).to.be.false;
called = true;
expect(info.topic).to.equal('foo');
expect(info.single).to.be.true;
expect(data.toString()).to.equal('bar');
setTimeout(
() => cb(null, iferr(done, () => qpg2.stop(done))),
2000);
}
parallel([
cb => qpg.subscribe('foo', (...args) => handler(...args), cb),
cb => qpg.subscribe('foo', (...args) => handler(...args), cb),
cb => qpg2.subscribe('foo', (...args) => handler(...args), cb),
cb => qpg2.subscribe('foo', (...args) => handler(...args), cb),
cb => qpg.publish('foo', 'bar', { single: true }, cb)
]);
}), { name: 'test2' });
});
it('should put work back on the queue', function (done) {
this.timeout(5000);
let count = 0;
qpg.subscribe('foo', function (data, info, cb) {
if (++count === 1) {
return cb('dummy failure');
}
cb(null, done);
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
});
it('should allow handlers to refuse work', function (done) {
function handler1() {
done(new Error('should not be called'));
}
function handler2(data, info, cb) {
cb(null, done);
}
after_each(iferr(done, () => {
before_each(iferr(done, () => {
qpg.subscribe('foo', handler1, iferr(done, () => {
qpg.subscribe('foo', handler2, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
}), {
filter: function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
handlers.delete(handler1);
cb(null, true, handlers);
}
});
}));
});
it('should not allow filters to modify qlobber matches', function (done) {
let count = 0;
function handler(data, info, cb) {
expect(count).to.equal(2);
cb(null, done);
}
after_each(iferr(done, () => {
before_each(iferr(done, () => {
qpg.subscribe('foo', handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}), {
filter: function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
if (++count === 1) {
handlers.delete(handler);
return cb(null, false);
}
cb(null, true, handlers);
}
});
}));
});
it('should be able to set filter by property', function (done) {
function handler1() {
done(new Error('should not be called'));
}
function handler2(data, info, cb) {
cb(null, done);
}
qpg.filters.push(function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
handlers.delete(handler1);
cb(null, true, handlers);
});
qpg.subscribe('foo', handler1, iferr(done, () => {
qpg.subscribe('foo', handler2, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should be able to pass filtered handlers as iterator (Set)', function (done) {
function handler1() {
done(new Error('should not be called'));
}
function handler2(data, info, cb) {
cb(null, done);
}
qpg.filters.push(function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
cb(null, true, wu(handlers).filter(h => h !== handler1));
});
qpg.subscribe('foo', handler1, iferr(done, () => {
qpg.subscribe('foo', handler2, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should be able to pass filtered handlers as iterator (Array)', function (done) {
function handler1() {
done(new Error('should not be called'));
}
function handler2(data, info, cb) {
cb(null, done);
}
after_each(iferr(done, () => {
before_each(iferr(done, () => {
qpg.subscribe('foo', handler1, iferr(done, () => {
qpg.subscribe('foo', handler2, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
}), {
filter: function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
cb(null, true, wu(handlers).filter(h => h !== handler1));
},
dedup: false
});
}));
});
it('should support multiple filters', function (done) {
function handler1() {
done(new Error('should not be called'));
}
function handler2() {
done(new Error('should not be called'));
}
function handler3(data, info, cb) {
cb(null, done);
}
qpg.filters.push(
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
handlers.delete(handler1);
cb(null, true, handlers);
},
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
handlers.delete(handler2);
cb(null, true, handlers);
}
);
qpg.subscribe('foo', handler1, iferr(done, () => {
qpg.subscribe('foo', handler2, iferr(done, () => {
qpg.subscribe('foo', handler3, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
}));
});
it('should not call other filters if error', function (done) {
this.timeout(5000);
let called = false;
qpg.filters.push(
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
cb(new Error('dummy'));
if (called) {
return done();
}
called = true;
},
function () {
done(new Error('should not be called'));
}
);
function handler() {
done(new Error('should not be called'));
}
qpg.subscribe('foo', handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
});
it('should not call other filters if not ready', function (done) {
let called = false;
qpg.filters.push(
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
cb(null, false);
if (called) {
return done();
}
called = true;
},
function () {
done(new Error('should not be called'));
}
);
function handler() {
done(new Error('should not be called'));
}
qpg.subscribe('foo', handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
});
it('should put work back on queue for another handler', function (done) {
let filter_called = false;
function handler(data, info, cb) {
cb('dummy failure');
}
qpg.filters.push(
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
expect(info.single).to.be.true;
if (filter_called) {
handlers.delete(handler);
return cb(null, true, handlers);
}
filter_called = true;
cb(null, true, handlers);
}
);
qpg.subscribe('foo', handler, iferr(done, () => {
qpg.subscribe('foo', function (data, info, cb) {
if (filter_called) {
return cb(null, done);
}
cb('dummy failure2');
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
}));
});
it('should put work back on queue for a handler on another queue', function (done) {
let filter_called = false;
function handler(data, info, cb) {
cb('dummy failure');
}
qpg.filters.push(
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
expect(info.single).to.be.true;
if (filter_called) {
handlers.delete(handler);
}
filter_called = true;
cb(null, true, handlers);
}
);
make_qpg(iferr(done, qpg2 => {
qpg.subscribe('foo', handler, iferr(done, () => {
qpg2.subscribe('foo', function (data, info, cb) {
if (filter_called) {
return cb(null, iferr(done, () => qpg2.stop(done)));
}
cb('dummy failure2');
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
}));
}), { name: 'test2' });
});
it('should allow handlers to delay a message', function (done) {
this.timeout(30000);
let ready_multi = false;
let ready_single = false;
let got_multi = false;
let got_single = false;
let count = 0;
function handler(data, info, cb) {
expect(data.toString()).to.equal('bar');
cb(null, err => {
if (info.single) {
expect(got_single).to.be.false;
got_single = true;
} else {
expect(got_multi).to.be.false;
got_multi = true;
}
if (got_single && got_multi && ready_single && ready_multi) {
expect(count).to.equal(10);
done(err);
}
});
}
qpg.filters.push(
function (info, handlers, cb) {
expect(info.topic).to.equal('foo');
if (info.single) {
ready_single = true;
} else {
ready_multi = true;
}
cb(null, (++count % 5) === 0, handlers);
}
);
qpg.subscribe('foo', handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
});
it('should emit stop event', function (done) {
make_qpg(iferr(done, qpg2 => {
qpg2.stop();
qpg2.on('stop', done);
}), { name: 'test2' });
});
it('should support per-message time-to-live', function (done) {
qpg.subscribe('foo', function (data, info) {
exists(info.id, iferr(done, b => {
expect(b).to.be.true;
setTimeout(() => {
qpg.force_refresh();
setTimeout(() => {
exists(info.id, iferr(done, b => {
expect(b).to.be.false;
done();
}));
}, 500);
}, 500);
}));
}, iferr(done, () => {
qpg.publish('foo', 'bar', { ttl: 500 }, iferr(done, () => {}));
}));
});
it('should call error function', function (done) {
qpg.on('warning', function (err) {
expect(err).to.equal('dummy failure');
done();
});
qpg.subscribe('foo', function (data, info, cb) {
cb('dummy failure');
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
});
it('should support custom polling interval', function (done) {
make_qpg(iferr(done, qpg2 => {
let time = Date.now();
let count = 0;
qpg2.subscribe('foo', function (data, info, cb) {
const time2 = Date.now();
expect(time2 - time).to.be.below(100);
time = time2;
if (++count === 10) {
return cb(null, iferr(done, () => qpg2.stop(done)));
}
cb('dummy failure');
}, iferr(done, () => {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
}), {
name: 'test2',
poll_interval: 50
});
});
it('should support unsubscribing', function (done) {
this.timeout(5000);
let count = 0;
function handler(data, info, cb) {
if (++count > 1) {
return done(new Error('should not be called'));
}
qpg.unsubscribe('foo', handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {
setTimeout(cb, 2000, null, done);
}));
}));
}
qpg.subscribe('foo', handler, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
});
it('should support unsubscribing all handlers for a topic', function (done) {
this.timeout(5000);
let count = 0;
function handler(data, info, cb) {
if (++count > 2) {
return done(new Error('should not be called'));
}
if (count === 2) {
qpg.unsubscribe('foo', undefined, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {
setTimeout(cb, 2000, null, done);
}));
}));
}
}
qpg.subscribe('foo', function (data, info, cb) {
handler(data, info, cb);
}, iferr(done, () => {
qpg.subscribe('foo', function (data, info, cb) {
handler(data, info, cb);
}, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should support unsubscribing all handlers', function (done) {
this.timeout(5000);
let count = 0;
function handler(data, info, cb) {
if (++count > 2) {
return done(new Error('should not be called'));
}
if (count === 2) {
qpg.subscribe('foo2', function () {
done(new Error('should not be called'));
}, iferr(done, () => {
qpg.unsubscribe(iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {
qpg.publish('foo2', 'bar2', iferr(done, () => {
setTimeout(cb, 2000, null, done);
}));
}));
}));
}));
}
}
qpg.subscribe('foo', function (data, info, cb) {
handler(data, info, cb);
}, iferr(done, () => {
qpg.subscribe('foo', function (data, info, cb) {
handler(data, info, cb);
}, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
}));
}));
});
it('should support changing the default time-to-live', function (done) {
this.timeout(5000);
after_each(iferr(done, () => {
before_each(iferr(done, () => {
let got_single = false;
let got_multi = false;
qpg.subscribe('foo', function (data, info, cb) {
cb(null, () => {
if (info.single) {
got_single = true;
} else {
got_multi = true;
}
if (got_single && got_multi) {
setTimeout(() => {
qpg.force_refresh();
setTimeout(() => {
exists(info.id, iferr(done, b => {
expect(b).to.be.false;
done();
}));
}, 1000);
}, 1000);
}
});
}, iferr(done, () => {
qpg.publish('foo', 'bar', iferr(done, () => {}));
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {}));
}));
}), {
multi_ttl: 1000,
single_ttl: 1000
});
}));
});
it('should publish and receive twice', function (done) {
let count_multi = 0;
let count_single = 0;
qpg.subscribe('foo', function (data, info, cb) {
cb(null, () => {
if (info.single) {
++count_single;
} else {
++count_multi;
}
if ((count_single == 2) && (count_multi === 2)) {
done();
} else if ((count_single > 2) || (count_multi > 2)) {
done(new Error('called too many times'));
}
});
}, iferr(done, () => {
timesSeries(2, (n, cb) => {
eachSeries([true, false], (single, cb) => {
qpg.publish('foo', 'bar', { single }, cb);
}, cb);
}, iferr(done, () => {}));
}));
});
it('should fail to publish and subscribe to messages with > 255 character topics', function (done) {
const arr = [];
arr.length = 257;
const topic = arr.join('a');
qpg.subscribe(topic, () => {}, err => {
expect(err.message).to.equal(`topic too long: ${topic}`);
qpg.publish(topic, 'bar', { ttl: 1000 }, err => {
expect(err.message).to.equal(`topic too long: ${topic}`);
done();
});
});
});
it('should publish and subscribe to messages with 255 character topics', function (done) {
this.timeout(5000);
const arr = [];
arr.length = 256;
const topic = arr.join('a');
qpg.subscribe(topic, function (data, info) {
expect(info.topic).to.equal(topic);
expect(info.single).to.equal(false);
expect(data.toString()).to.equal('bar');
setTimeout(() => {
qpg.force_refresh();
setTimeout(() => {
exists(info.id, iferr(done, b => {
expect(b).to.be.false;
done();
}));
}, 500);
}, 1000);
}, iferr(done, () => {
qpg.publish(topic, 'bar', { ttl: 1000 }, iferr(done, () => {}));
}));
});
it('should not read multi-worker messages which already exist', function (done) {
this.timeout(10000);
qpg.publish('foo', 'bar', iferr(done, () => {
after_each(iferr(done, () => {
before_each(iferr(done, () => {
qpg.subscribe('foo', function () {
done(new Error('should not be called'));
}, iferr(done, () => {
setTimeout(done, 5000);
}));
}));
}));
}));
});
it('should read single-worker messages which already exist', function (done) {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {
after_each(iferr(done, () => {
before_each(iferr(done, () => {
qpg.subscribe('foo', function (data, info, cb) {
cb(null, done);
}, iferr(done, () => {}));
}));
}));
}));
});
it('should read single-worker messages which already exist (before start)', function (done) {
qpg.publish('foo', 'bar', { single: true }, iferr(done, () => {
after_each(iferr(done, () => {
const qpg = make_qpg(null, { name: 'test2' });
qpg.subscribe('foo', function (data, info, cb) {
cb(null, iferr(done, () => qpg.stop(done)));
}, iferr(done, () => {}));
}));
}));
});
it('should error if stopped while publishing', function (done) {
qpg.publish('foo', 'bar', err => {
expect(err.message).to.equal('stopped');
done();
});
qpg.stop();
});
it('should error if connection terminated while publishing', function (done) {
const orig_query = qpg._client.query;
qpg._client.query = function (...args) {
const r = orig_query.apply(this, args);
qpg.stop();
return r;
};
qpg.publish('foo', 'bar', err => {
expect(err.message).to.equal('Connection terminated');
done();
});
});
it('should error if stopped while subscribing', function (done) {
const orig_update_trigger = qpg._update_trigger;
let update_trigger_cb;
qpg._update_trigger = function (cb) {
update_trigger_cb = cb;
};
qpg.subscribe('foo', function () {
orig_update_trigger.call(this, update_trigger_cb);
this.stop();
}, err => {
expect(err.message).to.equal('stopped');
done();
});
// Note: publish can't error because qpg._check query won't get to
// front of queue until publish task completes
qpg.publish('foo', 'bar', iferr(done, () => {}));
});
it('should error if connection terminated while subscribing', function (done) {
const orig_update_trigger = qpg._update_trigger;
let update_trigger_cb;
qpg._update_trigger = function (cb) {
update_trigger_cb = cb;
};
qpg.subscribe('foo', function () {
const orig_query = qpg._client.query;
qpg._client.query = function (...args) {
const r = orig_query.apply(this, args);
qpg.stop();
return r;
};
orig_update_trigger.call(this, update_trigger_cb);
}, err => {
expect(err.message).to.equal('Connection terminated');
done();
});
// Note: publish can't error because qpg._check query won't get to
// front of queue until publish task completes
qpg.publish('foo', 'bar', iferr(done, () => {}));
});
it('should error if stopped while unsubscribing', function (done) {
const orig_update_trigger = qpg._update_trigger;
qpg._update_trigger = function (cb) {
this.stop();
orig_update_trigger.call(this, cb);
};
qpg.unsubscribe(err => {
expect(err.message).to.equal('stopped');
done();
});
});
it('should error if connection terminated while unsubscribing', function (done) {
const orig_update_trigger = qpg._update_trigger;
qpg._update_trigger = function (cb) {
const orig_query = qpg._client.query;
qpg._client.query = function (...args) {
const r = orig_query.apply(this, args);
qpg.stop();
return r;
};
orig_update_trigger.call(this, cb);
};
qpg.unsubscribe(err => {
expect(err.message).to.equal('Connection terminated');
done();
});
});
it("should not error if done while publishing (would be stopped error if after_each didn't wait)", function (done) {
qpg.publish('foo', 'bar', iferr(done, () => {}));
done();
});
it("should not error if done while publishing (would be Connection terminated error if after_each didn't wait)", function (done) {
const orig_query = qpg._client.query;
qpg._client.query = function (...args) {
qpg._client.query = orig_query;
const r = orig_query.apply(this, args);
done();
return r;
};
qpg.publish('foo', 'bar', iferr(done, () => {}));
});
it("should not error if done while subscribing (would be stopped error if after_each didn't wait)", function (done) {
const orig_update_trigger = qpg._update_trigger;
let update_trigger_cb;
qpg._update_trigger = function (cb) {
update_trigger_cb = cb;
};
qpg.subscribe('foo', function () {
orig_update_trigger.call(this, update_trigger_cb);
done();
}, iferr(done, () => {}));
// Note: publish can't error because qpg._check query won't get to
// front of queue until publish task completes
qpg.publish('foo', 'bar', iferr(done, () => {}));
});
it("should not error if done while subscribing (would be Connection terminated error if after each didn't wait)", function (done) {
const orig_update_trigger = qpg._update_trigger;
let update_trigger_cb;
qpg._update_trigger = function (cb) {
update_trigger_cb = cb;
};
qpg.subscribe('foo', function () {
const orig_query = qpg._client.query;
qpg._client.query = function (...args) {
qpg._client.query = orig_query;
const r = orig_query.apply(this, args);
done();
return r;
};
orig_update_trigger.call(this, update_trigger_cb);
}, iferr(done, () => {}));
// Note: publish can't error because qpg._check query won't get to
// front of queue until publish task completes
qpg.publish('foo', 'bar', iferr(done, () => {}));
});
it("should not error if done while unsubscribing (would be stopped error if after_each didn't wait)", function (done) {
const orig_update_trigger = qpg._update_trigger;
qpg._update_trigger = function (cb) {
done();
orig_update_trigger.call(this, cb);
};
qpg.unsubscribe(iferr(done, () => {}));
});
it("should not error if done while unsubscribing (would be Connection terminated error if after_each didn't wait)", function (done) {
const orig_update_trigger = qpg._update_trigger;
qpg._update_trigger = function (cb) {
const orig_query = qpg._client.query;
qpg._client.query = function (...args) {
qpg._client.query = orig_query;
const r = orig_query.apply(this, args);
done();
return r;
};
orig_update_trigger.call(this, cb);
};
qpg.unsubscribe(iferr(done, () => {}));
});
it('should support streaming interfaces', function (done) {
let stream_multi;
let stream_single;
let stream_file;
let sub_multi_called = false;
let sub_single_called = false;
let pub_multi_called = false;
let pub_single_called = false;
function handler(stream, info, cb) {
const hash = createHash('sha256');
let len = 0;
stream.on('readable', function () {
while (true) { // eslint-disable-line no-constant-condition
const chunk = stream.read();
if (!chunk) {
break;
}
len += chunk.length;
hash.update(chunk);
}
});
stream.on('end', function () {
expect(len).to.equal(1024 * 1024);
expect(hash.digest().equals(random_hash)).to.be.true;
cb(null, function () {
if (info.single) {
expect(sub_single_called).to.be.false;
sub_single_called = true;
} else {
expect(sub_multi_called).to.be.false;
sub_multi_called = true;
}
if (pub_multi_called && pub_single_called &&
sub_multi_called && sub_single_called) {
done();
}
});
});
}
handler.accept_stream = true;
function published() {
if (pub_multi_called && pub_single_called &&
sub_multi_called && sub_single_called) {
done();
}
}
qpg.subscribe('foo', handler, iferr(done, () => {
stream_multi = qpg.publish('foo', iferr(done, () => {
expect(pub_multi_called).to.be.false;
pub_multi_called = true;
published();
}));
stream_single = qpg.publish('foo', { single: true }, iferr(done, () => {
expect(pub_single_called).to.be.false;
pub_single_called = true;
published();
}));
stream_file = createReadStream(random_path);
stream_file.pipe(stream_multi);
stream_file.pipe(stream_single);
}));
});
it('should pipe to more than one stream', function (done) {
let done1 = false;
let done2 = false;
class CheckStream extends Writable {
constructor() {
super();
this._hash = createHash('sha256');
this._len = 0;
this.on('finish', () => {
this.emit('done', {
digest: this._hash.digest(),
len: this._len
});
});
}
_write(chunk, encoding, cb) {
this._len += chunk.length;
this._hash.update(chunk);
cb();
}
}
function check(obj, cb) {
expect(obj.len).to.equal(1024 * 1024);
expect(obj.digest.equals(random_hash)).to.be.true;
if (done1 && done2) {
return cb(null, done);
}
cb();
}
function handler1(stream, info, cb) {
const cs = new CheckStream();
cs.on('done', obj => {
done1 = true;
check(obj, cb);
});
stream.pipe(cs);
}
handler1.accept_stream = true;
function handler2(stream, info, cb) {
const cs = new CheckStream();
cs.on('done', obj => {
done2 = true;
check(obj, cb);