wikimedia-kad-fork
Version:
implementation of the kademlia dht for node
247 lines (211 loc) • 7.67 kB
JavaScript
;
var expect = require('chai').expect;
var sinon = require('sinon');
var EventEmitter = require('events').EventEmitter;
var proxyquire = require('proxyquire');
var RPC = require('../../lib/transports/tcp');
var AddressPortContact = require('../../lib/contacts/address-port-contact');
var Message = require('../../lib/message');
describe('Transports/TCP', function() {
describe('@constructor', function() {
it('should create an instance with the `new` keyword', function() {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc = new RPC(contact);
expect(rpc).to.be.instanceOf(RPC);
});
it('should create an instance without the `new` keyword', function() {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc = RPC(contact);
expect(rpc).to.be.instanceOf(RPC);
});
it('should bind to the given port (or random port)', function(done) {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc = RPC(contact);
rpc.on('ready', function() {
expect(rpc._socket.address().address).to.equal('0.0.0.0');
expect(typeof rpc._socket.address().port).to.equal('number');
done();
});
});
});
describe('#_createContact', function() {
it('should create an AddressPortContact', function() {
var rpc = new RPC(AddressPortContact({ address: '0.0.0.0', port: 1 }));
var contact = rpc._createContact({ address: '0.0.0.0', port: 0 });
expect(contact).to.be.instanceOf(AddressPortContact);
});
});
describe('#send', function() {
var contact1 = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var contact2 = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc1;
var rpc2;
before(function(done) {
var count = 0;
function inc() {
count++;
ready();
}
function ready() {
if (count === 2) {
done();
}
}
rpc1 = new RPC(contact1);
rpc2 = new RPC(contact2);
rpc1.on('ready', inc);
rpc2.on('ready', inc);
});
after(function() {
rpc1.close();
rpc2.close();
});
it('should throw with invalid message', function() {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 1234 });
expect(function() {
rpc1.send(contact, {});
}).to.throw(Error, 'Invalid message supplied');
});
it('should send a message and create a response handler', function() {
var addr1 = rpc1._socket.address();
var addr2 = rpc2._socket.address();
var contactRpc1 = new AddressPortContact(addr1);
var contactRpc2 = new AddressPortContact(addr2);
var msg = new Message({
method: 'PING',
params: { contact: contactRpc1 },
});
var handler = sinon.stub();
rpc1.send(contactRpc2, msg, handler);
var calls = Object.keys(rpc1._pendingCalls);
expect(calls).to.have.lengthOf(1);
expect(rpc1._pendingCalls[calls[0]].callback).to.equal(handler);
});
it('should send a message and forget it', function() {
var addr1 = rpc1._socket.address();
var addr2 = rpc2._socket.address();
var contactRpc1 = new AddressPortContact(addr1);
var contactRpc2 = new AddressPortContact(addr2);
var msg = new Message({
method: 'PING',
params: { contact: contactRpc2 },
});
rpc2.send(contactRpc1, msg);
var calls = Object.keys(rpc2._pendingCalls);
expect(calls).to.have.lengthOf(0);
});
});
describe('#close', function() {
it('should close the underlying socket', function(done) {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc = new RPC(contact);
rpc.on('ready', function() {
rpc._socket.on('close', done);
rpc.close();
});
});
});
describe('#receive', function() {
var contact1 = new AddressPortContact({ address: '0.0.0.0', port: 1234 });
var contact2 = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var validMsg1 = Message({
method: 'PING',
params: { contact: contact1 },
}).serialize();
validMsg1.id = 10;
var validMsg2 = Message({
id: 10,
result: { contact: contact1 },
}).serialize();
var invalidMsg = Buffer(JSON.stringify({ type: 'WRONG', params: {} }));
var invalidJSON = Buffer('i am a bad message');
var rpc = new RPC(contact2);
it('should drop the message if invalid JSON', function(done) {
rpc.once('MESSAGE_DROP', function() {
done();
});
rpc.receive(invalidJSON, {});
});
it('should drop the message if invalid message type', function(done) {
rpc.once('MESSAGE_DROP', function() {
done();
});
rpc.receive(invalidMsg, {});
});
it('should emit the message type if not a reply', function(done) {
rpc.once('PING', function(data) {
expect(typeof data).to.equal('object');
done();
});
rpc.receive(validMsg1, { address: '127.0.0.1', port: 1234 });
});
it('should call the message callback if a reply', function(done) {
rpc._pendingCalls[10] = {
callback: function(err, msg) {
expect(err).to.equal(null);
expect(msg.id).to.equal(10);
done();
}
};
rpc.receive(validMsg2, { address: '127.0.0.1', port: 1234 });
});
});
describe('#_expireCalls', function() {
it('should call expired handler with error and remove it', function() {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc = new RPC(contact);
var freshHandler = sinon.stub();
var staleHandler = sinon.spy();
rpc._pendingCalls.rpc_id_1 = {
timestamp: new Date('1970-1-1'),
callback: staleHandler
};
rpc._pendingCalls.rpc_id_2 = {
timestamp: new Date('3070-1-1'),
callback: freshHandler
};
rpc._expireCalls();
expect(Object.keys(rpc._pendingCalls)).to.have.lengthOf(1);
expect(freshHandler.callCount).to.equal(0);
expect(staleHandler.callCount).to.equal(1);
expect(staleHandler.getCall(0).args[0]).to.be.instanceOf(Error);
});
});
describe('#_send', function() {
it('should log an error if once is encountered', function() {
var emitter = new EventEmitter();
emitter.write = sinon.stub();
emitter.pipe = sinon.stub().returns(emitter);
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var TCP = proxyquire('../../lib/transports/tcp', {
net: {
createConnection: function() {
return emitter;
}
}
});
var rpc = new TCP(contact);
var _log = sinon.stub(rpc._log, 'error');
rpc._send(new Buffer(JSON.stringify({id:'id'})), {});
setImmediate(function() {
emitter.emit('error', new Error('failed'));
expect(_log.called).to.equal(true);
});
});
});
describe('#_handleConnection', function() {
it('should close the socket on a parser error', function(done) {
var contact = new AddressPortContact({ address: '0.0.0.0', port: 0 });
var rpc = new RPC(contact);
var socket = new EventEmitter();
socket.end = sinon.stub();
socket.pipe = sinon.stub().returns(socket);
rpc._handleConnection(socket);
setImmediate(function() {
socket.emit('data', 'not a json string');
expect(socket.end.called).to.equal(true);
done();
});
});
});
});