100% test coverage for address service
This commit is contained in:
parent
599d1166cf
commit
51c7af57d0
@ -21,7 +21,6 @@ AddressService.prototype.getSummary = function(address, confirmations) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var tip, allOutputs, spent;
|
var tip, allOutputs, spent;
|
||||||
confirmations = confirmations || 6;
|
|
||||||
|
|
||||||
return Promise.try(function() {
|
return Promise.try(function() {
|
||||||
|
|
||||||
@ -64,10 +63,10 @@ AddressService.prototype.getAllOutputs = function(address) {
|
|||||||
lte: TransactionService.Index.getOutputsForAddress(address, LASTTXHASH, MAXOUTPUT)
|
lte: TransactionService.Index.getOutputsForAddress(address, LASTTXHASH, MAXOUTPUT)
|
||||||
}).on('data', function(element) {
|
}).on('data', function(element) {
|
||||||
results.push(AddressService.processOutput(element));
|
results.push(AddressService.processOutput(element));
|
||||||
}).on('close', function() {
|
}).on('error', function() {
|
||||||
reject();
|
return reject();
|
||||||
}).on('end', function() {
|
}).on('end', function() {
|
||||||
resolve(results);
|
return resolve(results);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -94,6 +93,7 @@ AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs
|
|||||||
|
|
||||||
var result = {};
|
var result = {};
|
||||||
var transactionsAppended = {};
|
var transactionsAppended = {};
|
||||||
|
confirmations = confirmations || 6;
|
||||||
|
|
||||||
result.address = address.toString();
|
result.address = address.toString();
|
||||||
result.transactions = [];
|
result.transactions = [];
|
||||||
@ -116,10 +116,14 @@ AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs
|
|||||||
outputValues[output.txId + '-' + output.outputIndex] = value;
|
outputValues[output.txId + '-' + output.outputIndex] = value;
|
||||||
result.unconfirmed.balance += value;
|
result.unconfirmed.balance += value;
|
||||||
result.unconfirmed.received += value;
|
result.unconfirmed.received += value;
|
||||||
if (tip.height - output.heightConfirmed - 1 >= confirmations) {
|
if (tip.height - output.heightConfirmed + 1 >= confirmations) {
|
||||||
result.confirmed.balance += value;
|
result.confirmed.balance += value;
|
||||||
result.confirmed.received += value;
|
result.confirmed.received += value;
|
||||||
}
|
}
|
||||||
|
if (!transactionsAppended[output.txId]) {
|
||||||
|
transactionsAppended[output.txId] = true;
|
||||||
|
result.transactions.push(output.txId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
_.each(spent, function(output) {
|
_.each(spent, function(output) {
|
||||||
var value = outputValues[output.spendInput.prevTxId + '-' + output.spendInput.outputIndex];
|
var value = outputValues[output.spendInput.prevTxId + '-' + output.spendInput.outputIndex];
|
||||||
@ -128,13 +132,9 @@ AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs
|
|||||||
transactionsAppended[output.spentTx] = true;
|
transactionsAppended[output.spentTx] = true;
|
||||||
result.transactions.push(output.spentTx);
|
result.transactions.push(output.spentTx);
|
||||||
}
|
}
|
||||||
if (!transactionsAppended[output.spendInput.prevTxId]) {
|
|
||||||
transactionsAppended[output.spendInput.prevTxId] = true;
|
|
||||||
result.transactions.push(output.spendInput.prevTxId);
|
|
||||||
}
|
|
||||||
result.unconfirmed.balance -= value;
|
result.unconfirmed.balance -= value;
|
||||||
result.unconfirmed.sent += value;
|
result.unconfirmed.sent += value;
|
||||||
if (tip.height - output.heightSpent - 1 >= confirmations) {
|
if (tip.height - output.heightSpent + 1 >= confirmations) {
|
||||||
result.confirmed.balance -= value;
|
result.confirmed.balance -= value;
|
||||||
result.confirmed.sent += value;
|
result.confirmed.sent += value;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,27 +2,263 @@
|
|||||||
|
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var should = require('chai').should();
|
var should = require('chai').should();
|
||||||
|
var events = require('events');
|
||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
|
|
||||||
var TransactionService = require('../../lib/services/transaction');
|
var AddressService = require('../../lib/services/address');
|
||||||
|
|
||||||
describe.only('AddressService', function() {
|
describe('AddressService', function() {
|
||||||
|
|
||||||
it('initializes correctly', function() {
|
var database, rpc, blockService, transactionService, service;
|
||||||
var database = 'mock';
|
|
||||||
var rpc = 'mock';
|
function initialize() {
|
||||||
var blockService = 'mock';
|
database = {};
|
||||||
var transactionService = 'mock';
|
rpc = {};
|
||||||
var service = new TransactionService({
|
blockService = {};
|
||||||
|
transactionService = {};
|
||||||
|
service = new AddressService({
|
||||||
database: database,
|
database: database,
|
||||||
transactionService: transactionService,
|
transactionService: transactionService,
|
||||||
blockService: blockService,
|
blockService: blockService,
|
||||||
rpc: rpc
|
rpc: rpc
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('initializes correctly', function() {
|
||||||
|
initialize();
|
||||||
should.exist(service);
|
should.exist(service);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var thenCaller = {
|
||||||
|
then: function(arg) {
|
||||||
|
return arg();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('getSummary', function() {
|
||||||
|
|
||||||
|
beforeEach(initialize);
|
||||||
|
|
||||||
|
it('calls internal functions as expected', function(done) {
|
||||||
|
service.blockService = { getLatest: sinon.mock() };
|
||||||
|
service.getAllOutputs = sinon.mock();
|
||||||
|
service.getSpent = sinon.mock();
|
||||||
|
service.buildAddressSummary = sinon.mock();
|
||||||
|
|
||||||
|
service.blockService.getLatest.onFirstCall().returns(thenCaller);
|
||||||
|
service.getAllOutputs.onFirstCall().returns(thenCaller);
|
||||||
|
service.getSpent.onFirstCall().returns(thenCaller);
|
||||||
|
service.buildAddressSummary.onFirstCall().returns(thenCaller);
|
||||||
|
|
||||||
|
var address = 'address';
|
||||||
|
var confirmations = 100;
|
||||||
|
var promise = service.getSummary(address, confirmations);
|
||||||
|
promise.then(function() {
|
||||||
|
|
||||||
|
service.blockService.getLatest.calledOnce.should.equal(true);
|
||||||
|
service.getAllOutputs.calledOnce.should.equal(true);
|
||||||
|
service.getSpent.calledOnce.should.equal(true);
|
||||||
|
service.buildAddressSummary.calledOnce.should.equal(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('processOutput works as expected', function() {
|
||||||
|
AddressService.processOutput({
|
||||||
|
key: 'txas-A-B-C',
|
||||||
|
value: '{"a": "b"}'
|
||||||
|
}).should.deep.equal({
|
||||||
|
address: 'A',
|
||||||
|
txId: 'B',
|
||||||
|
outputIndex: 'C',
|
||||||
|
a: 'b'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAllOutputs rejects promise on error', function(done) {
|
||||||
|
var dataCall = new events.EventEmitter();
|
||||||
|
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||||
|
service.database.createReadStream = sinon.mock();
|
||||||
|
service.database.createReadStream.onFirstCall().returns(dataCall);
|
||||||
|
service.getAllOutputs(address).catch(done);
|
||||||
|
dataCall.emit('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSpent rejects promise on error', function(done) {
|
||||||
|
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||||
|
var dataCall = new events.EventEmitter();
|
||||||
|
service.database.createReadStream = sinon.mock();
|
||||||
|
service.database.createReadStream.onFirstCall().returns(dataCall);
|
||||||
|
service.getSpent(address).catch(done);
|
||||||
|
dataCall.emit('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAllOutputs calls the expected functions', function(done) {
|
||||||
|
service.database.createReadStream = sinon.mock();
|
||||||
|
var dataCall = new events.EventEmitter();
|
||||||
|
service.database.createReadStream.onFirstCall().returns(dataCall);
|
||||||
|
|
||||||
|
AddressService.processOutput = sinon.stub(AddressService, 'processOutput');
|
||||||
|
AddressService.processOutput.onFirstCall().returns('processed');
|
||||||
|
|
||||||
|
var element = {key: 'key', value: 'value'};
|
||||||
|
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||||
|
service.getAllOutputs(address).then(function(arg) {
|
||||||
|
service.database.createReadStream.firstCall.args[0].should.deep.equal(
|
||||||
|
{
|
||||||
|
gte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||||
|
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||||
|
lte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||||
|
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
AddressService.processOutput.firstCall.args[0].should.equal(element);
|
||||||
|
AddressService.processOutput.reset();
|
||||||
|
arg[0].should.equal('processed');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
dataCall.emit('data', element);
|
||||||
|
dataCall.emit('end');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSpent calls the expected functions', function(done) {
|
||||||
|
service.database.createReadStream = sinon.mock();
|
||||||
|
var dataCall = new events.EventEmitter();
|
||||||
|
service.database.createReadStream.onFirstCall().returns(dataCall);
|
||||||
|
|
||||||
|
var element = {key: 'key', value: JSON.stringify({a: 'b'})};
|
||||||
|
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
|
||||||
|
service.getSpent(address).then(function(arg) {
|
||||||
|
service.database.createReadStream.firstCall.args[0].should.deep.equal(
|
||||||
|
{
|
||||||
|
gte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||||
|
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
|
||||||
|
lte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
|
||||||
|
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
arg[0].should.deep.equal({a: 'b'});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
dataCall.emit('data', element);
|
||||||
|
dataCall.emit('end');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buildAddressSummary', function() {
|
||||||
|
|
||||||
|
beforeEach(initialize);
|
||||||
|
var address = new bitcore.Address('12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S');
|
||||||
|
var tip = {
|
||||||
|
height: 10
|
||||||
|
};
|
||||||
|
var allOutputs = [
|
||||||
|
{
|
||||||
|
satoshis: 10,
|
||||||
|
txId: 'A',
|
||||||
|
outputIndex: 1,
|
||||||
|
heightConfirmed: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
it('calculates balance correctly for confirmed balance', function() {
|
||||||
|
var allOutputs = [ { satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 } ];
|
||||||
|
var spendOutputs = [];
|
||||||
|
|
||||||
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
|
address: address.toString(),
|
||||||
|
transactions: ['A'],
|
||||||
|
confirmed: { balance: 10, sent: 0, received: 10 },
|
||||||
|
unconfirmed: { balance: 10, sent: 0, received: 10 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates balance correctly for unconfirmed balance', function() {
|
||||||
|
var allOutputs = [
|
||||||
|
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
|
||||||
|
];
|
||||||
|
var spendOutputs = [ ];
|
||||||
|
|
||||||
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
|
address: address.toString(),
|
||||||
|
transactions: ['B'],
|
||||||
|
confirmed: { balance: 0, sent: 0, received: 0 },
|
||||||
|
unconfirmed: { balance: 20, sent: 0, received: 20 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with multiple transactions', function() {
|
||||||
|
var allOutputs = [
|
||||||
|
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
||||||
|
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
|
||||||
|
];
|
||||||
|
var spendOutputs = [
|
||||||
|
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'A', heightSpent: 10 }
|
||||||
|
];
|
||||||
|
|
||||||
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
|
address: address.toString(),
|
||||||
|
transactions: ['A', 'B'],
|
||||||
|
confirmed: { balance: 10, sent: 0, received: 10 },
|
||||||
|
unconfirmed: { balance: 20, sent: 10, received: 30 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with a medium amount of transactions', function() {
|
||||||
|
var allOutputs = [
|
||||||
|
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
||||||
|
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 5 },
|
||||||
|
{ satoshis: 30, txId: 'C', outputIndex: 1, heightConfirmed: 10 }
|
||||||
|
];
|
||||||
|
var spendOutputs = [
|
||||||
|
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'D', heightSpent: 10 }
|
||||||
|
];
|
||||||
|
|
||||||
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
|
address: address.toString(),
|
||||||
|
transactions: ['A', 'B', 'C', 'D'],
|
||||||
|
confirmed: { balance: 30, sent: 0, received: 30 },
|
||||||
|
unconfirmed: { balance: 50, sent: 10, received: 60 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with a transaction that includes twice the same address', function() {
|
||||||
|
var allOutputs = [
|
||||||
|
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
|
||||||
|
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
|
||||||
|
];
|
||||||
|
var spendOutputs = [];
|
||||||
|
|
||||||
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
|
address: address.toString(),
|
||||||
|
transactions: ['A'],
|
||||||
|
confirmed: { balance: 20, sent: 0, received: 20 },
|
||||||
|
unconfirmed: { balance: 20, sent: 0, received: 20 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('confirmed spent transactions change the balance', function() {
|
||||||
|
var allOutputs = [
|
||||||
|
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
|
||||||
|
];
|
||||||
|
var spendOutputs = [
|
||||||
|
{ spendInput: { prevTxId: 'A', outputIndex: 0 }, spentTx: 'D', heightSpent: 2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
|
||||||
|
address: address.toString(),
|
||||||
|
transactions: ['A', 'D'],
|
||||||
|
confirmed: { balance: 0, sent: 10, received: 10 },
|
||||||
|
unconfirmed: { balance: 0, sent: 10, received: 10 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user