From f1fd9361416d5f4f9c632e115b2b83c8adabc98c Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 10 Jun 2013 23:50:26 +0200 Subject: [PATCH] finished refactoring unit tests with email dao --- src/js/dao/email-dao.js | 354 ++++++++++++++-------------- src/js/model/account-model.js | 4 +- src/js/model/folder-model.js | 4 +- test/unit/devicestorage-dao-test.js | 4 +- test/unit/email-dao-test.js | 161 ++++++------- test/unit/index.html | 36 ++- test/unit/main.js | 3 +- 7 files changed, 286 insertions(+), 280 deletions(-) diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index e3f42d3..73c6f5a 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -2,219 +2,229 @@ * A high-level Data-Access Api for handling Email synchronization * between the cloud service and the device's local storage */ -app.dao.EmailDAO = function(jsonDB, crypto, devicestorage, cloudstorage, util, keychain) { +define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao', + 'js/dao/devicestorage-dao', 'js/model/account-model' +], function(_, util, crypto, jsonDB, devicestorage) { 'use strict'; - /** - * Inits all dependencies - */ - this.init = function(account, password, callback) { - this.account = account; + var EmailDAO = function(cloudstorage, keychain) { + var self = this; - // validate email address - var emailAddress = account.get('emailAddress'); - if (!validateEmail(emailAddress)) { - callback({ - errMsg: 'The user email address must be specified!' - }); - return; - } + /** + * Inits all dependencies + */ + self.init = function(account, password, callback) { + self.account = account; - // init user's local database - jsonDB.init(emailAddress); - - // call getUserKeyPair to read/sync keypair with devicestorage/cloud - keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) { - if (err) { - callback(err); + // validate email address + var emailAddress = account.get('emailAddress'); + if (!validateEmail(emailAddress)) { + callback({ + errMsg: 'The user email address must be specified!' + }); return; } - // init crypto - initCrypto(storedKeypair); - }); - function initCrypto(storedKeypair) { - crypto.init({ - emailAddress: emailAddress, - password: password, - keySize: account.get('symKeySize'), - rsaKeySize: account.get('asymKeySize'), - storedKeypair: storedKeypair - }, function(err, generatedKeypair) { + // init user's local database + jsonDB.init(emailAddress); + + // call getUserKeyPair to read/sync keypair with devicestorage/cloud + keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) { if (err) { callback(err); return; } - - if (generatedKeypair) { - // persist newly generated keypair - keychain.putUserKeyPair(generatedKeypair, callback); - } else { - callback(); - } + // init crypto + initCrypto(storedKeypair); }); - } - }; - /** - * Fetch an email with the following id - */ - this.getItem = function(folderName, itemId) { - var folder = this.account.get('folders').where({ - name: folderName - })[0]; - var mail = _.find(folder.get('items'), function(email) { - return email.id + '' === itemId + ''; - }); - return mail; - }; - - /** - * Fetch a list of emails from the device's local storage - * @param offset [Number] The offset of items to fetch (0 is the last stored item) - * @param num [Number] The number of items to fetch (null means fetch all) - */ - this.listItems = function(folderName, offset, num, callback) { - var collection, folder, already, pubkeyIds = [], - self = this; - - // check if items are in memory already (account.folders model) - folder = this.account.get('folders').where({ - name: folderName - })[0]; - - if (!folder) { - // get encrypted items from storage - devicestorage.listEncryptedItems('email_' + folderName, offset, num, function(err, encryptedList) { - if (err) { - callback(err); - return; - } - if (encryptedList.length === 0) { - callback(null, []); - return; - } - - // gather public key ids required to verify signatures - encryptedList.forEach(function(i) { - already = null; - already = _.findWhere(pubkeyIds, { - _id: i.senderPk - }); - if (!already) { - pubkeyIds.push({ - _id: i.senderPk - }); - } - }); - - // fetch public keys from keychain - keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) { + function initCrypto(storedKeypair) { + crypto.init({ + emailAddress: emailAddress, + password: password, + keySize: account.get('symKeySize'), + rsaKeySize: account.get('asymKeySize'), + storedKeypair: storedKeypair + }, function(err, generatedKeypair) { if (err) { callback(err); return; } - // decrypt list - crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) { + if (generatedKeypair) { + // persist newly generated keypair + keychain.putUserKeyPair(generatedKeypair, callback); + } else { + callback(); + } + }); + } + }; + + /** + * Fetch an email with the following id + */ + self.getItem = function(folderName, itemId) { + var folder = self.account.get('folders').where({ + name: folderName + })[0]; + var mail = _.find(folder.get('items'), function(email) { + return email.id + '' === itemId + ''; + }); + return mail; + }; + + /** + * Fetch a list of emails from the device's local storage + * @param offset [Number] The offset of items to fetch (0 is the last stored item) + * @param num [Number] The number of items to fetch (null means fetch all) + */ + self.listItems = function(folderName, offset, num, callback) { + var collection, folder, already, pubkeyIds = []; + + // check if items are in memory already (account.folders model) + folder = self.account.get('folders').where({ + name: folderName + })[0]; + + if (!folder) { + // get encrypted items from storage + devicestorage.listEncryptedItems('email_' + folderName, offset, num, function(err, encryptedList) { + if (err) { + callback(err); + return; + } + if (encryptedList.length === 0) { + callback(null, []); + return; + } + + // gather public key ids required to verify signatures + encryptedList.forEach(function(i) { + already = null; + already = _.findWhere(pubkeyIds, { + _id: i.senderPk + }); + if (!already) { + pubkeyIds.push({ + _id: i.senderPk + }); + } + }); + + // fetch public keys from keychain + keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) { if (err) { callback(err); return; } - // cache collection in folder memory - if (decryptedList.length > 0) { - folder = new app.model.Folder({ - name: folderName - }); - folder.set('items', decryptedList); - self.account.get('folders').add(folder); - } + // decrypt list + crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) { + if (err) { + callback(err); + return; + } + + // cache collection in folder memory + if (decryptedList.length > 0) { + folder = new app.model.Folder({ + name: folderName + }); + folder.set('items', decryptedList); + self.account.get('folders').add(folder); + } + + callback(null, decryptedList); + }); - callback(null, decryptedList); }); + }); + } else { + // read items from memory + collection = folder.get('items'); + callback(null, collection); + } + }; + + /** + * Synchronize a folder's items from the cloud to the device-storage + * @param folderName [String] The name of the folder e.g. 'inbox' + */ + self.syncFromCloud = function(folderName, callback) { + var folder; + + cloudstorage.listEncryptedItems('email', self.account.get('emailAddress'), folderName, function(err, data) { + // return if an error occured + if (err) { + callback({ + errMsg: 'Syncing encrypted items from cloud failed!', + err: err + }); // error + return; + } + + // TODO: remove old folder items from devicestorage + + // persist encrypted list in device storage + devicestorage.storeEcryptedList(data, 'email_' + folderName, function() { + // remove cached folder in account model + folder = self.account.get('folders').where({ + name: folderName + })[0]; + if (folder) { + self.account.get('folders').remove(folder); + } + callback(); }); }); + }; - } else { - // read items from memory - collection = folder.get('items'); - callback(null, collection); - } - }; + /** + * Send a plaintext Email to the user's outbox in the cloud + */ + self.sendEmail = function(email, callback) { + var userId = self.account.get('emailAddress'); - /** - * Synchronize a folder's items from the cloud to the device-storage - * @param folderName [String] The name of the folder e.g. 'inbox' - */ - this.syncFromCloud = function(folderName, callback) { - var folder, self = this; - - cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), folderName, function(err, data) { - // return if an error occured - if (err) { + // validate email addresses + var invalidRecipient; + _.each(email.to, function(address) { + if (!validateEmail(address)) { + invalidRecipient = address; + } + }); + if (invalidRecipient) { callback({ - errMsg: 'Syncing encrypted items from cloud failed!', - err: err - }); // error + errMsg: 'Invalid recipient: ' + invalidRecipient + }); + return; + } + if (!validateEmail(email.from)) { + callback({ + errMsg: 'Invalid sender: ' + email.from + }); return; } - // TODO: remove old folder items from devicestorage + // generate a new UUID for the new email + email.id = util.UUID(); - // persist encrypted list in device storage - devicestorage.storeEcryptedList(data, 'email_' + folderName, function() { - // remove cached folder in account model - folder = self.account.get('folders').where({ - name: folderName - })[0]; - if (folder) { - self.account.get('folders').remove(folder); - } - callback(); + // send email to cloud service + cloudstorage.putEncryptedItem(email, 'email', userId, 'outbox', function(err) { + callback(err); }); - }); + }; }; - /** - * Send a plaintext Email to the user's outbox in the cloud - */ - this.sendEmail = function(email, callback) { - var userId = this.account.get('emailAddress'); - - // validate email addresses - var invalidRecipient; - _.each(email.to, function(address) { - if (!validateEmail(address)) { - invalidRecipient = address; - } - }); - if (invalidRecipient) { - callback({ - errMsg: 'Invalid recipient: ' + invalidRecipient - }); - return; - } - if (!validateEmail(email.from)) { - callback({ - errMsg: 'Invalid sender: ' + email.from - }); - return; - } - - // generate a new UUID for the new email - email.id = util.UUID(); - - // send email to cloud service - cloudstorage.putEncryptedItem(email, 'email', userId, 'outbox', function(err) { - callback(err); - }); - }; + // + // helper functions + // function validateEmail(email) { var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); } -}; \ No newline at end of file + return EmailDAO; +}); \ No newline at end of file diff --git a/src/js/model/account-model.js b/src/js/model/account-model.js index 68f1008..33cb6e5 100644 --- a/src/js/model/account-model.js +++ b/src/js/model/account-model.js @@ -1,4 +1,4 @@ -(function() { +define(['backbone', 'js/model/folder-model'], function(Backbone) { 'use strict'; app.model.Account = Backbone.Model.extend({ @@ -23,4 +23,4 @@ }); -}()); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/js/model/folder-model.js b/src/js/model/folder-model.js index be637ca..9ad1408 100644 --- a/src/js/model/folder-model.js +++ b/src/js/model/folder-model.js @@ -1,4 +1,4 @@ -(function() { +define(['backbone'], function(Backbone) { 'use strict'; app.model.Folder = Backbone.Model.extend({ @@ -18,4 +18,4 @@ }); -}()); \ No newline at end of file +}); \ No newline at end of file diff --git a/test/unit/devicestorage-dao-test.js b/test/unit/devicestorage-dao-test.js index 2b5b046..f918dc7 100644 --- a/test/unit/devicestorage-dao-test.js +++ b/test/unit/devicestorage-dao-test.js @@ -1,4 +1,6 @@ -define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao', 'js/dao/devicestorage-dao', 'test/test-data'], function(_, util, crypto, jsonDao, storage, testData) { +define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao', + 'js/dao/devicestorage-dao', 'test/test-data' +], function(_, util, crypto, jsonDao, storage, testData) { 'use strict'; module("DeviceStorage"); diff --git a/test/unit/email-dao-test.js b/test/unit/email-dao-test.js index dc1e00b..fb00ecd 100644 --- a/test/unit/email-dao-test.js +++ b/test/unit/email-dao-test.js @@ -1,99 +1,100 @@ -'use strict'; +define(['js/dao/email-dao', 'js/dao/keychain-dao', 'js/dao/lawnchair-dao', + 'js/crypto/crypto', 'js/dao/devicestorage-dao', 'test/test-data' +], function(EmailDAO, KeychainDAO, jsonDao, crypto, storage, testData) { + 'use strict'; -module("Email DAO"); + module("Email DAO"); -var emaildao_test = { - user: 'test@atlasdev.onmicrosoft.com', - password: 'Xoza76645', - keySize: 128, - ivSize: 128, - rsaKeySize: 1024 -}; - -asyncTest("Init", 3, function() { - // init dependencies - var util = new cryptoLib.Util(window, uuid); - var jsonDao = new app.dao.LawnchairDAO(Lawnchair); - jsonDao.init(emaildao_test.user); - emaildao_test.crypto = new app.crypto.Crypto(window, util); - emaildao_test.storage = new app.dao.DeviceStorage(util, emaildao_test.crypto, jsonDao, null); - // cloud storage stub - var cloudstorageStub = { - putPublicKey: function(pk, callback) { - callback(); - }, - putPrivateKey: function(prk, callback) { - callback(); - }, - getPublicKeyByUserId: function(userId, callback) { - callback(); - } + var emaildaoTest = { + user: 'test@atlasdev.onmicrosoft.com', + password: 'Xoza76645', + keySize: 128, + ivSize: 128, + rsaKeySize: 1024 }; - emaildao_test.keychain = new app.dao.KeychainDAO(jsonDao, cloudstorageStub); - emaildao_test.emailDao = new app.dao.EmailDAO(jsonDao, emaildao_test.crypto, emaildao_test.storage, cloudstorageStub, util, emaildao_test.keychain); - // generate test data - emaildao_test.list = new TestData().getEmailCollection(100); - - var account = new app.model.Account({ - emailAddress: emaildao_test.user, - symKeySize: emaildao_test.keySize, - symIvSize: emaildao_test.ivSize, - asymKeySize: emaildao_test.rsaKeySize - }); - - // clear db before tests - jsonDao.clear(function(err) { - ok(!err, 'DB cleared. Error status: ' + err); - - emaildao_test.emailDao.init(account, emaildao_test.password, function(err) { - ok(!err); - equal(emaildao_test.emailDao.account.get('emailAddress'), emaildao_test.user, 'Email DAO Account'); - - start(); - }); - }); -}); - -asyncTest("Persist test emails", 4, function() { - emaildao_test.keychain.getUserKeyPair(emaildao_test.user, function(err, keypair) { - ok(!err && keypair, 'Fetch keypair from keychain'); - - var receiverPubkeys = [keypair.publicKey]; - - emaildao_test.crypto.encryptListForUser(emaildao_test.list.toJSON(), receiverPubkeys, function(err, encryptedList) { - ok(!err); - equal(encryptedList.length, emaildao_test.list.length, 'Encrypt list'); - - // add sent date to encrypted items - for (var i = 0; i < encryptedList.length; i++) { - encryptedList[i].sentDate = emaildao_test.list.at(i).get('sentDate'); + asyncTest("Init", 3, function() { + // init dependencies + jsonDao.init(emaildaoTest.user); + // cloud storage stub + var cloudstorageStub = { + putPublicKey: function(pk, callback) { + callback(); + }, + putPrivateKey: function(prk, callback) { + callback(); + }, + getPublicKeyByUserId: function(userId, callback) { + callback(); } + }; + emaildaoTest.keychain = new KeychainDAO(cloudstorageStub); + emaildaoTest.emailDao = new EmailDAO(cloudstorageStub, emaildaoTest.keychain); - emaildao_test.storage.storeEcryptedList(encryptedList, 'email_inbox', function() { - ok(true, 'Store encrypted list'); + // generate test data + emaildaoTest.list = testData.getEmailCollection(100); + + var account = new app.model.Account({ + emailAddress: emaildaoTest.user, + symKeySize: emaildaoTest.keySize, + symIvSize: emaildaoTest.ivSize, + asymKeySize: emaildaoTest.rsaKeySize + }); + + // clear db before tests + jsonDao.clear(function(err) { + ok(!err, 'DB cleared. Error status: ' + err); + + emaildaoTest.emailDao.init(account, emaildaoTest.password, function(err) { + ok(!err); + equal(emaildaoTest.emailDao.account.get('emailAddress'), emaildaoTest.user, 'Email DAO Account'); start(); }); }); }); -}); -asyncTest("List Email models", 2, function() { - emaildao_test.emailDao.listItems('inbox', 0, emaildao_test.list.length, function(err, gotten) { - ok(!err); + asyncTest("Persist test emails", 4, function() { + emaildaoTest.keychain.getUserKeyPair(emaildaoTest.user, function(err, keypair) { + ok(!err && keypair, 'Fetch keypair from keychain'); - var reference = emaildao_test.list.toJSON(); + var receiverPubkeys = [keypair.publicKey]; - deepEqual(gotten, reference, 'Compare collection'); + crypto.encryptListForUser(emaildaoTest.list.toJSON(), receiverPubkeys, function(err, encryptedList) { + ok(!err); + equal(encryptedList.length, emaildaoTest.list.length, 'Encrypt list'); + // add sent date to encrypted items + for (var i = 0; i < encryptedList.length; i++) { + encryptedList[i].sentDate = emaildaoTest.list.at(i).get('sentDate'); + } + + storage.storeEcryptedList(encryptedList, 'email_inbox', function() { + ok(true, 'Store encrypted list'); + + start(); + }); + }); + }); + }); + + asyncTest("List Email models", 2, function() { + emaildaoTest.emailDao.listItems('inbox', 0, emaildaoTest.list.length, function(err, gotten) { + ok(!err); + + var reference = emaildaoTest.list.toJSON(); + + deepEqual(gotten, reference, 'Compare collection'); + + start(); + }); + }); + + asyncTest("Get item", 1, function() { + var item = emaildaoTest.list.toJSON()[0]; + var mail = emaildaoTest.emailDao.getItem('inbox', item.id); + deepEqual(mail, item, 'Item correct'); start(); }); -}); -asyncTest("Get item", 1, function() { - var item = emaildao_test.list.toJSON()[0]; - var mail = emaildao_test.emailDao.getItem('inbox', item.id); - deepEqual(mail, item, 'Item correct'); - start(); }); \ No newline at end of file diff --git a/test/unit/index.html b/test/unit/index.html index 0ea3954..54ceadf 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -1,25 +1,17 @@ - - - JavaScript Unit Tests - - - -
-
- - - - - - - - + + + JavaScript Unit Tests + + + +
+
+ + + + + + \ No newline at end of file diff --git a/test/unit/main.js b/test/unit/main.js index 6628832..d1c30c0 100644 --- a/test/unit/main.js +++ b/test/unit/main.js @@ -27,7 +27,8 @@ function startTests() { 'test/unit/lawnchair-dao-test', 'test/unit/keychain-dao-test', 'test/unit/crypto-test', - 'test/unit/devicestorage-dao-test' + 'test/unit/devicestorage-dao-test', + 'test/unit/email-dao-test' ], function() { //Tests loaded, run tests QUnit.start();