From 08f918a9a7a5a3ab97fd12cabfa974a333f3fa88 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Wed, 16 Apr 2014 15:26:04 -0300 Subject: [PATCH] add File storage class and tests --- bin/copay | 5 +- js/models/storage/File.js | 87 ++++++++++++++++++++++ package.json | 3 +- test/test.storage.File.js | 147 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 js/models/storage/File.js create mode 100644 test/test.storage.File.js 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/storage/File.js b/js/models/storage/File.js new file mode 100644 index 000000000..1f659f8fc --- /dev/null +++ b/js/models/storage/File.js @@ -0,0 +1,87 @@ +'use strict'; +var imports = require('soop').imports(); +var fs = imports.fs || require('fs'); + +function Storage(opts) { + opts = opts || {}; + + this.data = {}; + this.filename = opts.filename; +} + +Storage.prototype.load = function(callback) { + if (!this.filename) + throw new Error('No filename'); + + fs.readFile(this.filename, function(err, data) { + if (err) return callback(err); + + try { + this.data = JSON.parse(data); + } catch (err) { + return callback(err); + } + + return callback(null); + }); +}; + +Storage.prototype.save = function(callback) { + var data = JSON.stringify(this.data); + + //TODO: update to use a queue to ensure that saves are made sequentially + fs.writeFile(this.filename, data, function(err) { + return callback(err); + }); +}; + +Storage.prototype._read = function(k) { + return this.data[k]; +}; + +Storage.prototype._write = function(k, v, callback) { + this.data[k] = v; + this.save(callback); +}; + +// get value by key +Storage.prototype.getGlobal = function(k) { + return this.data[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) { + delete this.data[k]; + this.save(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); +}; + +// remove all values +Storage.prototype.clearAll = function(callback) { + this.data = {}; + this.save(callback); +}; + +module.exports = require('soop')(Storage); 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/test.storage.File.js b/test/test.storage.File.js new file mode 100644 index 000000000..d3d47ca96 --- /dev/null +++ b/test/test.storage.File.js @@ -0,0 +1,147 @@ +'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({filename: 'myfilename', password: 'password'}); + storage.load(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({filename: 'myfilename', password: 'password'}); + storage.save(function(err) { + done(); + }); + }); + }); + + describe('#_read', function() { + it('should return the value of a key', function() { + var storage = new Storage(); + storage.data = {'test':'data'}; + storage._read('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(callback) { + storage.data['key'].should.equal('value'); + callback(); + }; + storage._write('key', 'value', function() { + done(); + }); + }); + }); + + describe('#getGlobal', function() { + it('should store a global key', function(done) { + var storage = new Storage(); + storage.save = function(callback) { + storage.data['key'].should.equal('value'); + callback(); + }; + storage.setGlobal('key', 'value', function() { + done(); + }); + }); + }); + + describe('#setGlobal', function() { + it('should store a global key', function(done) { + var storage = new Storage(); + storage.save = function(callback) { + storage.data['key'].should.equal('value'); + callback(); + }; + storage.setGlobal('key', 'value', function() { + done(); + }); + }); + }); + + describe('#removeGlobal', function() { + it('should remove a global key', function(done) { + var storage = new Storage(); + storage.data.key = 'value'; + storage.save = function(callback) { + should.not.exist(storage.data['key']); + callback(); + }; + storage.removeGlobal('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() { + + }); + }); + +});