UNPKG

amqplib

Version:

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

331 lines (304 loc) 10.5 kB
const { describe, it, beforeEach, afterEach } = require('node:test'); const assert = require('node:assert'); const defs = require('../lib/defs'); const Connection = require('../lib/connection').Connection; const HEARTBEAT = require('../lib/frame').HEARTBEAT; const HB_BUF = require('../lib/frame').HEARTBEAT_BUF; const OPEN_OPTS = require('./lib/data').OPEN_OPTS; const heartbeat = require('../lib/heartbeat'); const { latch, handshake, runServer, socketPair } = require('./lib/util'); const LOG_ERRORS = process.env.LOG_ERRORS; function connectionTest(client, server) { return (_t, done) => { const decrementLatch = latch(2, done); const pair = socketPair(); const c = new Connection(pair.client); if (LOG_ERRORS) c.on('error', console.warn); client(c, decrementLatch); // NB only not a race here because the writes are synchronous const protocolHeader = pair.server.read(8); assert.deepEqual(Buffer.from(`AMQP${String.fromCharCode(0, 0, 9, 1)}`), protocolHeader); runServer(pair.server, (send, wait) => { server(send, wait, decrementLatch, pair.server); }); }; } describe('Connection', () => { describe('Connection errors', () => { it('socket close during open', (_t, done) => { // RabbitMQ itself will take at least 3 seconds to close the socket // in the event of a handshake problem. Instead of using a live // connection, I'm just going to pretend. const pair = socketPair(); const conn = new Connection(pair.client); pair.server.on('readable', () => pair.server.end()); conn.open({}, (err) => { assert.ok(err); assert.match(err.message, /Socket closed abruptly during opening handshake/); done(); }); }); it('bad frame during open', (_t, done) => { const ss = socketPair(); const conn = new (require('../lib/connection').Connection)(ss.client); ss.server.on('readable', () => { ss.server.write(Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); }); conn.open({}, (err) => { assert.ok(err); assert.match(err.message, /Invalid frame/); done(); }); }); }); describe('Connection open', () => { it('happy', connectionTest((c, cb) => { c.open(OPEN_OPTS, (err) => { assert.ifError(err); cb(); }) }, (send, wait, cb) => { handshake(send, wait) .then(cb, cb); })); it('wrong first frame', connectionTest((c, cb) => { c.open(OPEN_OPTS, (err) => { assert.ok(err); assert.match(err.message, /Expected ConnectionStart; got <ConnectionTune channel:0/); cb() }); }, (send, _wait, cb) => { // bad server! bad! whatever were you thinking? send(defs.ConnectionTune, { channelMax: 0, heartbeat: 0, frameMax: 0 }) cb(); })); it('unexpected socket close', connectionTest((c, cb) => { c.open(OPEN_OPTS, (err) => { assert.ok(err); assert.match(err.message, /Socket closed abruptly during opening handshake/); cb(); }); }, (send, wait, cb, socket) => { send(defs.ConnectionStart, { versionMajor: 0, versionMinor: 9, serverProperties: {}, mechanisms: Buffer.from('PLAIN'), locales: Buffer.from('en_US'), }); return wait(defs.ConnectionStartOk)() .then(() => socket.end()) .then(cb, cb); })); }); describe('Connection running', () => { it('wrong frame on channel 0', connectionTest((c, cb) => { c.once('error', (err) => { assert.match(err.message, /Unexpected frame on channel 0/); cb() }); c.open(OPEN_OPTS); }, (send, wait, cb) => { handshake(send, wait) // there's actually nothing that would plausibly be sent to a // just opened connection, so this is violating more than one // rule. Nonetheless. .then(() => send(defs.ChannelOpenOk, { channelId: Buffer.from('') }, 0)) .then(wait(defs.ConnectionClose)) .then((_close) => send(defs.ConnectionCloseOk, {}, 0)) .then(cb, cb); })); it('unopened channel', connectionTest((c, cb) => { c.once('error', (err) => { assert.match(err.message, /Frame on unknown channel/); cb(); }); c.open(OPEN_OPTS); }, (send, wait, cb) => { handshake(send, wait) // there's actually nothing that would plausibly be sent to a // just opened connection, so this is violating more than one // rule. Nonetheless. .then(() => send(defs.ChannelOpenOk, { channelId: Buffer.from('') }, 3)) .then(wait(defs.ConnectionClose)) .then((_close) => send(defs.ConnectionCloseOk, {}, 0)) .then(cb, cb); })); it('unexpected socket close', connectionTest((c, cb) => { const decrementLatch = latch(2, cb); c.on('error', (err) => { assert.match(err.message, /Unexpected close/); decrementLatch(); }); c.on('close', (err) => { assert.match(err.message, /Unexpected close/); decrementLatch(); }); c.open(OPEN_OPTS, (err) => { assert.ifError(err); c.sendHeartbeat(); }); }, (send, wait, cb, socket) => { handshake(send, wait) .then(wait()) .then(() => socket.end()) .then(cb, cb); })); it('connection.blocked', connectionTest((c, cb) => { c.on('blocked', (reason) => { assert.strictEqual(reason, 'felt like it'); cb(); }); c.open(OPEN_OPTS); }, (send, wait, cb, _socket) => { handshake(send, wait) .then(() => send(defs.ConnectionBlocked, { reason: 'felt like it' }, 0)) .then(cb, cb); })); it('connection.unblocked', connectionTest((c, cb) => { c.on('unblocked', () => cb()); c.open(OPEN_OPTS); }, (send, wait, cb, _socket) => { handshake(send, wait) .then(() => send(defs.ConnectionUnblocked, {}, 0)) .then(cb, cb); })); }); describe('Connection close', () => { it('happy', connectionTest((c, cb) => { const decrementLatch = latch(2, cb); c.on('close', () => decrementLatch()); c.open(OPEN_OPTS, (err) => { assert.ifError(err); c.close((err) => { assert.ifError(err); decrementLatch(); }); }); }, (send, wait, cb) => { handshake(send, wait) .then(wait(defs.ConnectionClose)) .then((_close) => send(defs.ConnectionCloseOk, {})) .then(cb, cb); })); it('interleaved close frames', connectionTest((c, cb) => { const decrementLatch = latch(2, cb); c.on('close', () => decrementLatch()); c.open(OPEN_OPTS, (err) => { assert.ifError(err); c.close((err) => { assert.ifError(err); decrementLatch(); }); }); }, (send, wait, cb) => { handshake(send, wait) .then(wait(defs.ConnectionClose)) .then(() => send(defs.ConnectionClose, { replyText: 'Ha!', replyCode: defs.constants.REPLY_SUCCESS, methodId: 0, classId: 0, })) .then(wait(defs.ConnectionCloseOk)) .then(() => send(defs.ConnectionCloseOk, {})) .then(cb, cb); })); it('server error close', connectionTest((c, cb) => { const decrementLatch = latch(2, cb); c.once('close', (err) => { assert.match(err.message, /Connection closed: 541 \(INTERNAL-ERROR\) with message "Begone"/); decrementLatch(); }); c.once('error', (err) => { assert.match(err.message, /Connection closed: 541 \(INTERNAL-ERROR\) with message "Begone"/); decrementLatch(); }); c.open(OPEN_OPTS); }, (send, wait, cb) => { handshake(send, wait) .then(() => send(defs.ConnectionClose, { replyText: 'Begone', replyCode: defs.constants.INTERNAL_ERROR, methodId: 0, classId: 0, })) .then(wait(defs.ConnectionCloseOk)) .then(cb, cb); })); it('operator-intiated close', connectionTest((c, cb) => { c.once('close', (err) => { assert.match(err.message, /Connection closed: 320 \(CONNECTION-FORCED\) with message "Begone"/); cb(); }); c.once('error', (err) => assert.fail(`Unexepcted error: ${err.message}`)); c.open(OPEN_OPTS); }, (send, wait, cb) => { handshake(send, wait) .then(() => send(defs.ConnectionClose, { replyText: 'Begone', replyCode: defs.constants.CONNECTION_FORCED, methodId: 0, classId: 0, })) .then(wait(defs.ConnectionCloseOk)) .then(cb, cb); })); it('double close', connectionTest((c, cb) => { c.open(OPEN_OPTS, (err) => { assert.ifError(err); c.close(); // NB no synchronisation, we do this straight away assert.throws(() => c.close()); cb(); }); }, (send, wait, cb) => { handshake(send, wait) .then(wait(defs.ConnectionClose)) .then(() => send(defs.ConnectionCloseOk, {})) .then(cb, cb); })); }); describe('heartbeats', () => { beforeEach(() => { heartbeat.UNITS_TO_MS = 20; }); afterEach(() => { heartbeat.UNITS_TO_MS = 1000; }); it('send heartbeat after open', connectionTest((c, cb) => { const opts = Object.create(OPEN_OPTS); opts.heartbeat = 1; // Don't leave the error waiting to happen for the next test c.on('error', (err) => { assert.match(err.message, /Heartbeat timeout/) }); c.open(opts); cb() }, (send, wait, cb, socket) => { let timer; handshake(send, wait) .then(() => { timer = setInterval(() => socket.write(HB_BUF), heartbeat.UNITS_TO_MS); }) .then(wait()) .then((hb) => { assert.strictEqual(hb, HEARTBEAT); cb(); }).finally(() => clearInterval(timer)); })); it('detect lack of heartbeats', connectionTest((c, cb) => { const opts = Object.create(OPEN_OPTS); opts.heartbeat = 1; c.once('error', (err) => { assert.match(err.message, /Heartbeat timeout/) cb(); }); c.open(opts); }, (send, wait, cb, _socket) => { handshake(send, wait) .then(cb, cb); // conspicuously not sending anything ... })); }); });