You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
10 KiB
390 lines
10 KiB
'use strict'; |
|
|
|
var assert = require('assert'); |
|
var defs = require('../lib/defs'); |
|
var Connection = require('../lib/connection').Connection; |
|
var HEARTBEAT = require('../lib/frame').HEARTBEAT; |
|
var HB_BUF = require('../lib/frame').HEARTBEAT_BUF; |
|
var util = require('./util'); |
|
var succeed = util.succeed, fail = util.fail, latch = util.latch; |
|
var completes = util.completes; |
|
var kCallback = util.kCallback; |
|
|
|
var LOG_ERRORS = process.env.LOG_ERRORS; |
|
|
|
var OPEN_OPTS = { |
|
// start-ok |
|
'clientProperties': {}, |
|
'mechanism': 'PLAIN', |
|
'response': new Buffer(['', 'guest', 'guest'].join(String.fromCharCode(0))), |
|
'locale': 'en_US', |
|
|
|
// tune-ok |
|
'channelMax': 0, |
|
'frameMax': 0, |
|
'heartbeat': 0, |
|
|
|
// open |
|
'virtualHost': '/', |
|
'capabilities': '', |
|
'insist': 0 |
|
}; |
|
module.exports.OPEN_OPTS = OPEN_OPTS; |
|
|
|
function happy_open(send, await) { |
|
// kick it off |
|
send(defs.ConnectionStart, |
|
{versionMajor: 0, |
|
versionMinor: 9, |
|
serverProperties: {}, |
|
mechanisms: new Buffer('PLAIN'), |
|
locales: new Buffer('en_US')}); |
|
return await(defs.ConnectionStartOk)() |
|
.then(function(f) { |
|
send(defs.ConnectionTune, |
|
{channelMax: 0, |
|
heartbeat: 0, |
|
frameMax: 0}); |
|
}) |
|
.then(await(defs.ConnectionTuneOk)) |
|
.then(await(defs.ConnectionOpen)) |
|
.then(function(f) { |
|
send(defs.ConnectionOpenOk, |
|
{knownHosts: ''}); |
|
}); |
|
} |
|
module.exports.connection_handshake = happy_open; |
|
|
|
function connectionTest(client, server) { |
|
return function(done) { |
|
var bothDone = latch(2, done); |
|
var pair = util.socketPair(); |
|
var c = new Connection(pair.client); |
|
if (LOG_ERRORS) c.on('error', console.warn); |
|
client(c, bothDone); |
|
|
|
// NB only not a race here because the writes are synchronous |
|
var protocolHeader = pair.server.read(8); |
|
assert.deepEqual(new Buffer("AMQP" + String.fromCharCode(0,0,9,1)), |
|
protocolHeader); |
|
|
|
var s = util.runServer(pair.server, function(send, await) { |
|
server(send, await, bothDone, pair.server); |
|
}); |
|
}; |
|
} |
|
|
|
suite("Connection errors", function() { |
|
|
|
test("socket close during open", function(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. |
|
var pair = util.socketPair(); |
|
var conn = new Connection(pair.client); |
|
pair.server.on('readable', function() { |
|
pair.server.end(); |
|
}); |
|
conn.open({}, kCallback(fail(done), succeed(done))); |
|
}); |
|
|
|
test("bad frame during open", function(done) { |
|
var ss = util.socketPair(); |
|
var conn = new (require('../lib/connection').Connection)(ss.client); |
|
ss.server.on('readable', function() { |
|
ss.server.write(new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); |
|
}); |
|
conn.open({}, kCallback(fail(done), succeed(done))); |
|
}); |
|
|
|
}); |
|
|
|
suite("Connection open", function() { |
|
|
|
test("happy", connectionTest( |
|
function(c, done) { |
|
c.open(OPEN_OPTS, kCallback(succeed(done), fail(done))); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("wrong first frame", connectionTest( |
|
function(c, done) { |
|
c.open(OPEN_OPTS, kCallback(fail(done), succeed(done))); |
|
}, |
|
function(send, await, done) { |
|
// bad server! bad! whatever were you thinking? |
|
completes(function() { |
|
send(defs.ConnectionTune, |
|
{channelMax: 0, |
|
heartbeat: 0, |
|
frameMax: 0}); |
|
}, done); |
|
})); |
|
|
|
test("unexpected socket close", connectionTest( |
|
function(c, done) { |
|
c.open(OPEN_OPTS, kCallback(fail(done), succeed(done))); |
|
}, |
|
function(send, await, done, socket) { |
|
send(defs.ConnectionStart, |
|
{versionMajor: 0, |
|
versionMinor: 9, |
|
serverProperties: {}, |
|
mechanisms: new Buffer('PLAIN'), |
|
locales: new Buffer('en_US')}); |
|
return await(defs.ConnectionStartOk)() |
|
.then(function() { |
|
socket.end(); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
}); |
|
|
|
suite("Connection running", function() { |
|
|
|
test("wrong frame on channel 0", connectionTest( |
|
function(c, done) { |
|
c.on('error', succeed(done)); |
|
c.open(OPEN_OPTS); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(function() { |
|
// there's actually nothing that would plausibly be sent to a |
|
// just opened connection, so this is violating more than one |
|
// rule. Nonetheless. |
|
send(defs.ChannelOpenOk, {channelId: new Buffer('')}, 0); |
|
}) |
|
.then(await(defs.ConnectionClose)) |
|
.then(function(close) { |
|
send(defs.ConnectionCloseOk, {}, 0); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("unopened channel", connectionTest( |
|
function(c, done) { |
|
c.on('error', succeed(done)); |
|
c.open(OPEN_OPTS); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(function() { |
|
// there's actually nothing that would plausibly be sent to a |
|
// just opened connection, so this is violating more than one |
|
// rule. Nonetheless. |
|
send(defs.ChannelOpenOk, {channelId: new Buffer('')}, 3); |
|
}) |
|
.then(await(defs.ConnectionClose)) |
|
.then(function(close) { |
|
send(defs.ConnectionCloseOk, {}, 0); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("unexpected socket close", connectionTest( |
|
function(c, done) { |
|
var errorAndClosed = latch(2, done); |
|
c.on('error', succeed(errorAndClosed)); |
|
c.on('close', succeed(errorAndClosed)); |
|
c.open(OPEN_OPTS, kCallback(function() { |
|
c.sendHeartbeat(); |
|
}, fail(errorAndClosed))); |
|
}, |
|
function(send, await, done, socket) { |
|
happy_open(send, await) |
|
.then(await()) |
|
.then(function() { |
|
socket.end(); |
|
}).then(succeed(done)); |
|
})); |
|
|
|
test("connection.blocked", connectionTest( |
|
function(c, done) { |
|
c.on('blocked', succeed(done)); |
|
c.open(OPEN_OPTS); |
|
}, |
|
function(send, await, done, socket) { |
|
happy_open(send, await) |
|
.then(function() { |
|
send(defs.ConnectionBlocked, {reason: 'felt like it'}, 0); |
|
}) |
|
.then(succeed(done)); |
|
})); |
|
|
|
test("connection.unblocked", connectionTest( |
|
function(c, done) { |
|
c.on('unblocked', succeed(done)); |
|
c.open(OPEN_OPTS); |
|
}, |
|
function(send, await, done, socket) { |
|
happy_open(send, await) |
|
.then(function() { |
|
send(defs.ConnectionUnblocked, {}, 0); |
|
}) |
|
.then(succeed(done)); |
|
})); |
|
|
|
|
|
}); |
|
|
|
suite("Connection close", function() { |
|
|
|
test("happy", connectionTest( |
|
function(c, done0) { |
|
var done = latch(2, done0); |
|
c.on('close', done); |
|
c.open(OPEN_OPTS, kCallback(function(_ok) { |
|
c.close(kCallback(succeed(done), fail(done))); |
|
}, function() {})); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(await(defs.ConnectionClose)) |
|
.then(function(close) { |
|
send(defs.ConnectionCloseOk, {}); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("interleaved close frames", connectionTest( |
|
function(c, done0) { |
|
var done = latch(2, done0); |
|
c.on('close', done); |
|
c.open(OPEN_OPTS, kCallback(function(_ok) { |
|
c.close(kCallback(succeed(done), fail(done))); |
|
}, done)); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(await(defs.ConnectionClose)) |
|
.then(function(f) { |
|
send(defs.ConnectionClose, { |
|
replyText: "Ha!", |
|
replyCode: defs.constants.REPLY_SUCCESS, |
|
methodId: 0, classId: 0 |
|
}); |
|
}) |
|
.then(await(defs.ConnectionCloseOk)) |
|
.then(function(f) { |
|
send(defs.ConnectionCloseOk, {}); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("server error close", connectionTest( |
|
function(c, done0) { |
|
var done = latch(2, done0); |
|
c.on('close', succeed(done)); |
|
c.on('error', succeed(done)); |
|
c.open(OPEN_OPTS); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(function(f) { |
|
send(defs.ConnectionClose, { |
|
replyText: "Begone", |
|
replyCode: defs.constants.INTERNAL_ERROR, |
|
methodId: 0, classId: 0 |
|
}); |
|
}) |
|
.then(await(defs.ConnectionCloseOk)) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("operator-intiated close", connectionTest( |
|
function(c, done) { |
|
c.on('close', succeed(done)); |
|
c.on('error', fail(done)); |
|
c.open(OPEN_OPTS); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(function(f) { |
|
send(defs.ConnectionClose, { |
|
replyText: "Begone", |
|
replyCode: defs.constants.CONNECTION_FORCED, |
|
methodId: 0, classId: 0 |
|
}); |
|
}) |
|
.then(await(defs.ConnectionCloseOk)) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
|
|
test("double close", connectionTest( |
|
function(c, done) { |
|
c.open(OPEN_OPTS, kCallback(function() { |
|
c.close(); |
|
// NB no synchronisation, we do this straight away |
|
assert.throws(function() { |
|
c.close(); |
|
}); |
|
done(); |
|
}, done)); |
|
}, |
|
function(send, await, done) { |
|
happy_open(send, await) |
|
.then(await(defs.ConnectionClose)) |
|
.then(function() { |
|
send(defs.ConnectionCloseOk, {}); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
}); |
|
|
|
suite("heartbeats", function() { |
|
|
|
var heartbeat = require('../lib/heartbeat'); |
|
|
|
setup(function() { |
|
heartbeat.UNITS_TO_MS = 20; |
|
}); |
|
|
|
teardown(function() { |
|
heartbeat.UNITS_TO_MS = 1000; |
|
}); |
|
|
|
test("send heartbeat after open", connectionTest( |
|
function(c, done) { |
|
completes(function() { |
|
var opts = Object.create(OPEN_OPTS); |
|
opts.heartbeat = 1; |
|
// Don't leave the error waiting to happen for the next test, this |
|
// confuses mocha awfully |
|
c.on('error', function() {}); |
|
c.open(opts); |
|
}, done); |
|
}, |
|
function(send, await, done, socket) { |
|
var timer; |
|
happy_open(send, await) |
|
.then(function() { |
|
timer = setInterval(function() { |
|
socket.write(HB_BUF); |
|
}, heartbeat.UNITS_TO_MS); |
|
}) |
|
.then(await()) |
|
.then(function(hb) { |
|
if (hb === HEARTBEAT) done(); |
|
else done("Next frame after silence not a heartbeat"); |
|
clearInterval(timer); |
|
}); |
|
})); |
|
|
|
test("detect lack of heartbeats", connectionTest( |
|
function(c, done) { |
|
var opts = Object.create(OPEN_OPTS); |
|
opts.heartbeat = 1; |
|
c.on('error', succeed(done)); |
|
c.open(opts); |
|
}, |
|
function(send, await, done, socket) { |
|
happy_open(send, await) |
|
.then(succeed(done), fail(done)); |
|
// conspicuously not sending anything ... |
|
})); |
|
|
|
});
|
|
|