1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-26 10:52:17 -05:00

finished refactoring unit tests with email dao

This commit is contained in:
Tankred Hase 2013-06-10 23:50:26 +02:00
parent deb47fd4df
commit f1fd936141
7 changed files with 286 additions and 280 deletions

View File

@ -2,219 +2,229 @@
* A high-level Data-Access Api for handling Email synchronization * A high-level Data-Access Api for handling Email synchronization
* between the cloud service and the device's local storage * 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'; 'use strict';
/** var EmailDAO = function(cloudstorage, keychain) {
* Inits all dependencies var self = this;
*/
this.init = function(account, password, callback) {
this.account = account;
// validate email address /**
var emailAddress = account.get('emailAddress'); * Inits all dependencies
if (!validateEmail(emailAddress)) { */
callback({ self.init = function(account, password, callback) {
errMsg: 'The user email address must be specified!' self.account = account;
});
return;
}
// init user's local database // validate email address
jsonDB.init(emailAddress); var emailAddress = account.get('emailAddress');
if (!validateEmail(emailAddress)) {
// call getUserKeyPair to read/sync keypair with devicestorage/cloud callback({
keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) { errMsg: 'The user email address must be specified!'
if (err) { });
callback(err);
return; return;
} }
// init crypto
initCrypto(storedKeypair);
});
function initCrypto(storedKeypair) { // init user's local database
crypto.init({ jsonDB.init(emailAddress);
emailAddress: emailAddress,
password: password, // call getUserKeyPair to read/sync keypair with devicestorage/cloud
keySize: account.get('symKeySize'), keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) {
rsaKeySize: account.get('asymKeySize'),
storedKeypair: storedKeypair
}, function(err, generatedKeypair) {
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
// init crypto
if (generatedKeypair) { initCrypto(storedKeypair);
// persist newly generated keypair
keychain.putUserKeyPair(generatedKeypair, callback);
} else {
callback();
}
}); });
}
};
/** function initCrypto(storedKeypair) {
* Fetch an email with the following id crypto.init({
*/ emailAddress: emailAddress,
this.getItem = function(folderName, itemId) { password: password,
var folder = this.account.get('folders').where({ keySize: account.get('symKeySize'),
name: folderName rsaKeySize: account.get('asymKeySize'),
})[0]; storedKeypair: storedKeypair
var mail = _.find(folder.get('items'), function(email) { }, function(err, generatedKeypair) {
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) {
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
// decrypt list if (generatedKeypair) {
crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) { // 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) { if (err) {
callback(err); callback(err);
return; return;
} }
// cache collection in folder memory // decrypt list
if (decryptedList.length > 0) { crypto.decryptListForUser(encryptedList, senderPubkeys, function(err, decryptedList) {
folder = new app.model.Folder({ if (err) {
name: folderName callback(err);
}); return;
folder.set('items', decryptedList); }
self.account.get('folders').add(folder);
} // 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 * Send a plaintext Email to the user's outbox in the cloud
collection = folder.get('items'); */
callback(null, collection); self.sendEmail = function(email, callback) {
} var userId = self.account.get('emailAddress');
};
/** // validate email addresses
* Synchronize a folder's items from the cloud to the device-storage var invalidRecipient;
* @param folderName [String] The name of the folder e.g. 'inbox' _.each(email.to, function(address) {
*/ if (!validateEmail(address)) {
this.syncFromCloud = function(folderName, callback) { invalidRecipient = address;
var folder, self = this; }
});
cloudstorage.listEncryptedItems('email', this.account.get('emailAddress'), folderName, function(err, data) { if (invalidRecipient) {
// return if an error occured
if (err) {
callback({ callback({
errMsg: 'Syncing encrypted items from cloud failed!', errMsg: 'Invalid recipient: ' + invalidRecipient
err: err });
}); // error return;
}
if (!validateEmail(email.from)) {
callback({
errMsg: 'Invalid sender: ' + email.from
});
return; 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 // send email to cloud service
devicestorage.storeEcryptedList(data, 'email_' + folderName, function() { cloudstorage.putEncryptedItem(email, 'email', userId, 'outbox', function(err) {
// remove cached folder in account model callback(err);
folder = self.account.get('folders').where({
name: folderName
})[0];
if (folder) {
self.account.get('folders').remove(folder);
}
callback();
}); });
}); };
}; };
/** //
* Send a plaintext Email to the user's outbox in the cloud // helper functions
*/ //
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);
});
};
function validateEmail(email) { 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,}))$/; 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); return re.test(email);
} }
}; return EmailDAO;
});

View File

@ -1,4 +1,4 @@
(function() { define(['backbone', 'js/model/folder-model'], function(Backbone) {
'use strict'; 'use strict';
app.model.Account = Backbone.Model.extend({ app.model.Account = Backbone.Model.extend({
@ -23,4 +23,4 @@
}); });
}()); });

View File

@ -1,4 +1,4 @@
(function() { define(['backbone'], function(Backbone) {
'use strict'; 'use strict';
app.model.Folder = Backbone.Model.extend({ app.model.Folder = Backbone.Model.extend({
@ -18,4 +18,4 @@
}); });
}()); });

View File

@ -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'; 'use strict';
module("DeviceStorage"); module("DeviceStorage");

View File

@ -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 = { var emaildaoTest = {
user: 'test@atlasdev.onmicrosoft.com', user: 'test@atlasdev.onmicrosoft.com',
password: 'Xoza76645', password: 'Xoza76645',
keySize: 128, keySize: 128,
ivSize: 128, ivSize: 128,
rsaKeySize: 1024 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();
}
}; };
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 asyncTest("Init", 3, function() {
emaildao_test.list = new TestData().getEmailCollection(100); // init dependencies
jsonDao.init(emaildaoTest.user);
var account = new app.model.Account({ // cloud storage stub
emailAddress: emaildao_test.user, var cloudstorageStub = {
symKeySize: emaildao_test.keySize, putPublicKey: function(pk, callback) {
symIvSize: emaildao_test.ivSize, callback();
asymKeySize: emaildao_test.rsaKeySize },
}); putPrivateKey: function(prk, callback) {
callback();
// clear db before tests },
jsonDao.clear(function(err) { getPublicKeyByUserId: function(userId, callback) {
ok(!err, 'DB cleared. Error status: ' + err); callback();
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');
} }
};
emaildaoTest.keychain = new KeychainDAO(cloudstorageStub);
emaildaoTest.emailDao = new EmailDAO(cloudstorageStub, emaildaoTest.keychain);
emaildao_test.storage.storeEcryptedList(encryptedList, 'email_inbox', function() { // generate test data
ok(true, 'Store encrypted list'); 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(); start();
}); });
}); });
}); });
});
asyncTest("List Email models", 2, function() { asyncTest("Persist test emails", 4, function() {
emaildao_test.emailDao.listItems('inbox', 0, emaildao_test.list.length, function(err, gotten) { emaildaoTest.keychain.getUserKeyPair(emaildaoTest.user, function(err, keypair) {
ok(!err); 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(); 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();
}); });

View File

@ -1,25 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>JavaScript Unit Tests</title> <title>JavaScript Unit Tests</title>
<link rel="stylesheet" href="../qunit-1.11.0.css"> <link rel="stylesheet" href="../qunit-1.11.0.css">
</head> </head>
<body> <body>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"></div> <div id="qunit-fixture"></div>
<script src="../qunit-1.11.0.js"></script> <script src="../qunit-1.11.0.js"></script>
<script>QUnit.config.autostart = false;</script> <script>QUnit.config.autostart = false;</script>
<script data-main="main.js" src="../../src/lib/require.js"></script> <script data-main="main.js" src="../../src/lib/require.js"></script>
<!--<script src="aes-test.js"></script> </body>
<script src="rsa-test.js"></script>
<script src="lawnchair-dao-test.js"></script>
<script src="keychain-dao-test.js"></script>
<script src="crypto-test.js"></script>
<script src="devicestorage-test.js"></script>
<script src="email-dao-test.js"></script>-->
</body>
</html> </html>

View File

@ -27,7 +27,8 @@ function startTests() {
'test/unit/lawnchair-dao-test', 'test/unit/lawnchair-dao-test',
'test/unit/keychain-dao-test', 'test/unit/keychain-dao-test',
'test/unit/crypto-test', 'test/unit/crypto-test',
'test/unit/devicestorage-dao-test' 'test/unit/devicestorage-dao-test',
'test/unit/email-dao-test'
], function() { ], function() {
//Tests loaded, run tests //Tests loaded, run tests
QUnit.start(); QUnit.start();