From 251264835fd94001067594eab78be513573b16ce Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Mon, 1 Dec 2014 14:55:03 +0100 Subject: [PATCH] [WO-767] Initialize lawnchair asynchronously during startup --- src/js/controller/login/login.js | 39 ++++---- src/js/email/account.js | 22 ++--- src/js/service/auth.js | 10 +- src/js/service/devicestorage.js | 8 +- src/js/service/lawnchair.js | 10 +- test/integration/email-dao-test.js | 93 ++++++++++--------- test/unit/controller/login/login-ctrl-test.js | 34 ++++--- test/unit/email/account-test.js | 2 +- test/unit/service/auth-test.js | 21 +++++ test/unit/service/devicestorage-dao-test.js | 17 +++- test/unit/service/lawnchair-dao-test.js | 9 +- 11 files changed, 167 insertions(+), 98 deletions(-) diff --git a/src/js/controller/login/login.js b/src/js/controller/login/login.js index 6db122b..3d04571 100644 --- a/src/js/controller/login/login.js +++ b/src/js/controller/login/login.js @@ -9,31 +9,36 @@ var LoginCtrl = function($scope, $timeout, $location, updateHandler, account, au function initializeUser() { // init the auth modules - auth.init(); - // get OAuth token from chrome - auth.getEmailAddress(function(err, info) { + auth.init(function(err) { if (err) { - dialog.error(err); - return; + return dialog.error(err); } - // check if account needs to be selected - if (!info.emailAddress) { - $scope.goTo('/add-account'); - return; - } - - // initiate the account by initializing the email dao and user storage - account.init({ - emailAddress: info.emailAddress, - realname: info.realname - }, function(err, availableKeys) { + // get OAuth token from chrome + auth.getEmailAddress(function(err, info) { if (err) { dialog.error(err); return; } - redirect(availableKeys); + // check if account needs to be selected + if (!info.emailAddress) { + $scope.goTo('/add-account'); + return; + } + + // initiate the account by initializing the email dao and user storage + account.init({ + emailAddress: info.emailAddress, + realname: info.realname + }, function(err, availableKeys) { + if (err) { + dialog.error(err); + return; + } + + redirect(availableKeys); + }); }); }); } diff --git a/src/js/email/account.js b/src/js/email/account.js index 2d383c4..c6ae40a 100644 --- a/src/js/email/account.js +++ b/src/js/email/account.js @@ -60,20 +60,20 @@ Account.prototype.init = function(options, callback) { // Pre-Flight check: initialize and prepare user's local database function prepareDatabase() { - try { - self._accountStore.init(options.emailAddress); - } catch (err) { - callback(err); - return; - } - - // Migrate the databases if necessary - self._updateHandler.update(function(err) { + self._accountStore.init(options.emailAddress, function(err) { if (err) { - return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message)); + return callback(err); } - prepareKeys(); + // Migrate the databases if necessary + self._updateHandler.update(function(err) { + if (err) { + return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message)); + } + + prepareKeys(); + }); + }); } diff --git a/src/js/service/auth.js b/src/js/service/auth.js index 4150d17..2c3fc52 100644 --- a/src/js/service/auth.js +++ b/src/js/service/auth.js @@ -8,6 +8,7 @@ var axe = require('axe-logger'), cfg = require('../app-config').config, str = require('../app-config').string; +var APP_CONFIG_DB_NAME = 'app-config'; var EMAIL_ADDR_DB_KEY = 'emailaddress'; var USERNAME_DB_KEY = 'username'; var REALNAME_DB_KEY = 'realname'; @@ -34,8 +35,13 @@ function Auth(appConfigStore, oauth, pgp) { /** * Initialize the service */ -Auth.prototype.init = function() { - this._initialized = true; +Auth.prototype.init = function(callback) { + var self = this; + + self._appConfigStore.init(APP_CONFIG_DB_NAME, function(error) { + self._initialized = !error; + callback(error); + }); }; /** diff --git a/src/js/service/devicestorage.js b/src/js/service/devicestorage.js index 99b2433..e6bb39f 100644 --- a/src/js/service/devicestorage.js +++ b/src/js/service/devicestorage.js @@ -4,9 +4,7 @@ var ngModule = angular.module('woServices'); // expose an instance with the static dbName 'app-config' to store configuration data ngModule.factory('appConfigStore', function(appConfigLawnchair) { - var deviceStorage = new DeviceStorage(appConfigLawnchair); - deviceStorage.init('app-config'); - return deviceStorage; + return new DeviceStorage(appConfigLawnchair); }); // expose a singleton instance of DeviceStorage called 'accountStore' to persist user data @@ -31,8 +29,8 @@ function DeviceStorage(lawnchairDAO) { * Initialize the lawnchair database * @param {String} dbName The name of the database */ -DeviceStorage.prototype.init = function(dbName) { - this._lawnchairDAO.init(dbName); +DeviceStorage.prototype.init = function(dbName, callback) { + this._lawnchairDAO.init(dbName, callback); }; /** diff --git a/src/js/service/lawnchair.js b/src/js/service/lawnchair.js index 0e3c714..819c76e 100644 --- a/src/js/service/lawnchair.js +++ b/src/js/service/lawnchair.js @@ -14,13 +14,17 @@ function LawnchairDAO() {} * Initialize the lawnchair database * @param {String} dbName The name of the database */ -LawnchairDAO.prototype.init = function(dbName) { +LawnchairDAO.prototype.init = function(dbName, callback) { + var self = this; + if (!dbName) { - throw new Error('Lawnchair DB name must be specified!'); + return callback(new Error('Lawnchair DB name must be specified!')); } - this._db = new Lawnchair({ + self._db = new Lawnchair({ name: dbName + }, function(success) { + callback(success ? undefined : new Error('Lawnchair initialization ' + dbName + ' failed!')); }); }; diff --git a/test/integration/email-dao-test.js b/test/integration/email-dao-test.js index eb637a6..9e6b8a3 100644 --- a/test/integration/email-dao-test.js +++ b/test/integration/email-dao-test.js @@ -14,7 +14,7 @@ var ImapClient = require('imap-client'), describe('Email DAO integration tests', function() { this.timeout(100000); - var accountService, emailDao, imapClient, imapMessages, imapFolders, imapServer, smtpServer, smtpClient, userStorage, + var accountService, emailDao, imapClient, imapMessages, imapFolders, imapServer, smtpServer, smtpClient, userStorage, auth, mockKeyPair, inbox, spam; var testAccount = { @@ -252,58 +252,67 @@ describe('Email DAO integration tests', function() { // clear the local database before each test var cleanup = new DeviceStorageDAO(new LawnchairDAO()); - cleanup.init(testAccount.user); - cleanup.clear(function(err) { - expect(err).to.not.exist; - - userStorage = accountService._accountStore; - - accountService.init({ - emailAddress: testAccount.user - }, function(err) { + cleanup.init(testAccount.user, function() { + cleanup.clear(function(err) { expect(err).to.not.exist; - emailDao = accountService._emailDao; + onCleaned(); + }); + }); - // stub rest request to key server - sinon.stub(emailDao._keychain._publicKeyDao, 'get').yields(null, mockKeyPair.publicKey); - sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').yields(null, mockKeyPair.publicKey); + function onCleaned() { + userStorage = accountService._accountStore; + auth = accountService._auth; - emailDao.onIncomingMessage = function(messages) { - expect(messages.length).to.equal(imapMessages.length); - inbox = emailDao._account.folders.filter(function(folder) { - return folder.path === 'INBOX'; - }).pop(); - spam = emailDao._account.folders.filter(function(folder) { - return folder.path === '[Gmail]/Spam'; - }).pop(); - expect(inbox).to.exist; - expect(spam).to.exist; - - inbox.messages.sort(function(a, b) { - return a.uid - b.uid; - }); - - // phantomjs is really slow, so setting the tcp socket timeouts to 200s will effectively disarm the timeout - imapClient._client.client.TIMEOUT_SOCKET_LOWER_BOUND = 999999999; - imapClient._listeningClient.client.TIMEOUT_SOCKET_LOWER_BOUND = 999999999; - smtpClient.TIMEOUT_SOCKET_LOWER_BOUND = 999999999; - - done(); - }; - - emailDao.unlock({ - passphrase: testAccount.pass, - keypair: mockKeyPair + auth.init(function(err) { + expect(err).to.not.exist; + accountService.init({ + emailAddress: testAccount.user }, function(err) { expect(err).to.not.exist; - accountService.onConnect(function(err) { + emailDao = accountService._emailDao; + + // stub rest request to key server + sinon.stub(emailDao._keychain._publicKeyDao, 'get').yields(null, mockKeyPair.publicKey); + sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').yields(null, mockKeyPair.publicKey); + + emailDao.onIncomingMessage = function(messages) { + expect(messages.length).to.equal(imapMessages.length); + inbox = emailDao._account.folders.filter(function(folder) { + return folder.path === 'INBOX'; + }).pop(); + spam = emailDao._account.folders.filter(function(folder) { + return folder.path === '[Gmail]/Spam'; + }).pop(); + expect(inbox).to.exist; + expect(spam).to.exist; + + inbox.messages.sort(function(a, b) { + return a.uid - b.uid; + }); + + // phantomjs is really slow, so setting the tcp socket timeouts to 200s will effectively disarm the timeout + imapClient._client.client.TIMEOUT_SOCKET_LOWER_BOUND = 999999999; + imapClient._listeningClient.client.TIMEOUT_SOCKET_LOWER_BOUND = 999999999; + smtpClient.TIMEOUT_SOCKET_LOWER_BOUND = 999999999; + + done(); + }; + + emailDao.unlock({ + passphrase: testAccount.pass, + keypair: mockKeyPair + }, function(err) { expect(err).to.not.exist; + + accountService.onConnect(function(err) { + expect(err).to.not.exist; + }); }); }); }); - }); + } } }); diff --git a/test/unit/controller/login/login-ctrl-test.js b/test/unit/controller/login/login-ctrl-test.js index 9efd9fc..450d3dd 100644 --- a/test/unit/controller/login/login-ctrl-test.js +++ b/test/unit/controller/login/login-ctrl-test.js @@ -54,6 +54,7 @@ describe('Login Controller unit test', function() { afterEach(function() {}); it('should fail for auth.getEmailAddress', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(new Error()); createController(); @@ -63,7 +64,21 @@ describe('Login Controller unit test', function() { expect(dialogMock.error.calledOnce).to.be.true; }); + it('should fail for auth.init', function() { + authMock.init.yields(new Error()); + authMock.getEmailAddress.yields(null, { + emailAddress: emailAddress + }); + + createController(); + + expect(authMock.init.calledOnce).to.be.true; + expect(accountMock.init.called).to.be.false; + expect(dialogMock.error.calledOnce).to.be.true; + }); + it('should redirect to /add-account', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, {}); createController(); @@ -71,19 +86,8 @@ describe('Login Controller unit test', function() { expect(goToStub.withArgs('/add-account').calledOnce).to.be.true; }); - it('should fail for auth.init', function() { - authMock.getEmailAddress.yields(null, { - emailAddress: emailAddress - }); - accountMock.init.yields(new Error()); - - createController(); - - expect(accountMock.init.calledOnce).to.be.true; - expect(dialogMock.error.calledOnce).to.be.true; - }); - it('should redirect to /login-existing', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); @@ -99,6 +103,7 @@ describe('Login Controller unit test', function() { }); it('should fail for auth.storeCredentials', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); @@ -115,6 +120,7 @@ describe('Login Controller unit test', function() { }); it('should redirect to /desktop', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); @@ -131,6 +137,7 @@ describe('Login Controller unit test', function() { }); it('should fail for keychain.requestPrivateKeyDownload', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); @@ -145,6 +152,7 @@ describe('Login Controller unit test', function() { }); it('should redirect to /login-privatekey-download', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); @@ -159,6 +167,7 @@ describe('Login Controller unit test', function() { }); it('should redirect to /login-new-device', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); @@ -173,6 +182,7 @@ describe('Login Controller unit test', function() { }); it('should redirect to /login-initial', function() { + authMock.init.yields(); authMock.getEmailAddress.yields(null, { emailAddress: emailAddress }); diff --git a/test/unit/email/account-test.js b/test/unit/email/account-test.js index c796be0..5e5cd27 100644 --- a/test/unit/email/account-test.js +++ b/test/unit/email/account-test.js @@ -67,7 +67,7 @@ describe('Account Service unit test', function() { }); it('should fail for _accountStore.init', function() { - devicestorageStub.init.throws(new Error('asdf')); + devicestorageStub.init.yields(new Error('asdf')); account.init({ emailAddress: dummyUser, diff --git a/test/unit/service/auth-test.js b/test/unit/service/auth-test.js index e8b23db..5d48b80 100644 --- a/test/unit/service/auth-test.js +++ b/test/unit/service/auth-test.js @@ -13,6 +13,8 @@ describe('Auth unit tests', function() { var PASSWD_DB_KEY = 'password'; var IMAP_DB_KEY = 'imap'; var SMTP_DB_KEY = 'smtp'; + var APP_CONFIG_DB_NAME = 'app-config'; + // SUT var auth; @@ -46,6 +48,25 @@ describe('Auth unit tests', function() { auth = new Auth(storageStub, oauthStub, pgpStub); }); + describe('#init', function() { + it('should initialize a user db', function(done) { + storageStub.init.withArgs(APP_CONFIG_DB_NAME).yields(); + auth.init(function(err) { + expect(err).to.not.exist; + expect(auth._initialized).to.be.true; + done(); + }); + }); + it('should initialize a user db', function(done) { + storageStub.init.withArgs(APP_CONFIG_DB_NAME).yields(new Error()); + auth.init(function(err) { + expect(err).to.exist; + expect(auth._initialized).to.be.false; + done(); + }); + }); + }); + describe('#getCredentials', function() { it('should load credentials and retrieve credentials from cfg', function(done) { storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]); diff --git a/test/unit/service/devicestorage-dao-test.js b/test/unit/service/devicestorage-dao-test.js index 933df5c..3a0187a 100644 --- a/test/unit/service/devicestorage-dao-test.js +++ b/test/unit/service/devicestorage-dao-test.js @@ -18,8 +18,21 @@ describe('Device Storage DAO unit tests', function() { describe('init', function() { it('should work', function() { - storageDao.init(testUser); - expect(lawnchairDaoStub.init.calledOnce).to.be.true; + lawnchairDaoStub.init.yields(); + + storageDao.init(testUser, function(err) { + expect(err).to.not.exist; + expect(lawnchairDaoStub.init.calledOnce).to.be.true; + }); + }); + + it('should fail', function() { + lawnchairDaoStub.init.yields(new Error()); + + storageDao.init(testUser, function(err) { + expect(err).to.exist; + expect(lawnchairDaoStub.init.calledOnce).to.be.true; + }); }); }); diff --git a/test/unit/service/lawnchair-dao-test.js b/test/unit/service/lawnchair-dao-test.js index 0ece202..b3b8d9e 100644 --- a/test/unit/service/lawnchair-dao-test.js +++ b/test/unit/service/lawnchair-dao-test.js @@ -20,10 +20,13 @@ var data2 = { describe('Lawnchair DAO unit tests', function() { var lawnchairDao; - beforeEach(function() { + beforeEach(function(done) { lawnchairDao = new LawnchairDAO(); - lawnchairDao.init(dbName); - expect(lawnchairDao._db).to.exist; + lawnchairDao.init(dbName, function(err) { + expect(err).to.not.exist; + expect(lawnchairDao._db).to.exist; + done(); + }); }); afterEach(function(done) {