amqplib
Version:
An AMQP 0-9-1 (e.g., RabbitMQ) library and client.
440 lines (387 loc) • 12.8 kB
JavaScript
const { describe, it, afterEach } = require('node:test');
const assert = require('node:assert');
const domain = require('node:domain');
const { connect } = require('../callback_api');
const { schedule, randomString, latch } = require('./lib/util');
function waitForMessages(ch, q, cb) {
ch.checkQueue(q, (e, ok) => {
if (e != null) return cb(e);
else if (ok.messageCount > 0) return cb(null, ok);
else schedule(waitForMessages.bind(null, ch, q, cb));
});
}
describe('Callback API', () => {
describe('connect', () => {
it('at all', (_t, done) => {
connect((err, c) => {
assert.ifError(err);
c.close(done);
});
});
});
describe('updateSecret', () => {
let c;
afterEach((_t, done) => {
if (c) c.close(() => done())
else done();
});
it('updateSecret', (_t, done) => {
connect((err, _c) => {
assert.ifError(err);
c = _c;
c.updateSecret(Buffer.from('new secret'), 'no reason', (err) => {
assert.ifError(err);
done();
});
});
});
it('emits update-secret-ok event', (_t, done) => {
connect((err, _c) => {
assert.ifError(err);
c = _c;
c.on('update-secret-ok', () => done());
c.updateSecret(Buffer.from('new secret'), 'no reason', (err) => {
assert.ifError(err);
});
});
});
});
const channel_test_fn = (method) => {
return (name, options, chfun) => {
if (typeof options === 'function') {
chfun = options;
options = {};
}
it(name, (_t, done) => {
connect((err, c) => {
assert.ifError(err);
c[method](options, ((err, ch) => {
assert.ifError(err);
chfun(ch, done);
}));
});
});
};
};
const channel_test = channel_test_fn('createChannel');
const confirm_channel_test = channel_test_fn('createConfirmChannel');
describe('channel open', () => {
channel_test('at all', (ch, done) => {
ch.connection.close(done);
});
channel_test('open and close', (ch, done) => {
ch.close((err) => {
assert.ifError(err);
ch.connection.close(done);
});
});
});
describe('assert, check, delete', () => {
let ch;
afterEach((_t, done) => {
ch?.connection?.close(done);
});
channel_test('assert, check, delete queue', (_ch, done) => {
ch = _ch;
ch.assertQueue('test.cb.queue', {}, (err, { queue }) => {
assert.ifError(err);
assert.strictEqual(queue, 'test.cb.queue');
ch.checkQueue('test.cb.queue', (err, ok) => {
assert.ifError(err);
assert.ok(ok);
ch.deleteQueue('test.cb.queue', {}, done);
})
});
});
channel_test('assert, check, delete exchange', (_ch, done) => {
ch = _ch;
ch.assertExchange('test.cb.exchange', 'topic', {}, (err, { exchange }) => {
assert.ifError(err);
assert.strictEqual(exchange, 'test.cb.exchange');
ch.checkExchange('test.cb.exchange', (err, _ok) => {
assert.ifError(err);
ch.deleteExchange('test.cb.exchange', {}, done);
});
});
});
channel_test('fail on check non-queue', (_ch, done) => {
ch = _ch;
const decrementLatch = latch(2, done);
ch.on('error', (err) => {
assert.match(err.message, /Channel closed by server/)
decrementLatch()
});
ch.checkQueue('test.cb.nothere', (err) => {
assert.match(err.message, /QueueDeclare; 404 \(NOT-FOUND\)/);
decrementLatch();
});
});
channel_test('fail on check non-exchange', (_ch, done) => {
ch = _ch;
const decrementLatch = latch(2, done);
ch.on('error', (err) => {
assert.match(err.message, /Channel closed by server/)
decrementLatch()
});
ch.checkExchange('test.cb.nothere', (err) => {
assert.match(err.message, /ExchangeDeclare; 404 \(NOT-FOUND\)/);
decrementLatch();
});
});
});
describe('bindings', () => {
let ch;
afterEach((_t, done) => {
ch?.connection?.close(done);
});
channel_test('bind queue', (_ch, done) => {
ch = _ch;
ch.assertQueue('test.cb.bindq', {}, (err, q) => {
assert.ifError(err);
ch.assertExchange('test.cb.bindex', 'fanout', {}, (err, ex) => {
assert.ifError(err);
ch.bindQueue(q.queue, ex.exchange, '', {}, done);
});
});
});
channel_test('bind exchange', (_ch, done) => {
ch = _ch;
ch.assertExchange('test.cb.bindex1', 'fanout', {}, (err, ex1) => {
assert.ifError(err);
ch.assertExchange('test.cb.bindex2', 'fanout', {}, (err, ex2) => {
assert.ifError(err);
ch.bindExchange(ex1.exchange, ex2.exchange, '', {}, done);
});
});
});
});
describe('sending messages', () => {
let ch;
afterEach((_t, done) => {
ch?.connection?.close(done);
});
channel_test('send to queue and consume noAck', (_ch, done) => {
ch = _ch;
const msg = randomString();
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
ch.consume(queue, (m) => {
assert.strictEqual(m.content.toString(), msg);
done();
}, { noAck: true, exclusive: true });
ch.sendToQueue(queue, Buffer.from(msg));
});
});
channel_test('send to queue and consume ack', (_ch, done) => {
ch = _ch;
const msg = randomString();
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
ch.consume(queue, (m) => {
assert.strictEqual(m.content.toString(), msg);
ch.ack(m);
done();
}, { noAck: false, exclusive: true });
ch.sendToQueue(queue, Buffer.from(msg));
});
});
channel_test('send to and get from queue', (_ch, done) => {
ch = _ch;
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
const msg = randomString();
ch.sendToQueue(queue, Buffer.from(msg));
waitForMessages(ch, queue, (err, _) => {
assert.ifError(err);
ch.get(queue, { noAck: true }, (err, m) => {
assert.ifError(err);
assert.ok(m, 'Expected message, got empty/false');
assert.strictEqual(m.content.toString(), msg);
done();
});
});
});
});
const channelOptions = {};
channel_test('find high watermark', (_ch, done) => {
ch = _ch;
const msg = randomString();
let baseline = 0;
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
while (ch.sendToQueue(queue, Buffer.from(msg))) {
baseline++;
}
channelOptions.highWaterMark = baseline * 2;
done();
});
});
channel_test('set high watermark', channelOptions, (_ch, done) => {
ch = _ch;
const msg = randomString();
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
let ok;
for (let i = 0; i < channelOptions.highWaterMark; i++) {
ok = ch.sendToQueue(queue, Buffer.from(msg));
assert.equal(ok, true);
}
done();
});
});
});
describe('ConfirmChannel', () => {
let ch;
afterEach((_t, done) => {
ch?.connection?.close(done);
});
confirm_channel_test('Receive confirmation', (_ch, done) => {
ch = _ch;
// An unroutable message, on the basis that you're not allowed a
// queue with an empty name, and you can't make bindings to the
// default exchange. Tricky eh?
ch.publish('', '', Buffer.from('foo'), {}, done);
});
confirm_channel_test('Wait for confirms', (_ch, done) => {
ch = _ch;
for (let i = 0; i < 1000; i++) {
ch.publish('', '', Buffer.from('foo'), {});
}
ch.waitForConfirms(done);
});
const channelOptions = {};
confirm_channel_test('find high watermark', (_ch, done) => {
ch = _ch;
const msg = randomString();
let baseline = 0;
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
while (ch.sendToQueue(queue, Buffer.from(msg))) {
baseline++;
}
channelOptions.highWaterMark = baseline * 2;
done();
});
});
confirm_channel_test('set high watermark', channelOptions, (_ch, done) => {
ch = _ch;
const msg = randomString();
ch.assertQueue('', { exclusive: true }, (err, { queue }) => {
assert.ifError(err);
let ok;
for (let i = 0; i < channelOptions.highWaterMark; i++) {
ok = ch.sendToQueue(queue, Buffer.from(msg));
assert.equal(ok, true);
}
done();
});
});
confirm_channel_test('publish on closed channel does not leak callbacks', (_ch, done) => {
ch = _ch;
ch.close(() => {
for (let i = 0; i < 10; i++) {
try { ch.publish('', '', Buffer.from('x'), {}, () => {}); } catch (_) {}
}
assert.strictEqual(ch.unconfirmed.length, 0);
done();
});
});
});
describe('Error handling', () => {
let c;
afterEach((_t, done) => {
c?.close(() => done());
})
it('Throw error in connection open callback', (_t, done) => {
const dom = domain.createDomain();
dom.on('error', (err) => {
assert.match(err.message, /Spurious connection open callback error/);
done();
});
connect(dom.bind((_err, _c) => {
c = _c;
throw new Error('Spurious connection open callback error');
}));
});
function error_test(name, fun) {
it(name, (_t, done) => {
const dom = domain.createDomain();
dom.run(() => {
connect((err, _c) => {
assert.ifError(err);
c = _c;
// Seems like there were some unironed wrinkles in 0.8's
// implementation of domains; explicitly adding the connection
// to the domain makes sure any exception thrown in the course
// of processing frames is handled by the domain. For other
// versions of Node.JS, this ends up being belt-and-braces.
dom.add(c);
c.createChannel((_err, ch) => {
fun(ch, done, dom);
});
});
});
});
}
error_test('Channel open callback throws an error', (_ch, done, dom) => {
dom.on('error', (err) => {
assert.match(err.message, /Error in open callback/)
done();
});
throw new Error('Error in open callback');
});
error_test('RPC callback throws error', (ch, done, dom) => {
dom.on('error', (err) => {
assert.match(err.message, /Spurious callback error/)
done();
});
ch.prefetch(0, false, (_err, _ok) => {
throw new Error('Spurious callback error');
});
});
error_test('Get callback throws error', (ch, done, dom) => {
dom.on('error', (err) => {
assert.match(err.message, /Spurious callback error/)
done();
});
ch.assertQueue('test.cb.get-with-error', {}, (_err, _ok) => {
ch.get('test.cb.get-with-error', { noAck: true }, () => {
throw new Error('Spurious callback error');
});
});
});
error_test('Consume callback throws error', (ch, done, dom) => {
dom.on('error', (err) => {
assert.match(err.message, /Spurious callback error/)
done();
});
ch.assertQueue('test.cb.consume-with-error', {}, (_err, _ok) => {
ch.consume('test.cb.consume-with-error', () => { }, { noAck: true }, () => {
throw new Error('Spurious callback error');
});
});
});
error_test('Get from non-queue invokes error', (ch, done, dom) => {
const decrementLatch = latch(2, () => done());
dom.on('error', (err) => {
assert.match(err.message, /404 \(NOT-FOUND\)/);
decrementLatch();
});
ch.get('', {}, (err) => {
assert.match(err.message, /404 \(NOT-FOUND\)/)
decrementLatch()
});
});
error_test('Consume from non-queue invokes error', (ch, done, dom) => {
const decrementLatch = latch(2, done);
dom.on('error', (err) => {
assert.match(err.message, /404 \(NOT-FOUND\)/);
decrementLatch();
});
ch.consume('', () => { }, {}, (err) => {
assert.match(err.message, /404 \(NOT-FOUND\)/)
decrementLatch()
});
});
});
});