amqplib
Version:
An AMQP 0-9-1 (e.g., RabbitMQ) library and client.
773 lines (718 loc) • 26.5 kB
JavaScript
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);
}));
it('close while blocked closes immediately', connectionTest((c, cb) => {
const decrementLatch = latch(2, cb);
c.on('close', () => decrementLatch());
c.open(OPEN_OPTS, (err) => {
assert.ifError(err);
c.once('blocked', () => {
c.close((err) => {
assert.ifError(err);
decrementLatch();
});
});
});
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
.then(wait(defs.ConnectionClose))
// deliberately do NOT send ConnectionCloseOk — client must close anyway
.then(cb, cb);
}));
});
describe('Event handler errors - without handler-event listener', () => {
let prevUncaughtExceptionListeners;
beforeEach(() => {
prevUncaughtExceptionListeners = process.rawListeners('uncaughtException').slice();
process.removeAllListeners('uncaughtException');
});
afterEach(() => {
prevUncaughtExceptionListeners.forEach((h) => process.on('uncaughtException', h));
heartbeat.UNITS_TO_MS = 1000;
});
it('throw in close handler from server-initiated close is swallowed without handler-error listener', connectionTest((c, cb) => {
c.on('close', () => { throw new Error('user handler explodes'); });
c.open(OPEN_OPTS);
cb();
}, (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('throw in close handler from client-initiated close is swallowed without handler-error listener', connectionTest((c, cb) => {
c.open(OPEN_OPTS, (err) => {
assert.ifError(err);
c.on('close', () => { throw new Error('user handler explodes on client close'); });
c.close();
cb();
});
}, (send, wait, cb) => {
handshake(send, wait)
.then(wait(defs.ConnectionClose))
.then(() => send(defs.ConnectionCloseOk, {}))
.then(cb, cb);
}));
it('throw in error handler becomes uncaught exception', connectionTest((c, cb) => {
const expectedErr = new Error('user error handler explodes');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.on('error', () => { throw expectedErr; });
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('throw in blocked handler becomes uncaught exception', connectionTest((c, cb) => {
const expectedErr = new Error('user blocked handler explodes');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.on('blocked', () => { throw expectedErr; });
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
.then(cb, cb);
}));
it('throw in unblocked handler becomes uncaught exception', connectionTest((c, cb) => {
const expectedErr = new Error('user unblocked handler explodes');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.on('unblocked', () => { throw expectedErr; });
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ConnectionUnblocked, {}, 0))
.then(cb, cb);
}));
it('throw in update-secret-ok handler becomes uncaught exception', connectionTest((c, cb) => {
const expectedErr = new Error('user update-secret-ok handler explodes');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.open(OPEN_OPTS, (err) => {
assert.ifError(err);
c.on('update-secret-ok', () => { throw expectedErr; });
c._updateSecret(Buffer.from('new secret'), 'reason', () => {});
});
}, (send, wait, cb) => {
handshake(send, wait)
.then(wait(defs.ConnectionUpdateSecret))
.then(() => send(defs.ConnectionUpdateSecretOk, {}, 0))
.then(cb, cb);
}));
it('throw in error handler from closeWithError becomes uncaught exception', connectionTest((c, cb) => {
const expectedErr = new Error('user error handler explodes on closeWithError');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.once('error', (err) => {
assert.match(err.message, /Unexpected frame on channel 0/);
throw expectedErr;
});
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ChannelOpenOk, { channelId: Buffer.from('') }, 0))
.then(cb, cb);
}));
it('throw in error handler from onSocketError', connectionTest((c, cb) => {
const expectedErr = new Error('user error handler explodes on socket error');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.once('error', (err) => {
assert.match(err.message, /Unexpected close/);
throw expectedErr;
});
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('throw in error handler from heartbeat timeout', connectionTest((c, cb) => {
heartbeat.UNITS_TO_MS = 20;
const expectedErr = new Error('user error handler explodes on heartbeat timeout');
const opts = Object.create(OPEN_OPTS);
opts.heartbeat = 1;
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
c.heartbeater.clear();
cb();
});
c.on('error', (err) => {
assert.match(err.message, /Heartbeat timeout/);
throw expectedErr;
});
c.open(opts);
}, (send, wait, cb) => {
handshake(send, wait)
.then(cb, cb);
// conspicuously not sending anything ...
}));
});
describe('Event handler errors - with handler-error listener', () => {
let prevUncaughtExceptionListeners;
beforeEach(() => {
prevUncaughtExceptionListeners = process.rawListeners('uncaughtException').slice();
process.removeAllListeners('uncaughtException');
});
afterEach(() => {
prevUncaughtExceptionListeners.forEach((h) => process.on('uncaughtException', h));
heartbeat.UNITS_TO_MS = 1000;
});
it('throw in close handler is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user close handler explodes');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'close');
cb();
});
c.on('close', () => { throw expectedErr; });
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('throw in error handler is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user error handler explodes');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'error');
cb();
});
c.on('error', () => { throw expectedErr; });
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('throw in blocked handler is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user blocked handler explodes');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'blocked');
cb();
});
c.on('blocked', () => { throw expectedErr; });
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
.then(cb, cb);
}));
it('throw in unblocked handler is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user unblocked handler explodes');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'unblocked');
cb();
});
c.on('unblocked', () => { throw expectedErr; });
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ConnectionUnblocked, {}, 0))
.then(cb, cb);
}));
it('throw in update-secret-ok handler is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user update-secret-ok handler explodes');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'update-secret-ok');
cb();
});
c.open(OPEN_OPTS, (err) => {
assert.ifError(err);
c.on('update-secret-ok', () => { throw expectedErr; });
c._updateSecret(Buffer.from('new secret'), 'reason', () => {});
});
}, (send, wait, cb) => {
handshake(send, wait)
.then(wait(defs.ConnectionUpdateSecret))
.then(() => send(defs.ConnectionUpdateSecretOk, {}, 0))
.then(cb, cb);
}));
it('throw in error handler from heartbeat timeout is delivered via handler-error event', connectionTest((c, cb) => {
heartbeat.UNITS_TO_MS = 20;
const expectedErr = new Error('user error handler explodes on heartbeat timeout');
const opts = Object.create(OPEN_OPTS);
opts.heartbeat = 1;
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'error');
c.heartbeater.clear();
cb();
});
c.on('error', (err) => {
assert.match(err.message, /Heartbeat timeout/);
throw expectedErr;
});
c.open(opts);
}, (send, wait, cb) => {
handshake(send, wait)
.then(cb, cb);
// conspicuously not sending anything ...
}));
it('throw in error handler from closeWithError is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user error handler explodes on closeWithError');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'error');
cb();
});
c.once('error', (err) => {
assert.match(err.message, /Unexpected frame on channel 0/);
throw expectedErr;
});
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ChannelOpenOk, { channelId: Buffer.from('') }, 0))
.then(cb, cb);
}));
it('throw in error handler from onSocketError is delivered via handler-error event', connectionTest((c, cb) => {
const expectedErr = new Error('user error handler explodes on socket error');
c.on('handler-error', (err, event) => {
assert.strictEqual(err, expectedErr);
assert.strictEqual(event, 'error');
cb();
});
c.once('error', (err) => {
assert.match(err.message, /Unexpected close/);
throw expectedErr;
});
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('throw in handler-error handler becomes uncaught exception', connectionTest((c, cb) => {
const expectedErr = new Error('handler-error handler explodes');
process.once('uncaughtException', (err) => {
assert.strictEqual(err, expectedErr);
cb();
});
c.on('handler-error', () => { throw expectedErr; });
c.on('blocked', () => { throw new Error('user blocked handler explodes'); });
c.open(OPEN_OPTS);
}, (send, wait, cb) => {
handshake(send, wait)
.then(() => send(defs.ConnectionBlocked, { reason: 'memory' }, 0))
.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 ...
}));
it('without a heartbeat option, server suggested value is used', connectionTest((c, cb) => {
// OPEN_OPTS.heartbeat is null — no preference set by client
c.once('error', (err) => {
assert.match(err.message, /Heartbeat timeout/);
cb();
});
c.open(OPEN_OPTS, (err) => {
assert.ifError(err);
assert.strictEqual(c.heartbeat, 1);
});
}, (send, wait, cb) => {
send(defs.ConnectionStart, {
versionMajor: 0, versionMinor: 9,
serverProperties: {},
mechanisms: Buffer.from('PLAIN'),
locales: Buffer.from('en_US'),
});
wait(defs.ConnectionStartOk)()
.then(() => send(defs.ConnectionTune, { channelMax: 0, heartbeat: 1, frameMax: 0 }))
.then(wait(defs.ConnectionTuneOk))
.then(wait(defs.ConnectionOpen))
.then(() => send(defs.ConnectionOpenOk, { knownHosts: '' }))
.then(cb, cb);
}));
it('heartbeat:0 means no heartbeater is started', connectionTest((c, cb) => {
const opts = Object.create(OPEN_OPTS);
opts.heartbeat = 0;
c.open(opts, (err) => {
assert.ifError(err);
assert.strictEqual(c.heartbeat, 0);
assert.strictEqual(c.heartbeater, null);
cb();
});
}, (send, wait, cb) => {
send(defs.ConnectionStart, {
versionMajor: 0, versionMinor: 9,
serverProperties: {},
mechanisms: Buffer.from('PLAIN'),
locales: Buffer.from('en_US'),
});
wait(defs.ConnectionStartOk)()
.then(() => send(defs.ConnectionTune, { channelMax: 0, heartbeat: 60, frameMax: 0 }))
.then(wait(defs.ConnectionTuneOk))
.then(wait(defs.ConnectionOpen))
.then(() => send(defs.ConnectionOpenOk, { knownHosts: '' }))
.then(cb, cb);
}));
it('heartbeat:0 disables heartbeats even if server suggests one', connectionTest((c, cb) => {
const opts = Object.create(OPEN_OPTS);
opts.heartbeat = 0;
c.open(opts, cb);
}, (send, wait, cb) => {
send(defs.ConnectionStart, {
versionMajor: 0, versionMinor: 9,
serverProperties: {},
mechanisms: Buffer.from('PLAIN'),
locales: Buffer.from('en_US'),
});
wait(defs.ConnectionStartOk)()
.then(() => send(defs.ConnectionTune, { channelMax: 0, heartbeat: 60, frameMax: 0 }))
.then(wait(defs.ConnectionTuneOk))
.then((tuneOk) => {
assert.strictEqual(tuneOk.fields.heartbeat, 0);
})
.then(wait(defs.ConnectionOpen))
.then(() => send(defs.ConnectionOpenOk, { knownHosts: '' }))
.then(cb, cb);
}));
});
});