amqp-node
Version:
An AMQP 0-9-1 (e.g., RabbitMQ) library and client.
663 lines (615 loc) • 18.2 kB
JavaScript
// Test the channel machinery
;
var assert = require('assert');
var Promise = require('bluebird');
var defer = Promise.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) {
return new Promise(function (resolve, reject) {
ch.allocate();
ch._rpc(defs.ChannelOpen, {
outOfBand: ''
}, defs.ChannelOpenOk,
function (err, _) {
if (err) {
return reject(err);
}
resolve();
});
});
}
describe('channel open and close', function () {
it('open', channelTest(
function (ch, done) {
open(ch).then(succeed(done), fail(done));
},
function (send, await, done) {
done();
}));
it('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));
}));
it('open, close', channelTest(
function (ch, done) {
open(ch)
.then(function () {
return new Promise(function (resolve, reject) {
ch.closeBecause('Bye', defs.constants.REPLY_SUCCESS,
resolve);
});
})
.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));
}));
it('server close', channelTest(
function (ch, done) {
ch.on('error', 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));
}));
it('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));
}));
it('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
describe('channel machinery', function () {
it('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));
}));
it('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', 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));
}));
it('RPC on closed channel', channelTest(
function (ch, done) {
open(ch);
var expectedFailure = function () {
return function (err) {
return new Promise(function (resolve, reject) {
if (err !== null) {
// has err, failed
resolve();
} else {
// not failed, reject
reject();
}
});
};
};
var close = expectedFailure(),
fail1 = expectedFailure(),
fail2 = expectedFailure();
ch.on('error', close);
ch._rpc(defs.BasicRecover, {
requeue: true
}, defs.BasicRecoverOk,
fail1);
ch._rpc(defs.BasicRecover, {
requeue: true
}, defs.BasicRecoverOk,
fail2);
close().then(function () {
return Promise.all([fail1, fail2]);
}).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));
}));
it('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));
}));
it('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));
}));
it('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));
}));
it('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));
}));
it('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);
}));
it('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);
}));
it('bad delivery', channelTest(
function (ch, done) {
var errorAndClose = latch(2, done);
ch.on('error', 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));
}));
it('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();
}));
it('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();
}));
it('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', 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));
}));
it('bad send in consumer', channelTest(
function (ch, done) {
var errorAndClose = latch(2, done);
ch.on('close', succeed(errorAndClose));
ch.on('error', 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));
}));
it('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);
}));
it('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 it('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);
it('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);
}));
it('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);
}));
});