diff --git a/API.js b/API.js index dd6d2fe19..76f30a29e 100644 --- a/API.js +++ b/API.js @@ -10,23 +10,13 @@ API.prototype._init = function(opts) { opts = opts || {}; self.opts = opts; - var Wallet = require('soop').load('./js/models/core/Wallet', { + var WalletFactory = require('soop').load('./js/models/core/WalletFactory', { Storage: opts.Storage || require('./test/mocks/FakeStorage'), - Network: opts.Network || require('./js/models/network/WebRTC'), + Network: opts.Network || require('./js/models/network/Base'), Blockchain: opts.Blockchain || require('./js/models/blockchain/Insight') }); - - var config = { - wallet: { - requiredCopayers: opts.requiredCopayers || 3, - totalCopayers: opts.totalCopayers || 5, - } - }; - var walletConfig = opts.walletConfig || config; - var walletOpts = opts.walletOpts || {}; - - self.wallet = self.opts.wallet || Wallet.factory.create(walletConfig, walletOpts); + this.walletFactory = new WalletFactory(opts); }; API._coerceArgTypes = function(args, argTypes) { @@ -179,13 +169,13 @@ API.prototype.getCommands = decorate('getCommands', [ ['callback', 'function'] ]); -API.prototype._cmd_getPublicKeyRingId = function(callback) { +API.prototype._cmd_getWalletIds = function(callback) { var self = this; - return callback(null, self.wallet.publicKeyRing.walletId); + return callback(null, self.walletFactory.getWalletIds()); }; -API.prototype.getPublicKeyRingId = decorate('getPublicKeyRingId', [ +API.prototype.getWalletIds = decorate('getWalletIds', [ ['callback', 'function'] ]); diff --git a/bin/copay b/bin/copay index 2721da0e2..9890f66cd 100755 --- a/bin/copay +++ b/bin/copay @@ -18,11 +18,10 @@ var main = function() { var api = new API(commander); var args = commander.args; + var command = args[0]; + var commandArgs = args.slice(1); try { - var command = args[0]; - var commandArgs = args.slice(1); - if (command[0] == '_' || typeof api[command] != 'function') throw new Error('invalid command'); diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index baef0b27c..3bcd14070 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -110,7 +110,6 @@ WalletFactory.prototype.create = function(opts) { opts.totalCopayers = totalCopayers; var w = new Wallet(opts); w.store(); - this.addWalletId(w.id); return w; }; @@ -156,28 +155,11 @@ WalletFactory.prototype.openRemote = function(peedId) { }; WalletFactory.prototype.getWalletIds = function() { - var ids = this.storage.getGlobal('walletIds'); - return ids || []; + return this.storage.getWalletIds(); } -WalletFactory.prototype._delWalletId = function(walletId) { - var ids = this.getWalletIds(); - var index = ids.indexOf(walletId); - if (index === -1) return; - ids.splice(index, 1); // removes walletId - this.storage.setGlobal('walletIds', ids); -}; - WalletFactory.prototype.remove = function(walletId) { - WalletFactory._delWalletId(walletId); - // TODO remove wallet contents, not only the id (Wallet.remove?) -}; - -WalletFactory.prototype.addWalletId = function(walletId) { - var ids = this.getWalletIds(); - if (ids.indexOf(walletId) !== -1) return; - ids.push(walletId); - this.storage.setGlobal('walletIds', ids); + // TODO remove wallet contents }; diff --git a/js/models/storage/Base.js b/js/models/storage/Base.js index 8f61f4dfe..29ca2564c 100644 --- a/js/models/storage/Base.js +++ b/js/models/storage/Base.js @@ -17,6 +17,9 @@ Storage.prototype.set = function(walletId,v) { Storage.prototype.remove = function(walletId, k) { }; +Storage.prototype.getWalletIds = function() { +}; + // remove all values Storage.prototype.clearAll = function() { }; diff --git a/js/models/storage/File.js b/js/models/storage/File.js new file mode 100644 index 000000000..2c07b7010 --- /dev/null +++ b/js/models/storage/File.js @@ -0,0 +1,102 @@ +'use strict'; +var imports = require('soop').imports(); +var fs = imports.fs || require('fs'); + +function Storage(opts) { + opts = opts || {}; + + this.data = {}; +} + +Storage.prototype.load = function(walletId, callback) { + fs.readFile(walletId, function(err, data) { + if (err) return callback(err); + + try { + this.data[walletId] = JSON.parse(data); + } catch (err) { + if (callback) + return callback(err); + } + + if (callback) + return callback(null); + }); +}; + +Storage.prototype.save = function(walletId, callback) { + var data = JSON.stringify(this.data[walletId]); + + //TODO: update to use a queue to ensure that saves are made sequentially + fs.writeFile(walletId, data, function(err) { + if (callback) + return callback(err); + }); +}; + +Storage.prototype._read = function(k) { + var split = k.split('::'); + var walletId = split[0]; + var key = split[1]; + return this.data[walletId][key]; +}; + +Storage.prototype._write = function(k, v, callback) { + var split = k.split('::'); + var walletId = split[0]; + var key = split[1]; + if (!this.data[walletId]) + this.data[walletId] = {}; + this.data[walletId][key] = v; + this.save(walletId, callback); +}; + +// get value by key +Storage.prototype.getGlobal = function(k) { + return this._read(k); +}; + +// set value for key +Storage.prototype.setGlobal = function(k, v, callback) { + this._write(k, v, callback); +}; + +// remove value for key +Storage.prototype.removeGlobal = function(k, callback) { + var split = k.split('::'); + var walletId = split[0]; + var key = split[1]; + delete this.data[walletId][key]; + this.save(walletId, callback); +}; + +Storage.prototype._key = function(walletId, k) { + return walletId + '::' + k; +}; + +// get value by key +Storage.prototype.get = function(walletId, k) { + return this.getGlobal(this._key(walletId, k)); +}; + +// set value for key +Storage.prototype.set = function(walletId, k, v, callback) { + this.setGlobal(this._key(walletId,k), v, callback); +}; + +// remove value for key +Storage.prototype.remove = function(walletId, k, callback) { + this.removeGlobal(this._key(walletId, k), callback); +}; + +Storage.prototype.getWalletIds = function() { + return []; +}; + +// remove all values +Storage.prototype.clearAll = function(callback) { + this.data = {}; + this.save(callback); +}; + +module.exports = require('soop')(Storage); diff --git a/js/models/storage/LocalPlain.js b/js/models/storage/LocalPlain.js index 1bda410f2..d32234899 100644 --- a/js/models/storage/LocalPlain.js +++ b/js/models/storage/LocalPlain.js @@ -52,6 +52,21 @@ Storage.prototype.remove = function(walletId, k) { this.removeGlobal(this._key(walletId,k)); }; +Storage.prototype.getWalletIds = function() { + var walletIds = []; + + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + var split = key.split('::'); + if (split.length == 2) { + var walletId = split[0]; + walletIds.push(walletId); + } + } + + return walletIds; +}; + // remove all values Storage.prototype.clearAll = function() { localStorage.clear(); diff --git a/package.json b/package.json index c16e0a9a7..7e0cbdd62 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "uglifyify": "~1.2.3", "soop": "~0.1.5", "bitcore": "git://github.com/maraoz/bitcore.git#5e636f6b9c7f8e629b1a502025556e886c3b75e1", - "chai": "~1.9.1" + "chai": "~1.9.1", + "sinon": "~1.9.1" } } diff --git a/test/mocks/FakeStorage.js b/test/mocks/FakeStorage.js index ef50df677..5819905e5 100644 --- a/test/mocks/FakeStorage.js +++ b/test/mocks/FakeStorage.js @@ -23,4 +23,8 @@ FakeStorage.prototype.clear = function() { delete this['storage']; } +FakeStorage.prototype.getWalletIds = function() { + return []; +}; + module.exports = require('soop')(FakeStorage); diff --git a/test/test.API.js b/test/test.API.js index 75161997e..96795f75a 100644 --- a/test/test.API.js +++ b/test/test.API.js @@ -4,11 +4,13 @@ var chai = chai || require('chai'); var should = chai.should(); var copay = copay || require('../copay'); var API = API || copay.API; +var Storage = Storage || require('../test/mocks/FakeStorage'); describe('API', function() { it('should have a command called "echo"', function() { - var api = new API(); + + var api = new API({Storage: Storage}); should.exist(api.echo); }); @@ -22,7 +24,7 @@ describe('API', function() { }) it('should throw an error for all commands when called with wrong number of arguments', function() { - var api = new API(); + var api = new API({Storage: Storage}); for (var i in API.prototype) { var f = API.prototype[i]; if (i[0] != '_' && typeof f == 'function') { @@ -49,7 +51,7 @@ describe('API', function() { describe('#echo', function() { it('should echo a string', function(done) { - var api = new API(); + var api = new API({Storage: Storage}); var str = 'mystr'; api.echo(str, function(err, result) { result.should.equal(str); @@ -60,7 +62,7 @@ describe('API', function() { describe('#echoNumber', function() { it('should echo a number', function(done) { - var api = new API(); + var api = new API({Storage: Storage}); var num = 500; api.echoNumber(num, function(err, result) { result.should.equal(num); @@ -72,7 +74,7 @@ describe('API', function() { describe('#echoObject', function() { it('should echo an object', function(done) { - var api = new API(); + var api = new API({Storage: Storage}); var obj = {test:'test'}; api.echoObject(obj, function(err, result) { result.test.should.equal(obj.test); @@ -84,7 +86,7 @@ describe('API', function() { describe('#getArgTypes', function() { it('should get the argTypes of echo', function(done) { - var api = new API(); + var api = new API({Storage: Storage}); api.getArgTypes('echo', function(err, result) { result[0][1].should.equal('string'); done(); @@ -94,7 +96,7 @@ describe('API', function() { describe('#getCommands', function() { it('should get all the commands', function(done) { - var api = new API(); + var api = new API({Storage: Storage}); var n = 0; for (var i in api) @@ -108,11 +110,11 @@ describe('API', function() { }); }); - describe('#getPublicKeyRingId', function() { - it('should get a public key ring ID', function(done) { - var api = new API(); - api.getPublicKeyRingId(function(err, result) { - result.length.should.be.greaterThan(5); + describe('#getWalletIds', function() { + it('should get the wallet ids', function(done) { + var api = new API({Storage: Storage}); + api.getWalletIds(function(err, result) { + result.length.should.be.greaterThan(-1); done(); }); }); @@ -120,7 +122,7 @@ describe('API', function() { describe('#help', function() { it('should call _cmd_getCommands', function(done) { - var api = new API(); + var api = new API({Storage: Storage}); api._cmd_getCommands = function(callback) { (typeof arguments[0]).should.equal('function'); callback(null, ['item']); diff --git a/test/test.storage.File.js b/test/test.storage.File.js new file mode 100644 index 000000000..b54a34215 --- /dev/null +++ b/test/test.storage.File.js @@ -0,0 +1,144 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var Storage = Storage || require('../js/models/storage/File.js'); +var sinon = sinon || require('sinon'); + +describe('Storage/File', function() { + it('should exist', function() { + should.exist(Storage); + }); + + describe('#load', function(done) { + it('should call fs.readFile', function(done) { + var fs = {} + fs.readFile = function(filename, callback) { + filename.should.equal('myfilename'); + callback(); + }; + var Storage = require('soop').load('../js/models/storage/File.js', {fs: fs}); + var storage = new Storage({password: 'password'}); + storage.load('myfilename', function(err) { + done(); + }); + }); + }); + + describe('#save', function(done) { + it('should call fs.writeFile', function(done) { + var fs = {} + fs.writeFile = function(filename, data, callback) { + filename.should.equal('myfilename'); + callback(); + }; + var Storage = require('soop').load('../js/models/storage/File.js', {fs: fs}); + var storage = new Storage({password: 'password'}); + storage.save('myfilename', function(err) { + done(); + }); + }); + }); + + describe('#_read', function() { + it('should return the value of a key', function() { + var storage = new Storage(); + storage.data = {'walletId':{'test':'data'}}; + storage._read('walletId::test').should.equal('data'); + }); + }); + + describe('#_write', function() { + it('should save the value of a key and then run save', function(done) { + var storage = new Storage(); + storage.save = function(walletId, callback) { + storage.data[walletId]['key'].should.equal('value'); + callback(); + }; + storage._write('walletId::key', 'value', function() { + done(); + }); + }); + }); + + describe('#getGlobal', function() { + it('should call storage._read', function() { + var storage = new Storage(); + storage.data = {'walletId':{'test':'test'}}; + storage._read = sinon.spy(); + storage.getGlobal('walletId::test'); + storage._read.calledOnce.should.equal(true); + }); + }); + + describe('#setGlobal', function() { + it('should store a global key', function(done) { + var storage = new Storage(); + storage.save = function(walletId, callback) { + storage.data[walletId]['key'].should.equal('value'); + callback(); + }; + storage.setGlobal('walletId::key', 'value', function() { + done(); + }); + }); + }); + + describe('#removeGlobal', function() { + it('should remove a global key', function(done) { + var storage = new Storage(); + storage.data = {'walletId':{'key':'value'}}; + storage.save = function(walletId, callback) { + should.not.exist(storage.data[walletId]['key']); + callback(); + }; + storage.removeGlobal('walletId::key', function() { + done(); + }); + }); + }); + + describe('#_key', function() { + it('should merge the wallet id and item key', function() { + var storage = new Storage(); + storage._key('wallet', 'key').should.equal('wallet::key'); + }); + }); + + describe('#get', function() { + it('should call getGlobal with the correct key', function() { + var storage = new Storage(); + storage.getGlobal = sinon.spy(); + storage.get('wallet', 'key'); + storage.getGlobal.calledOnce.should.equal(true); + storage.getGlobal.calledWith('wallet::key').should.equal(true); + }); + }); + + describe('#set', function() { + it('should call setGlobal with the correct key', function() { + var storage = new Storage(); + storage.setGlobal = sinon.spy(); + storage.set('wallet', 'key'); + storage.setGlobal.calledOnce.should.equal(true); + storage.setGlobal.calledWith('wallet::key').should.equal(true); + }); + }); + + describe('#remove', function() { + it('should call removeGlobal with the correct key', function() { + var storage = new Storage(); + storage.removeGlobal = sinon.spy(); + storage.remove('wallet', 'key'); + storage.removeGlobal.calledOnce.should.equal(true); + storage.removeGlobal.calledWith('wallet::key').should.equal(true); + }); + }); + + describe('#clearAll', function() { + it('should set data to {}', function() { + + }); + }); + +});