UNPKG

amqplib

Version:

An AMQP 0-9-1 (e.g., RabbitMQ) library and client.

440 lines (387 loc) 12.8 kB
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() }); }); }); });