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.
606 lines
16 KiB
606 lines
16 KiB
// Test the channel machinery |
|
|
|
'use strict'; |
|
|
|
var assert = require('assert'); |
|
var defer = require('when').defer; |
|
var Channel = require('../lib/channel').Channel; |
|
var Connection = require('../lib/connection').Connection; |
|
var util = require('./util'); |
|
var succeed = util.succeed, fail = util.fail, latch = util.latch; |
|
var completes = util.completes; |
|
var defs = require('../lib/defs'); |
|
var conn_handshake = require('./connection').connection_handshake; |
|
var OPEN_OPTS = require('./connection').OPEN_OPTS; |
|
|
|
var LOG_ERRORS = process.env.LOG_ERRORS; |
|
|
|
function baseChannelTest(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); |
|
|
|
c.open(OPEN_OPTS, function(err, ok) { |
|
if (err === null) client(c, bothDone); |
|
else fail(bothDone); |
|
}); |
|
|
|
pair.server.read(8); // discard the protocol header |
|
var s = util.runServer(pair.server, function(send, await) { |
|
conn_handshake(send, await) |
|
.then(function() { |
|
server(send, await, bothDone); |
|
}, fail(bothDone)); |
|
}); |
|
}; |
|
} |
|
|
|
function channelTest(client, server) { |
|
return baseChannelTest( |
|
function(conn, done) { |
|
var ch = new Channel(conn); |
|
if (LOG_ERRORS) ch.on('error', console.warn); |
|
client(ch, done, conn); |
|
}, |
|
function(send, await, done) { |
|
channel_handshake(send, await) |
|
.then(function(ch) { |
|
return server(send, await, done, ch); |
|
}).then(null, fail(done)); // so you can return a promise to let |
|
// errors bubble out |
|
} |
|
); |
|
}; |
|
|
|
function channel_handshake(send, await) { |
|
return await(defs.ChannelOpen)() |
|
.then(function(open) { |
|
assert.notEqual(0, open.channel); |
|
send(defs.ChannelOpenOk, {channelId: new Buffer('')}, open.channel); |
|
return open.channel; |
|
}); |
|
} |
|
|
|
// fields for deliver and publish and get-ok |
|
var DELIVER_FIELDS = { |
|
consumerTag: 'fake', |
|
deliveryTag: 1, |
|
redelivered: false, |
|
exchange: 'foo', |
|
routingKey: 'bar', |
|
replyCode: defs.constants.NO_ROUTE, |
|
replyText: 'derp', |
|
}; |
|
|
|
function open(ch) { |
|
var opened = defer(); |
|
ch.allocate(); |
|
ch._rpc(defs.ChannelOpen, {outOfBand: ''}, defs.ChannelOpenOk, |
|
function(err, _) { |
|
if (err === null) opened.resolve(); |
|
else opened.reject(err); |
|
}); |
|
return opened.promise; |
|
} |
|
|
|
suite("channel open and close", function() { |
|
|
|
test("open", channelTest( |
|
function(ch, done) { |
|
open(ch).then(succeed(done), fail(done)); |
|
}, |
|
function(send, await, done) { |
|
done(); |
|
})); |
|
|
|
test("bad server", baseChannelTest( |
|
function(c, done) { |
|
var ch = new Channel(c); |
|
open(ch).then(fail(done), succeed(done)); |
|
}, |
|
function(send, await, done) { |
|
return await(defs.ChannelOpen)() |
|
.then(function(open) { |
|
send(defs.ChannelCloseOk, {}, open.channel); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("open, close", channelTest( |
|
function(ch, done) { |
|
open(ch) |
|
.then(function() { |
|
var closed = defer(); |
|
ch.closeBecause("Bye", defs.constants.REPLY_SUCCESS, |
|
closed.resolve); |
|
return closed.promise; |
|
}) |
|
.then(succeed(done), fail(done)); |
|
}, |
|
function(send, await, done, ch) { |
|
return await(defs.ChannelClose)() |
|
.then(function(close) { |
|
send(defs.ChannelCloseOk, {}, ch); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("server close", channelTest( |
|
function(ch, done) { |
|
ch.on('error', function(error) { |
|
assert.strictEqual(504, error.code); |
|
succeed(done)(); |
|
}); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
send(defs.ChannelClose, { |
|
replyText: 'Forced close', |
|
replyCode: defs.constants.CHANNEL_ERROR, |
|
classId: 0, methodId: 0 |
|
}, ch); |
|
await(defs.ChannelCloseOk)() |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("overlapping channel/server close", channelTest( |
|
function(ch, done, conn) { |
|
var both = latch(2, done); |
|
conn.on('error', succeed(both)); |
|
ch.on('close', succeed(both)); |
|
open(ch).then(function() { |
|
ch.closeBecause("Bye", defs.constants.REPLY_SUCCESS); |
|
}, fail(both)); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.ChannelClose)() |
|
.then(function() { |
|
send(defs.ConnectionClose, { |
|
replyText: 'Got there first', |
|
replyCode: defs.constants.INTERNAL_ERROR, |
|
classId: 0, methodId: 0 |
|
}, 0); |
|
}) |
|
.then(await(defs.ConnectionCloseOk)) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("double close", channelTest( |
|
function(ch, done) { |
|
open(ch).then(function() { |
|
ch.closeBecause("First close", defs.constants.REPLY_SUCCESS); |
|
// NB no synchronisation, we do this straight away |
|
assert.throws(function() { |
|
ch.closeBecause("Second close", defs.constants.REPLY_SUCCESS); |
|
}); |
|
}).then(succeed(done), fail(done)); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.ChannelClose)() |
|
.then(function() { |
|
send(defs.ChannelCloseOk, { |
|
}, ch); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
}); //suite |
|
|
|
suite("channel machinery", function() { |
|
|
|
test("RPC", channelTest( |
|
function(ch, done) { |
|
var rpcLatch = latch(3, done); |
|
open(ch).then(function() { |
|
|
|
function wheeboom(err, f) { |
|
if (err !== null) rpcLatch(err); |
|
else rpcLatch(); |
|
} |
|
|
|
var fields = { |
|
prefetchCount: 10, |
|
prefetchSize: 0, |
|
global: false |
|
}; |
|
|
|
ch._rpc(defs.BasicQos, fields, defs.BasicQosOk, wheeboom); |
|
ch._rpc(defs.BasicQos, fields, defs.BasicQosOk, wheeboom); |
|
ch._rpc(defs.BasicQos, fields, defs.BasicQosOk, wheeboom); |
|
}).then(null, fail(rpcLatch)); |
|
}, |
|
function(send, await, done, ch) { |
|
function sendOk(f) { |
|
send(defs.BasicQosOk, {}, ch); |
|
} |
|
|
|
return await(defs.BasicQos)() |
|
.then(sendOk) |
|
.then(await(defs.BasicQos)) |
|
.then(sendOk) |
|
.then(await(defs.BasicQos)) |
|
.then(sendOk) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("Bad RPC", channelTest( |
|
function(ch, done) { |
|
// We want to see the RPC rejected and the channel closed (with an |
|
// error) |
|
var errLatch = latch(2, done); |
|
ch.on('error', function(error) { |
|
assert.strictEqual(505, error.code); |
|
succeed(errLatch)(); |
|
}); |
|
|
|
open(ch) |
|
.then(function() { |
|
ch._rpc(defs.BasicRecover, {requeue: true}, defs.BasicRecoverOk, |
|
function(err) { |
|
if (err !== null) errLatch(); |
|
else errLatch(new Error('Expected RPC failure')); |
|
}); |
|
}, fail(errLatch)); |
|
}, |
|
function(send, await, done, ch) { |
|
return await()() |
|
.then(function() { |
|
send(defs.BasicGetEmpty, {clusterId: ''}, ch); |
|
}) // oh wait! that was wrong! expect a channel close |
|
.then(await(defs.ChannelClose)) |
|
.then(function() { |
|
send(defs.ChannelCloseOk, {}, ch); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("RPC on closed channel", channelTest( |
|
function(ch, done) { |
|
open(ch); |
|
var close = defer(), fail1 = defer(), fail2 = defer(); |
|
ch.on('error', function(error) { |
|
assert.strictEqual(504, error.code); |
|
close.resolve(); |
|
}); |
|
|
|
function failureCb(d) { |
|
return function(err) { |
|
if (err !== null) d.resolve(); |
|
else d.reject(); |
|
} |
|
} |
|
|
|
ch._rpc(defs.BasicRecover, {requeue:true}, defs.BasicRecoverOk, |
|
failureCb(fail1)); |
|
ch._rpc(defs.BasicRecover, {requeue:true}, defs.BasicRecoverOk, |
|
failureCb(fail2)); |
|
close.promise |
|
.then(function() { return fail1.promise; }) |
|
.then(function() { return fail2.promise; }) |
|
.then(succeed(done), fail(done)); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.BasicRecover)() |
|
.then(function() { |
|
send(defs.ChannelClose, { |
|
replyText: 'Nuh-uh!', |
|
replyCode: defs.constants.CHANNEL_ERROR, |
|
methodId: 0, classId: 0 |
|
}, ch); |
|
return await(defs.ChannelCloseOk); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("publish all < single chunk threshold", channelTest( |
|
function(ch, done) { |
|
open(ch) |
|
.then(function() { |
|
ch.sendMessage({ |
|
exchange: 'foo', routingKey: 'bar', |
|
mandatory: false, immediate: false, ticket: 0 |
|
}, {}, new Buffer('foobar')); |
|
}) |
|
.then(succeed(done), fail(done)); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.BasicPublish)() |
|
.then(await(defs.BasicProperties)) |
|
.then(await(undefined)) // content frame |
|
.then(function(f) { |
|
assert.equal('foobar', f.content.toString()); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("publish content > single chunk threshold", channelTest( |
|
function(ch, done) { |
|
open(ch); |
|
completes(function() { |
|
ch.sendMessage({ |
|
exchange: 'foo', routingKey: 'bar', |
|
mandatory: false, immediate: false, ticket: 0 |
|
}, {}, new Buffer(3000)); |
|
}, done); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.BasicPublish)() |
|
.then(await(defs.BasicProperties)) |
|
.then(await(undefined)) // content frame |
|
.then(function(f) { |
|
assert.equal(3000, f.content.length); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("publish method & headers > threshold", channelTest( |
|
function(ch, done) { |
|
open(ch); |
|
completes(function() { |
|
ch.sendMessage({ |
|
exchange: 'foo', routingKey: 'bar', |
|
mandatory: false, immediate: false, ticket: 0 |
|
}, { |
|
headers: {foo: new Buffer(3000)} |
|
}, new Buffer('foobar')); |
|
}, done); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.BasicPublish)() |
|
.then(await(defs.BasicProperties)) |
|
.then(await(undefined)) // content frame |
|
.then(function(f) { |
|
assert.equal('foobar', f.content.toString()); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("publish zero-length message", channelTest( |
|
function(ch, done) { |
|
open(ch); |
|
completes(function() { |
|
ch.sendMessage({ |
|
exchange: 'foo', routingKey: 'bar', |
|
mandatory: false, immediate: false, ticket: 0 |
|
}, {}, new Buffer(0)); |
|
ch.sendMessage({ |
|
exchange: 'foo', routingKey: 'bar', |
|
mandatory: false, immediate: false, ticket: 0 |
|
}, {}, new Buffer(0)); |
|
}, done); |
|
}, |
|
function(send, await, done, ch) { |
|
await(defs.BasicPublish)() |
|
.then(await(defs.BasicProperties)) |
|
// no content frame for a zero-length message |
|
.then(await(defs.BasicPublish)) |
|
.then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("delivery", channelTest( |
|
function(ch, done) { |
|
open(ch); |
|
ch.on('delivery', function(m) { |
|
completes(function() { |
|
assert.equal('barfoo', m.content.toString()); |
|
}, done); |
|
}); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicDeliver, DELIVER_FIELDS, ch, new Buffer('barfoo')); |
|
}, done); |
|
})); |
|
|
|
test("zero byte msg", channelTest( |
|
function(ch, done) { |
|
open(ch); |
|
ch.on('delivery', function(m) { |
|
completes(function() { |
|
assert.deepEqual(new Buffer(0), m.content); |
|
}, done); |
|
}); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicDeliver, DELIVER_FIELDS, ch, new Buffer('')); |
|
}, done); |
|
})); |
|
|
|
test("bad delivery", channelTest( |
|
function(ch, done) { |
|
var errorAndClose = latch(2, done); |
|
ch.on('error', function(error) { |
|
assert.strictEqual(505, error.code); |
|
succeed(errorAndClose)(); |
|
}); |
|
ch.on('close', succeed(errorAndClose)); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
send(defs.BasicDeliver, DELIVER_FIELDS, ch); |
|
// now send another deliver without having sent the content |
|
send(defs.BasicDeliver, DELIVER_FIELDS, ch); |
|
return await(defs.ChannelClose)() |
|
.then(function() { |
|
send(defs.ChannelCloseOk, {}, ch); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("bad content send", channelTest( |
|
function(ch, done) { |
|
completes(function() { |
|
open(ch); |
|
assert.throws(function() { |
|
ch.sendMessage({routingKey: 'foo', |
|
exchange: 'amq.direct'}, |
|
{}, null); |
|
}); |
|
}, done); |
|
}, |
|
function(send, await, done, ch) { |
|
done(); |
|
})); |
|
|
|
test("bad properties send", channelTest( |
|
function(ch, done) { |
|
completes(function() { |
|
open(ch); |
|
assert.throws(function() { |
|
ch.sendMessage({routingKey: 'foo', |
|
exchange: 'amq.direct'}, |
|
{contentEncoding: 7}, |
|
new Buffer('foobar')); |
|
}); |
|
}, done); |
|
}, |
|
function(send, await, done, ch) { |
|
done(); |
|
})); |
|
|
|
test("bad consumer", channelTest( |
|
function(ch, done) { |
|
var errorAndClose = latch(2, done); |
|
ch.on('delivery', function() { |
|
throw new Error("I am a bad consumer"); |
|
}); |
|
ch.on('error', function(error) { |
|
assert.strictEqual(541, error.code); |
|
succeed(errorAndClose)(); |
|
}); |
|
ch.on('close', succeed(errorAndClose)); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
send(defs.BasicDeliver, DELIVER_FIELDS, ch, new Buffer('barfoo')); |
|
return await(defs.ChannelClose)() |
|
.then(function() { |
|
send(defs.ChannelCloseOk, {}, ch); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("bad send in consumer", channelTest( |
|
function(ch, done) { |
|
var errorAndClose = latch(2, done); |
|
ch.on('close', succeed(errorAndClose)); |
|
ch.on('error', function(error) { |
|
assert.strictEqual(541, error.code); |
|
succeed(errorAndClose)(); |
|
}); |
|
|
|
ch.on('delivery', function() { |
|
ch.sendMessage({routingKey: 'foo', |
|
exchange: 'amq.direct'}, |
|
{}, null); // can't send null |
|
}); |
|
|
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicDeliver, DELIVER_FIELDS, ch, |
|
new Buffer('barfoo')); |
|
}, done); |
|
return await(defs.ChannelClose)() |
|
.then(function() { |
|
send(defs.ChannelCloseOk, {}, ch); |
|
}).then(succeed(done), fail(done)); |
|
})); |
|
|
|
test("return", channelTest( |
|
function(ch, done) { |
|
ch.on('return', function(m) { |
|
completes(function() { |
|
assert.equal('barfoo', m.content.toString()); |
|
}, done); |
|
}); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicReturn, DELIVER_FIELDS, ch, new Buffer('barfoo')); |
|
}, done); |
|
})); |
|
|
|
test("cancel", channelTest( |
|
function(ch, done) { |
|
ch.on('cancel', function(f) { |
|
completes(function() { |
|
assert.equal('product of society', f.consumerTag); |
|
}, done); |
|
}); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicCancel, { |
|
consumerTag: 'product of society', |
|
nowait: false |
|
}, ch); |
|
}, done); |
|
})); |
|
|
|
function confirmTest(variety, Method) { |
|
return test('confirm ' + variety, channelTest( |
|
function(ch, done) { |
|
ch.on(variety, function(f) { |
|
completes(function() { |
|
assert.equal(1, f.deliveryTag); |
|
}, done); |
|
}); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(Method, { |
|
deliveryTag: 1, |
|
multiple: false |
|
}, ch); |
|
}, done); |
|
})); |
|
} |
|
|
|
confirmTest("ack", defs.BasicAck); |
|
confirmTest("nack", defs.BasicNack); |
|
|
|
test("out-of-order acks", channelTest( |
|
function(ch, done) { |
|
var allConfirms = latch(3, function() { |
|
completes(function() { |
|
assert.equal(0, ch.unconfirmed.length); |
|
assert.equal(4, ch.lwm); |
|
}, done); |
|
}); |
|
ch.pushConfirmCallback(allConfirms); |
|
ch.pushConfirmCallback(allConfirms); |
|
ch.pushConfirmCallback(allConfirms); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicAck, {deliveryTag: 2, multiple: false}, ch); |
|
send(defs.BasicAck, {deliveryTag: 3, multiple: false}, ch); |
|
send(defs.BasicAck, {deliveryTag: 1, multiple: false}, ch); |
|
}, done); |
|
})); |
|
|
|
test("not all out-of-order acks", channelTest( |
|
function(ch, done) { |
|
var allConfirms = latch(2, function() { |
|
completes(function() { |
|
assert.equal(1, ch.unconfirmed.length); |
|
assert.equal(3, ch.lwm); |
|
}, done); |
|
}); |
|
ch.pushConfirmCallback(allConfirms); // tag = 1 |
|
ch.pushConfirmCallback(allConfirms); // tag = 2 |
|
ch.pushConfirmCallback(function() { |
|
done(new Error('Confirm callback should not be called')); |
|
}); |
|
open(ch); |
|
}, |
|
function(send, await, done, ch) { |
|
completes(function() { |
|
send(defs.BasicAck, {deliveryTag: 2, multiple: false}, ch); |
|
send(defs.BasicAck, {deliveryTag: 1, multiple: false}, ch); |
|
}, done); |
|
})); |
|
|
|
});
|
|
|