diff --git a/lib/server.js b/lib/server.js index c6a48e6..9f61da4 100644 --- a/lib/server.js +++ b/lib/server.js @@ -942,7 +942,7 @@ WalletService.prototype.getPendingTxs = function(opts, cb) { * @param {Object} opts.minTs (defaults to 0) * @param {Object} opts.maxTs (defaults to now) * @param {Object} opts.limit - * @returns {TxProposal[]} Transaction proposals, first newer + * @returns {TxProposal[]} Transaction proposals, newer first */ WalletService.prototype.getTxs = function(opts, cb) { var self = this; diff --git a/lib/storage_mongo.js b/lib/storage_mongo.js index cb0e9bd..19fe047 100644 --- a/lib/storage_mongo.js +++ b/lib/storage_mongo.js @@ -12,6 +12,13 @@ var mongodb = require('mongodb'); var Model = require('./model'); +var collections = { + WALLETS: 'wallets', + TXS: 'txs', + ADDRESSES: 'addresses', + NOTIFICATIONS: 'notifications', +}; + var Storage = function(opts) { opts = opts || {}; this.db = opts.db; @@ -30,7 +37,7 @@ var Storage = function(opts) { }; Storage.prototype.fetchWallet = function(id, cb) { - this.db.collection('wallets').findOne({ + this.db.collection(collections.WALLETS).findOne({ id: id }, function(err, result) { if (err) return cb(err); @@ -40,7 +47,7 @@ Storage.prototype.fetchWallet = function(id, cb) { }; Storage.prototype.storeWallet = function(wallet, cb) { - this.db.collection('wallets').update({ + this.db.collection(collections.WALLETS).update({ id: wallet.id }, wallet, { w: 1, @@ -53,7 +60,7 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) { }; Storage.prototype.fetchCopayerLookup = function(copayerId, cb) { - this.db.collection('wallets').findOne({ + this.db.collection(collections.WALLETS).findOne({ 'copayers.id': copayerId }, { fields: { @@ -91,7 +98,7 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) { Storage.prototype.fetchTx = function(walletId, txProposalId, cb) { var self = this; - this.db.collection('txs').findOne({ + this.db.collection(collections.TXS).findOne({ id: txProposalId, walletId: walletId }, function(err, result) { @@ -105,11 +112,11 @@ Storage.prototype.fetchTx = function(walletId, txProposalId, cb) { Storage.prototype.fetchPendingTxs = function(walletId, cb) { var self = this; - this.db.collection('txs').find({ + this.db.collection(collections.TXS).find({ walletId: walletId, isPending: true }).sort({ - createdOn: 1 + createdOn: -1 }).toArray(function(err, result) { if (err) return cb(err); if (!result) return cb(); @@ -145,8 +152,8 @@ Storage.prototype.fetchTxs = function(walletId, opts, cb) { var mods = {}; if (_.isNumber(opts.limit)) mods.limit = opts.limit; - this.db.collection('txs').find(filter, mods).sort({ - createdOn: 1 + this.db.collection(collections.TXS).find(filter, mods).sort({ + createdOn: -1 }).toArray(function(err, result) { if (err) return cb(err); if (!result) return cb(); @@ -165,6 +172,7 @@ Storage.prototype.fetchTxs = function(walletId, opts, cb) { * @param opts.minTs * @param opts.maxTs * @param opts.limit + * @param opts.reverse */ Storage.prototype.fetchNotifications = function(walletId, opts, cb) { var self = this; @@ -183,8 +191,8 @@ Storage.prototype.fetchNotifications = function(walletId, opts, cb) { var mods = {}; if (_.isNumber(opts.limit)) mods.limit = opts.limit; - this.db.collection('notifications').find(filter, mods).sort({ - createdOn: 1 + this.db.collection(collections.NOTIFICATIONS).find(filter, mods).sort({ + id: opts.reverse ? -1 : 1, }).toArray(function(err, result) { if (err) return cb(err); if (!result) return cb(); @@ -198,7 +206,7 @@ Storage.prototype.fetchNotifications = function(walletId, opts, cb) { // TODO: remove walletId from signature Storage.prototype.storeNotification = function(walletId, notification, cb) { - this.db.collection('notifications').insert(notification, { + this.db.collection(collections.NOTIFICATIONS).insert(notification, { w: 1 }, cb); }; @@ -206,7 +214,7 @@ Storage.prototype.storeNotification = function(walletId, notification, cb) { // TODO: remove walletId from signature Storage.prototype.storeTx = function(walletId, txp, cb) { txp.isPending = txp.isPending(); // Persist attribute to use when querying - this.db.collection('txs').update({ + this.db.collection(collections.TXS).update({ id: txp.id, walletId: walletId }, txp, { @@ -216,7 +224,7 @@ Storage.prototype.storeTx = function(walletId, txp, cb) { }; Storage.prototype.removeTx = function(walletId, txProposalId, cb) { - this.db.collection('txs').findAndRemove({ + this.db.collection(collections.TXS).findAndRemove({ id: txProposalId, walletId: walletId }, { @@ -230,22 +238,22 @@ Storage.prototype.removeWallet = function(walletId, cb) { async.parallel([ function(next) { - this.db.collections('wallets').findAndRemove({ + self.db.collection(collections.WALLETS).findAndRemove({ id: walletId }, next); }, function(next) { - this.db.collections('addresses').findAndRemove({ + self.db.collection(collections.ADDRESSES).remove({ walletId: walletId }, next); }, function(next) { - this.db.collections('txs').findAndRemove({ + self.db.collection(collections.TXS).remove({ walletId: walletId }, next); }, function(next) { - this.db.collections('notifications').findAndRemove({ + self.db.collection(collections.NOTIFICATIONS).remove({ walletId: walletId }, next); }, @@ -256,7 +264,7 @@ Storage.prototype.removeWallet = function(walletId, cb) { Storage.prototype.fetchAddresses = function(walletId, cb) { var self = this; - this.db.collection('addresses').find({ + this.db.collection(collections.ADDRESSES).find({ walletId: walletId, }).sort({ createdOn: 1 @@ -272,7 +280,9 @@ Storage.prototype.fetchAddresses = function(walletId, cb) { Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) { var self = this; - this.db.collection('addresses').insert([].concat(addresses), { + var addresses = [].concat(addresses); + if (addresses.length == 0) return cb(); + this.db.collection(collections.ADDRESSES).insert(addresses, { w: 1 }, function(err) { if (err) return cb(err); @@ -282,14 +292,16 @@ Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) { Storage.prototype._dump = function(cb, fn) { fn = fn || console.log; + cb = cb || function() {}; var self = this; this.db.collections(function(err, collections) { if (err) return cb(err); async.eachSeries(collections, function(col, next) { - fn('--------' + col); - col.find().toArray(function(err, item) { - fn(item); + col.find().toArray(function(err, items) { + fn('--------', col.s.name); + fn(items); + fn('------------------------------------------------------------------\n\n'); next(err); }); }, cb); diff --git a/test/integration/server.js b/test/integration/server.js index 9e3c728..1b05a35 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -9,13 +9,14 @@ var sinon = require('sinon'); var should = chai.should(); var levelup = require('levelup'); var memdown = require('memdown'); +var mongodb = require('mongodb'); var log = require('npmlog'); log.debug = log.verbose; var Utils = require('../../lib/utils'); var WalletUtils = require('bitcore-wallet-utils'); var Bitcore = WalletUtils.Bitcore; -var Storage = require('../../lib/storage'); +var Storage = require('../../lib/storage_mongo'); var BlockchainMonitor = require('../../lib/blockchainmonitor'); var Wallet = require('../../lib/model/wallet'); @@ -208,24 +209,56 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var db, storage, blockchainExplorer; - -describe('Wallet service', function() { - beforeEach(function() { - db = levelup(memdown, { - valueEncoding: 'json' +function openDb(cb) { + function dropDb(cb) { + db.dropDatabase(function(err) { + should.not.exist(err); + return cb(); }); - storage = new Storage({ - db: db + }; + if (db) { + return dropDb(cb); + } else { + var url = 'mongodb://localhost:27017/bws'; + mongodb.MongoClient.connect(url, function(err, _db) { + should.not.exist(err); + db = _db; + return dropDb(cb); }); - blockchainExplorer = sinon.stub(); + } +}; - WalletService.initialize({ - storage: storage, - blockchainExplorer: blockchainExplorer, +function closeDb(cb) { + if (db) { + db.close(true, function(err) { + should.not.exist(err); + db = null; + return cb(); }); - helpers.offset = 0; - }); + } else { + return cb(); + } +}; +describe('Wallet service', function() { + beforeEach(function(done) { + openDb(function() { + storage = new Storage({ + db: db + }); + blockchainExplorer = sinon.stub(); + + WalletService.initialize({ + storage: storage, + blockchainExplorer: blockchainExplorer, + }); + helpers.offset = 0; + done(); + }); + }); + after(function(done) { + closeDb(done); + }); describe('#getInstanceWithAuth', function() { beforeEach(function() {}); @@ -1172,6 +1205,7 @@ describe('Wallet service', function() { helpers.stubUtxos(server, wallet, _.range(1, 9), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { + should.not.exist(err); should.exist(tx); txid = tx.id; @@ -1814,9 +1848,7 @@ describe('Wallet service', function() { var server, wallet, clock; beforeEach(function(done) { - if (server) return done(); this.timeout(5000); - console.log('\tCreating TXS...'); clock = sinon.useFakeTimers(); helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; @@ -1824,7 +1856,7 @@ describe('Wallet service', function() { helpers.stubUtxos(server, wallet, _.range(10), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey_1H_0); async.eachSeries(_.range(10), function(i, next) { - clock.tick(10000); + clock.tick(10 * 1000); server.createTx(txOpts, function(err, tx) { next(); }); @@ -1883,17 +1915,18 @@ describe('Wallet service', function() { }); - it('should txs from times 50 to 70', function(done) { - server.getTxs({ - minTs: 50, - maxTs: 70, - }, function(err, txps) { - should.not.exist(err); - var times = _.pluck(txps, 'createdOn'); - times.should.deep.equal([70, 60, 50]); - done(); + it('should txs from times 50 to 70', + function(done) { + server.getTxs({ + minTs: 50, + maxTs: 70, + }, function(err, txps) { + should.not.exist(err); + var times = _.pluck(txps, 'createdOn'); + times.should.deep.equal([70, 60, 50]); + done(); + }); }); - }); }); describe('Notifications', function() { @@ -2073,62 +2106,110 @@ describe('Wallet service', function() { }); }); }); + it('should delete a wallet', function(done) { - var i = 0; - var count = function() { - return ++i; - }; - server.storage._dump(function() { - i.should.above(1); - server.removeWallet({}, function(err) { - i = 0; - server.storage._dump(function() { - server.storage._dump(); - i.should.equal(0); + server.removeWallet({}, function(err) { + should.not.exist(err); + server.getWallet({}, function(err, w) { + should.exist(err); + err.message.should.equal('Wallet not found'); + should.not.exist(w); + async.parallel([ + + function(next) { + server.storage.fetchAddresses(wallet.id, function(err, items) { + items.length.should.equal(0); + next(); + }); + }, + function(next) { + server.storage.fetchTxs(wallet.id, {}, function(err, items) { + items.length.should.equal(0); + next(); + }); + }, + function(next) { + server.storage.fetchNotifications(wallet.id, {}, function(err, items) { + items.length.should.equal(0); + next(); + }); + }, + ], function(err) { + should.not.exist(err); done(); - }, count); + }); }); - }, count); + }); }); // creates 2 wallet, and deletes only 1. it('should delete a wallet, and only that wallet', function(done) { - var i = 0; - var db = []; - var cat = function(data) { - db.push(data); - }; - server.storage._dump(function() { - var before = _.clone(db); - db.length.should.above(1); - - helpers.offset = 1; - helpers.createAndJoinWallet(2, 3, function(s, w) { - server = s; - wallet = w; - - helpers.stubUtxos(server, wallet, _.range(2), function() { - var txOpts = { - toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', - amount: helpers.toSatoshi(0.1), - }; - async.eachSeries(_.range(2), function(i, next) { - server.createTx(txOpts, function(err, tx) { - next(); - }); - }, function() { - server.removeWallet({}, function(err) { - db = []; - server.storage._dump(function() { - var after = _.clone(db); - after.should.deep.equal(before); - done(); - }, cat); - }); - }, cat); + var server2, wallet2; + async.series([ + + function(next) { + helpers.offset = 1; + helpers.createAndJoinWallet(1, 1, function(s, w) { + server2 = s; + wallet2 = w; + + helpers.stubUtxos(server2, wallet2, _.range(1, 3), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, 'some message', TestData.copayers[1].privKey_1H_0); + async.eachSeries(_.range(2), function(i, next) { + server2.createTx(txOpts, function(err, tx) { + should.not.exist(err); + next(err); + }); + }, next); + }); }); - }); - }, cat); + }, + function(next) { + server.removeWallet({}, next); + }, + function(next) { + server.getWallet({}, function(err, wallet) { + should.exist(err); + err.message.should.contain('not found'); + next(); + }); + }, + function(next) { + server2.getWallet({}, function(err, wallet) { + should.not.exist(err); + should.exist(wallet); + wallet.id.should.equal(wallet2.id); + next(); + }); + }, + function(next) { + server2.getMainAddresses({}, function(err, addresses) { + should.not.exist(err); + should.exist(addresses); + addresses.length.should.above(0); + next(); + }); + }, + function(next) { + server2.getTxs({}, function(err, txs) { + should.not.exist(err); + should.exist(txs); + txs.length.should.equal(2); + next(); + }); + }, + function(next) { + server2.getNotifications({}, function(err, notifications) { + should.not.exist(err); + should.exist(notifications); + notifications.length.should.above(0); + next(); + }); + }, + ], function(err) { + should.not.exist(err); + done(); + }); }); }); @@ -2967,29 +3048,32 @@ describe('Wallet service', function() { describe('Blockchain monitor', function() { var addressSubscriber; - beforeEach(function() { - db = levelup(memdown, { - valueEncoding: 'json' - }); - storage = new Storage({ - db: db - }); - blockchainExplorer = sinon.stub(); - - WalletService.initialize({ - storage: storage, - blockchainExplorer: blockchainExplorer, - }); - helpers.offset = 0; - + beforeEach(function(done) { addressSubscriber = sinon.stub(); addressSubscriber.subscribe = sinon.stub(); sinon.stub(BlockchainMonitor.prototype, '_getAddressSubscriber').onFirstCall().returns(addressSubscriber); - }); + openDb(function() { + storage = new Storage({ + db: db + }); + blockchainExplorer = sinon.stub(); + + WalletService.initialize({ + storage: storage, + blockchainExplorer: blockchainExplorer, + }); + helpers.offset = 0; + + done(); + }); + }); afterEach(function() { BlockchainMonitor.prototype._getAddressSubscriber.restore(); }); + after(function(done) { + closeDb(done); + }); it('should subscribe wallet', function(done) { var monitor = new BlockchainMonitor();