Add tests for event bus.

This commit is contained in:
Braydon Fuller 2015-07-29 17:22:34 -04:00
parent 097fd6e3ba
commit 725120a24e
6 changed files with 314 additions and 72 deletions

View File

@ -11,30 +11,30 @@ function Bus(params) {
util.inherits(Bus, events.EventEmitter);
Bus.prototype.subscribe = function(name) {
for (var i = 0; i < this.db.modules; i++) {
var module = this.db.modules[i];
var events = module.getEvents();
for (var j = 0; i < events.length; j++) {
var eventName = events[0];
var subscribeHandler = events[2];
var params = arguments.slice(1);
if (name === eventName) {
subscribeHandler.apply(events[1], params);
for (var i = 0; i < this.db.modules.length; i++) {
var mod = this.db.modules[i];
var events = mod.getPublishEvents();
for (var j = 0; j < events.length; j++) {
var event = events[j];
var params = Array.prototype.slice.call(arguments).slice(1);
params.unshift(this);
if (name === event.name) {
event.subscribe.apply(event.scope, params);
}
}
}
};
Bus.prototype.unsubscribe = function(name) {
for (var i = 0; i < this.db.modules; i++) {
var module = this.db.modules[i];
var events = module.getEvents();
for (var j = 0; i < events.length; j++) {
var eventName = events[0];
var unsubscribeHandler = events[3];
var params = arguments.slice(1);
if (name === eventName) {
unsubscribeHandler.apply(events[1], params);
for (var i = 0; i < this.db.modules.length; i++) {
var mod = this.db.modules[i];
var events = mod.getPublishEvents();
for (var j = 0; j < events.length; j++) {
var event = events[j];
var params = Array.prototype.slice.call(arguments).slice(1);
params.unshift(this);
if (name === event.name) {
event.unsubscribe.apply(event.scope, params);
}
}
}

View File

@ -15,6 +15,18 @@ Module.prototype.blockHandler = function(block, add, callback) {
setImmediate(callback);
};
/**
* the bus events available for subscription
* @return {Array} an array of event info
*/
Module.prototype.getPublishEvents = function() {
// Example:
// return [
// ['eventname', this, this.subscribeEvent, this.unsubscribeEvent],
// ];
return [];
};
/**
* the API methods to expose
* @return {Array} return array of methods
@ -33,4 +45,4 @@ Module.prototype.getAPIMethods = function() {
//
// };
module.exports = Module;
module.exports = Module;

View File

@ -7,14 +7,17 @@ var chainlib = require('chainlib');
var log = chainlib.log;
var errors = chainlib.errors;
var bitcore = require('bitcore');
var $ = bitcore.util.preconditions;
var EventEmitter = require('events').EventEmitter;
var PublicKey = bitcore.PublicKey;
var Address = bitcore.Address;
var AddressModule = function(options) {
BaseModule.call(this, options);
this.txSubscriptions = {};
this.balanceSubscriptions = {};
this.subscriptions = {};
this.subscriptions.transaction = {};
this.subscriptions.balance = {};
};
inherits(AddressModule, BaseModule);
@ -32,10 +35,20 @@ AddressModule.prototype.getAPIMethods = function() {
];
};
AddressModule.prototype.getEvents = function() {
AddressModule.prototype.getPublishEvents = function() {
return [
['transaction', this, this.subscribeTransaction, this.unsubscribeTransaction],
['balance', this, this.subscribeBalance, this.unsubscribeBalance]
{
name: 'transaction',
scope: this,
subscribe: this.subscribe.bind(this, 'transaction'),
unsubscribe: this.unsubscribe.bind(this, 'transaction')
},
{
name: 'balance',
scope: this,
subscribe: this.subscribe.bind(this, 'balance'),
unsubscribe: this.unsubscribe.bind(this, 'balance')
}
];
};
@ -93,25 +106,10 @@ AddressModule.prototype.blockHandler = function(block, addOutput, callback) {
value: [output.satoshis, script, height].join(':')
});
if(self.txSubscriptions[address]) {
var subscriptions = self.txSubscriptions[address];
for(var i = 0; i < subscriptions.length; i++) {
subscriptions[i].emit('transaction', tx);
}
}
// publish events to any subscribers
this.transactionEventHandler(block, address, tx);
this.balanceEventHandler(block, address);
if(self.balanceSubscriptions[address]) {
var emitters = self.balanceSubsriptions[address];
self.getBalance(address, true, function(err, balance) {
if(err) {
return self.emit(err);
}
for(var i = 0; i < subscriptions.length; i++) {
emitters[i].emit('balance', address, balance, block);
}
});
}
}
if(tx.isCoinbase()) {
@ -125,42 +123,49 @@ AddressModule.prototype.blockHandler = function(block, addOutput, callback) {
});
};
AddressModule.prototype.subscribeTransaction = function(emitter, addresses) {
for(var i = 0; i < addresses.length; i++) {
if(!this.txSubscriptions[addresses[i]]) {
this.txSubscriptions[addresses[i]] = [];
AddressModule.prototype.transactionEventHandler = function(block, address, tx) {
if(this.subscriptions.transaction[address]) {
var emitters = this.subscriptions.transaction[address];
for(var k = 0; k < emitters.length; k++) {
emitters[k].emit('transaction', address, tx, block);
}
this.txSubscriptions[addresses[i]].push(emitter);
}
};
AddressModule.prototype.subscribeBalance = function(emitter, addresses) {
for(var i = 0; i < addresses.length; i++) {
if(!this.balanceSubscriptions[addresses[i]]) {
this.balanceSubscriptions[addresses[i]] = [];
}
this.balanceSubscriptions[addresses[i]].push(emitter);
}
};
AddressModule.prototype.unsubscribeTransaction = function(emitter, addresses) {
for(var i = 0; i < addresses.length; i++) {
if(this.txSubscriptions[addresses[i]]) {
var emitters = self.txSubscriptions[addresses[i]];
var index = emitters.indexOf(emitter);
if(index > -1) {
emitters.splice(index, 1);
AddressModule.prototype.balanceEventHandler = function(block, address) {
if(this.subscriptions.balance[address]) {
var emitters = this.subscriptions.balance[address];
this.getBalance(address, true, function(err, balance) {
if(err) {
return this.emit(err);
}
}
for(var i = 0; i < emitters.length; i++) {
emitters[i].emit('balance', address, balance, block);
}
});
}
};
AddressModule.prototype.unsubscribeBalance = function(emitter, addresses) {
AddressModule.prototype.subscribe = function(name, emitter, addresses) {
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
$.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses');
for(var i = 0; i < addresses.length; i++) {
if(this.balanceSubscriptions[addresses[i]]) {
var emitters = self.balanceSubscriptions[addresses[i]];
if(!this.subscriptions[name][addresses[i]]) {
this.subscriptions[name][addresses[i]] = [];
}
this.subscriptions[name][addresses[i]].push(emitter);
}
};
AddressModule.prototype.unsubscribe = function(name, emitter, addresses) {
$.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter');
$.checkArgument(Array.isArray(addresses), 'Second argument is expected to be an Array of addresses');
for(var i = 0; i < addresses.length; i++) {
if(this.subscriptions[name][addresses[i]]) {
var emitters = this.subscriptions[name][addresses[i]];
var index = emitters.indexOf(emitter);
if(index > -1) {
emitters.splice(index, 1);
@ -276,4 +281,4 @@ AddressModule.prototype.isSpent = function(output, queryMempool, callback) {
});
};
module.exports = AddressModule;
module.exports = AddressModule;

61
test/bus.unit.js Normal file
View File

@ -0,0 +1,61 @@
'use strict';
var should = require('chai').should();
var sinon = require('sinon');
var Bus = require('../lib/bus');
describe('Bus', function() {
describe('#subscribe', function() {
it('will call modules subscribe function with the correct arguments', function() {
var subscribe = sinon.spy();
var db = {
modules: [
{
getPublishEvents: sinon.stub().returns([
{
name: 'test',
scope: this,
subscribe: subscribe,
}
])
}
]
};
var bus = new Bus({db: db});
bus.subscribe('test', 'a', 'b', 'c');
subscribe.callCount.should.equal(1);
subscribe.args[0][0].should.equal(bus);
subscribe.args[0][1].should.equal('a');
subscribe.args[0][2].should.equal('b');
subscribe.args[0][3].should.equal('c');
});
});
describe('#unsubscribe', function() {
it('will call modules unsubscribe function with the correct arguments', function() {
var unsubscribe = sinon.spy();
var db = {
modules: [
{
getPublishEvents: sinon.stub().returns([
{
name: 'test',
scope: this,
unsubscribe: unsubscribe
}
])
}
]
};
var bus = new Bus({db: db});
bus.unsubscribe('test', 'a', 'b', 'c');
unsubscribe.callCount.should.equal(1);
unsubscribe.args[0][0].should.equal(bus);
unsubscribe.args[0][1].should.equal('a');
unsubscribe.args[0][2].should.equal('b');
unsubscribe.args[0][3].should.equal('c');
});
});
});

View File

@ -2,8 +2,6 @@
var should = require('chai').should();
var sinon = require('sinon');
var chainlib = require('chainlib');
var levelup = chainlib.deps.levelup;
var bitcoindjs = require('../../');
var AddressModule = bitcoindjs.modules.AddressModule;
var blockData = require('../data/livenet-345003.json');
@ -21,6 +19,40 @@ describe('AddressModule', function() {
});
});
describe('#getPublishEvents', function() {
it('will return an array of publish event objects', function() {
var am = new AddressModule({});
am.subscribe = sinon.spy();
am.unsubscribe = sinon.spy();
var events = am.getPublishEvents();
var callCount = 0;
function testName(event, name) {
event.name.should.equal(name);
event.scope.should.equal(am);
var emitter = new EventEmitter();
var addresses = [];
event.subscribe(emitter, addresses);
am.subscribe.callCount.should.equal(callCount + 1);
am.subscribe.args[callCount][0].should.equal(name);
am.subscribe.args[callCount][1].should.equal(emitter);
am.subscribe.args[callCount][2].should.equal(addresses);
am.subscribe.thisValues[callCount].should.equal(am);
event.unsubscribe(emitter, addresses);
am.unsubscribe.callCount.should.equal(callCount + 1);
am.unsubscribe.args[callCount][0].should.equal(name);
am.unsubscribe.args[callCount][1].should.equal(emitter);
am.unsubscribe.args[callCount][2].should.equal(addresses);
am.unsubscribe.thisValues[callCount].should.equal(am);
callCount++;
}
events.forEach(function(event) {
testName(event, event.name);
});
});
});
describe('#blockHandler', function() {
var block = bitcore.Block.fromString(blockData);
var db = {
@ -124,6 +156,129 @@ describe('AddressModule', function() {
done();
});
});
it('will call event handlers', function() {
var block = bitcore.Block.fromString(blockData);
var db = {
getTransactionsFromBlock: function() {
return block.transactions.slice(0, 8);
}
};
var am = new AddressModule({db: db, network: 'livenet'});
am.transactionEventHandler = sinon.spy();
am.balanceEventHandler = sinon.spy();
am.blockHandler(
{
__height: 345003,
timestamp: new Date(1424836934000)
},
true,
function(err) {
if (err) {
throw err;
}
am.transactionEventHandler.callCount.should.equal(11);
am.balanceEventHandler.callCount.should.equal(11);
}
);
});
});
describe('#transactionEventHandler', function() {
it('will emit a transaction if there is a subscriber', function(done) {
var am = new AddressModule({});
var emitter = new EventEmitter();
am.subscriptions.transaction = {
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter]
};
var block = {};
var tx = {};
emitter.on('transaction', function(address, t, b) {
address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
t.should.equal(tx);
b.should.equal(block);
done();
});
am.transactionEventHandler(block, '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N', tx);
});
});
describe('#balanceEventHandler', function() {
it('will emit a balance if there is a subscriber', function(done) {
var am = new AddressModule({});
var emitter = new EventEmitter();
am.subscriptions.balance = {
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter]
};
var block = {};
var balance = 1000;
am.getBalance = sinon.stub().callsArgWith(2, null, balance);
emitter.on('balance', function(address, bal, b) {
address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
bal.should.equal(balance);
b.should.equal(block);
done();
});
am.balanceEventHandler(block, '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
});
});
describe('#subscribe', function() {
it('will add emitters to the subscribers array (transaction)', function() {
var am = new AddressModule({});
var emitter = new EventEmitter();
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
var name = 'transaction';
am.subscribe(name, emitter, [address]);
am.subscriptions.transaction[address].should.deep.equal([emitter]);
var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
am.subscribe(name, emitter, [address2]);
am.subscriptions.transaction[address2].should.deep.equal([emitter]);
var emitter2 = new EventEmitter();
am.subscribe(name, emitter2, [address]);
am.subscriptions.transaction[address].should.deep.equal([emitter, emitter2]);
});
it('will add an emitter to the subscribers array (balance)', function() {
var am = new AddressModule({});
var emitter = new EventEmitter();
var name = 'balance';
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
am.subscribe(name, emitter, [address]);
am.subscriptions.balance[address].should.deep.equal([emitter]);
var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
am.subscribe(name, emitter, [address2]);
am.subscriptions.balance[address2].should.deep.equal([emitter]);
var emitter2 = new EventEmitter();
am.subscribe(name, emitter2, [address]);
am.subscriptions.balance[address].should.deep.equal([emitter, emitter2]);
});
});
describe('#unsubscribe', function() {
it('will remove emitter from subscribers array (transaction)', function() {
var am = new AddressModule({});
var emitter = new EventEmitter();
var emitter2 = new EventEmitter();
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
am.subscriptions.transaction[address] = [emitter, emitter2];
var name = 'transaction';
am.unsubscribe(name, emitter, [address]);
am.subscriptions.transaction[address].should.deep.equal([emitter2]);
});
it('will remove emitter from subscribers array (balance)', function() {
var am = new AddressModule({});
var emitter = new EventEmitter();
var emitter2 = new EventEmitter();
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
var name = 'balance';
am.subscriptions.balance[address] = [emitter, emitter2];
am.unsubscribe(name, emitter, [address]);
am.subscriptions.balance[address].should.deep.equal([emitter2]);
});
});
describe('#getBalance', function() {

View File

@ -31,6 +31,15 @@ var Node = proxyquire('../lib/node', {
chainlib.Node = OriginalNode;
describe('Bitcoind Node', function() {
describe('#openBus', function() {
it('will create a new bus', function() {
var node = new Node({});
var db = {};
node.db = db;
var bus = node.openBus();
bus.db.should.equal(db);
});
});
describe('#_loadConfiguration', function() {
it('should call the necessary methods', function() {
var node = new Node({});