Merge pull request #235 from whiteout-io/dev/WO-766

Dev/wo 766
This commit is contained in:
Tankred Hase 2014-12-19 15:29:40 +01:00
commit 37fb3f5f8d
86 changed files with 5937 additions and 6341 deletions

View File

@ -1,51 +1,52 @@
{ {
"indent": 4, "indent": 4,
"strict": true, "strict": true,
"globalstrict": true, "globalstrict": true,
"node": true, "node": true,
"browser": true, "browser": true,
"nonew": true, "nonew": true,
"curly": true, "curly": true,
"eqeqeq": true, "eqeqeq": true,
"immed": true, "immed": true,
"newcap": true, "newcap": true,
"regexp": true, "regexp": true,
"evil": true, "evil": true,
"eqnull": true, "eqnull": true,
"expr": true, "expr": true,
"trailing": true, "trailing": true,
"undef": true, "undef": true,
"unused": true, "unused": true,
"predef": [ "predef": [
"$", "$",
"inject", "inject",
"Promise", "Promise",
"self", "resolves",
"importScripts", "rejects",
"console", "self",
"process", "importScripts",
"chrome", "console",
"Notification", "process",
"Event", "chrome",
"sinon", "Notification",
"mocha", "Event",
"chai", "sinon",
"expect", "mocha",
"describe", "chai",
"it", "expect",
"before", "describe",
"beforeEach", "it",
"after", "before",
"afterEach", "beforeEach",
"FastClick", "after",
"angular", "afterEach",
"forge", "FastClick",
"Lawnchair", "angular",
"_", "forge",
"openpgp" "Lawnchair",
], "_",
"openpgp"
],
"globals": { "globals": {}
}
} }

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dialog) { var AccountCtrl = function($scope, $q, auth, keychain, pgp, appConfig, download, dialog) {
var userId = auth.emailAddress; var userId = auth.emailAddress;
if (!userId) { if (!userId) {
return; return;
@ -34,12 +34,13 @@ var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dia
// //
$scope.exportKeyFile = function() { $scope.exportKeyFile = function() {
keychain.getUserKeyPair(userId, function(err, keys) { return $q(function(resolve) {
if (err) { resolve();
dialog.error(err);
return;
}
}).then(function() {
return keychain.getUserKeyPair(userId);
}).then(function(keys) {
var keyId = keys.publicKey._id; var keyId = keys.publicKey._id;
var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length); var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length);
@ -48,7 +49,8 @@ var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dia
filename: file + '.asc', filename: file + '.asc',
contentType: 'text/plain' contentType: 'text/plain'
}); });
});
}).catch(dialog.error);
}; };
}; };

View File

@ -2,7 +2,7 @@
var JUNK_FOLDER_TYPE = 'Junk'; var JUNK_FOLDER_TYPE = 'Junk';
var ActionBarCtrl = function($scope, email, dialog, status) { var ActionBarCtrl = function($scope, $q, email, dialog, status) {
// //
// scope functions // scope functions
@ -23,24 +23,28 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
// show message // show message
status.update('Moving message...'); status.update('Moving message...');
email.moveMessage({ return $q(function(resolve) {
folder: currentFolder(), resolve();
destination: destination,
message: message }).then(function() {
}, function(err) { return email.moveMessage({
if (err) { folder: currentFolder(),
// show errors where appropriate destination: destination,
if (err.code === 42) { message: message
$scope.select(message); });
status.update('Unable to move message in offline mode!');
return; }).then(function() {
} status.update('Online');
status.update('Error during move!');
dialog.error(err); }).catch(function(err) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
status.update('Unable to move message in offline mode!');
return; return;
} }
status.update('Message moved.'); status.update('Error during move!');
$scope.$apply(); return dialog.error(err);
}); });
}; };
@ -83,23 +87,27 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
status.setReading(false); status.setReading(false);
status.update('Deleting message...'); status.update('Deleting message...');
email.deleteMessage({ return $q(function(resolve) {
folder: currentFolder(), resolve();
message: message
}, function(err) { }).then(function() {
if (err) { return email.deleteMessage({
// show errors where appropriate folder: currentFolder(),
if (err.code === 42) { message: message
$scope.select(message); });
status.update('Unable to delete message in offline mode!');
return; }).then(function() {
} status.update('Online');
status.update('Error during delete!');
dialog.error(err); }).catch(function(err) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
status.update('Unable to delete message in offline mode!');
return; return;
} }
status.update('Message deleted.'); status.update('Error during delete!');
$scope.$apply(); return dialog.error(err);
}); });
}; };
@ -130,25 +138,29 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
var originalState = message.unread; var originalState = message.unread;
message.unread = unread; message.unread = unread;
email.setFlags({
folder: currentFolder(), return $q(function(resolve) {
message: message resolve();
}, function(err) {
if (err && err.code === 42) { }).then(function() {
return email.setFlags({
folder: currentFolder(),
message: message
});
}).then(function() {
status.update('Online');
}).catch(function(err) {
if (err.code === 42) {
// offline, restore // offline, restore
message.unread = originalState; message.unread = originalState;
status.update('Unable to mark message in offline mode!'); status.update('Unable to mark message in offline mode!');
return; return;
} }
if (err) { status.update('Error on sync!');
status.update('Error on sync!'); return dialog.error(err);
dialog.error(err);
return;
}
status.update('Online');
$scope.$apply();
}); });
}; };
@ -176,25 +188,29 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
var originalState = message.flagged; var originalState = message.flagged;
message.flagged = flagged; message.flagged = flagged;
email.setFlags({
folder: currentFolder(), return $q(function(resolve) {
message: message resolve();
}, function(err) {
if (err && err.code === 42) { }).then(function() {
return email.setFlags({
folder: currentFolder(),
message: message
});
}).then(function() {
status.update('Online');
}).catch(function(err) {
if (err.code === 42) {
// offline, restore // offline, restore
message.unread = originalState; message.unread = originalState;
status.update('Unable to ' + (flagged ? 'add star to' : 'remove star from') + ' message in offline mode!'); status.update('Unable to ' + (flagged ? 'add star to' : 'remove star from') + ' message in offline mode!');
return; return;
} }
if (err) { status.update('Error on sync!');
status.update('Error on sync!'); return dialog.error(err);
dialog.error(err);
return;
}
status.update('Online');
$scope.$apply();
}); });
}; };
@ -208,12 +224,27 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
}); });
}; };
/**
* This method is called when the user changes the searchText
*/
$scope.displaySearchResults = function(searchText) {
$scope.$root.$broadcast('search', searchText);
};
//
// scope state
//
// share local scope functions with root state // share local scope functions with root state
$scope.state.actionBar = { $scope.state.actionBar = {
markMessage: $scope.markMessage, markMessage: $scope.markMessage,
flagMessage: $scope.flagMessage flagMessage: $scope.flagMessage
}; };
//
// Helper functions
//
function currentFolder() { function currentFolder() {
return $scope.state.nav.currentFolder; return $scope.state.nav.currentFolder;
} }
@ -223,13 +254,6 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
return message.checked; return message.checked;
}); });
} }
/**
* This method is called when the user changes the searchText
*/
$scope.displaySearchResults = function(searchText) {
$scope.$root.$broadcast('search', searchText);
};
}; };
module.exports = ActionBarCtrl; module.exports = ActionBarCtrl;

View File

@ -4,7 +4,7 @@
// Controller // Controller
// //
var ContactsCtrl = function($scope, keychain, pgp, dialog) { var ContactsCtrl = function($scope, $q, keychain, pgp, dialog) {
// //
// scope state // scope state
@ -13,7 +13,7 @@ var ContactsCtrl = function($scope, keychain, pgp, dialog) {
$scope.state.contacts = { $scope.state.contacts = {
toggle: function(to) { toggle: function(to) {
$scope.state.lightbox = (to) ? 'contacts' : undefined; $scope.state.lightbox = (to) ? 'contacts' : undefined;
$scope.listKeys(); return $scope.listKeys();
} }
}; };
@ -22,22 +22,21 @@ var ContactsCtrl = function($scope, keychain, pgp, dialog) {
// //
$scope.listKeys = function() { $scope.listKeys = function() {
keychain.listLocalPublicKeys(function(err, keys) { return $q(function(resolve) {
if (err) { resolve();
dialog.error(err);
return;
}
keys.forEach(addParams); }).then(function() {
return keychain.listLocalPublicKeys();
$scope.keys = keys; }).then(function(keys) {
$scope.$apply(); // add params to key objects
keys.forEach(function(key) {
function addParams(key) {
var params = pgp.getKeyParams(key.publicKey); var params = pgp.getKeyParams(key.publicKey);
_.extend(key, params); _.extend(key, params);
} });
}); $scope.keys = keys;
}).catch(dialog.error);
}; };
$scope.getFingerprint = function(key) { $scope.getFingerprint = function(key) {
@ -74,29 +73,18 @@ var ContactsCtrl = function($scope, keychain, pgp, dialog) {
imported: true // mark manually imported keys imported: true // mark manually imported keys
}; };
keychain.saveLocalPublicKey(pubkey, function(err) { return keychain.saveLocalPublicKey(pubkey).then(function() {
if (err) {
dialog.error(err);
return;
}
// update displayed keys // update displayed keys
$scope.listKeys(); return $scope.listKeys();
}); }).catch(dialog.error);
}; };
$scope.removeKey = function(key) { $scope.removeKey = function(key) {
keychain.removeLocalPublicKey(key._id, function(err) { return keychain.removeLocalPublicKey(key._id).then(function() {
if (err) {
dialog.error(err);
return;
}
// update displayed keys // update displayed keys
$scope.listKeys(); return $scope.listKeys();
}); }).catch(dialog.error);
}; };
}; };
module.exports = ContactsCtrl; module.exports = ContactsCtrl;

View File

@ -40,6 +40,7 @@ var DialogCtrl = function($scope, dialog) {
$scope.positiveBtnStr = options.positiveBtnStr || 'Ok'; $scope.positiveBtnStr = options.positiveBtnStr || 'Ok';
$scope.negativeBtnStr = options.negativeBtnStr || 'Cancel'; $scope.negativeBtnStr = options.negativeBtnStr || 'Cancel';
$scope.showNegativeBtn = options.showNegativeBtn || false; $scope.showNegativeBtn = options.showNegativeBtn || false;
$scope.showBugReporter = false;
$scope.callback = options.callback; $scope.callback = options.callback;
} }

View File

@ -11,7 +11,7 @@ var INIT_DISPLAY_LEN = 50,
FOLDER_TYPE_INBOX = 'Inbox', FOLDER_TYPE_INBOX = 'Inbox',
NOTIFICATION_INBOX_TIMEOUT = 5000; NOTIFICATION_INBOX_TIMEOUT = 5000;
var MailListCtrl = function($scope, $timeout, $location, $filter, status, notification, email, keychain, dialog, search, dummy) { var MailListCtrl = function($scope, $timeout, $location, $filter, $q, status, notification, email, keychain, dialog, search, dummy) {
// //
// scope state // scope state
@ -55,23 +55,26 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
// //
$scope.getBody = function(message) { $scope.getBody = function(message) {
email.getBody({ return $q(function(resolve) {
folder: currentFolder(), resolve();
message: message
}, function(err) {
if (err && err.code !== 42) {
dialog.error(err);
return;
}
// display fetched body }).then(function() {
$scope.$digest(); return email.getBody({
folder: currentFolder(),
message: message
});
}).then(function() {
// automatically decrypt if it's the selected message // automatically decrypt if it's the selected message
if (message === currentMessage()) { if (message === currentMessage()) {
email.decryptBody({ return email.decryptBody({
message: message message: message
}, dialog.error); });
}
}).catch(function(err) {
if (err.code !== 42) {
dialog.error(err);
} }
}); });
}; };
@ -93,19 +96,20 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
return; return;
} }
keychain.refreshKeyForUserId({ return $q(function(resolve) {
userId: message.from[0].address resolve();
}, onKeyRefreshed);
function onKeyRefreshed(err) { }).then(function() {
if (err) { return keychain.refreshKeyForUserId({
dialog.error(err); userId: message.from[0].address
} });
email.decryptBody({ }).then(function() {
return email.decryptBody({
message: message message: message
}, dialog.error); });
}).then(function() {
// if the message is unread, please sync the new state. // if the message is unread, please sync the new state.
// otherweise forget about it. // otherweise forget about it.
if (!message.unread) { if (!message.unread) {
@ -119,12 +123,13 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
} }
} }
$scope.state.actionBar.markMessage(message, false, true); return $scope.state.actionBar.markMessage(message, false, true);
}
}).catch(dialog.error);
}; };
$scope.flag = function(message, flagged) { $scope.flag = function(message, flagged) {
$scope.state.actionBar.flagMessage(message, flagged); return $scope.state.actionBar.flagMessage(message, flagged);
}; };
/** /**
@ -164,7 +169,7 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
} }
// display and select first // display and select first
openCurrentFolder(); return openCurrentFolder();
}); });
$scope.watchMessages = $scope.$watchCollection('state.nav.currentFolder.messages', function(messages) { $scope.watchMessages = $scope.$watchCollection('state.nav.currentFolder.messages', function(messages) {
@ -246,10 +251,10 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
*/ */
$scope.watchOnline = $scope.$watch('account.online', function(isOnline) { $scope.watchOnline = $scope.$watch('account.online', function(isOnline) {
// wait one cycle for the status display controllers to init // wait one cycle for the status display controllers to init
$timeout(function() { return $timeout(function() {
if (isOnline) { if (isOnline) {
status.update('Online'); status.update('Online');
openCurrentFolder(); return openCurrentFolder();
} else { } else {
status.update('Offline mode'); status.update('Offline mode');
} }
@ -265,18 +270,24 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
return; return;
} }
email.openFolder({ return $q(function(resolve) {
folder: currentFolder() resolve();
}, function(error) {
}).then(function() {
return email.openFolder({
folder: currentFolder()
}).catch(function(err) {
// don't display err for offline case
if (err.code !== 42) {
throw err;
}
});
}).then(function() {
// dont wait until scroll to load visible mail bodies // dont wait until scroll to load visible mail bodies
$scope.loadVisibleBodies(); $scope.loadVisibleBodies();
// don't display error for offline case }).catch(dialog.error);
if (error && error.code === 42) {
return;
}
dialog.error(error);
});
} }
function currentFolder() { function currentFolder() {

View File

@ -11,7 +11,7 @@ var NOTIFICATION_SENT_TIMEOUT = 2000;
// Controller // Controller
// //
var NavigationCtrl = function($scope, $location, account, email, outbox, notification, appConfig, dialog, dummy) { var NavigationCtrl = function($scope, $location, $q, account, email, outbox, notification, appConfig, dialog, dummy) {
if (!$location.search().dev && !account.isLoggedIn()) { if (!$location.search().dev && !account.isLoggedIn()) {
$location.path('/'); // init app $location.path('/'); // init app
return; return;
@ -102,18 +102,24 @@ var NavigationCtrl = function($scope, $location, account, email, outbox, notific
}); });
ob.count = count; ob.count = count;
email.refreshFolder({ return $q(function(resolve) {
folder: ob resolve();
}, dialog.error);
}).then(function() {
return email.refreshFolder({
folder: ob
});
}).catch(dialog.error);
}; };
$scope.logout = function() { $scope.logout = function() {
dialog.confirm({ return dialog.confirm({
title: str.logoutTitle, title: str.logoutTitle,
message: str.logoutMessage, message: str.logoutMessage,
callback: function(confirm) { callback: function(confirm) {
if (confirm) { if (confirm) {
account.logout(); account.logout().catch(dialog.error);
} }
} }
}); });

View File

@ -2,7 +2,7 @@
var util = require('crypto-lib').util; var util = require('crypto-lib').util;
var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) { var PrivateKeyUploadCtrl = function($scope, $q, keychain, pgp, dialog, auth) {
// //
// scope state // scope state
@ -19,16 +19,15 @@ var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) {
// show syncing status // show syncing status
$scope.step = 4; $scope.step = 4;
// check if key is already synced // check if key is already synced
$scope.checkServerForKey(function(privateKeySynced) { return $scope.checkServerForKey().then(function(privateKeySynced) {
if (privateKeySynced) { if (privateKeySynced) {
// close lightbox // close lightbox
$scope.state.lightbox = undefined; $scope.state.lightbox = undefined;
// show message // show message
dialog.info({ return dialog.info({
title: 'Info', title: 'Info',
message: 'Your PGP key has already been synced.' message: 'Your PGP key has already been synced.'
}); });
return;
} }
// show sync ui if key is not synced // show sync ui if key is not synced
@ -41,24 +40,22 @@ var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) {
// scope functions // scope functions
// //
$scope.checkServerForKey = function(callback) { $scope.checkServerForKey = function() {
var keyParams = pgp.getKeyParams(); var keyParams = pgp.getKeyParams();
keychain.hasPrivateKey({
userId: keyParams.userId,
keyId: keyParams._id
}, function(err, privateKeySynced) {
if (err) {
dialog.error(err);
return;
}
if (privateKeySynced) { return $q(function(resolve) {
callback(privateKeySynced); resolve();
return;
}
callback(); }).then(function() {
}); return keychain.hasPrivateKey({
userId: keyParams.userId,
keyId: keyParams._id
});
}).then(function(privateKeySynced) {
return privateKeySynced ? privateKeySynced : undefined;
}).catch(dialog.error);
}; };
$scope.displayUploadUi = function() { $scope.displayUploadUi = function() {
@ -82,29 +79,37 @@ var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) {
return true; return true;
}; };
$scope.setDeviceName = function(callback) { $scope.setDeviceName = function() {
keychain.setDeviceName($scope.deviceName, callback); return $q(function(resolve) {
resolve();
}).then(function() {
return keychain.setDeviceName($scope.deviceName);
});
}; };
$scope.encryptAndUploadKey = function(callback) { $scope.encryptAndUploadKey = function() {
var userId = auth.emailAddress; var userId = auth.emailAddress;
var code = $scope.code; var code = $scope.code;
// register device to keychain service // register device to keychain service
keychain.registerDevice({ return $q(function(resolve) {
userId: userId resolve();
}, function(err) {
if (err) {
dialog.error(err);
return;
}
}).then(function() {
// register the device
return keychain.registerDevice({
userId: userId
});
}).then(function() {
// encrypt private PGP key using code and upload // encrypt private PGP key using code and upload
keychain.uploadPrivateKey({ return keychain.uploadPrivateKey({
userId: userId, userId: userId,
code: code code: code
}, callback); });
});
}).catch(dialog.error);
}; };
$scope.goBack = function() { $scope.goBack = function() {
@ -126,32 +131,22 @@ var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) {
if ($scope.step === 3) { if ($scope.step === 3) {
// set device name to local storage // set device name to local storage
$scope.setDeviceName(function(err) { return $scope.setDeviceName().then(function() {
if (err) {
dialog.error(err);
return;
}
// show spinner // show spinner
$scope.step++; $scope.step++;
$scope.$apply();
// init key sync // init key sync
$scope.encryptAndUploadKey(function(err) { return $scope.encryptAndUploadKey();
if (err) {
dialog.error(err);
return;
}
// close sync dialog }).then(function() {
$scope.state.privateKeyUpload.toggle(false); // close sync dialog
// show success message $scope.state.privateKeyUpload.toggle(false);
dialog.info({ // show success message
title: 'Success', dialog.info({
message: 'Whiteout Keychain setup successful!' title: 'Success',
}); message: 'Whiteout Keychain setup successful!'
}); });
});
}).catch(dialog.error);
} }
}; };

View File

@ -4,7 +4,7 @@
// Controller // Controller
// //
var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) { var ReadCtrl = function($scope, $location, $q, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) {
var str = appConfig.string; var str = appConfig.string;
@ -52,25 +52,24 @@ var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keych
return; return;
} }
$scope.keyId = 'Searching...'; return $q(function(resolve) {
keychain.getReceiverPublicKey(address, function(err, pubkey) { $scope.keyId = 'Searching...';
if (err) { resolve();
dialog.error(err);
return;
}
}).then(function() {
return keychain.getReceiverPublicKey(address);
}).then(function(pubkey) {
if (!pubkey) { if (!pubkey) {
$scope.keyId = 'User has no key. Click to invite.'; $scope.keyId = 'User has no key. Click to invite.';
$scope.$apply();
return; return;
} }
var fpr = pgp.getFingerprint(pubkey.publicKey); var fpr = pgp.getFingerprint(pubkey.publicKey);
var formatted = fpr.slice(32); var formatted = fpr.slice(32);
$scope.keyId = 'PGP key: ' + formatted; $scope.keyId = 'PGP key: ' + formatted;
$scope.$apply();
}); }).catch(dialog.error);
}; };
$scope.$watch('state.mailList.selected', function(mail) { $scope.$watch('state.mailList.selected', function(mail) {
@ -89,24 +88,20 @@ var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keych
function checkPublicKey(user) { function checkPublicKey(user) {
user.secure = undefined; user.secure = undefined;
if (!keychain) { return $q(function(resolve) {
return; resolve();
}
keychain.getReceiverPublicKey(user.address, function(err, pubkey) { }).then(function() {
if (err) { return keychain.getReceiverPublicKey(user.address);
dialog.error(err);
return;
}
}).then(function(pubkey) {
if (pubkey && pubkey.publicKey) { if (pubkey && pubkey.publicKey) {
user.secure = true; user.secure = true;
} else { } else {
user.secure = false; user.secure = false;
} }
$scope.$apply(); }).catch(dialog.error);
});
} }
$scope.download = function(attachment) { $scope.download = function(attachment) {
@ -122,11 +117,18 @@ var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keych
var folder = $scope.state.nav.currentFolder; var folder = $scope.state.nav.currentFolder;
var message = $scope.state.mailList.selected; var message = $scope.state.mailList.selected;
email.getAttachment({
folder: folder, return $q(function(resolve) {
uid: message.uid, resolve();
attachment: attachment
}, dialog.error); }).then(function() {
return email.getAttachment({
folder: folder,
uid: message.uid,
attachment: attachment
});
}).catch(dialog.error);
}; };
$scope.invite = function(user) { $scope.invite = function(user) {
@ -135,20 +137,20 @@ var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keych
return; return;
} }
$scope.keyId = 'Sending invitation...';
var sender = auth.emailAddress, var sender = auth.emailAddress,
recipient = user.address; recipient = user.address;
invitation.invite({ return $q(function(resolve) {
recipient: recipient, $scope.keyId = 'Sending invitation...';
sender: sender resolve();
}, function(err) {
if (err) {
dialog.error(err);
return;
}
}).then(function() {
return invitation.invite({
recipient: recipient,
sender: sender
});
}).then(function() {
var invitationMail = { var invitationMail = {
from: [{ from: [{
address: sender address: sender
@ -161,10 +163,10 @@ var ReadCtrl = function($scope, $location, email, invitation, outbox, pgp, keych
subject: str.invitationSubject, subject: str.invitationSubject,
body: str.invitationMessage body: str.invitationMessage
}; };
// send invitation mail // send invitation mail
outbox.put(invitationMail, dialog.error); return outbox.put(invitationMail);
});
}).catch(dialog.error);
}; };
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) { var SetPassphraseCtrl = function($scope, $q, pgp, keychain, dialog) {
// //
// scope variables // scope variables
@ -76,52 +76,44 @@ var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
$scope.setPassphrase = function() { $scope.setPassphrase = function() {
var keyId = pgp.getKeyParams()._id; var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) {
if (err) {
dialog.error(err);
return;
}
pgp.changePassphrase({ return $q(function(resolve) {
resolve();
}).then(function() {
return keychain.lookupPrivateKey(keyId);
}).then(function(savedKey) {
// change passphrase
return pgp.changePassphrase({
privateKeyArmored: savedKey.encryptedKey, privateKeyArmored: savedKey.encryptedKey,
oldPassphrase: $scope.oldPassphrase, oldPassphrase: $scope.oldPassphrase,
newPassphrase: $scope.newPassphrase newPassphrase: $scope.newPassphrase
}, onPassphraseChanged); }).catch(function(err) {
}); err.showBugReporter = false;
throw err;
});
}).then(function(newPrivateKeyArmored) {
// persist new armored key
var keyParams = pgp.getKeyParams(newPrivateKeyArmored);
var privateKey = {
_id: keyParams._id,
userId: keyParams.userId,
userIds: keyParams.userIds,
encryptedKey: newPrivateKeyArmored
};
return keychain.saveLocalPrivateKey(privateKey);
}).then(function() {
$scope.state.setPassphrase.toggle(false);
return dialog.info({
title: 'Success',
message: 'Passphrase change complete.'
});
}).catch(dialog.error);
}; };
function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) {
err.showBugReporter = false;
dialog.error(err);
return;
}
// persist new armored key
var keyParams = pgp.getKeyParams(newPrivateKeyArmored);
var privateKey = {
_id: keyParams._id,
userId: keyParams.userId,
userIds: keyParams.userIds,
encryptedKey: newPrivateKeyArmored
};
keychain.saveLocalPrivateKey(privateKey, onKeyPersisted);
}
function onKeyPersisted(err) {
if (err) {
dialog.error(err);
return;
}
$scope.state.setPassphrase.toggle(false);
$scope.$apply();
dialog.info({
title: 'Success',
message: 'Passphrase change complete.'
});
}
}; };
module.exports = SetPassphraseCtrl; module.exports = SetPassphraseCtrl;

View File

@ -218,34 +218,31 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain
return; return;
} }
// keychain is undefined in local dev environment // check if to address is contained in known public keys
if (keychain) { // when we write an email, we always need to work with the latest keys available
// check if to address is contained in known public keys return $q(function(resolve) {
// when we write an email, we always need to work with the latest keys available resolve();
keychain.refreshKeyForUserId({
}).then(function() {
return keychain.refreshKeyForUserId({
userId: recipient.address userId: recipient.address
}, function(err, key) {
if (err) {
dialog.error(err);
return;
}
if (key) {
// compare again since model could have changed during the roundtrip
var matchingUserId = _.findWhere(key.userIds, {
emailAddress: recipient.address
});
// compare either primary userId or (if available) multiple IDs
if (key.userId === recipient.address || matchingUserId) {
recipient.key = key;
recipient.secure = true;
}
}
$scope.checkSendStatus();
$scope.$digest();
}); });
}
}).then(function(key) {
if (key) {
// compare again since model could have changed during the roundtrip
var matchingUserId = _.findWhere(key.userIds, {
emailAddress: recipient.address
});
// compare either primary userId or (if available) multiple IDs
if (key.userId === recipient.address || matchingUserId) {
recipient.key = key;
recipient.secure = true;
}
}
$scope.checkSendStatus();
}).catch(dialog.error);
}; };
/** /**
@ -347,12 +344,13 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain
} }
// persist the email to disk for later sending // persist the email to disk for later sending
outbox.put(message, function(err) { return $q(function(resolve) {
if (err) { resolve();
dialog.error(err);
return;
}
}).then(function() {
return outbox.put(message);
}).then(function() {
// if we need to synchronize replyTo.answered = true to imap, // if we need to synchronize replyTo.answered = true to imap,
// let's do that. otherwise, we're done // let's do that. otherwise, we're done
if (!$scope.replyTo || $scope.replyTo.answered) { if (!$scope.replyTo || $scope.replyTo.answered) {
@ -360,20 +358,16 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain
} }
$scope.replyTo.answered = true; $scope.replyTo.answered = true;
email.setFlags({ return email.setFlags({
folder: currentFolder(), folder: currentFolder(),
message: $scope.replyTo message: $scope.replyTo
}, function(err) {
if (err && err.code !== 42) {
dialog.error(err);
return;
}
// offline or no error, let's apply the ui changes
$scope.$apply();
}); });
});
}).catch(function(err) {
if (err.code !== 42) {
dialog.error(err);
}
});
}; };
// //
@ -389,37 +383,29 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain
}; };
$scope.lookupAddressBook = function(query) { $scope.lookupAddressBook = function(query) {
var deferred = $q.defer(); return $q(function(resolve) {
resolve();
if (!$scope.addressBookCache) { }).then(function() {
if ($scope.addressBookCache) {
return;
}
// populate address book cache // populate address book cache
keychain.listLocalPublicKeys(function(err, keys) { return keychain.listLocalPublicKeys().then(function(keys) {
if (err) {
dialog.error(err);
return;
}
$scope.addressBookCache = keys.map(function(key) { $scope.addressBookCache = keys.map(function(key) {
return { return {
address: key.userId address: key.userId
}; };
}); });
filter();
}); });
} else { }).then(function() {
filter(); // filter the address book cache
} return $scope.addressBookCache.filter(function(i) {
// query address book cache
function filter() {
var addresses = $scope.addressBookCache.filter(function(i) {
return i.address.indexOf(query) !== -1; return i.address.indexOf(query) !== -1;
}); });
deferred.resolve(addresses);
}
return deferred.promise; }).catch(dialog.error);
}; };
// //

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth, dialog) { var AddAccountCtrl = function($scope, $location, $routeParams, $timeout, $q, mailConfig, auth, dialog) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.getAccountSettings = function() { $scope.getAccountSettings = function() {
@ -9,10 +9,15 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
return; return;
} }
$scope.busy = true; return $q(function(resolve) {
$scope.errMsg = undefined; // reset error msg $scope.busy = true;
$scope.errMsg = undefined; // reset error msg
resolve();
return mailConfig.get($scope.emailAddress).then(function(config) { }).then(function() {
return mailConfig.get($scope.emailAddress);
}).then(function(config) {
$scope.busy = false; $scope.busy = false;
$scope.state.login = { $scope.state.login = {
mailConfig: config, mailConfig: config,
@ -22,10 +27,10 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
var hostname = config.imap.hostname; var hostname = config.imap.hostname;
if (auth.useOAuth(hostname)) { if (auth.useOAuth(hostname)) {
// check for oauth support // check for oauth support
$scope.oauthPossible(); return $scope.oauthPossible();
} else { } else {
// use standard password login // use standard password login
$scope.setCredentials(); return $scope.setCredentials();
} }
}).catch(function() { }).catch(function() {
@ -36,7 +41,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
$scope.oauthPossible = function() { $scope.oauthPossible = function() {
// ask user to use the platform's native OAuth api // ask user to use the platform's native OAuth api
dialog.confirm({ return dialog.confirm({
title: 'Google Account Login', title: 'Google Account Login',
message: 'You are signing into a Google account. Would you like to sign in with Google or just continue with a password login?', message: 'You are signing into a Google account. Would you like to sign in with Google or just continue with a password login?',
positiveBtnStr: 'Google sign in', positiveBtnStr: 'Google sign in',
@ -46,29 +51,28 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
callback: function(granted) { callback: function(granted) {
if (granted) { if (granted) {
// query oauth token // query oauth token
getOAuthToken(); return getOAuthToken();
} else { } else {
// use normal user/password login // use normal user/password login
$scope.setCredentials(); $scope.setCredentials();
$scope.$apply();
} }
} }
}); });
function getOAuthToken() { function getOAuthToken() {
// fetches the email address from the chrome identity api // fetches the email address from the chrome identity api
auth.getOAuthToken(function(err) { return auth.getOAuthToken().then(function() {
if (err) { // continue to setting credentials
return dialog.error(err); return $scope.setCredentials();
}
$scope.setCredentials(); }).catch(dialog.error);
$scope.$apply();
});
} }
}; };
$scope.setCredentials = function() { $scope.setCredentials = function() {
$location.path('/login-set-credentials'); return $timeout(function() {
$location.path('/login-set-credentials');
});
}; };
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var CreateAccountCtrl = function($scope, $location, $routeParams, auth, admin, appConfig) { var CreateAccountCtrl = function($scope, $location, $routeParams, $q, auth, admin, appConfig) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.createWhiteoutAccount = function() { $scope.createWhiteoutAccount = function() {
@ -9,35 +9,37 @@ var CreateAccountCtrl = function($scope, $location, $routeParams, auth, admin, a
return; return;
} }
$scope.busy = true;
$scope.errMsg = undefined; // reset error msg
var emailAddress = $scope.user + '@' + appConfig.config.wmailDomain; var emailAddress = $scope.user + '@' + appConfig.config.wmailDomain;
// set to state for next view return $q(function(resolve) {
auth.setCredentials({ $scope.busy = true;
emailAddress: emailAddress, $scope.errMsg = undefined; // reset error msg
password: $scope.pass, resolve();
realname: $scope.realname
});
// call REST api }).then(function() {
admin.createUser({ // set to state for next view
emailAddress: emailAddress, auth.setCredentials({
password: $scope.pass, emailAddress: emailAddress,
phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number password: $scope.pass,
betaCode: $scope.betaCode.toUpperCase() realname: $scope.realname
}, function(err) { });
// call REST api
return admin.createUser({
emailAddress: emailAddress,
password: $scope.pass,
phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number
betaCode: $scope.betaCode.toUpperCase()
});
}).then(function() {
$scope.busy = false; $scope.busy = false;
if (err) {
$scope.errMsg = err.errMsg || err.message;
$scope.$apply();
return;
}
// proceed to login and keygen // proceed to login and keygen
$location.path('/validate-phone'); $location.path('/validate-phone');
$scope.$apply();
}).catch(function(err) {
$scope.busy = false;
$scope.errMsg = err.errMsg || err.message;
}); });
}; };
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, keychain) { var LoginExistingCtrl = function($scope, $location, $routeParams, $q, email, auth, keychain) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.confirmPassphrase = function() { $scope.confirmPassphrase = function() {
@ -9,50 +9,39 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, k
return; return;
} }
$scope.busy = true; return $q(function(resolve) {
$scope.errMsg = undefined; $scope.busy = true;
$scope.incorrect = false; $scope.errMsg = undefined;
$scope.incorrect = false;
resolve();
unlockCrypto(); }).then(function() {
}; // key keypair
var userId = auth.emailAddress;
return keychain.getUserKeyPair(userId);
function unlockCrypto() { }).then(function(keypair) {
var userId = auth.emailAddress; // unlock email service
keychain.getUserKeyPair(userId, function(err, keypair) { return email.unlock({
if (err) {
displayError(err);
return;
}
email.unlock({
keypair: keypair, keypair: keypair,
passphrase: $scope.passphrase passphrase: $scope.passphrase
}, onUnlock); });
});
}
function onUnlock(err) { }).then(function() {
if (err) { // persist credentials locally
displayError(err); return auth.storeCredentials();
return;
}
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;
}
}).then(function() {
// go to main account screen
$location.path('/account'); $location.path('/account');
$scope.$apply();
}); }).catch(displayError);
} };
function displayError(err) { function displayError(err) {
$scope.busy = false; $scope.busy = false;
$scope.incorrect = true; $scope.incorrect = true;
$scope.errMsg = err.errMsg || err.message; $scope.errMsg = err.errMsg || err.message;
$scope.$apply();
} }
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter, email, auth) { var LoginInitialCtrl = function($scope, $location, $routeParams, $q, newsletter, email, auth) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
var emailAddress = auth.emailAddress; var emailAddress = auth.emailAddress;
@ -44,31 +44,30 @@ var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter, ema
return; return;
} }
$scope.errMsg = undefined;
// sing up to newsletter // sing up to newsletter
newsletter.signup(emailAddress, $scope.newsletter); newsletter.signup(emailAddress, $scope.newsletter);
// go to set keygen screen // go to set keygen screen
$scope.setState(states.PROCESSING); $scope.setState(states.PROCESSING);
email.unlock({ return $q(function(resolve) {
passphrase: undefined // generate key without passphrase $scope.errMsg = undefined;
}, function(err) { resolve();
if (err) {
displayError(err);
return;
}
auth.storeCredentials(function(err) { }).then(function() {
if (err) { // generate key without passphrase
displayError(err); return email.unlock({
return; passphrase: undefined
}
$location.path('/account');
$scope.$apply();
}); });
});
}).then(function() {
// persist credentials locally
return auth.storeCredentials();
}).then(function() {
// go to main account screen
$location.path('/account');
}).catch(displayError);
}; };
$scope.setState = function(state) { $scope.setState = function(state) {
@ -78,7 +77,6 @@ var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter, ema
function displayError(err) { function displayError(err) {
$scope.setState(states.IDLE); $scope.setState(states.IDLE);
$scope.errMsg = err.errMsg || err.message; $scope.errMsg = err.errMsg || err.message;
$scope.$apply();
} }
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, pgp, keychain) { var LoginExistingCtrl = function($scope, $location, $routeParams, $q, email, auth, pgp, keychain) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.incorrect = false; $scope.incorrect = false;
@ -11,31 +11,28 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, p
return; return;
} }
$scope.busy = true; var userId = auth.emailAddress,
$scope.errMsg = undefined; // reset error msg keypair;
$scope.incorrect = false;
unlockCrypto(); return $q(function(resolve) {
}; $scope.busy = true;
$scope.errMsg = undefined; // reset error msg
$scope.incorrect = false;
resolve();
function unlockCrypto() { }).then(function() {
var userId = auth.emailAddress; // check if user already has a public key on the key server
// check if user already has a public key on the key server return keychain.getUserKeyPair(userId);
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
$scope.displayError(err);
return;
}
keypair = keypair || {}; }).then(function(keys) {
keypair = keys || {};
// extract public key from private key block if missing in key file // extract public key from private key block if missing in key file
if (!$scope.key.publicKeyArmored || $scope.key.publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) { if (!$scope.key.publicKeyArmored || $scope.key.publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
try { try {
$scope.key.publicKeyArmored = pgp.extractPublicKey($scope.key.privateKeyArmored); $scope.key.publicKeyArmored = pgp.extractPublicKey($scope.key.privateKeyArmored);
} catch (e) { } catch (e) {
$scope.displayError(new Error('Error reading PGP key!')); throw new Error('Error reading PGP key!');
return;
} }
} }
@ -45,8 +42,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, p
privKeyParams = pgp.getKeyParams($scope.key.privateKeyArmored); privKeyParams = pgp.getKeyParams($scope.key.privateKeyArmored);
pubKeyParams = pgp.getKeyParams($scope.key.publicKeyArmored); pubKeyParams = pgp.getKeyParams($scope.key.publicKeyArmored);
} catch (e) { } catch (e) {
$scope.displayError(new Error('Error reading key params!')); throw new Error('Error reading key paramaters!');
return;
} }
// set parsed private key // set parsed private key
@ -68,43 +64,33 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, p
} }
// import and validate keypair // import and validate keypair
email.unlock({ return email.unlock({
keypair: keypair, keypair: keypair,
passphrase: $scope.passphrase passphrase: $scope.passphrase
}, function(err) { }).catch(function(err) {
if (err) { $scope.incorrect = true;
$scope.incorrect = true; throw err;
$scope.displayError(err);
return;
}
keychain.putUserKeyPair(keypair, onUnlock);
}); });
});
}
function onUnlock(err) { }).then(function() {
if (err) { // perist keys locally
$scope.displayError(err); return keychain.putUserKeyPair(keypair);
return;
}
auth.storeCredentials(function(err) { }).then(function() {
if (err) { // persist credentials locally
$scope.displayError(err); return auth.storeCredentials();
return;
}
}).then(function() {
// go to main account screen
$location.path('/account'); $location.path('/account');
$scope.$apply();
});
}
$scope.displayError = function(err) { }).catch(displayError);
};
function displayError(err) {
$scope.busy = false; $scope.busy = false;
$scope.errMsg = err.errMsg || err.message; $scope.errMsg = err.errMsg || err.message;
$scope.$apply(); }
};
}; };
module.exports = LoginExistingCtrl; module.exports = LoginExistingCtrl;

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth, email, keychain) { var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, $q, auth, email, keychain) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.step = 1; $scope.step = 1;
@ -15,42 +15,32 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth
return; return;
} }
$scope.busy = true;
$scope.errMsg = undefined;
$scope.verifyRecoveryToken(function() {
$scope.busy = false;
$scope.errMsg = undefined;
$scope.step++;
$scope.$apply();
});
};
$scope.verifyRecoveryToken = function(callback) {
var userId = auth.emailAddress; var userId = auth.emailAddress;
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
displayError(err);
return;
}
return $q(function(resolve) {
$scope.busy = true;
$scope.errMsg = undefined;
resolve();
}).then(function() {
// get public key id for reference
return keychain.getUserKeyPair(userId);
}).then(function(keypair) {
// remember for storage later // remember for storage later
$scope.cachedKeypair = keypair; $scope.cachedKeypair = keypair;
return keychain.downloadPrivateKey({
keychain.downloadPrivateKey({
userId: userId, userId: userId,
keyId: keypair.publicKey._id, keyId: keypair.publicKey._id,
recoveryToken: $scope.recoveryToken.toUpperCase() recoveryToken: $scope.recoveryToken.toUpperCase()
}, function(err, encryptedPrivateKey) {
if (err) {
displayError(err);
return;
}
$scope.encryptedPrivateKey = encryptedPrivateKey;
callback();
}); });
});
}).then(function(encryptedPrivateKey) {
$scope.encryptedPrivateKey = encryptedPrivateKey;
$scope.busy = false;
$scope.step++;
}).catch(displayError);
}; };
// //
@ -63,47 +53,39 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth
return; return;
} }
$scope.busy = true;
$scope.errMsg = undefined;
$scope.decryptAndStorePrivateKeyLocally();
};
$scope.decryptAndStorePrivateKeyLocally = function() {
var options = $scope.encryptedPrivateKey; var options = $scope.encryptedPrivateKey;
options.code = $scope.code.toUpperCase(); options.code = $scope.code.toUpperCase();
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) { return $q(function(resolve) {
if (err) { $scope.busy = true;
displayError(err); $scope.errMsg = undefined;
return; resolve();
}
}).then(function() {
return keychain.decryptAndStorePrivateKeyLocally(options);
}).then(function(privateKey) {
// add private key to cached keypair object // add private key to cached keypair object
$scope.cachedKeypair.privateKey = privateKey; $scope.cachedKeypair.privateKey = privateKey;
// try empty passphrase // try empty passphrase
email.unlock({ return email.unlock({
keypair: $scope.cachedKeypair, keypair: $scope.cachedKeypair,
passphrase: undefined passphrase: undefined
}, function(err) { }).catch(function(err) {
if (err) { // passphrase incorrct ... go to passphrase login screen
// go to passphrase login screen $scope.goTo('/login-existing');
$scope.goTo('/login-existing'); throw err;
return;
}
// passphrase is corrent ... go to main app
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;
}
$scope.goTo('/account');
});
}); });
});
}).then(function() {
// passphrase is corrent ...
return auth.storeCredentials();
}).then(function() {
// continue to main app
$scope.goTo('/account');
}).catch(displayError);
}; };
// //
@ -112,14 +94,12 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth
$scope.goTo = function(location) { $scope.goTo = function(location) {
$location.path(location); $location.path(location);
$scope.$apply();
}; };
function displayError(err) { function displayError(err) {
$scope.busy = false; $scope.busy = false;
$scope.incorrect = true; $scope.incorrect = true;
$scope.errMsg = err.errMsg || err.message; $scope.errMsg = err.errMsg || err.message;
$scope.$apply();
} }
}; };

View File

@ -4,7 +4,7 @@ var ENCRYPTION_METHOD_NONE = 0;
var ENCRYPTION_METHOD_STARTTLS = 1; var ENCRYPTION_METHOD_STARTTLS = 1;
var ENCRYPTION_METHOD_TLS = 2; var ENCRYPTION_METHOD_TLS = 2;
var SetCredentialsCtrl = function($scope, $location, $routeParams, auth, connectionDoctor) { var SetCredentialsCtrl = function($scope, $location, $routeParams, $q, auth, connectionDoctor) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
// //
@ -82,19 +82,23 @@ var SetCredentialsCtrl = function($scope, $location, $routeParams, auth, connect
connectionDoctor.configure(credentials); connectionDoctor.configure(credentials);
// run connection doctor test suite // run connection doctor test suite
$scope.busy = true; return $q(function(resolve) {
connectionDoctor.check(function(err) { $scope.busy = true;
if (err) { resolve();
// display the error in the settings UI
$scope.connectionError = err;
} else {
// persists the credentials and forwards to /login
auth.setCredentials(credentials);
$location.path('/login');
}
}).then(function() {
return connectionDoctor.check();
}).then(function() {
// persists the credentials and forwards to /login
auth.setCredentials(credentials);
$scope.busy = false;
$location.path('/login');
}).catch(function(err) {
// display the error in the settings UI
$scope.connectionError = err;
$scope.busy = false; $scope.busy = false;
$scope.$apply();
}); });
}; };
}; };

View File

@ -1,91 +1,74 @@
'use strict'; 'use strict';
var LoginCtrl = function($scope, $timeout, $location, updateHandler, account, auth, email, keychain, dialog) { var LoginCtrl = function($scope, $timeout, $location, updateHandler, account, auth, email, keychain, dialog, appConfig) {
// check for app update //
updateHandler.checkForUpdate(); // Scope functions
// initialize the user account //
initializeUser();
function initializeUser() { $scope.init = function() {
// init the auth modules // initialize the user account
auth.init(function(err) { return auth.init().then(function() {
if (err) { // get email address
return dialog.error(err); return auth.getEmailAddress();
}).then(function(info) {
// check if account needs to be selected
if (!info.emailAddress) {
return $scope.goTo('/add-account');
} }
// get OAuth token from chrome // initiate the account by initializing the email dao and user storage
auth.getEmailAddress(function(err, info) { return account.init({
if (err) { emailAddress: info.emailAddress,
dialog.error(err); realname: info.realname
return; }).then(function(availableKeys) {
} 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);
});
}); });
});
} }).catch(dialog.error);
};
function redirect(availableKeys) { function redirect(availableKeys) {
if (availableKeys && availableKeys.publicKey && availableKeys.privateKey) { if (availableKeys && availableKeys.publicKey && availableKeys.privateKey) {
// public and private key available, try empty passphrase // public and private key available, try empty passphrase
email.unlock({ var passphraseIncorrect;
return email.unlock({
keypair: availableKeys, keypair: availableKeys,
passphrase: undefined passphrase: undefined
}, function(err) { }).catch(function() {
if (err) { passphraseIncorrect = true;
$scope.goTo('/login-existing'); // passphrase set... ask for passphrase
return $scope.goTo('/login-existing');
}).then(function() {
if (passphraseIncorrect) {
return; return;
} }
// no passphrase set... go to main screen
auth.storeCredentials(function(err) { return auth.storeCredentials().then(function() {
if (err) { return $scope.goTo('/account');
return dialog.error(err);
}
$scope.goTo('/account');
}); });
}); });
} else if (availableKeys && availableKeys.publicKey && !availableKeys.privateKey) { } else if (availableKeys && availableKeys.publicKey && !availableKeys.privateKey) {
// check if private key is synced // check if private key is synced
keychain.requestPrivateKeyDownload({ return keychain.requestPrivateKeyDownload({
userId: availableKeys.publicKey.userId, userId: availableKeys.publicKey.userId,
keyId: availableKeys.publicKey._id, keyId: availableKeys.publicKey._id,
}, function(err, privateKeySynced) { }).then(function(privateKeySynced) {
if (err) {
dialog.error(err);
return;
}
if (privateKeySynced) { if (privateKeySynced) {
// private key is synced, proceed to download // private key is synced, proceed to download
$scope.goTo('/login-privatekey-download'); return $scope.goTo('/login-privatekey-download');
return; } else {
// no private key, import key file
return $scope.goTo('/login-new-device');
} }
// no private key, import key file
$scope.goTo('/login-new-device');
}); });
} else { } else {
// no public key available, start onboarding process // no public key available, start onboarding process
$scope.goTo('/login-initial'); return $scope.goTo('/login-initial');
} }
} }
@ -94,6 +77,19 @@ var LoginCtrl = function($scope, $timeout, $location, updateHandler, account, au
$location.path(location); $location.path(location);
}); });
}; };
//
// Start the app
//
// check for app update
updateHandler.checkForUpdate();
// init the app
if (!appConfig.preventAutoStart) {
$scope.init();
}
}; };
module.exports = LoginCtrl; module.exports = LoginCtrl;

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig, auth, admin) { var ValidatePhoneCtrl = function($scope, $location, $routeParams, $q, mailConfig, auth, admin) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.validateUser = function() { $scope.validateUser = function() {
@ -9,23 +9,25 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig, au
return; return;
} }
$scope.busy = true; return $q(function(resolve) {
$scope.errMsg = undefined; // reset error msg $scope.busy = true;
$scope.errMsg = undefined; // reset error msg
resolve();
// verify user to REST api }).then(function() {
admin.validateUser({ // verify user to REST api
emailAddress: auth.emailAddress, return admin.validateUser({
token: $scope.token.toUpperCase() emailAddress: auth.emailAddress,
}, function(err) { token: $scope.token.toUpperCase()
if (err) { });
$scope.busy = false;
$scope.errMsg = err.errMsg || err.message;
$scope.$apply();
return;
}
}).then(function() {
// proceed to login // proceed to login
$scope.login(); return $scope.login();
}).catch(function(err) {
$scope.busy = false;
$scope.errMsg = err.errMsg || err.message;
}); });
}; };
@ -54,10 +56,9 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig, au
$location.path('/login'); $location.path('/login');
}).catch(function() { }).catch(function() {
$scope.busy = false; throw new Error('Error fetching IMAP settings!');
$scope.errMsg = 'Error fetching IMAP settings!';
}); });
}; };
}; };
module.exports = ValidatePhoneCtrl; module.exports = ValidatePhoneCtrl;

View File

@ -20,20 +20,13 @@ function Crypto() {}
* @param {String} plaintext The input string in UTF-16 * @param {String} plaintext The input string in UTF-16
* @param {String} key The base64 encoded key * @param {String} key The base64 encoded key
* @param {String} iv The base64 encoded IV * @param {String} iv The base64 encoded IV
* @param {Function} callback(error, ciphertext)
* @return {String} The base64 encoded ciphertext * @return {String} The base64 encoded ciphertext
*/ */
Crypto.prototype.encrypt = function(plaintext, key, iv, callback) { Crypto.prototype.encrypt = function(plaintext, key, iv) {
var ct; return new Promise(function(resolve) {
var ct = aes.encrypt(plaintext, key, iv);
try { resolve(ct);
ct = aes.encrypt(plaintext, key, iv); });
} catch (err) {
callback(err);
return;
}
callback(null, ct);
}; };
/** /**
@ -41,34 +34,26 @@ Crypto.prototype.encrypt = function(plaintext, key, iv, callback) {
* @param {String} ciphertext The base64 encoded ciphertext * @param {String} ciphertext The base64 encoded ciphertext
* @param {String} key The base64 encoded key * @param {String} key The base64 encoded key
* @param {String} iv The base64 encoded IV * @param {String} iv The base64 encoded IV
* @param {Function} callback(error, plaintext)
* @return {String} The decrypted plaintext in UTF-16 * @return {String} The decrypted plaintext in UTF-16
*/ */
Crypto.prototype.decrypt = function(ciphertext, key, iv, callback) { Crypto.prototype.decrypt = function(ciphertext, key, iv) {
var pt; return new Promise(function(resolve) {
var pt = aes.decrypt(ciphertext, key, iv);
try { resolve(pt);
pt = aes.decrypt(ciphertext, key, iv); });
} catch (err) {
callback(err);
return;
}
callback(null, pt);
}; };
/** /**
* Do PBKDF2 key derivation in a WebWorker thread * Do PBKDF2 key derivation in a WebWorker thread
*/ */
Crypto.prototype.deriveKey = function(password, salt, keySize, callback) { Crypto.prototype.deriveKey = function(password, salt, keySize) {
startWorker({ return this.startWorker({
script: config.workerPath + '/pbkdf2-worker.min.js', script: config.workerPath + '/pbkdf2-worker.min.js',
args: { args: {
password: password, password: password,
salt: salt, salt: salt,
keySize: keySize keySize: keySize
}, },
callback: callback,
noWorker: function() { noWorker: function() {
return pbkdf2.getKey(password, salt, keySize); return pbkdf2.getKey(password, salt, keySize);
} }
@ -79,43 +64,33 @@ Crypto.prototype.deriveKey = function(password, salt, keySize, callback) {
// helper functions // helper functions
// //
function startWorker(options) { Crypto.prototype.startWorker = function(options) {
// check for WebWorker support return new Promise(function(resolve, reject) {
if (window.Worker) { // check for WebWorker support
// init webworker thread if (window.Worker) {
var worker = new Worker(options.script); // init webworker thread
worker.onmessage = function(e) { var worker = new Worker(options.script);
if (e.data.err) { worker.onmessage = function(e) {
options.callback(e.data.err); // return result from the worker
return; if (e.data.err) {
} reject(e.data.err);
// return result from the worker } else {
options.callback(null, e.data); resolve(e.data);
}; }
worker.onerror = function(e) { };
// show error message in logger worker.onerror = function(e) {
axe.error('Error handling web worker: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message); // show error message in logger
// return error axe.error('Error handling web worker: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message);
options.callback({ // return error
errMsg: (e.message) ? e.message : e reject(e);
}); };
// send data to the worker
worker.postMessage(options.args);
return; return;
}; }
// send data to the worker
worker.postMessage(options.args);
return;
}
// no WebWorker support... do synchronous call // no WebWorker support... do synchronous call
var result; var result = options.noWorker();
try { resolve(result);
result = options.noWorker(); });
} catch (e) { };
// return error
options.callback({
errMsg: (e.message) ? e.message : e
});
return;
}
options.callback(null, result);
}

View File

@ -17,30 +17,39 @@ function PGP() {
/** /**
* Generate a key pair for the user * Generate a key pair for the user
* @return {Promise}
*/ */
PGP.prototype.generateKeys = function(options, callback) { PGP.prototype.generateKeys = function(options) {
var userId, passphrase; return new Promise(function(resolve) {
var userId, passphrase;
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) { if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
callback(new Error('Crypto init failed. Not all options set!')); throw new Error('Crypto init failed. Not all options set!');
return; }
}
// generate keypair // generate keypair
userId = 'Whiteout User <' + options.emailAddress + '>'; userId = 'Whiteout User <' + options.emailAddress + '>';
passphrase = (options.passphrase) ? options.passphrase : undefined; passphrase = (options.passphrase) ? options.passphrase : undefined;
openpgp.generateKeyPair({
keyType: 1, // (keytype 1=RSA) resolve({
numBits: options.keySize, userId: userId,
userId: userId, passphrase: passphrase
passphrase: passphrase });
}).then(function(res) {
return openpgp.generateKeyPair({
keyType: 1, // (keytype 1=RSA)
numBits: options.keySize,
userId: res.userId,
passphrase: res.passphrase
});
}).then(function(keys) { }).then(function(keys) {
callback(null, { return {
keyId: keys.key.primaryKey.getKeyId().toHex().toUpperCase(), keyId: keys.key.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: keys.privateKeyArmored, privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored publicKeyArmored: keys.publicKeyArmored
}); };
}).catch(callback); });
}; };
/** /**
@ -141,153 +150,156 @@ PGP.prototype.extractPublicKey = function(privateKeyArmored) {
/** /**
* Import the user's key pair * Import the user's key pair
* @return {Promise}
*/ */
PGP.prototype.importKeys = function(options, callback) { PGP.prototype.importKeys = function(options) {
var pubKeyId, privKeyId, self = this; var self = this;
return new Promise(function(resolve) {
var pubKeyId, privKeyId;
// check options // check options
if (!options.privateKeyArmored || !options.publicKeyArmored) { if (!options.privateKeyArmored || !options.publicKeyArmored) {
callback(new Error('Importing keys failed. Not all options set!')); throw new Error('Importing keys failed. Not all options set!');
return; }
}
function resetKeys() { function resetKeys() {
self._publicKey = undefined; self._publicKey = undefined;
self._privateKey = undefined; self._privateKey = undefined;
} }
// read armored keys // read armored keys
try { try {
this._publicKey = openpgp.key.readArmored(options.publicKeyArmored).keys[0]; self._publicKey = openpgp.key.readArmored(options.publicKeyArmored).keys[0];
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0]; self._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) { } catch (e) {
resetKeys(); resetKeys();
callback(new Error('Importing keys failed. Parsing error!')); throw new Error('Importing keys failed. Parsing error!');
return; }
}
// decrypt private key with passphrase // decrypt private key with passphrase
if (!this._privateKey.decrypt(options.passphrase)) { if (!self._privateKey.decrypt(options.passphrase)) {
resetKeys(); resetKeys();
callback(new Error('Incorrect passphrase!')); throw new Error('Incorrect passphrase!');
return; }
}
// check if keys have the same id // check if keys have the same id
pubKeyId = this._publicKey.primaryKey.getKeyId().toHex(); pubKeyId = self._publicKey.primaryKey.getKeyId().toHex();
privKeyId = this._privateKey.primaryKey.getKeyId().toHex(); privKeyId = self._privateKey.primaryKey.getKeyId().toHex();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) { if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
resetKeys(); resetKeys();
callback(new Error('Key IDs dont match!')); throw new Error('Key IDs dont match!');
return; }
}
callback(); resolve();
});
}; };
/** /**
* Export the user's key pair * Export the user's key pair
* @return {Promise}
*/ */
PGP.prototype.exportKeys = function(callback) { PGP.prototype.exportKeys = function() {
if (!this._publicKey || !this._privateKey) { var self = this;
callback(new Error('Could not export keys!')); return new Promise(function(resolve) {
return; if (!self._publicKey || !self._privateKey) {
} throw new Error('Could not export keys!');
}
callback(null, { resolve({
keyId: this._publicKey.primaryKey.getKeyId().toHex().toUpperCase(), keyId: self._publicKey.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: this._privateKey.armor(), privateKeyArmored: self._privateKey.armor(),
publicKeyArmored: this._publicKey.armor() publicKeyArmored: self._publicKey.armor()
});
}); });
}; };
/** /**
* Change the passphrase of an ascii armored private key. * Change the passphrase of an ascii armored private key.
* @return {Promise}
*/ */
PGP.prototype.changePassphrase = function(options, callback) { PGP.prototype.changePassphrase = function(options) {
var privKey, packets, newPassphrase, newKeyArmored; return new Promise(function(resolve) {
var privKey, packets, newPassphrase, newKeyArmored;
// set undefined instead of empty string as passphrase // set undefined instead of empty string as passphrase
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined; newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
if (!options.privateKeyArmored) { if (!options.privateKeyArmored) {
callback(new Error('Private key must be specified to change passphrase!')); throw new Error('Private key must be specified to change passphrase!');
return;
}
if (options.oldPassphrase === newPassphrase ||
(!options.oldPassphrase && !newPassphrase)) {
callback(new Error('New and old passphrase are the same!'));
return;
}
// read armored key
try {
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
callback(new Error('Importing key failed. Parsing error!'));
return;
}
// decrypt private key with passphrase
if (!privKey.decrypt(options.oldPassphrase)) {
callback(new Error('Old passphrase incorrect!'));
return;
}
// encrypt key with new passphrase
try {
packets = privKey.getAllKeyPackets();
for (var i = 0; i < packets.length; i++) {
packets[i].encrypt(newPassphrase);
} }
newKeyArmored = privKey.armor();
} catch (e) {
callback(new Error('Setting new passphrase failed!'));
return;
}
// check if new passphrase really works if (options.oldPassphrase === newPassphrase ||
if (!privKey.decrypt(newPassphrase)) { (!options.oldPassphrase && !newPassphrase)) {
callback(new Error('Decrypting key with new passphrase failed!')); throw new Error('New and old passphrase are the same!');
return; }
}
callback(null, newKeyArmored); // read armored key
try {
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
throw new Error('Importing key failed. Parsing error!');
}
// decrypt private key with passphrase
if (!privKey.decrypt(options.oldPassphrase)) {
throw new Error('Old passphrase incorrect!');
}
// encrypt key with new passphrase
try {
packets = privKey.getAllKeyPackets();
for (var i = 0; i < packets.length; i++) {
packets[i].encrypt(newPassphrase);
}
newKeyArmored = privKey.armor();
} catch (e) {
throw new Error('Setting new passphrase failed!');
}
// check if new passphrase really works
if (!privKey.decrypt(newPassphrase)) {
throw new Error('Decrypting key with new passphrase failed!');
}
resolve(newKeyArmored);
});
}; };
/** /**
* Encrypt and sign a pgp message for a list of receivers * Encrypt and sign a pgp message for a list of receivers
* @return {Promise}
*/ */
PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) { PGP.prototype.encrypt = function(plaintext, publicKeysArmored) {
var publicKeys; var self = this;
return new Promise(function(resolve) {
var publicKeys;
// check keys // check keys
if (!this._privateKey) { if (!self._privateKey) {
callback(new Error('Error encrypting. Keys must be set!')); throw new Error('Error encrypting. Keys must be set!');
return;
}
// parse armored public keys
try {
if (publicKeysArmored && publicKeysArmored.length) {
publicKeys = [];
publicKeysArmored.forEach(function(pubkeyArmored) {
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
});
} }
} catch (err) { // parse armored public keys
callback(new Error('Error encrypting plaintext!')); try {
return; if (publicKeysArmored && publicKeysArmored.length) {
} publicKeys = [];
publicKeysArmored.forEach(function(pubkeyArmored) {
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
});
}
} catch (err) {
throw new Error('Error encrypting plaintext!');
}
resolve(publicKeys);
if (publicKeys) { }).then(function(publicKeys) {
// encrypt and sign the plaintext if (publicKeys) {
openpgp.signAndEncryptMessage(publicKeys, this._privateKey, plaintext).then(callback.bind(null, null)).catch(callback); // encrypt and sign the plaintext
} else { return openpgp.signAndEncryptMessage(publicKeys, self._privateKey, plaintext);
// if no public keys are available encrypt for myself } else {
openpgp.signAndEncryptMessage([this._publicKey], this._privateKey, plaintext).then(callback.bind(null, null)).catch(callback); // if no public keys are available encrypt for myself
} return openpgp.signAndEncryptMessage([self._publicKey], self._privateKey, plaintext);
}
});
}; };
/** /**
@ -295,36 +307,45 @@ PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) {
* @param {String} ciphertext The encrypted PGP message block * @param {String} ciphertext The encrypted PGP message block
* @param {String} publicKeyArmored The public key used to sign the message * @param {String} publicKeyArmored The public key used to sign the message
* @param {Function} callback(error, plaintext, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed. * @param {Function} callback(error, plaintext, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
* @return {Promise}
*/ */
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) { PGP.prototype.decrypt = function(ciphertext, publicKeyArmored) {
var publicKeys, message; var self = this;
return new Promise(function(resolve) {
var publicKeys, message;
// check keys // check keys
if (!this._privateKey) { if (!self._privateKey) {
callback(new Error('Error decrypting. Keys must be set!')); throw new Error('Error decrypting. Keys must be set!');
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
} }
message = openpgp.message.readArmored(ciphertext); // read keys and ciphertext message
} catch (err) { try {
callback(new Error('Error parsing encrypted PGP message!')); if (publicKeyArmored) {
return; // parse public keys if available ...
} publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [self._publicKey];
}
message = openpgp.message.readArmored(ciphertext);
} catch (err) {
throw new Error('Error parsing encrypted PGP message!');
}
resolve({
publicKeys: publicKeys,
message: message
});
// decrypt and verify pgp message }).then(function(res) {
openpgp.decryptAndVerifyMessage(this._privateKey, publicKeys, message).then(function(decrypted) { // decrypt and verify pgp message
return openpgp.decryptAndVerifyMessage(self._privateKey, res.publicKeys, res.message);
}).then(function(decrypted) {
// return decrypted plaintext // return decrypted plaintext
callback(null, decrypted.text, checkSignatureValidity(decrypted.signatures)); return {
}).catch(callback); decrypted: decrypted.text,
signaturesValid: checkSignatureValidity(decrypted.signatures)
};
});
}; };
/** /**
@ -332,35 +353,40 @@ PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
* @param {String} clearSignedText The clearsigned text, usually from a signed pgp/inline message * @param {String} clearSignedText The clearsigned text, usually from a signed pgp/inline message
* @param {String} publicKeyArmored The public key used to signed the message * @param {String} publicKeyArmored The public key used to signed the message
* @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed. * @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
* @return {Promise}
*/ */
PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmored, callback) { PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmored) {
var publicKeys, var self = this;
message; return new Promise(function(resolve) {
var publicKeys, message;
// check keys // check keys
if (!this._privateKey) { if (!self._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!')); throw new Error('Error verifying signed PGP message. Keys must be set!');
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
} }
message = openpgp.cleartext.readArmored(clearSignedText); // read keys and ciphertext message
} catch (err) { try {
callback(new Error('Error verifying signed PGP message!')); if (publicKeyArmored) {
return; // parse public keys if available ...
} publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [self._publicKey];
}
message = openpgp.cleartext.readArmored(clearSignedText);
} catch (err) {
throw new Error('Error verifying signed PGP message!');
}
resolve({
publicKeys: publicKeys,
message: message
});
openpgp.verifyClearSignedMessage(publicKeys, message).then(function(result) { }).then(function(res) {
callback(null, checkSignatureValidity(result.signatures)); return openpgp.verifyClearSignedMessage(res.publicKeys, res.message);
}).catch(callback); }).then(function(result) {
return checkSignatureValidity(result.signatures);
});
}; };
/** /**
@ -369,40 +395,39 @@ PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmo
* @param {String} pgpSignature The detached signature, usually from a signed pgp/mime message * @param {String} pgpSignature The detached signature, usually from a signed pgp/mime message
* @param {String} publicKeyArmored The public key used to signed the message * @param {String} publicKeyArmored The public key used to signed the message
* @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed. * @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
* @return {Promise}
*/ */
PGP.prototype.verifySignedMessage = function(message, pgpSignature, publicKeyArmored, callback) { PGP.prototype.verifySignedMessage = function(message, pgpSignature, publicKeyArmored) {
var publicKeys; var self = this;
return new Promise(function(resolve) {
var publicKeys, signatures;
// check keys // check keys
if (!this._privateKey) { if (!self._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!')); throw new Error('Error verifying signed PGP message. Keys must be set!');
return; }
} // read keys and ciphertext message
try {
// read keys and ciphertext message if (publicKeyArmored) {
try { // parse public keys if available ...
if (publicKeyArmored) { publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
// parse public keys if available ... } else {
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys; // use own public key to know if signatures are available
} else { publicKeys = [self._publicKey];
// use own public key to know if signatures are available }
publicKeys = [this._publicKey]; } catch (err) {
throw new Error('Error verifying signed PGP message!');
}
// check signatures
try {
var msg = openpgp.message.readSignedContent(message, pgpSignature);
signatures = msg.verify(publicKeys);
} catch (err) {
throw new Error('Error verifying signed PGP message!');
} }
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
var signatures; resolve(checkSignatureValidity(signatures));
try { });
var msg = openpgp.message.readSignedContent(message, pgpSignature);
signatures = msg.verify(publicKeys);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
callback(null, checkSignatureValidity(signatures));
}; };
/** /**

View File

@ -41,7 +41,7 @@ Account.prototype.list = function() {
/** /**
* Fire up the database, retrieve the available keys for the user and initialize the email data access object * Fire up the database, retrieve the available keys for the user and initialize the email data access object
*/ */
Account.prototype.init = function(options, callback) { Account.prototype.init = function(options) {
var self = this; var self = this;
// account information for the email dao // account information for the email dao
@ -53,68 +53,43 @@ Account.prototype.init = function(options, callback) {
// Pre-Flight check: don't even start to initialize stuff if the email address is not valid // Pre-Flight check: don't even start to initialize stuff if the email address is not valid
if (!util.validateEmailAddress(options.emailAddress)) { if (!util.validateEmailAddress(options.emailAddress)) {
return callback(new Error('The user email address is invalid!')); return new Promise(function() {
throw new Error('The user email address is invalid!');
});
} }
prepareDatabase();
// Pre-Flight check: initialize and prepare user's local database // Pre-Flight check: initialize and prepare user's local database
function prepareDatabase() { return self._accountStore.init(options.emailAddress).then(function() {
self._accountStore.init(options.emailAddress, function(err) { // Migrate the databases if necessary
if (err) { return self._updateHandler.update().catch(function(err) {
return callback(err); throw new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message);
} });
// Migrate the databases if necessary }).then(function() {
self._updateHandler.update(function(err) { // retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before
if (err) { return self._keychain.getUserKeyPair(options.emailAddress);
return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message));
}
prepareKeys(); }).then(function(keys) {
// this is either a first start on a new device, OR a subsequent start without completing the signup,
// since we can't differenciate those cases here, do a public key refresh because it might be outdated
if (keys && keys.publicKey && !keys.privateKey) {
return self._keychain.refreshKeyForUserId({
userId: options.emailAddress,
overridePermission: true
}).then(function(publicKey) {
return {
publicKey: publicKey
};
}); });
}
// either signup was complete or no pubkey is available, so we're good here.
return keys;
}); }).then(function(keys) {
} // init the email data access object
return self._emailDao.init({
// retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before
function prepareKeys() {
self._keychain.getUserKeyPair(options.emailAddress, function(err, keys) {
if (err) {
return callback(err);
}
// this is either a first start on a new device, OR a subsequent start without completing the signup,
// since we can't differenciate those cases here, do a public key refresh because it might be outdated
if (keys && keys.publicKey && !keys.privateKey) {
self._keychain.refreshKeyForUserId({
userId: options.emailAddress,
overridePermission: true
}, function(err, publicKey) {
if (err) {
return callback(err);
}
initEmailDao({
publicKey: publicKey
});
});
return;
}
// either signup was complete or no pubkey is available, so we're good here.
initEmailDao(keys);
});
}
function initEmailDao(keys) {
self._emailDao.init({
account: account account: account
}, function(err) { }).then(function() {
if (err) {
return callback(err);
}
// Handle offline and online gracefully ... arm dom event // Handle offline and online gracefully ... arm dom event
window.addEventListener('online', self.onConnect.bind(self)); window.addEventListener('online', self.onConnect.bind(self));
window.addEventListener('offline', self.onDisconnect.bind(self)); window.addEventListener('offline', self.onDisconnect.bind(self));
@ -122,9 +97,9 @@ Account.prototype.init = function(options, callback) {
// add account object to the accounts array for the ng controllers // add account object to the accounts array for the ng controllers
self._accounts.push(account); self._accounts.push(account);
callback(null, keys); return keys;
}); });
} });
}; };
/** /**
@ -140,7 +115,7 @@ Account.prototype.isOnline = function() {
Account.prototype.onConnect = function(callback) { Account.prototype.onConnect = function(callback) {
var self = this; var self = this;
var config = self._appConfig.config; var config = self._appConfig.config;
callback = callback || self._dialog.error; callback = callback || self._dialog.error;
if (!self.isOnline() || !self._emailDao || !self._emailDao._account) { if (!self.isOnline() || !self._emailDao || !self._emailDao._account) {
@ -148,16 +123,8 @@ Account.prototype.onConnect = function(callback) {
return; return;
} }
self._auth.getCredentials(function(err, credentials) { // init imap/smtp clients
if (err) { self._auth.getCredentials().then(function(credentials) {
callback(err);
return;
}
initClients(credentials);
});
function initClients(credentials) {
// add the maximum update batch size for imap folders to the imap configuration // add the maximum update batch size for imap folders to the imap configuration
credentials.imap.maxUpdateSize = config.imapUpdateBatchSize; credentials.imap.maxUpdateSize = config.imapUpdateBatchSize;
@ -174,12 +141,12 @@ Account.prototype.onConnect = function(callback) {
pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self.onConnect.bind(self), self._dialog.error); pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self.onConnect.bind(self), self._dialog.error);
// connect to clients // connect to clients
self._emailDao.onConnect({ return self._emailDao.onConnect({
imapClient: imapClient, imapClient: imapClient,
pgpMailer: pgpMailer, pgpMailer: pgpMailer,
ignoreUploadOnSent: self._emailDao.checkIgnoreUploadOnSent(credentials.imap.host) ignoreUploadOnSent: self._emailDao.checkIgnoreUploadOnSent(credentials.imap.host)
}, callback); });
} }).then(callback).catch(callback);
function onConnectionError(error) { function onConnectionError(error) {
axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : '')); axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : ''));
@ -203,7 +170,7 @@ Account.prototype.onConnect = function(callback) {
* Event handler that is called when the user agent goes offline. * Event handler that is called when the user agent goes offline.
*/ */
Account.prototype.onDisconnect = function() { Account.prototype.onDisconnect = function() {
this._emailDao.onDisconnect(); return this._emailDao.onDisconnect();
}; };
/** /**
@ -211,28 +178,18 @@ Account.prototype.onDisconnect = function() {
*/ */
Account.prototype.logout = function() { Account.prototype.logout = function() {
var self = this; var self = this;
// clear app config store // clear app config store
self._auth.logout(function(err) { return self._auth.logout().then(function() {
if (err) {
self._dialog.error(err);
return;
}
// delete instance of imap-client and pgp-mailer // delete instance of imap-client and pgp-mailer
self._emailDao.onDisconnect(function(err) { return self._emailDao.onDisconnect();
if (err) {
self._dialog.error(err);
return;
}
if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) { }).then(function() {
// reload chrome app if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) {
chrome.runtime.reload(); // reload chrome app
} else { chrome.runtime.reload();
// navigate to login } else {
window.location.href = '/'; // navigate to login
} window.location.href = '/';
}); }
}); });
}; };

File diff suppressed because it is too large Load Diff

View File

@ -56,8 +56,9 @@ Outbox.prototype.stopChecking = function() {
* Put a email dto in the outbox for sending when ready * Put a email dto in the outbox for sending when ready
* @param {Object} mail The Email DTO * @param {Object} mail The Email DTO
* @param {Function} callback Invoked when the object was encrypted and persisted to disk * @param {Function} callback Invoked when the object was encrypted and persisted to disk
* @returns {Promise}
*/ */
Outbox.prototype.put = function(mail, callback) { Outbox.prototype.put = function(mail) {
var self = this, var self = this,
allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail
@ -66,67 +67,46 @@ Outbox.prototype.put = function(mail, callback) {
// do not encrypt mails with a bcc recipient, due to a possible privacy leak // do not encrypt mails with a bcc recipient, due to a possible privacy leak
if (mail.bcc.length > 0) { if (mail.bcc.length > 0) {
storeAndForward(mail); return storeAndForward(mail);
return;
} }
checkRecipients(allReaders); return checkRecipients(allReaders).then(checkEncrypt);
// check if there are unregistered recipients // check if there are unregistered recipients
function checkRecipients(recipients) { function checkRecipients(recipients) {
var after = _.after(recipients.length, function() { var pubkeyJobs = [];
checkEncrypt();
});
// find out if there are unregistered users
recipients.forEach(function(recipient) { recipients.forEach(function(recipient) {
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) { var promise = self._keychain.getReceiverPublicKey(recipient.address).then(function(key) {
if (err) {
callback(err);
return;
}
// if a public key is available, add the recipient's key to the armored public keys, // if a public key is available, add the recipient's key to the armored public keys,
// otherwise remember the recipient as unregistered for later sending // otherwise remember the recipient as unregistered for later sending
if (key) { if (key) {
mail.publicKeysArmored.push(key.publicKey); mail.publicKeysArmored.push(key.publicKey);
} }
after();
}); });
pubkeyJobs.push(promise);
}); });
return Promise.all(pubkeyJobs);
} }
function checkEncrypt() { function checkEncrypt() {
// only encrypt if all recipients have public keys // only encrypt if all recipients have public keys
if (mail.publicKeysArmored.length < allReaders.length) { if (mail.publicKeysArmored.length < allReaders.length) {
storeAndForward(mail); return storeAndForward(mail);
return;
} }
// encrypts the body and attachments and persists the mail object // encrypts the body and attachments and persists the mail object
self._emailDao.encrypt({ return self._emailDao.encrypt({
mail: mail, mail: mail,
publicKeysArmored: mail.publicKeysArmored publicKeysArmored: mail.publicKeysArmored
}, function(err) { }).then(function() {
if (err) { return storeAndForward(mail);
callback(err);
return;
}
storeAndForward(mail);
}); });
} }
function storeAndForward(mail) { function storeAndForward(mail) {
// store in outbox // store in outbox
self._devicestorage.storeList([mail], outboxDb, function(err) { return self._devicestorage.storeList([mail], outboxDb).then(function() {
if (err) {
callback(err);
return;
}
callback();
// don't wait for next round // don't wait for next round
self._processOutbox(self._onUpdate); self._processOutbox(self._onUpdate);
}); });
@ -149,83 +129,63 @@ Outbox.prototype._processOutbox = function(callback) {
self._outboxBusy = true; self._outboxBusy = true;
// get pending mails from the outbox // get pending mails from the outbox
self._devicestorage.listItems(outboxDb, 0, null, function(err, pendingMails) { self._devicestorage.listItems(outboxDb, 0, null).then(function(pendingMails) {
// error, we're done here // if we're not online, don't even bother sending mails.
if (err) { if (!self._emailDao._account.online || _.isEmpty(pendingMails)) {
self._outboxBusy = false; unsentMails = pendingMails.length;
callback(err);
return; return;
} }
// if we're not online, don't even bother sending mails. var sendJobs = [];
if (!self._emailDao._account.online || _.isEmpty(pendingMails)) { // send pending mails if possible
self._outboxBusy = false; pendingMails.forEach(function(mail) {
callback(null, pendingMails.length); sendJobs.push(send(mail));
return; });
}
// we're done after all the mails have been handled // we're done after all the mails have been handled
// update the outbox count... // update the outbox count...
var after = _.after(pendingMails.length, function() { return Promise.all(sendJobs);
self._outboxBusy = false;
callback(null, unsentMails);
});
// send pending mails if possible }).then(function() {
pendingMails.forEach(function(mail) { self._outboxBusy = false;
send(mail, after); callback(null, unsentMails);
});
}).catch(function(err) {
self._outboxBusy = false;
callback(err);
}); });
// send the message // send the message
function send(mail, done) { function send(mail) {
// check is email is to be sent encrypted or as plaintex // check is email is to be sent encrypted or as plaintex
if (mail.encrypted === true) { if (mail.encrypted === true) {
// email was already encrypted before persisting in outbox, tell pgpmailer to send encrypted and not encrypt again // email was already encrypted before persisting in outbox, tell pgpmailer to send encrypted and not encrypt again
self._emailDao.sendEncrypted({ return self._emailDao.sendEncrypted({
email: mail email: mail
}, onSend); }).then(onSend).catch(sendFailed);
} else { } else {
// send email as plaintext // send email as plaintext
self._emailDao.sendPlaintext({ return self._emailDao.sendPlaintext({
email: mail email: mail
}, onSend); }).then(onSend).catch(sendFailed);
} }
function onSend(err) { function onSend() {
if (err) {
self._outboxBusy = false;
if (err.code === 42) {
// offline try again later
done();
} else {
self._outboxBusy = false;
callback(err);
}
return;
}
// remove the pending mail from the storage
removeFromStorage(mail, done);
// fire sent notification // fire sent notification
if (typeof self.onSent === 'function') { if (typeof self.onSent === 'function') {
self.onSent(mail); self.onSent(mail);
} }
}
}
// removes the mail object from disk after successfully sending it // removes the mail object from disk after successfully sending it
function removeFromStorage(mail, done) { return self._devicestorage.removeList(outboxDb + '_' + mail.uid);
self._devicestorage.removeList(outboxDb + '_' + mail.uid, function(err) { }
if (err) {
self._outboxBusy = false; function sendFailed(err) {
callback(err); if (err.code === 42) {
// offline. resolve promise and try again later
return; return;
} }
throw err;
done(); }
});
} }
}; };

View File

@ -13,27 +13,24 @@ function Admin(adminRestDao) {
* @param {String} options.emailAddress The desired email address * @param {String} options.emailAddress The desired email address
* @param {String} options.password The password to be used for the account. * @param {String} options.password The password to be used for the account.
* @param {String} options.phone The user's mobile phone number (required for verification and password reset). * @param {String} options.phone The user's mobile phone number (required for verification and password reset).
* @param {Function} callback(error)
*/ */
Admin.prototype.createUser = function(options, callback) { Admin.prototype.createUser = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.emailAddress || !options.password || !options.phone) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options.emailAddress || !options.password || !options.phone) { }).then(function() {
callback(new Error('Incomplete arguments!')); return self._restDao.post(options, '/user');
return;
}
uri = '/user'; }).catch(function(err) {
this._restDao.post(options, uri, function(err) {
if (err && err.code === 409) { if (err && err.code === 409) {
callback(new Error('User name is already taken!')); throw new Error('User name is already taken!');
return;
} else if (err) {
callback(new Error('Error creating new user!'));
return;
} }
callback(); throw new Error('Error creating new user!');
}); });
}; };
@ -41,23 +38,25 @@ Admin.prototype.createUser = function(options, callback) {
* Verify a user's phone number by confirming a token to the server. * Verify a user's phone number by confirming a token to the server.
* @param {String} options.emailAddress The desired email address * @param {String} options.emailAddress The desired email address
* @param {String} options.token The validation token. * @param {String} options.token The validation token.
* @param {Function} callback(error)
*/ */
Admin.prototype.validateUser = function(options, callback) { Admin.prototype.validateUser = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.emailAddress || !options.token) { if (!options.emailAddress || !options.token) {
callback(new Error('Incomplete arguments!')); throw new Error('Incomplete arguments!');
return;
}
uri = '/user/validate';
this._restDao.post(options, uri, function(err) {
if (!err || (err && err.code === 202)) {
// success
callback();
} else {
callback(new Error('Validation failed!'));
} }
resolve();
}).then(function() {
var uri = '/user/validate';
return self._restDao.post(options, uri);
}).catch(function(err) {
if (err && err.code === 202) {
// success
return;
}
throw new Error('Validation failed!');
}); });
}; };

View File

@ -30,17 +30,17 @@ function Auth(appConfigStore, oauth, pgp) {
this._appConfigStore = appConfigStore; this._appConfigStore = appConfigStore;
this._oauth = oauth; this._oauth = oauth;
this._pgp = pgp; this._pgp = pgp;
this._initialized = false;
} }
/** /**
* Initialize the service * Initialize the service
*/ */
Auth.prototype.init = function(callback) { Auth.prototype.init = function() {
var self = this; var self = this;
return self._appConfigStore.init(APP_CONFIG_DB_NAME).then(function() {
self._appConfigStore.init(APP_CONFIG_DB_NAME, function(error) { self._initialized = true;
self._initialized = !error;
callback(error);
}); });
}; };
@ -57,84 +57,66 @@ Auth.prototype.isInitialized = function() {
* 2 a) ... in an oauth setting, retrieves a fresh oauth token from the Chrome Identity API. * 2 a) ... in an oauth setting, retrieves a fresh oauth token from the Chrome Identity API.
* 2 b) ... in a user/passwd setting, does not need to do additional work. * 2 b) ... in a user/passwd setting, does not need to do additional work.
* 3) Loads the intermediate certs from the configuration. * 3) Loads the intermediate certs from the configuration.
*
* @param {Function} callback(err, credentials)
*/ */
Auth.prototype.getCredentials = function(callback) { Auth.prototype.getCredentials = function() {
var self = this; var self = this;
if (!self.emailAddress) { if (!self.emailAddress) {
// we're not yet initialized, so let's load our stuff from disk // we're not yet initialized, so let's load our stuff from disk
self._loadCredentials(function(err) { return self._loadCredentials().then(chooseLogin);
if (err) {
return callback(err);
}
chooseLogin();
});
return;
} }
chooseLogin(); return chooseLogin();
function chooseLogin() { function chooseLogin() {
if (self.useOAuth(self.imap.host) && !self.password) { if (self.useOAuth(self.imap.host) && !self.password) {
// oauth login // oauth login
self.getOAuthToken(function(err) { return self.getOAuthToken().then(done);
if (err) {
return callback(err);
}
done();
});
return;
} }
if (self.passwordNeedsDecryption) { if (self.passwordNeedsDecryption) {
// decrypt password // decrypt password
self._pgp.decrypt(self.password, undefined, function(err, cleartext) { return self._pgp.decrypt(self.password, undefined).then(function(pt) {
if (err) { if (!pt.signaturesValid) {
return callback(err); throw new Error('Verifying PGP signature of encrypted password failed!');
} }
self.passwordNeedsDecryption = false; self.passwordNeedsDecryption = false;
self.password = cleartext; self.password = pt.decrypted;
}).then(done);
done();
});
return;
} }
done(); return done();
} }
function done() { function done() {
var credentials = { return new Promise(function(resolve) {
imap: { var credentials = {
secure: self.imap.secure, imap: {
port: self.imap.port, secure: self.imap.secure,
host: self.imap.host, port: self.imap.port,
ca: self.imap.ca, host: self.imap.host,
auth: { ca: self.imap.ca,
user: self.username, auth: {
xoauth2: self.oauthToken, // password or oauthToken is undefined user: self.username,
pass: self.password xoauth2: self.oauthToken, // password or oauthToken is undefined
pass: self.password
}
},
smtp: {
secure: self.smtp.secure,
port: self.smtp.port,
host: self.smtp.host,
ca: self.smtp.ca,
auth: {
user: self.username,
xoauth2: self.oauthToken,
pass: self.password // password or oauthToken is undefined
}
} }
}, };
smtp: { resolve(credentials);
secure: self.smtp.secure, });
port: self.smtp.port,
host: self.smtp.host,
ca: self.smtp.ca,
auth: {
user: self.username,
xoauth2: self.oauthToken,
pass: self.password // password or oauthToken is undefined
}
}
};
callback(null, credentials);
} }
}; };
@ -158,100 +140,69 @@ Auth.prototype.setCredentials = function(options) {
this.imap = options.imap; // host, port, secure, ca this.imap = options.imap; // host, port, secure, ca
}; };
Auth.prototype.storeCredentials = function(callback) { Auth.prototype.storeCredentials = function() {
var self = this; var self = this;
if (!self.credentialsDirty) { if (!self.credentialsDirty) {
return callback(); // nothing to store if credentials not dirty
return new Promise(function(resolve) {
resolve();
});
} }
// persist the config // persist the config
self._appConfigStore.storeList([self.smtp], SMTP_DB_KEY, function(err) { var storeSmtp = self._appConfigStore.storeList([self.smtp], SMTP_DB_KEY);
if (err) { var storeImap = self._appConfigStore.storeList([self.imap], IMAP_DB_KEY);
return callback(err); var storeEmailAddress = self._appConfigStore.storeList([self.emailAddress], EMAIL_ADDR_DB_KEY);
var storeUsername = self._appConfigStore.storeList([self.username], USERNAME_DB_KEY);
var storeRealname = self._appConfigStore.storeList([self.realname], REALNAME_DB_KEY);
var storePassword = new Promise(function(resolve) {
if (!self.password) {
resolve();
return;
} }
self._appConfigStore.storeList([self.imap], IMAP_DB_KEY, function(err) { if (self.passwordNeedsDecryption) {
if (err) { // password is not decrypted yet, so no need to re-encrypt it before storing...
return callback(err); return self._appConfigStore.storeList([self.password], PASSWD_DB_KEY).then(resolve);
} }
return self._pgp.encrypt(self.password, undefined).then(function(ciphertext) {
self._appConfigStore.storeList([self.emailAddress], EMAIL_ADDR_DB_KEY, function(err) { return self._appConfigStore.storeList([ciphertext], PASSWD_DB_KEY).then(resolve);
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.username], USERNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.realname], REALNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
if (!self.password) {
self.credentialsDirty = false;
return callback();
}
if (self.passwordNeedsDecryption) {
// password is not decrypted yet, so no need to re-encrypt it before storing...
self._appConfigStore.storeList([self.password], PASSWD_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self.credentialsDirty = false;
callback();
});
return;
}
self._pgp.encrypt(self.password, undefined, function(err, ciphertext) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([ciphertext], PASSWD_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self.credentialsDirty = false;
callback();
});
});
});
});
});
}); });
}); });
return Promise.all([
storeSmtp,
storeImap,
storeEmailAddress,
storeUsername,
storeRealname,
storePassword
]).then(function() {
self.credentialsDirty = false;
});
}; };
/** /**
* Returns the email address. Loads it from disk, if necessary * Returns the email address. Loads it from disk, if necessary
*/ */
Auth.prototype.getEmailAddress = function(callback) { Auth.prototype.getEmailAddress = function() {
var self = this; var self = this;
if (self.emailAddress) { if (self.emailAddress) {
return callback(null, { return new Promise(function(resolve) {
emailAddress: self.emailAddress, resolve({
realname: self.realname emailAddress: self.emailAddress,
realname: self.realname
});
}); });
} }
self._loadCredentials(function(err) { return self._loadCredentials().then(function() {
if (err) { return {
return callback(err);
}
callback(null, {
emailAddress: self.emailAddress, emailAddress: self.emailAddress,
realname: self.realname realname: self.realname
}); };
}); });
}; };
@ -286,40 +237,31 @@ Auth.prototype.useOAuth = function(hostname) {
* is android only, since the desktop chrome will query the user that is logged into chrome * is android only, since the desktop chrome will query the user that is logged into chrome
* 3) fetch the email address for the oauth token from the chrome identity api * 3) fetch the email address for the oauth token from the chrome identity api
*/ */
Auth.prototype.getOAuthToken = function(callback) { Auth.prototype.getOAuthToken = function() {
var self = this; var self = this;
if (self.oauthToken) { if (self.oauthToken) {
// removed cached token and get a new one // removed cached token and get a new one
self._oauth.refreshToken({ return self._oauth.refreshToken({
emailAddress: self.emailAddress, emailAddress: self.emailAddress,
oldToken: self.oauthToken oldToken: self.oauthToken
}, onToken); }).then(onToken);
} else { } else {
// get a fresh oauth token // get a fresh oauth token
self._oauth.getOAuthToken(self.emailAddress, onToken); return self._oauth.getOAuthToken(self.emailAddress).then(onToken);
} }
function onToken(err, oauthToken) { function onToken(oauthToken) {
if (err) {
return callback(err);
}
// shortcut if the email address is already known // shortcut if the email address is already known
if (self.emailAddress) { if (self.emailAddress) {
self.oauthToken = oauthToken; self.oauthToken = oauthToken;
return callback(); return;
} }
// query the email address // query the email address
self._oauth.queryEmailAddress(oauthToken, function(err, emailAddress) { return self._oauth.queryEmailAddress(oauthToken).then(function(emailAddress) {
if (err) {
return callback(err);
}
self.oauthToken = oauthToken; self.oauthToken = oauthToken;
self.emailAddress = emailAddress; self.emailAddress = emailAddress;
callback();
}); });
} }
}; };
@ -327,67 +269,44 @@ Auth.prototype.getOAuthToken = function(callback) {
/** /**
* Loads email address, password, ... from disk and sets them on `this` * Loads email address, password, ... from disk and sets them on `this`
*/ */
Auth.prototype._loadCredentials = function(callback) { Auth.prototype._loadCredentials = function() {
var self = this; var self = this;
if (self.initialized) { if (self.initialized) {
callback(); return new Promise(function(resolve) {
resolve();
});
} }
loadFromDB(SMTP_DB_KEY, function(err, smtp) { return loadFromDB(SMTP_DB_KEY).then(function(smtp) {
if (err) { self.smtp = smtp;
return callback(err); return loadFromDB(IMAP_DB_KEY);
}
}).then(function(imap) {
self.imap = imap;
return loadFromDB(USERNAME_DB_KEY);
loadFromDB(IMAP_DB_KEY, function(err, imap) { }).then(function(username) {
if (err) { self.username = username;
return callback(err); return loadFromDB(REALNAME_DB_KEY);
}
}).then(function(realname) {
self.realname = realname;
return loadFromDB(EMAIL_ADDR_DB_KEY);
loadFromDB(USERNAME_DB_KEY, function(err, username) { }).then(function(emailAddress) {
if (err) { self.emailAddress = emailAddress;
return callback(err); return loadFromDB(PASSWD_DB_KEY);
}
}).then(function(password) {
loadFromDB(REALNAME_DB_KEY, function(err, realname) { self.password = password;
if (err) { self.passwordNeedsDecryption = !!password;
return callback(err); self.initialized = true;
}
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) {
if (err) {
return callback(err);
}
loadFromDB(PASSWD_DB_KEY, function(err, password) {
if (err) {
return callback(err);
}
self.emailAddress = emailAddress;
self.password = password;
self.passwordNeedsDecryption = !!password;
self.username = username;
self.realname = realname;
self.smtp = smtp;
self.imap = imap;
self.initialized = true;
callback();
});
});
});
});
});
}); });
function loadFromDB(key, callback) { function loadFromDB(key) {
self._appConfigStore.listItems(key, 0, null, function(err, cachedItems) { return self._appConfigStore.listItems(key, 0, null).then(function(cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0])); return cachedItems && cachedItems[0];
}); });
} }
}; };
@ -407,7 +326,7 @@ Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback
// no previous ssl cert, trust on first use // no previous ssl cert, trust on first use
self[component].ca = pemEncodedCert; self[component].ca = pemEncodedCert;
self.credentialsDirty = true; self.credentialsDirty = true;
self.storeCredentials(callback); self.storeCredentials().then(callback).catch(callback);
return; return;
} }
@ -431,14 +350,9 @@ Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback
self[component].ca = pemEncodedCert; self[component].ca = pemEncodedCert;
self.credentialsDirty = true; self.credentialsDirty = true;
self.storeCredentials(function(err) { self.storeCredentials().then(function() {
if (err) {
callback(err);
return;
}
onConnect(callback); onConnect(callback);
}); }).catch(callback);
} }
}); });
}; };
@ -446,22 +360,15 @@ Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback
/** /**
* Logout of the app by clearing the app config store and in memory credentials * Logout of the app by clearing the app config store and in memory credentials
*/ */
Auth.prototype.logout = function(callback) { Auth.prototype.logout = function() {
var self = this; var self = this;
// clear app config db // clear app config db
self._appConfigStore.clear(function(err) { return self._appConfigStore.clear().then(function() {
if (err) {
callback(err);
return;
}
// clear in memory cache // clear in memory cache
self.setCredentials({}); self.setCredentials({});
self.initialized = undefined; self.initialized = undefined;
self.credentialsDirty = undefined; self.credentialsDirty = undefined;
self.passwordNeedsDecryption = undefined; self.passwordNeedsDecryption = undefined;
callback();
}); });
}; };

View File

@ -28,50 +28,57 @@ function DeviceStorage(lawnchairDAO) {
/** /**
* Initialize the lawnchair database * Initialize the lawnchair database
* @param {String} dbName The name of the database * @param {String} dbName The name of the database
* @return {Promise}
*/ */
DeviceStorage.prototype.init = function(dbName, callback) { DeviceStorage.prototype.init = function(dbName) {
this._lawnchairDAO.init(dbName, callback); return this._lawnchairDAO.init(dbName);
}; };
/** /**
* Stores a list of encrypted items in the object store * Stores a list of encrypted items in the object store
* @param list [Array] The list of items to be persisted * @param list [Array] The list of items to be persisted
* @param type [String] The type of item to be persisted e.g. 'email' * @param type [String] The type of item to be persisted e.g. 'email'
* @return {Promise}
*/ */
DeviceStorage.prototype.storeList = function(list, type, callback) { DeviceStorage.prototype.storeList = function(list, type) {
var key, items = []; var self = this;
return new Promise(function(resolve) {
var key, items = [];
list = list || [];
// nothing to store // validate type
if (!list || list.length === 0) { if (!type) {
callback(); throw new Error('Type is not set!');
return; }
}
// validate type // format items for batch storing in dao
if (!type) { list.forEach(function(i) {
callback({ key = createKey(i, type);
errMsg: 'Type is not set!'
items.push({
key: key,
object: i
});
}); });
return;
}
// format items for batch storing in dao resolve(items);
list.forEach(function(i) {
key = createKey(i, type);
items.push({ }).then(function(items) {
key: key, // nothing to store
object: i if (items.length === 0) {
}); return;
}
return self._lawnchairDAO.batch(items);
}); });
this._lawnchairDAO.batch(items, callback);
}; };
/** /**
* Deletes items of a certain type from storage * Deletes items of a certain type from storage
* @return {Promise}
*/ */
DeviceStorage.prototype.removeList = function(type, callback) { DeviceStorage.prototype.removeList = function(type) {
this._lawnchairDAO.removeList(type, callback); return this._lawnchairDAO.removeList(type);
}; };
/** /**
@ -79,17 +86,19 @@ DeviceStorage.prototype.removeList = function(type, callback) {
* @param type [String] The type of item e.g. 'email' * @param type [String] The type of item e.g. 'email'
* @param offset [Number] The offset of items to fetch (0 is the last stored item) * @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) * @param num [Number] The number of items to fetch (null means fetch all)
* @return {Promise}
*/ */
DeviceStorage.prototype.listItems = function(type, offset, num, callback) { DeviceStorage.prototype.listItems = function(type, offset, num) {
// fetch all items of a certain type from the data-store // fetch all items of a certain type from the data-store
this._lawnchairDAO.list(type, offset, num, callback); return this._lawnchairDAO.list(type, offset, num);
}; };
/** /**
* Clear the whole device data-store * Clear the whole device data-store
* @return {Promise}
*/ */
DeviceStorage.prototype.clear = function(callback) { DeviceStorage.prototype.clear = function() {
this._lawnchairDAO.clear(callback); return this._lawnchairDAO.clear();
}; };
// //

View File

@ -12,14 +12,6 @@ function Invitation(invitationRestDao) {
this._restDao = invitationRestDao; this._restDao = invitationRestDao;
} }
//
// Constants
//
Invitation.INVITE_MISSING = 1;
Invitation.INVITE_PENDING = 2;
Invitation.INVITE_SUCCESS = 4;
// //
// API // API
// //
@ -28,35 +20,18 @@ Invitation.INVITE_SUCCESS = 4;
* Notes an invite for the recipient by the sender in the invitation web service * Notes an invite for the recipient by the sender in the invitation web service
* @param {String} options.recipient User ID of the recipient * @param {String} options.recipient User ID of the recipient
* @param {String} options.sender User ID of the sender * @param {String} options.sender User ID of the sender
* @param {Function} callback(error, status) Returns information if the invitation worked (INVITE_SUCCESS), if an invitation is already pendin (INVITE_PENDING), or information if an error occurred. * @return {Promise}
*/ */
Invitation.prototype.invite = function(options, callback) { Invitation.prototype.invite = function(options) {
if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.recipient !== 'string') { var self = this;
callback({ return new Promise(function(resolve) {
errMsg: 'erroneous usage of api: incorrect parameters!' if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.sender !== 'string') {
}); throw new Error('erroneous usage of api: incorrect parameters!');
return;
}
var uri = '/invitation/recipient/' + options.recipient + '/sender/' + options.sender;
this._restDao.put({}, uri, completed);
function completed(error, res, status) {
if (error) {
callback(error);
return;
} }
resolve();
if (status === 201) { }).then(function() {
callback(null, Invitation.INVITE_SUCCESS); var uri = '/invitation/recipient/' + options.recipient + '/sender/' + options.sender;
return; return self._restDao.put({}, uri);
} else if (status === 304) { });
callback(null, Invitation.INVITE_PENDING);
return;
}
callback({
errMsg: 'unexpected invitation state'
});
}
}; };

File diff suppressed because it is too large Load Diff

View File

@ -13,87 +13,91 @@ function LawnchairDAO() {}
/** /**
* Initialize the lawnchair database * Initialize the lawnchair database
* @param {String} dbName The name of the database * @param {String} dbName The name of the database
* @return {Promise}
*/ */
LawnchairDAO.prototype.init = function(dbName, callback) { LawnchairDAO.prototype.init = function(dbName) {
var self = this; var self = this;
return new Promise(function(resolve, reject) {
if (!dbName) {
throw new Error('Lawnchair DB name must be specified!');
}
if (!dbName) { self._db = new Lawnchair({
return callback(new Error('Lawnchair DB name must be specified!')); name: dbName
} }, function(success) {
if (success) {
self._db = new Lawnchair({ resolve();
name: dbName } else {
}, function(success) { reject(new Error('Lawnchair initialization ' + dbName + ' failed!'));
callback(success ? undefined : new Error('Lawnchair initialization ' + dbName + ' failed!')); }
});
}); });
}; };
/** /**
* Create or update an object * Create or update an object
* @return {Promise}
*/ */
LawnchairDAO.prototype.persist = function(key, object, callback) { LawnchairDAO.prototype.persist = function(key, object) {
if (!key || !object) { var self = this;
callback({ return new Promise(function(resolve, reject) {
errMsg: 'Key and Object must be set!' if (!key || !object) {
}); throw new Error('Key and Object must be set!');
return;
}
this._db.save({
key: key,
object: object
}, function(persisted) {
if (persisted.key !== key) {
callback({
errMsg: 'Persisting failed!'
});
return;
} }
callback(); self._db.save({
key: key,
object: object
}, function(persisted) {
if (persisted.key !== key) {
reject(new Error('Persisting failed!'));
return;
}
resolve();
});
}); });
}; };
/** /**
* Persist a bunch of items at once * Persist a bunch of items at once
* @return {Promise}
*/ */
LawnchairDAO.prototype.batch = function(list, callback) { LawnchairDAO.prototype.batch = function(list) {
if (!(list instanceof Array)) { var self = this;
callback({ return new Promise(function(resolve, reject) {
errMsg: 'Input must be of type Array!' if (!(list instanceof Array)) {
}); throw new Error('Input must be of type Array!');
return;
}
this._db.batch(list, function(res) {
if (!res) {
callback({
errMsg: 'Persisting batch failed!'
});
return;
} }
callback(); self._db.batch(list, function(res) {
if (!res) {
reject(new Error('Persisting batch failed!'));
} else {
resolve();
}
});
}); });
}; };
/** /**
* Read a single item by its key * Read a single item by its key
* @return {Promise}
*/ */
LawnchairDAO.prototype.read = function(key, callback) { LawnchairDAO.prototype.read = function(key) {
if (!key) { var self = this;
callback({ return new Promise(function(resolve) {
errMsg: 'Key must be specified!' if (!key) {
}); throw new Error('Key must be specified!');
return;
}
this._db.get(key, function(o) {
if (o) {
callback(null, o.object);
} else {
callback();
} }
self._db.get(key, function(o) {
if (o) {
resolve(o.object);
} else {
resolve();
}
});
}); });
}; };
@ -102,113 +106,131 @@ LawnchairDAO.prototype.read = function(key, callback) {
* @param type [String] The type of item e.g. 'email' * @param type [String] The type of item e.g. 'email'
* @param offset [Number] The offset of items to fetch (0 is the last stored item) * @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) * @param num [Number] The number of items to fetch (null means fetch all)
* @return {Promise}
*/ */
LawnchairDAO.prototype.list = function(type, offset, num, callback) { LawnchairDAO.prototype.list = function(type, offset, num) {
var self = this, var self = this;
i, from, to, return new Promise(function(resolve) {
matchingKeys = [], var i, from, to,
intervalKeys = [], matchingKeys = [],
list = []; intervalKeys = [],
list = [];
// validate input // validate input
if (!type || typeof offset === 'undefined' || typeof num === 'undefined') { if (!type || typeof offset === 'undefined' || typeof num === 'undefined') {
callback({ throw new Error('Args not is not set!');
errMsg: 'Args not is not set!'
});
return;
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
// sort keys
matchingKeys.sort();
// set window of items to fetch
// if num is null, list all items
from = (num) ? matchingKeys.length - offset - num : 0;
to = matchingKeys.length - 1 - offset;
// filter items within requested interval
for (i = 0; i < matchingKeys.length; i++) {
if (i >= from && i <= to) {
intervalKeys.push(matchingKeys[i]);
}
} }
// return if there are no matching keys // get all keys
if (intervalKeys.length === 0) { self._db.keys(function(keys) {
callback(null, list); // check if key begins with type
return; keys.forEach(function(key) {
} if (key.indexOf(type) === 0) {
matchingKeys.push(key);
// fetch all items from data-store with matching key }
self._db.get(intervalKeys, function(intervalList) {
intervalList.forEach(function(item) {
list.push(item.object);
}); });
// return only the interval between offset and num // sort keys
callback(null, list); matchingKeys.sort();
});
// set window of items to fetch
// if num is null, list all items
from = (num) ? matchingKeys.length - offset - num : 0;
to = matchingKeys.length - 1 - offset;
// filter items within requested interval
for (i = 0; i < matchingKeys.length; i++) {
if (i >= from && i <= to) {
intervalKeys.push(matchingKeys[i]);
}
}
// return if there are no matching keys
if (intervalKeys.length === 0) {
resolve(list);
return;
}
// fetch all items from data-store with matching key
self._db.get(intervalKeys, function(intervalList) {
intervalList.forEach(function(item) {
list.push(item.object);
});
// return only the interval between offset and num
resolve(list);
});
});
}); });
}; };
/** /**
* Removes an object liter from local storage by its key (delete) * Removes an object liter from local storage by its key (delete)
* @return {Promise}
*/ */
LawnchairDAO.prototype.remove = function(key, callback) { LawnchairDAO.prototype.remove = function(key) {
this._db.remove(key, callback); var self = this;
return new Promise(function(resolve, reject) {
self._db.remove(key, function(err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}; };
/** /**
* Removes an object liter from local storage by its key (delete) * Removes an object liter from local storage by its key (delete)
* @return {Promise}
*/ */
LawnchairDAO.prototype.removeList = function(type, callback) { LawnchairDAO.prototype.removeList = function(type) {
var self = this, var self = this;
matchingKeys = [], return new Promise(function(resolve) {
after; var matchingKeys = [],
after;
// validate type // validate type
if (!type) { if (!type) {
callback({ throw new Error('Type is not set!');
errMsg: 'Type is not set!'
});
return;
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
if (matchingKeys.length < 1) {
callback();
return;
} }
// remove all matching keys // get all keys
after = _.after(matchingKeys.length, callback); self._db.keys(function(keys) {
_.each(matchingKeys, function(key) { // check if key begins with type
self._db.remove(key, after); keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
if (matchingKeys.length < 1) {
resolve();
return;
}
// remove all matching keys
after = _.after(matchingKeys.length, resolve);
_.each(matchingKeys, function(key) {
self._db.remove(key, after);
});
}); });
}); });
}; };
/** /**
* Clears the whole local storage cache * Clears the whole local storage cache
* @return {Promise}
*/ */
LawnchairDAO.prototype.clear = function(callback) { LawnchairDAO.prototype.clear = function() {
this._db.nuke(callback); var self = this;
}; return new Promise(function(resolve, reject) {
self._db.nuke(function(err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};

View File

@ -4,15 +4,13 @@ var ngModule = angular.module('woServices');
ngModule.service('newsletter', Newsletter); ngModule.service('newsletter', Newsletter);
module.exports = Newsletter; module.exports = Newsletter;
function Newsletter($q) { function Newsletter() {}
this._q = $q;
}
/** /**
* Sign up to the whiteout newsletter * Sign up to the whiteout newsletter
*/ */
Newsletter.prototype.signup = function(emailAddress, agree) { Newsletter.prototype.signup = function(emailAddress, agree) {
return this._q(function(resolve, reject) { return new Promise(function(resolve, reject) {
// validate email address // validate email address
if (emailAddress.indexOf('@') < 0) { if (emailAddress.indexOf('@') < 0) {
reject(new Error('Invalid email address!')); reject(new Error('Invalid email address!'));

View File

@ -20,33 +20,33 @@ OAuth.prototype.isSupported = function() {
* Request an OAuth token from chrome for gmail users * Request an OAuth token from chrome for gmail users
* @param {String} emailAddress The user's email address (optional) * @param {String} emailAddress The user's email address (optional)
*/ */
OAuth.prototype.getOAuthToken = function(emailAddress, callback) { OAuth.prototype.getOAuthToken = function(emailAddress) {
var idOptions = { return new Promise(function(resolve, reject) {
interactive: true var idOptions = {
}; interactive: true
};
// check which runtime the app is running under // check which runtime the app is running under
chrome.runtime.getPlatformInfo(function(platformInfo) { chrome.runtime.getPlatformInfo(function(platformInfo) {
if (chrome.runtime.lastError || !platformInfo) { if (chrome.runtime.lastError || !platformInfo) {
callback(new Error('Error getting chrome platform info!')); reject(new Error('Error getting chrome platform info!'));
return;
}
if (emailAddress && platformInfo.os.indexOf('android') !== -1) {
// set accountHint so that native Android account picker does not show up each time
idOptions.accountHint = emailAddress;
}
// get OAuth Token from chrome
chrome.identity.getAuthToken(idOptions, function(token) {
if (chrome.runtime.lastError || !token) {
callback({
errMsg: 'Error fetching an OAuth token for the user!'
});
return; return;
} }
callback(null, token); if (emailAddress && platformInfo.os.indexOf('android') !== -1) {
// set accountHint so that native Android account picker does not show up each time
idOptions.accountHint = emailAddress;
}
// get OAuth Token from chrome
chrome.identity.getAuthToken(idOptions, function(token) {
if (chrome.runtime.lastError || !token) {
reject(new Error('Error fetching an OAuth token for the user!'));
return;
}
resolve(token);
});
}); });
}); });
}; };
@ -56,20 +56,20 @@ OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
* @param {String} options.oldToken The old token to be removed * @param {String} options.oldToken The old token to be removed
* @param {String} options.emailAddress The user's email address (optional) * @param {String} options.emailAddress The user's email address (optional)
*/ */
OAuth.prototype.refreshToken = function(options, callback) { OAuth.prototype.refreshToken = function(options) {
var self = this; var self = this;
return new Promise(function(resolve) {
if (!options.oldToken) {
throw new Error('oldToken option not set!');
}
if (!options.oldToken) { // remove cached token
callback(new Error('oldToken option not set!')); chrome.identity.removeCachedAuthToken({
return; token: options.oldToken
} }, function() {
// get a new token
// remove cached token self.getOAuthToken(options.emailAddress).then(resolve);
chrome.identity.removeCachedAuthToken({ });
token: options.oldToken
}, function() {
// get a new token
self.getOAuthToken(options.emailAddress, callback);
}); });
}; };
@ -77,25 +77,26 @@ OAuth.prototype.refreshToken = function(options, callback) {
* Get email address from google api * Get email address from google api
* @param {String} token The oauth token * @param {String} token The oauth token
*/ */
OAuth.prototype.queryEmailAddress = function(token, callback) { OAuth.prototype.queryEmailAddress = function(token) {
if (!token) { var self = this;
callback({ return new Promise(function(resolve) {
errMsg: 'Invalid OAuth token!' if (!token) {
}); throw new Error('Invalid OAuth token!');
return;
}
// fetch gmail user's email address from the Google Authorization Server
this._googleApi.get({
uri: '/oauth2/v3/userinfo?access_token=' + token
}, function(err, info) {
if (err || !info || !info.email) {
callback({
errMsg: 'Error looking up email address on google api!'
});
return;
} }
callback(null, info.email); resolve();
}).then(function() {
// fetch gmail user's email address from the Google Authorization Server
return self._googleApi.get({
uri: '/oauth2/v3/userinfo?access_token=' + token
});
}).then(function(info) {
if (!info || !info.email) {
throw new Error('Error looking up email address on google api!');
}
return info.email;
}); });
}; };

View File

@ -16,19 +16,20 @@ function PrivateKey(privateKeyRestDao) {
* Request registration of a new device by fetching registration session key. * Request registration of a new device by fetching registration session key.
* @param {String} options.userId The user's email address * @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name * @param {String} options.deviceName The device's memorable name
* @param {Function} callback(error, regSessionKey)
* @return {Object} {encryptedRegSessionKey:[base64]} * @return {Object} {encryptedRegSessionKey:[base64]}
*/ */
PrivateKey.prototype.requestDeviceRegistration = function(options, callback) { PrivateKey.prototype.requestDeviceRegistration = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.userId || !options.deviceName) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options.userId || !options.deviceName) { }).then(function() {
callback(new Error('Incomplete arguments!')); var uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
return; return self._restDao.post(undefined, uri);
} });
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.post(undefined, uri, callback);
}; };
/** /**
@ -37,18 +38,19 @@ PrivateKey.prototype.requestDeviceRegistration = function(options, callback) {
* @param {String} options.deviceName The device's memorable name * @param {String} options.deviceName The device's memorable name
* @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret * @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret
* @param {String} options.iv The iv used for encryption * @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/ */
PrivateKey.prototype.uploadDeviceSecret = function(options, callback) { PrivateKey.prototype.uploadDeviceSecret = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) { }).then(function() {
callback(new Error('Incomplete arguments!')); var uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
return; return self._restDao.put(options, uri);
} });
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.put(options, uri, callback);
}; };
// //
@ -58,19 +60,20 @@ PrivateKey.prototype.uploadDeviceSecret = function(options, callback) {
/** /**
* Request authSessionKeys required for upload the encrypted private PGP key. * Request authSessionKeys required for upload the encrypted private PGP key.
* @param {String} options.userId The user's email address * @param {String} options.userId The user's email address
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]} * @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]}
*/ */
PrivateKey.prototype.requestAuthSessionKey = function(options, callback) { PrivateKey.prototype.requestAuthSessionKey = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.userId) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options.userId) { }).then(function() {
callback(new Error('Incomplete arguments!')); var uri = '/auth/user/' + options.userId;
return; return self._restDao.post(undefined, uri);
} });
uri = '/auth/user/' + options.userId;
this._restDao.post(undefined, uri, callback);
}; };
/** /**
@ -79,18 +82,19 @@ PrivateKey.prototype.requestAuthSessionKey = function(options, callback) {
* @param {String} options.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey * @param {String} options.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey
* @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey * @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey
* @param {String} options.iv The iv used for encryption * @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/ */
PrivateKey.prototype.verifyAuthentication = function(options, callback) { PrivateKey.prototype.verifyAuthentication = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) { }).then(function() {
callback(new Error('Incomplete arguments!')); var uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
return; return self._restDao.put(options, uri);
} });
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.put(options, uri, callback);
}; };
/** /**
@ -99,48 +103,50 @@ PrivateKey.prototype.verifyAuthentication = function(options, callback) {
* @param {String} options.userId The user's email address * @param {String} options.userId The user's email address
* @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key * @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key
* @param {String} options.sessionId The session id * @param {String} options.sessionId The session id
* @param {Function} callback(error)
*/ */
PrivateKey.prototype.upload = function(options, callback) { PrivateKey.prototype.upload = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) { }).then(function() {
callback(new Error('Incomplete arguments!')); var uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
return; return self._restDao.post(options, uri);
} });
uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.post(options, uri, callback);
}; };
/** /**
* Query if an encrypted private PGP key exists on the server without initializing the recovery procedure. * Query if an encrypted private PGP key exists on the server without initializing the recovery procedure.
* @param {String} options.userId The user's email address * @param {String} options.userId The user's email address
* @param {String} options.keyId The private PGP key id * @param {String} options.keyId The private PGP key id
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not. * @return {Boolean} whether the key was found on the server or not.
*/ */
PrivateKey.prototype.hasPrivateKey = function(options, callback) { PrivateKey.prototype.hasPrivateKey = function(options) {
if (!options.userId || !options.keyId) { var self = this;
callback(new Error('Incomplete arguments!')); return new Promise(function(resolve) {
return; if (!options.userId || !options.keyId) {
} throw new Error('Incomplete arguments!');
}
resolve();
this._restDao.get({ }).then(function() {
uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '?ignoreRecovery=true', return self._restDao.get({
}, function(err) { uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '?ignoreRecovery=true',
});
}).then(function() {
return true;
}).catch(function(err) {
// 404: there is no encrypted private key on the server // 404: there is no encrypted private key on the server
if (err && err.code !== 200) { if (err.code && err.code !== 200) {
callback(null, false); return false;
return;
} }
if (err) { throw err;
callback(err);
return;
}
callback(null, true);
}); });
}; };
@ -148,30 +154,31 @@ PrivateKey.prototype.hasPrivateKey = function(options, callback) {
* Request download for the encrypted private PGP key. * Request download for the encrypted private PGP key.
* @param {String} options.userId The user's email address * @param {String} options.userId The user's email address
* @param {String} options.keyId The private PGP key id * @param {String} options.keyId The private PGP key id
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not. * @return {Boolean} whether the key was found on the server or not.
*/ */
PrivateKey.prototype.requestDownload = function(options, callback) { PrivateKey.prototype.requestDownload = function(options) {
if (!options.userId || !options.keyId) { var self = this;
callback(new Error('Incomplete arguments!')); return new Promise(function(resolve) {
return; if (!options.userId || !options.keyId) {
} throw new Error('Incomplete arguments!');
}
resolve();
this._restDao.get({ }).then(function() {
uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId return self._restDao.get({
}, function(err) { uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId
});
}).then(function() {
return true;
}).catch(function(err) {
// 404: there is no encrypted private key on the server // 404: there is no encrypted private key on the server
if (err && err.code !== 200) { if (err.code && err.code !== 200) {
callback(null, false); return false;
return;
} }
if (err) { throw err;
callback(err);
return;
}
callback(null, true);
}); });
}; };
@ -180,19 +187,19 @@ PrivateKey.prototype.requestDownload = function(options, callback) {
* @param {String} options.userId The user's email address * @param {String} options.userId The user's email address
* @param {String} options.keyId The private key id * @param {String} options.keyId The private key id
* @param {String} options.recoveryToken The token proving the user own the email account * @param {String} options.recoveryToken The token proving the user own the email account
* @param {Function} callback(error, encryptedPrivateKey)
* @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]} * @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]}
*/ */
PrivateKey.prototype.download = function(options, callback) { PrivateKey.prototype.download = function(options) {
var uri; var self = this;
return new Promise(function(resolve) {
if (!options.userId || !options.keyId || !options.recoveryToken) {
throw new Error('Incomplete arguments!');
}
resolve();
if (!options.userId || !options.keyId || !options.recoveryToken) { }).then(function() {
callback(new Error('Incomplete arguments!')); return self._restDao.get({
return; uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken
} });
});
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken;
this._restDao.get({
uri: uri
}, callback);
}; };

View File

@ -11,95 +11,75 @@ function PublicKey(publicKeyRestDao) {
/** /**
* Verify the public key behind the given uuid * Verify the public key behind the given uuid
*/ */
PublicKey.prototype.verify = function(uuid, callback) { PublicKey.prototype.verify = function(uuid) {
var uri = '/verify/' + uuid; return this._restDao.get({
uri: '/verify/' + uuid,
this._restDao.get({
uri: uri,
type: 'text' type: 'text'
}, function(err, res, status) { }).catch(function(err) {
if (err && err.code === 400) { if (err.code === 400) {
// there was an attempt to verify a non-existing public key // there was an attempt to verify a non-existing public key
callback();
return; return;
} }
callback(err, res, status); throw err;
}); });
}; };
/** /**
* Find the user's corresponding public key * Find the user's corresponding public key
*/ */
PublicKey.prototype.get = function(keyId, callback) { PublicKey.prototype.get = function(keyId) {
var uri = '/publickey/key/' + keyId; return this._restDao.get({
uri: '/publickey/key/' + keyId
this._restDao.get({ }).catch(function(err) {
uri: uri if (err.code === 404) {
}, function(err, key) {
if (err && err.code === 404) {
callback();
return; return;
} }
if (err) { throw err;
callback(err);
return;
}
callback(null, (key && key._id) ? key : undefined);
}); });
}; };
/** /**
* Find the user's corresponding public key by email * Find the user's corresponding public key by email
*/ */
PublicKey.prototype.getByUserId = function(userId, callback) { PublicKey.prototype.getByUserId = function(userId) {
var uri = '/publickey/user/' + userId; return this._restDao.get({
uri: '/publickey/user/' + userId
this._restDao.get({ }).then(function(keys) {
uri: uri
}, function(err, keys) {
// not found
if (err && err.code === 404) {
callback();
return;
}
if (err) {
callback(err);
return;
}
if (!keys || keys.length < 1) { if (!keys || keys.length < 1) {
// 'No public key for that user!' // 'No public key for that user!'
callback();
return; return;
} }
if (keys.length > 1) { if (keys.length > 1) {
callback({ throw new Error('That user has multiple public keys!');
errMsg: 'That user has multiple public keys!' }
});
return keys[0];
}).catch(function(err) {
// not found
if (err.code === 404) {
return; return;
} }
callback(null, keys[0]); throw err;
}); });
}; };
/** /**
* Persist the user's publc key * Persist the user's publc key
*/ */
PublicKey.prototype.put = function(pubkey, callback) { PublicKey.prototype.put = function(pubkey) {
var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id; var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id;
this._restDao.put(pubkey, uri, callback); return this._restDao.put(pubkey, uri);
}; };
/** /**
* Delete the public key from the cloud storage service * Delete the public key from the cloud storage service
*/ */
PublicKey.prototype.remove = function(keyId, callback) { PublicKey.prototype.remove = function(keyId) {
var uri = '/publickey/key/' + keyId; var uri = '/publickey/key/' + keyId;
this._restDao.remove(uri, callback); return this._restDao.remove(uri);
}; };

View File

@ -54,105 +54,106 @@ RestDAO.prototype.setBaseUri = function(baseUri) {
* @param {String} options.uri URI relative to the base uri to perform the GET request with. * @param {String} options.uri URI relative to the base uri to perform the GET request with.
* @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json. * @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json.
*/ */
RestDAO.prototype.get = function(options, callback) { RestDAO.prototype.get = function(options) {
options.method = 'GET'; options.method = 'GET';
this._processRequest(options, callback); return this._processRequest(options);
}; };
/** /**
* POST (create) request * POST (create) request
*/ */
RestDAO.prototype.post = function(item, uri, callback) { RestDAO.prototype.post = function(item, uri) {
this._processRequest({ return this._processRequest({
method: 'POST', method: 'POST',
payload: item, payload: item,
uri: uri uri: uri
}, callback); });
}; };
/** /**
* PUT (update) request * PUT (update) request
*/ */
RestDAO.prototype.put = function(item, uri, callback) { RestDAO.prototype.put = function(item, uri) {
this._processRequest({ return this._processRequest({
method: 'PUT', method: 'PUT',
payload: item, payload: item,
uri: uri uri: uri
}, callback); });
}; };
/** /**
* DELETE (remove) request * DELETE (remove) request
*/ */
RestDAO.prototype.remove = function(uri, callback) { RestDAO.prototype.remove = function(uri) {
this._processRequest({ return this._processRequest({
method: 'DELETE', method: 'DELETE',
uri: uri uri: uri
}, callback); });
}; };
// //
// helper functions // helper functions
// //
RestDAO.prototype._processRequest = function(options, callback) { RestDAO.prototype._processRequest = function(options) {
var xhr, format; var self = this;
return new Promise(function(resolve, reject) {
var xhr, format;
if (typeof options.uri === 'undefined') { if (typeof options.uri === 'undefined') {
callback({ throw {
code: 400, code: 400,
errMsg: 'Bad Request! URI is a mandatory parameter.' message: 'Bad Request! URI is a mandatory parameter.'
}); };
return;
}
options.type = options.type || 'json';
if (options.type === 'json') {
format = 'application/json';
} else if (options.type === 'xml') {
format = 'application/xml';
} else if (options.type === 'text') {
format = 'text/plain';
} else {
callback({
code: 400,
errMsg: 'Bad Request! Unhandled data type.'
});
return;
}
xhr = new XMLHttpRequest();
xhr.open(options.method, this._baseUri + options.uri);
xhr.setRequestHeader('Accept', format);
xhr.setRequestHeader('Content-Type', format);
xhr.onload = function() {
var res;
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
if (options.type === 'json') {
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
} else {
res = xhr.responseText;
}
callback(null, res, xhr.status);
return;
} }
callback({ options.type = options.type || 'json';
code: xhr.status,
errMsg: xhr.statusText
});
};
xhr.onerror = function() { if (options.type === 'json') {
callback({ format = 'application/json';
code: 42, } else if (options.type === 'xml') {
errMsg: 'Error calling ' + options.method + ' on ' + options.uri format = 'application/xml';
}); } else if (options.type === 'text') {
}; format = 'text/plain';
} else {
throw {
code: 400,
message: 'Bad Request! Unhandled data type.'
};
}
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined); xhr = new XMLHttpRequest();
xhr.open(options.method, self._baseUri + options.uri);
xhr.setRequestHeader('Accept', format);
xhr.setRequestHeader('Content-Type', format);
xhr.onload = function() {
var res;
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
if (options.type === 'json') {
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
} else {
res = xhr.responseText;
}
resolve(res);
return;
}
reject({
code: xhr.status,
message: xhr.statusText
});
};
xhr.onerror = function() {
reject({
code: 42,
message: 'Error calling ' + options.method + ' on ' + options.uri
});
};
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
});
}; };

View File

@ -82,40 +82,25 @@ ConnectionDoctor.prototype.configure = function(credentials) {
* 3) Login to the server * 3) Login to the server
* 4) Perform some basic commands (e.g. list folders) * 4) Perform some basic commands (e.g. list folders)
* 5) Exposes error codes * 5) Exposes error codes
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/ */
ConnectionDoctor.prototype.check = function(callback) { ConnectionDoctor.prototype.check = function() {
var self = this; var self = this;
return new Promise(function(resolve) {
if (!self.credentials) { if (!self.credentials) {
return callback(new Error('You need to configure() the connection doctor first!')); throw new Error('You need to configure() the connection doctor first!');
} } else {
resolve();
self._checkOnline(function(error) {
if (error) {
return callback(error);
} }
}).then(function() {
self._checkReachable(self.credentials.imap, function(error) { return self._checkOnline();
if (error) { }).then(function() {
return callback(error); return self._checkReachable(self.credentials.imap);
} }).then(function() {
return self._checkReachable(self.credentials.smtp);
self._checkReachable(self.credentials.smtp, function(error) { }).then(function() {
if (error) { return self._checkImap();
return callback(error); }).then(function() {
} return self._checkSmtp();
self._checkImap(function(error) {
if (error) {
return callback(error);
}
self._checkSmtp(callback);
});
});
});
}); });
}; };
@ -126,15 +111,16 @@ ConnectionDoctor.prototype.check = function(callback) {
/** /**
* Checks if the browser is online * Checks if the browser is online
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if browser is offline
*/ */
ConnectionDoctor.prototype._checkOnline = function(callback) { ConnectionDoctor.prototype._checkOnline = function() {
if (navigator.onLine) { var self = this;
callback(); return new Promise(function(resolve) {
} else { if (navigator.onLine) {
callback(createError(OFFLINE, this._appConfig.string.connDocOffline)); resolve();
} } else {
throw createError(OFFLINE, self._appConfig.string.connDocOffline);
}
});
}; };
/** /**
@ -143,105 +129,113 @@ ConnectionDoctor.prototype._checkOnline = function(callback) {
* @param {String} options.host * @param {String} options.host
* @param {Number} options.port * @param {Number} options.port
* @param {Boolean} options.secure * @param {Boolean} options.secure
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/ */
ConnectionDoctor.prototype._checkReachable = function(options, callback) { ConnectionDoctor.prototype._checkReachable = function(options) {
var socket, var self = this;
error, // remember the error message return new Promise(function(resolve, reject) {
timeout, // remember the timeout object var socket,
host = options.host + ':' + options.port, error, // remember the error message
hasTimedOut = false, // prevents multiple callbacks timeout, // remember the timeout object
cfg = this._appConfig.config, host = options.host + ':' + options.port,
str = this._appConfig.string; hasTimedOut = false, // prevents multiple callbacks
cfg = self._appConfig.config,
str = self._appConfig.string;
timeout = setTimeout(function() { timeout = setTimeout(function() {
hasTimedOut = true; hasTimedOut = true;
callback(createError(HOST_TIMEOUT, str.connDocHostTimeout.replace('{0}', host).replace('{1}', cfg.connDocTimeout))); reject(createError(HOST_TIMEOUT, str.connDocHostTimeout.replace('{0}', host).replace('{1}', cfg.connDocTimeout)));
}, cfg.connDocTimeout); }, cfg.connDocTimeout);
socket = TCPSocket.open(options.host, options.port, { socket = TCPSocket.open(options.host, options.port, {
binaryType: 'arraybuffer', binaryType: 'arraybuffer',
useSecureTransport: options.secure, useSecureTransport: options.secure,
ca: options.ca, ca: options.ca,
tlsWorkerPath: this._workerPath tlsWorkerPath: self._workerPath
}); });
socket.ondata = function() {}; // we don't actually care about the data socket.ondata = function() {}; // we don't actually care about the data
// [WO-625] Mozilla forbids extensions to the TCPSocket object, // [WO-625] Mozilla forbids extensions to the TCPSocket object,
// throws an exception when assigned unexpected callback functions. // throws an exception when assigned unexpected callback functions.
// The exception can be safely ignored since we need the callback // The exception can be safely ignored since we need the callback
// for the other shims // for the other shims
try { try {
socket.oncert = function() { socket.oncert = function() {
if (options.ca) { if (options.ca) {
// the certificate we already have is outdated // the certificate we already have is outdated
error = createError(TLS_WRONG_CERT, str.connDocTlsWrongCert.replace('{0}', host)); error = createError(TLS_WRONG_CERT, str.connDocTlsWrongCert.replace('{0}', host));
}
};
} catch (e) {}
socket.onerror = function(e) {
if (!error) {
error = createError(HOST_UNREACHABLE, str.connDocHostUnreachable.replace('{0}', host), e.data);
} }
}; };
} catch (e) {}
socket.onerror = function(e) { socket.onopen = function() {
if (!error) { socket.close();
error = createError(HOST_UNREACHABLE, str.connDocHostUnreachable.replace('{0}', host), e.data); };
}
};
socket.onopen = function() { socket.onclose = function() {
socket.close(); if (!hasTimedOut) {
}; clearTimeout(timeout);
if (error) {
socket.onclose = function() { reject(error);
if (!hasTimedOut) { } else {
clearTimeout(timeout); resolve();
callback(error); }
} }
}; };
});
}; };
/** /**
* Checks if an IMAP server is reachable, accepts the credentials, can list folders and has an inbox and logs out. * Checks if an IMAP server is reachable, accepts the credentials, can list folders and has an inbox and logs out.
* Adds the certificate to the IMAP settings if not provided. * Adds the certificate to the IMAP settings if not provided.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/ */
ConnectionDoctor.prototype._checkImap = function(callback) { ConnectionDoctor.prototype._checkImap = function() {
var self = this, var self = this;
loggedIn = false, return new Promise(function(resolve, reject) {
host = self.credentials.imap.host + ':' + self.credentials.imap.port, var loggedIn = false,
str = this._appConfig.string; host = self.credentials.imap.host + ':' + self.credentials.imap.port,
str = self._appConfig.string;
self._imap.onCert = function(pemEncodedCert) { self._imap.onCert = function(pemEncodedCert) {
if (!self.credentials.imap.ca) { if (!self.credentials.imap.ca) {
self.credentials.imap.ca = pemEncodedCert; self.credentials.imap.ca = pemEncodedCert;
}
};
// login and logout do not use error objects in the callback, but rather invoke
// the global onError handler, so we need to track if login was successful
self._imap.onError = function(error) {
if (!loggedIn) {
callback(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
} else {
callback(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
}
};
self._imap.login(function() {
loggedIn = true;
self._imap.listWellKnownFolders(function(error, wellKnownFolders) {
if (error) {
return callback(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
} }
};
if (wellKnownFolders.Inbox.length === 0) { // login and logout do not use error objects in the callback, but rather invoke
// the client needs at least an inbox folder to work properly // the global onError handler, so we need to track if login was successful
return callback(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host))); self._imap.onError = function(error) {
if (!loggedIn) {
reject(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
} else {
reject(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
} }
};
self._imap.logout(function() { self._imap.login(function() {
callback(); loggedIn = true;
self._imap.listWellKnownFolders(function(error, wellKnownFolders) {
if (error) {
reject(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
return;
}
if (wellKnownFolders.Inbox.length === 0) {
// the client needs at least an inbox folder to work properly
reject(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host)));
return;
}
self._imap.logout(function() {
resolve();
});
}); });
}); });
}); });
@ -250,39 +244,39 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
/** /**
* Checks if an SMTP server is reachable and accepts the credentials and logs out. * Checks if an SMTP server is reachable and accepts the credentials and logs out.
* Adds the certificate to the SMTP settings if not provided. * Adds the certificate to the SMTP settings if not provided.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/ */
ConnectionDoctor.prototype._checkSmtp = function(callback) { ConnectionDoctor.prototype._checkSmtp = function() {
var self = this, var self = this;
host = self.credentials.smtp.host + ':' + self.credentials.smtp.port, return new Promise(function(resolve, reject) {
errored = false, // tracks if we need to invoke the callback at onclose or not var host = self.credentials.smtp.host + ':' + self.credentials.smtp.port,
str = this._appConfig.string; errored = false, // tracks if we need to invoke the callback at onclose or not
str = self._appConfig.string;
self._smtp.oncert = function(pemEncodedCert) { self._smtp.oncert = function(pemEncodedCert) {
if (!self.credentials.smtp.ca) { if (!self.credentials.smtp.ca) {
self.credentials.smtp.ca = pemEncodedCert; self.credentials.smtp.ca = pemEncodedCert;
} }
}; };
self._smtp.onerror = function(error) { self._smtp.onerror = function(error) {
if (error) { if (error) {
errored = true; errored = true;
callback(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error)); reject(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
} }
}; };
self._smtp.onidle = function() { self._smtp.onidle = function() {
self._smtp.quit(); self._smtp.quit();
}; };
self._smtp.onclose = function() { self._smtp.onclose = function() {
if (!errored) { if (!errored) {
callback(); resolve();
} }
}; };
self._smtp.connect(); self._smtp.connect();
});
}; };

View File

@ -58,5 +58,7 @@ Notif.prototype.create = function(options) {
}; };
Notif.prototype.close = function(notification) { Notif.prototype.close = function(notification) {
notification.close(); if (notification) {
notification.close();
}
}; };

View File

@ -25,76 +25,67 @@ function UpdateHandler(appConfigStore, accountStore, auth, dialog) {
/** /**
* Executes all the necessary updates * Executes all the necessary updates
* @param {Function} callback(error) Invoked when all the database updates were executed, or if an error occurred
*/ */
UpdateHandler.prototype.update = function(callback) { UpdateHandler.prototype.update = function() {
var self = this, var self = this,
currentVersion = 0, currentVersion = 0,
targetVersion = cfg.dbVersion, targetVersion = cfg.dbVersion,
versionDbType = 'dbVersion'; versionDbType = 'dbVersion';
self._appConfigStorage.listItems(versionDbType, 0, null, function(err, items) { return self._appConfigStorage.listItems(versionDbType, 0, null).then(function(items) {
if (err) {
callback(err);
return;
}
// parse the database version number // parse the database version number
if (items && items.length > 0) { if (items && items.length > 0) {
currentVersion = parseInt(items[0], 10); currentVersion = parseInt(items[0], 10);
} }
self._applyUpdate({ return self._applyUpdate({
currentVersion: currentVersion, currentVersion: currentVersion,
targetVersion: targetVersion targetVersion: targetVersion
}, callback); });
}); });
}; };
/** /**
* Schedules necessary updates and executes thom in order * Schedules necessary updates and executes thom in order
*/ */
UpdateHandler.prototype._applyUpdate = function(options, callback) { UpdateHandler.prototype._applyUpdate = function(options) {
var self = this, var self = this;
scriptOptions, return new Promise(function(resolve, reject) {
queue = []; var scriptOptions,
queue = [];
if (options.currentVersion >= options.targetVersion) { if (options.currentVersion >= options.targetVersion) {
// the current database version is up to date // the current database version is up to date
callback(); resolve();
return;
}
scriptOptions = {
appConfigStorage: self._appConfigStorage,
userStorage: self._userStorage,
auth: self._auth
};
// add all the necessary database updates to the queue
for (var i = options.currentVersion; i < options.targetVersion; i++) {
queue.push(self._updateScripts[i]);
}
// takes the next update from the queue and executes it
function executeNextUpdate(err) {
if (err) {
callback(err);
return; return;
} }
if (queue.length < 1) { scriptOptions = {
// we're done appConfigStorage: self._appConfigStorage,
callback(); userStorage: self._userStorage,
return; auth: self._auth
};
// add all the necessary database updates to the queue
for (var i = options.currentVersion; i < options.targetVersion; i++) {
queue.push(self._updateScripts[i]);
} }
// process next update // takes the next update from the queue and executes it
var script = queue.shift(); function executeNextUpdate() {
script(scriptOptions, executeNextUpdate); if (queue.length < 1) {
} // we're done
resolve();
return;
}
executeNextUpdate(); // process next update
var script = queue.shift();
script(scriptOptions).then(executeNextUpdate).catch(reject);
}
executeNextUpdate();
});
}; };
/** /**

View File

@ -7,20 +7,15 @@
* every non-prefixed mail in the IMAP folders would be nuked due to the implementation * every non-prefixed mail in the IMAP folders would be nuked due to the implementation
* of the delta sync. * of the delta sync.
*/ */
function updateV1(options, callback) { function updateV1(options) {
var emailDbType = 'email_', var emailDbType = 'email_',
versionDbType = 'dbVersion', versionDbType = 'dbVersion',
postUpdateDbVersion = 1; postUpdateDbVersion = 1;
// remove the emails // remove the emails
options.userStorage.removeList(emailDbType, function(err) { return options.userStorage.removeList(emailDbType).then(function() {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion // update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback); return options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType);
}); });
} }

View File

@ -6,20 +6,15 @@
* In database version 2, the stored email objects have to be purged, because the * In database version 2, the stored email objects have to be purged, because the
* new data model stores information about the email structure in the property 'bodyParts'. * new data model stores information about the email structure in the property 'bodyParts'.
*/ */
function updateV2(options, callback) { function updateV2(options) {
var emailDbType = 'email_', var emailDbType = 'email_',
versionDbType = 'dbVersion', versionDbType = 'dbVersion',
postUpdateDbVersion = 2; postUpdateDbVersion = 2;
// remove the emails // remove the emails
options.userStorage.removeList(emailDbType, function(err) { return options.userStorage.removeList(emailDbType).then(function() {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion // update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback); return options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType);
}); });
} }

View File

@ -6,20 +6,15 @@
* In database version 3, we introduced new flags to the messages, also * In database version 3, we introduced new flags to the messages, also
* the outbox uses artificial uids * the outbox uses artificial uids
*/ */
function update(options, callback) { function update(options) {
var emailDbType = 'email_', var emailDbType = 'email_',
versionDbType = 'dbVersion', versionDbType = 'dbVersion',
postUpdateDbVersion = 3; postUpdateDbVersion = 3;
// remove the emails // remove the emails
options.userStorage.removeList(emailDbType, function(err) { return options.userStorage.removeList(emailDbType).then(function() {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion // update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback); return options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType);
}); });
} }

View File

@ -7,7 +7,7 @@
* indexeddb. only gmail was allowed as a mail service provider before, * indexeddb. only gmail was allowed as a mail service provider before,
* so let's add this... * so let's add this...
*/ */
function update(options, callback) { function update(options) {
var VERSION_DB_TYPE = 'dbVersion', var VERSION_DB_TYPE = 'dbVersion',
EMAIL_ADDR_DB_KEY = 'emailaddress', EMAIL_ADDR_DB_KEY = 'emailaddress',
USERNAME_DB_KEY = 'username', USERNAME_DB_KEY = 'username',
@ -29,77 +29,45 @@ function update(options, callback) {
}; };
// load the email address (if existing) // load the email address (if existing)
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) { var emailAddress;
if (err) { return loadFromDB(EMAIL_ADDR_DB_KEY).then(function(address) {
return callback(err); emailAddress = address;
// load the provider (if existing)
return loadFromDB(PROVIDER_DB_KEY);
}).then(function(provider) {
// if there is an email address without a provider, we need to add the missing provider entry
// for any other situation, we're good.
if (!(emailAddress && !provider)) {
// update the database version to POST_UPDATE_DB_VERSION
return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE);
} }
// load the provider (if existing) // add the missing provider key
loadFromDB(PROVIDER_DB_KEY, function(err, provider) { var storeProvider = options.appConfigStorage.storeList(['gmail'], PROVIDER_DB_KEY);
if (err) { // add the missing user name key
return callback(err); var storeAdress = options.appConfigStorage.storeList([emailAddress], USERNAME_DB_KEY);
} // add the missing imap host info key
var storeImap = options.appConfigStorage.storeList([imap], IMAP_DB_KEY);
// add the missing empty real name
var storeEmptyName = options.appConfigStorage.storeList([''], REALNAME_DB_KEY);
// add the missing smtp host info key
var storeSmtp = options.appConfigStorage.storeList([smtp], SMTP_DB_KEY);
// if there is an email address without a provider, we need to add the missing provider entry return Promise.all([storeProvider, storeAdress, storeImap, storeEmptyName, storeSmtp]).then(function() {
// for any other situation, we're good. // reload the credentials
options.auth.initialized = false;
return options.auth._loadCredentials();
if (!(emailAddress && !provider)) { }).then(function() {
// update the database version to POST_UPDATE_DB_VERSION // update the database version to POST_UPDATE_DB_VERSION
return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback); return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE);
}
// add the missing provider key
options.appConfigStorage.storeList(['gmail'], PROVIDER_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing user name key
options.appConfigStorage.storeList([emailAddress], USERNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing imap host info key
options.appConfigStorage.storeList([imap], IMAP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing empty real name
options.appConfigStorage.storeList([''], REALNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing smtp host info key
options.appConfigStorage.storeList([smtp], SMTP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// reload the credentials
options.auth.initialized = false;
options.auth._loadCredentials(function(err) {
if (err) {
return callback(err);
}
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
});
});
});
});
});
}); });
}); });
function loadFromDB(key, callback) { function loadFromDB(key) {
options.appConfigStorage.listItems(key, 0, null, function(err, cachedItems) { return options.appConfigStorage.listItems(key, 0, null).then(function(cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0])); return cachedItems && cachedItems[0];
}); });
} }
} }

View File

@ -16,14 +16,9 @@ var POST_UPDATE_DB_VERSION = 5;
* Due to an overlooked issue, there may be multiple folders, e.g. for sent mails. * Due to an overlooked issue, there may be multiple folders, e.g. for sent mails.
* This removes the "duplicate" folders. * This removes the "duplicate" folders.
*/ */
function update(options, callback) { function update(options) {
// remove the emails // remove the emails
options.userStorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) { return options.userStorage.listItems(FOLDER_DB_TYPE, 0, null).then(function(stored) {
if (err) {
return callback(err);
}
var folders = stored[0] || []; var folders = stored[0] || [];
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) { [FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
var foldersForType = folders.filter(function(mbx) { var foldersForType = folders.filter(function(mbx) {
@ -39,15 +34,11 @@ function update(options, callback) {
folders.splice(folders.indexOf(foldersForType[i]), 1); folders.splice(folders.indexOf(foldersForType[i]), 1);
} }
}); });
return options.userStorage.storeList([folders], FOLDER_DB_TYPE);
options.userStorage.storeList([folders], FOLDER_DB_TYPE, function(err) { }).then(function() {
if (err) { // update the database version to POST_UPDATE_DB_VERSION
return callback(err); return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE);
}
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
}); });
} }

View File

@ -1,5 +1,5 @@
/** /**
* @license AngularJS v1.3.2 * @license AngularJS v1.3.7
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
@ -19,7 +19,7 @@
* # Usage * # Usage
* *
* To see animations in action, all that is required is to define the appropriate CSS classes * To see animations in action, all that is required is to define the appropriate CSS classes
* or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are: * or to register a JavaScript animation via the `myModule.animation()` function. The directives that support animation automatically are:
* `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
* by using the `$animate` service. * by using the `$animate` service.
* *
@ -161,8 +161,8 @@
* ### Structural transition animations * ### Structural transition animations
* *
* Structural transitions (such as enter, leave and move) will always apply a `0s none` transition * Structural transitions (such as enter, leave and move) will always apply a `0s none` transition
* value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave * value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave`
* or .ng-move) class. This means that any active transition animations operating on the element * or `.ng-move`) class. This means that any active transition animations operating on the element
* will be cut off to make way for the enter, leave or move animation. * will be cut off to make way for the enter, leave or move animation.
* *
* ### Class-based transition animations * ### Class-based transition animations
@ -245,7 +245,7 @@
* You then configure `$animate` to enforce this prefix: * You then configure `$animate` to enforce this prefix:
* *
* ```js * ```js
* $animateProvider.classNamePrefix(/animate-/); * $animateProvider.classNameFilter(/animate-/);
* ``` * ```
* </div> * </div>
* *
@ -479,11 +479,12 @@ angular.module('ngAnimate', ['ng'])
function isMatchingElement(elm1, elm2) { function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2); return extractElementNode(elm1) == extractElementNode(elm2);
} }
var $$jqLite;
$provide.decorator('$animate', $provide.decorator('$animate',
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite',
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) { function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest, $$$jqLite) {
$$jqLite = $$$jqLite;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState); $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// Wait until all directive and route-related templates are downloaded and // Wait until all directive and route-related templates are downloaded and
@ -877,22 +878,22 @@ angular.module('ngAnimate', ['ng'])
* *
* Below is a breakdown of each step that occurs during the `animate` animation: * Below is a breakdown of each step that occurs during the `animate` animation:
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| * |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
* | 1. $animate.animate(...) is called | class="my-animation" | * | 1. `$animate.animate(...)` is called | `class="my-animation"` |
* | 2. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | * | 2. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 3. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | * | 3. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 4. the className class value is added to the element | class="my-animation ng-animate className" | * | 4. the `className` class value is added to the element | `class="my-animation ng-animate className"` |
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate className" | * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate className"` |
* | 6. $animate blocks all CSS transitions on the element to ensure the .className class styling is applied right away| class="my-animation ng-animate className" | * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"` |
* | 7. $animate applies the provided collection of `from` CSS styles to the element | class="my-animation ng-animate className" | * | 7. `$animate` applies the provided collection of `from` CSS styles to the element | `class="my-animation ng-animate className"` |
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate className" | * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate className"` |
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate className" | * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate className"` |
* | 10. the className-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate className className-active" | * | 10. the `className-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate className className-active"` |
* | 11. $animate applies the collection of `to` CSS styles to the element which are then handled by the transition | class="my-animation ng-animate className className-active" | * | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition | `class="my-animation ng-animate className className-active"` |
* | 12. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate className className-active" | * | 12. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate className className-active"` |
* | 13. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | * | 13. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 14. The returned promise is resolved. | class="my-animation" | * | 14. The returned promise is resolved. | `class="my-animation"` |
* *
* @param {DOMElement} element the element that will be the focus of the enter animation * @param {DOMElement} element the element that will be the focus of the enter animation
* @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation * @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation
@ -923,21 +924,21 @@ angular.module('ngAnimate', ['ng'])
* *
* Below is a breakdown of each step that occurs during enter animation: * Below is a breakdown of each step that occurs during enter animation:
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. $animate.enter(...) is called | class="my-animation" | * | 1. `$animate.enter(...)` is called | `class="my-animation"` |
* | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" | * | 2. element is inserted into the `parentElement` element or beside the `afterElement` element | `class="my-animation"` |
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | * | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" | * | 5. the `.ng-enter` class is added to the element | `class="my-animation ng-animate ng-enter"` |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" | * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-enter"` |
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" | * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"` |
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" | * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-enter"` |
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" | * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-enter"` |
* | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" | * | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" | * | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | * | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 13. The returned promise is resolved. | class="my-animation" | * | 13. The returned promise is resolved. | `class="my-animation"` |
* *
* @param {DOMElement} element the element that will be the focus of the enter animation * @param {DOMElement} element the element that will be the focus of the enter animation
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
@ -969,21 +970,21 @@ angular.module('ngAnimate', ['ng'])
* *
* Below is a breakdown of each step that occurs during leave animation: * Below is a breakdown of each step that occurs during leave animation:
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. $animate.leave(...) is called | class="my-animation" | * | 1. `$animate.leave(...)` is called | `class="my-animation"` |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" | * | 4. the `.ng-leave` class is added to the element | `class="my-animation ng-animate ng-leave"` |
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" | * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-leave"` |
* | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave | * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"` |
* | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" | * | 7. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-leave"` |
* | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave | * | 8. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-leave"` |
* | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" | * | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
* | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" | * | 10. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
* | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | * | 11. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 12. The element is removed from the DOM | ... | * | 12. The element is removed from the DOM | ... |
* | 13. The returned promise is resolved. | ... | * | 13. The returned promise is resolved. | ... |
* *
* @param {DOMElement} element the element that will be the focus of the leave animation * @param {DOMElement} element the element that will be the focus of the leave animation
* @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
@ -1014,21 +1015,21 @@ angular.module('ngAnimate', ['ng'])
* *
* Below is a breakdown of each step that occurs during move animation: * Below is a breakdown of each step that occurs during move animation:
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| * |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. $animate.move(...) is called | class="my-animation" | * | 1. `$animate.move(...)` is called | `class="my-animation"` |
* | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" | * | 2. element is moved into the parentElement element or beside the afterElement element | `class="my-animation"` |
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" | * | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | * | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" | * | 5. the `.ng-move` class is added to the element | `class="my-animation ng-animate ng-move"` |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" | * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-move"` |
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move | * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"` |
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" | * | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-move"` |
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move | * | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-move"` |
* | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" | * | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-move ng-move-active"` |
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" | * | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-move ng-move-active"` |
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | * | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 13. The returned promise is resolved. | class="my-animation" | * | 13. The returned promise is resolved. | `class="my-animation"` |
* *
* @param {DOMElement} element the element that will be the focus of the move animation * @param {DOMElement} element the element that will be the focus of the move animation
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
@ -1062,18 +1063,18 @@ angular.module('ngAnimate', ['ng'])
* *
* Below is a breakdown of each step that occurs during addClass animation: * Below is a breakdown of each step that occurs during addClass animation:
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------| * |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
* | 1. $animate.addClass(element, 'super') is called | class="my-animation" | * | 1. `$animate.addClass(element, 'super')` is called | `class="my-animation"` |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" | * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" | * | 3. the `.super-add` class is added to the element | `class="my-animation ng-animate super-add"` |
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" | * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate super-add"` |
* | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" | * | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" | * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super super-add super-add-active"` |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation super super-add super-add-active" | * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super super-add super-add-active"` |
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" | * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation super"` |
* | 9. The super class is kept on the element | class="my-animation super" | * | 9. The super class is kept on the element | `class="my-animation super"` |
* | 10. The returned promise is resolved. | class="my-animation super" | * | 10. The returned promise is resolved. | `class="my-animation super"` |
* *
* @param {DOMElement} element the element that will be animated * @param {DOMElement} element the element that will be animated
* @param {string} className the CSS class that will be added to the element and then animated * @param {string} className the CSS class that will be added to the element and then animated
@ -1096,17 +1097,17 @@ angular.module('ngAnimate', ['ng'])
* *
* Below is a breakdown of each step that occurs during removeClass animation: * Below is a breakdown of each step that occurs during removeClass animation:
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------| * |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
* | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" | * | 1. `$animate.removeClass(element, 'super')` is called | `class="my-animation super"` |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" | * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation super ng-animate"` |
* | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" | * | 3. the `.super-remove` class is added to the element | `class="my-animation super ng-animate super-remove"` |
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" | * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation super ng-animate super-remove"` |
* | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" | * | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" | * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super-remove super-remove-active"` |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" | * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super-remove super-remove-active"` |
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 9. The returned promise is resolved. | class="my-animation" | * | 9. The returned promise is resolved. | `class="my-animation"` |
* *
* *
* @param {DOMElement} element the element that will be animated * @param {DOMElement} element the element that will be animated
@ -1124,19 +1125,19 @@ angular.module('ngAnimate', ['ng'])
* @name $animate#setClass * @name $animate#setClass
* *
* @description Adds and/or removes the given CSS classes to and from the element. * @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided). * Once complete, the `done()` callback will be fired (if provided).
* *
* | Animation Step | What the element class attribute looks like | * | Animation Step | What the element class attribute looks like |
* |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| * |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
* | 1. $animate.removeClass(element, on, off) is called | class="my-animation super off | * | 1. `$animate.setClass(element, 'on', 'off')` is called | `class="my-animation off"` |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate off | * | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate off"` |
* | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off | * | 3. the `.on-add` and `.off-remove` classes are added to the element | `class="my-animation ng-animate on-add off-remove off"` |
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off | * | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate on-add off-remove off"` |
* | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active | * | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" | * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" | * | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" | * | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation on"` |
* | 9. The returned promise is resolved. | class="my-animation on" | * | 9. The returned promise is resolved. | `class="my-animation on"` |
* *
* @param {DOMElement} element the element which will have its CSS classes changed * @param {DOMElement} element the element which will have its CSS classes changed
* removed from it * removed from it
@ -1274,7 +1275,7 @@ angular.module('ngAnimate', ['ng'])
all animations call this shared animation triggering function internally. all animations call this shared animation triggering function internally.
The animationEvent variable refers to the JavaScript animation event that will be triggered The animationEvent variable refers to the JavaScript animation event that will be triggered
and the className value is the name of the animation that will be applied within the and the className value is the name of the animation that will be applied within the
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation CSS code. Element, `parentElement` and `afterElement` are provided DOM elements for the animation
and the onComplete callback will be fired once the animation is fully complete. and the onComplete callback will be fired once the animation is fully complete.
*/ */
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) { function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
@ -1386,10 +1387,10 @@ angular.module('ngAnimate', ['ng'])
//the ng-animate class does nothing, but it's here to allow for //the ng-animate class does nothing, but it's here to allow for
//parent animations to find and cancel child animations when needed //parent animations to find and cancel child animations when needed
element.addClass(NG_ANIMATE_CLASS_NAME); $$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME);
if (options && options.tempClasses) { if (options && options.tempClasses) {
forEach(options.tempClasses, function(className) { forEach(options.tempClasses, function(className) {
element.addClass(className); $$jqLite.addClass(element, className);
}); });
} }
@ -1467,7 +1468,7 @@ angular.module('ngAnimate', ['ng'])
closeAnimation.hasBeenRun = true; closeAnimation.hasBeenRun = true;
if (options && options.tempClasses) { if (options && options.tempClasses) {
forEach(options.tempClasses, function(className) { forEach(options.tempClasses, function(className) {
element.removeClass(className); $$jqLite.removeClass(element, className);
}); });
} }
@ -1529,7 +1530,7 @@ angular.module('ngAnimate', ['ng'])
} }
if (removeAnimations || !data.totalActive) { if (removeAnimations || !data.totalActive) {
element.removeClass(NG_ANIMATE_CLASS_NAME); $$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE); element.removeData(NG_ANIMATE_STATE);
} }
} }
@ -1770,14 +1771,14 @@ angular.module('ngAnimate', ['ng'])
var staggerCacheKey = cacheKey + ' ' + staggerClassName; var staggerCacheKey = cacheKey + ' ' + staggerClassName;
var applyClasses = !lookupCache[staggerCacheKey]; var applyClasses = !lookupCache[staggerCacheKey];
applyClasses && element.addClass(staggerClassName); applyClasses && $$jqLite.addClass(element, staggerClassName);
stagger = getElementAnimationDetails(element, staggerCacheKey); stagger = getElementAnimationDetails(element, staggerCacheKey);
applyClasses && element.removeClass(staggerClassName); applyClasses && $$jqLite.removeClass(element, staggerClassName);
} }
element.addClass(className); $$jqLite.addClass(element, className);
var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {}; var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
var timings = getElementAnimationDetails(element, eventCacheKey); var timings = getElementAnimationDetails(element, eventCacheKey);
@ -1785,7 +1786,7 @@ angular.module('ngAnimate', ['ng'])
var animationDuration = timings.animationDuration; var animationDuration = timings.animationDuration;
if (structural && transitionDuration === 0 && animationDuration === 0) { if (structural && transitionDuration === 0 && animationDuration === 0) {
element.removeClass(className); $$jqLite.removeClass(element, className);
return false; return false;
} }
@ -1857,7 +1858,7 @@ angular.module('ngAnimate', ['ng'])
} }
if (!staggerTime) { if (!staggerTime) {
element.addClass(activeClassName); $$jqLite.addClass(element, activeClassName);
if (elementData.blockTransition) { if (elementData.blockTransition) {
blockTransitions(node, false); blockTransitions(node, false);
} }
@ -1867,7 +1868,7 @@ angular.module('ngAnimate', ['ng'])
var timings = getElementAnimationDetails(element, eventCacheKey); var timings = getElementAnimationDetails(element, eventCacheKey);
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
if (maxDuration === 0) { if (maxDuration === 0) {
element.removeClass(activeClassName); $$jqLite.removeClass(element, activeClassName);
animateClose(element, className); animateClose(element, className);
activeAnimationComplete(); activeAnimationComplete();
return; return;
@ -1889,7 +1890,7 @@ angular.module('ngAnimate', ['ng'])
//the jqLite object, so we're safe to use a single variable to house //the jqLite object, so we're safe to use a single variable to house
//the styles since there is always only one element being animated //the styles since there is always only one element being animated
var oldStyle = node.getAttribute('style') || ''; var oldStyle = node.getAttribute('style') || '';
if (oldStyle.charAt(oldStyle.length-1) !== ';') { if (oldStyle.charAt(oldStyle.length - 1) !== ';') {
oldStyle += ';'; oldStyle += ';';
} }
node.setAttribute('style', oldStyle + ' ' + style); node.setAttribute('style', oldStyle + ' ' + style);
@ -1902,7 +1903,7 @@ angular.module('ngAnimate', ['ng'])
var staggerTimeout; var staggerTimeout;
if (staggerTime > 0) { if (staggerTime > 0) {
element.addClass(pendingClassName); $$jqLite.addClass(element, pendingClassName);
staggerTimeout = $timeout(function() { staggerTimeout = $timeout(function() {
staggerTimeout = null; staggerTimeout = null;
@ -1913,8 +1914,8 @@ angular.module('ngAnimate', ['ng'])
blockAnimations(node, false); blockAnimations(node, false);
} }
element.addClass(activeClassName); $$jqLite.addClass(element, activeClassName);
element.removeClass(pendingClassName); $$jqLite.removeClass(element, pendingClassName);
if (styles) { if (styles) {
if (timings.transitionDuration === 0) { if (timings.transitionDuration === 0) {
@ -1941,8 +1942,8 @@ angular.module('ngAnimate', ['ng'])
// timeout done method. // timeout done method.
function onEnd() { function onEnd() {
element.off(css3AnimationEvents, onAnimationProgress); element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName); $$jqLite.removeClass(element, activeClassName);
element.removeClass(pendingClassName); $$jqLite.removeClass(element, pendingClassName);
if (staggerTimeout) { if (staggerTimeout) {
$timeout.cancel(staggerTimeout); $timeout.cancel(staggerTimeout);
} }
@ -2030,7 +2031,7 @@ angular.module('ngAnimate', ['ng'])
} }
function animateClose(element, className) { function animateClose(element, className) {
element.removeClass(className); $$jqLite.removeClass(element, className);
var data = element.data(NG_ANIMATE_CSS_DATA_KEY); var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if (data) { if (data) {
if (data.running) { if (data.running) {

View File

@ -1,5 +1,5 @@
/** /**
* @license AngularJS v1.3.2 * @license AngularJS v1.3.7
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
@ -117,7 +117,7 @@ angular.mock.$Browser = function() {
self.defer.now += delay; self.defer.now += delay;
} else { } else {
if (self.deferredFns.length) { if (self.deferredFns.length) {
self.defer.now = self.deferredFns[self.deferredFns.length-1].time; self.defer.now = self.deferredFns[self.deferredFns.length - 1].time;
} else { } else {
throw new Error('No deferred tasks to be flushed'); throw new Error('No deferred tasks to be flushed');
} }
@ -428,7 +428,7 @@ angular.mock.$LogProvider = function() {
}); });
}); });
if (errors.length) { if (errors.length) {
errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+ errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " +
"an expected log message was not checked and removed:"); "an expected log message was not checked and removed:");
errors.push(''); errors.push('');
throw new Error(errors.join('\n---------\n')); throw new Error(errors.join('\n---------\n'));
@ -461,17 +461,17 @@ angular.mock.$LogProvider = function() {
* @returns {promise} A promise which will be notified on each iteration. * @returns {promise} A promise which will be notified on each iteration.
*/ */
angular.mock.$IntervalProvider = function() { angular.mock.$IntervalProvider = function() {
this.$get = ['$rootScope', '$q', this.$get = ['$browser', '$rootScope', '$q', '$$q',
function($rootScope, $q) { function($browser, $rootScope, $q, $$q) {
var repeatFns = [], var repeatFns = [],
nextRepeatId = 0, nextRepeatId = 0,
now = 0; now = 0;
var $interval = function(fn, delay, count, invokeApply) { var $interval = function(fn, delay, count, invokeApply) {
var deferred = $q.defer(), var iteration = 0,
promise = deferred.promise, skipApply = (angular.isDefined(invokeApply) && !invokeApply),
iteration = 0, deferred = (skipApply ? $$q : $q).defer(),
skipApply = (angular.isDefined(invokeApply) && !invokeApply); promise = deferred.promise;
count = (angular.isDefined(count)) ? count : 0; count = (angular.isDefined(count)) ? count : 0;
promise.then(null, null, fn); promise.then(null, null, fn);
@ -494,7 +494,11 @@ angular.mock.$IntervalProvider = function() {
} }
} }
if (!skipApply) $rootScope.$apply(); if (skipApply) {
$browser.defer.flush();
} else {
$rootScope.$apply();
}
} }
repeatFns.push({ repeatFns.push({
@ -581,10 +585,10 @@ function jsonStringToDate(string) {
tzMin = int(match[9] + match[11]); tzMin = int(match[9] + match[11]);
} }
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour, date.setUTCHours(int(match[4] || 0) - tzHour,
int(match[5]||0) - tzMin, int(match[5] || 0) - tzMin,
int(match[6]||0), int(match[6] || 0),
int(match[7]||0)); int(match[7] || 0));
return date; return date;
} }
return string; return string;
@ -663,7 +667,7 @@ angular.mock.TzDate = function(offset, timestamp) {
} }
var localOffset = new Date(timestamp).getTimezoneOffset(); var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60;
self.date = new Date(timestamp + self.offsetDiff); self.date = new Date(timestamp + self.offsetDiff);
self.getTime = function() { self.getTime = function() {
@ -815,7 +819,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
animate.queue.push({ animate.queue.push({
event: method, event: method,
element: arguments[0], element: arguments[0],
options: arguments[arguments.length-1], options: arguments[arguments.length - 1],
args: arguments args: arguments
}); });
return $delegate[method].apply($delegate, arguments); return $delegate[method].apply($delegate, arguments);
@ -1115,7 +1119,7 @@ angular.mock.dump = function(object) {
``` ```
*/ */
angular.mock.$HttpBackendProvider = function() { angular.mock.$HttpBackendProvider = function() {
this.$get = ['$rootScope', createHttpBackendMock]; this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
}; };
/** /**
@ -1132,7 +1136,7 @@ angular.mock.$HttpBackendProvider = function() {
* @param {Object=} $browser Auto-flushing enabled if specified * @param {Object=} $browser Auto-flushing enabled if specified
* @return {Object} Instance of $httpBackend mock * @return {Object} Instance of $httpBackend mock
*/ */
function createHttpBackendMock($rootScope, $delegate, $browser) { function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var definitions = [], var definitions = [],
expectations = [], expectations = [],
responses = [], responses = [],
@ -1145,7 +1149,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
return function() { return function() {
return angular.isNumber(status) return angular.isNumber(status)
? [status, data, headers, statusText] ? [status, data, headers, statusText]
: [200, status, data]; : [200, status, data, headers];
}; };
} }
@ -1162,7 +1166,9 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
} }
function wrapResponse(wrapped) { function wrapResponse(wrapped) {
if (!$browser && timeout && timeout.then) timeout.then(handleTimeout); if (!$browser && timeout) {
timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
}
return handleResponse; return handleResponse;
@ -1770,7 +1776,7 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
} }
var length = queue.length; var length = queue.length;
for (var i=0;i<length;i++) { for (var i = 0; i < length; i++) {
queue[i](); queue[i]();
} }
@ -2036,7 +2042,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
*/ */
angular.mock.e2e = {}; angular.mock.e2e = {};
angular.mock.e2e.$httpBackendDecorator = angular.mock.e2e.$httpBackendDecorator =
['$rootScope', '$delegate', '$browser', createHttpBackendMock]; ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
/** /**
@ -2050,7 +2056,7 @@ angular.mock.e2e.$httpBackendDecorator =
* *
* In addition to all the regular `Scope` methods, the following helper methods are available: * In addition to all the regular `Scope` methods, the following helper methods are available:
*/ */
angular.mock.$RootScopeDecorator = function($delegate) { angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
var $rootScopePrototype = Object.getPrototypeOf($delegate); var $rootScopePrototype = Object.getPrototypeOf($delegate);
@ -2122,7 +2128,7 @@ angular.mock.$RootScopeDecorator = function($delegate) {
return count; return count;
} }
}; }];
if (window.jasmine || window.mocha) { if (window.jasmine || window.mocha) {

View File

@ -1,5 +1,5 @@
/** /**
* @license AngularJS v1.3.2 * @license AngularJS v1.3.7
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
@ -41,7 +41,7 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
*/ */
function $RouteProvider() { function $RouteProvider() {
function inherit(parent, extra) { function inherit(parent, extra) {
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); return angular.extend(Object.create(parent), extra);
} }
var routes = {}; var routes = {};
@ -151,6 +151,9 @@ function $RouteProvider() {
if (angular.isUndefined(routeCopy.reloadOnSearch)) { if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true; routeCopy.reloadOnSearch = true;
} }
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
}
routes[path] = angular.extend( routes[path] = angular.extend(
routeCopy, routeCopy,
path && pathRegExp(path, routeCopy) path && pathRegExp(path, routeCopy)
@ -158,9 +161,9 @@ function $RouteProvider() {
// create redirection for trailing slashes // create redirection for trailing slashes
if (path) { if (path) {
var redirectPath = (path[path.length-1] == '/') var redirectPath = (path[path.length - 1] == '/')
? path.substr(0, path.length-1) ? path.substr(0, path.length - 1)
: path +'/'; : path + '/';
routes[redirectPath] = angular.extend( routes[redirectPath] = angular.extend(
{redirectTo: path}, {redirectTo: path},
@ -171,6 +174,17 @@ function $RouteProvider() {
return this; return this;
}; };
/**
* @ngdoc property
* @name $routeProvider#caseInsensitiveMatch
* @description
*
* A boolean property indicating if routes defined
* using this provider should be matched using a case insensitive
* algorithm. Defaults to `false`.
*/
this.caseInsensitiveMatch = false;
/** /**
* @param path {string} path * @param path {string} path
* @param opts {Object} options * @param opts {Object} options
@ -639,11 +653,11 @@ function $RouteProvider() {
*/ */
function interpolate(string, params) { function interpolate(string, params) {
var result = []; var result = [];
angular.forEach((string||'').split(':'), function(segment, i) { angular.forEach((string || '').split(':'), function(segment, i) {
if (i === 0) { if (i === 0) {
result.push(segment); result.push(segment);
} else { } else {
var segmentMatch = segment.match(/(\w+)(.*)/); var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
var key = segmentMatch[1]; var key = segmentMatch[1];
result.push(params[key]); result.push(params[key]);
result.push(segmentMatch[2] || ''); result.push(segmentMatch[2] || '');
@ -774,7 +788,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
.view-animate-container { .view-animate-container {
position:relative; position:relative;
height:100px!important; height:100px!important;
position:relative;
background:white; background:white;
border:1px solid black; border:1px solid black;
height:40px; height:40px;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -43,4 +43,24 @@ require('../src/js/app-config');
require('../src/js/util'); require('../src/js/util');
require('../src/js/crypto'); require('../src/js/crypto');
require('../src/js/service'); require('../src/js/service');
require('../src/js/email'); require('../src/js/email');
//
// Global mocks
//
window.qMock = function(res, rej) {
return new Promise(res, rej);
};
window.resolves = function(val) {
return new Promise(function(res) {
res(val);
});
};
window.rejects = function(val) {
return new Promise(function(res, rej) {
rej(val);
});
};

View File

@ -43,6 +43,7 @@ describe('Account Controller unit test', function() {
scope.state = {}; scope.state = {};
accountCtrl = $controller(AccountCtrl, { accountCtrl = $controller(AccountCtrl, {
$scope: scope, $scope: scope,
$q: window.qMock,
auth: authStub, auth: authStub,
keychain: keychainStub, keychain: keychainStub,
pgp: pgpStub, pgp: pgpStub,
@ -63,8 +64,8 @@ describe('Account Controller unit test', function() {
}); });
}); });
describe('export to key file', function() { describe('export to key file', function() {
it('should work', function() { it('should work', function(done) {
keychainStub.getUserKeyPair.withArgs(emailAddress).yields(null, { keychainStub.getUserKeyPair.withArgs(emailAddress).returns(resolves({
publicKey: { publicKey: {
_id: dummyKeyId, _id: dummyKeyId,
publicKey: 'a' publicKey: 'a'
@ -72,24 +73,26 @@ describe('Account Controller unit test', function() {
privateKey: { privateKey: {
encryptedKey: 'b' encryptedKey: 'b'
} }
}); }));
downloadStub.createDownload.withArgs(sinon.match(function(arg) { downloadStub.createDownload.withArgs(sinon.match(function(arg) {
return arg.content === 'a\r\nb' && arg.filename === 'whiteout_mail_' + emailAddress + '_' + expectedKeyId + '.asc' && arg.contentType === 'text/plain'; return arg.content === 'a\r\nb' && arg.filename === 'whiteout_mail_' + emailAddress + '_' + expectedKeyId + '.asc' && arg.contentType === 'text/plain';
})).returns(); })).returns();
scope.exportKeyFile(); scope.exportKeyFile().then(function() {
expect(scope.state.lightbox).to.equal(undefined);
expect(scope.state.lightbox).to.equal(undefined); expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true; expect(downloadStub.createDownload.calledOnce).to.be.true;
expect(downloadStub.createDownload.calledOnce).to.be.true; done();
});
}); });
it('should not work when key export failed', function() { it('should not work when key export failed', function(done) {
keychainStub.getUserKeyPair.yields(new Error()); keychainStub.getUserKeyPair.returns(rejects(new Error()));
scope.exportKeyFile(); scope.exportKeyFile().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; done();
});
}); });
}); });
}); });

View File

@ -33,6 +33,7 @@ describe('Action Bar Controller unit test', function() {
actionBarCtrl = $controller(ActionBarCtrl, { actionBarCtrl = $controller(ActionBarCtrl, {
$scope: scope, $scope: scope,
$q: window.qMock,
email: emailMock, email: emailMock,
dialog: dialogMock, dialog: dialogMock,
status: statusMock status: statusMock
@ -47,13 +48,14 @@ describe('Action Bar Controller unit test', function() {
scope.deleteMessage(); scope.deleteMessage();
}); });
it('should delete the selected mail', function() { it('should delete the selected mail', function(done) {
emailMock.deleteMessage.yields(); emailMock.deleteMessage.returns(resolves());
scope.deleteMessage({}); scope.deleteMessage({}).then(function() {
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true;
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true; expect(emailMock.deleteMessage.calledOnce).to.be.true;
expect(emailMock.deleteMessage.calledOnce).to.be.true; done();
});
}); });
}); });
@ -79,13 +81,14 @@ describe('Action Bar Controller unit test', function() {
scope.moveMessage(); scope.moveMessage();
}); });
it('should move the selected mail', function() { it('should move the selected mail', function(done) {
emailMock.moveMessage.yields(); emailMock.moveMessage.returns(resolves());
scope.moveMessage({}, {}); scope.moveMessage({}, {}).then(function() {
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true;
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true; expect(emailMock.moveMessage.calledOnce).to.be.true;
expect(emailMock.moveMessage.calledOnce).to.be.true; done();
});
}); });
}); });
@ -144,22 +147,24 @@ describe('Action Bar Controller unit test', function() {
}, true); }, true);
}); });
it('should mark the selected mail', function() { it('should mark the selected mail', function(done) {
emailMock.setFlags.yields(); emailMock.setFlags.returns(resolves());
scope.markMessage({}, true); scope.markMessage({}, true).then(function() {
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true;
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true; expect(emailMock.setFlags.calledOnce).to.be.true;
expect(emailMock.setFlags.calledOnce).to.be.true; done();
});
}); });
it('should mark the selected mail and close read mode', function() { it('should mark the selected mail and close read mode', function(done) {
emailMock.setFlags.yields(); emailMock.setFlags.returns(resolves());
scope.markMessage({}, true, true); scope.markMessage({}, true, true).then(function() {
expect(statusMock.setReading.calledOnce).to.be.false;
expect(statusMock.setReading.calledOnce).to.be.false; expect(emailMock.setFlags.calledOnce).to.be.true;
expect(emailMock.setFlags.calledOnce).to.be.true; done();
});
}); });
}); });
@ -196,11 +201,11 @@ describe('Action Bar Controller unit test', function() {
}); });
it('should flag the selected mail', function() { it('should flag the selected mail', function() {
emailMock.setFlags.yields(); emailMock.setFlags.returns(resolves());
scope.flagMessage({}, true); scope.flagMessage({}, true).then(function() {
expect(emailMock.setFlags.calledOnce).to.be.true;
expect(emailMock.setFlags.calledOnce).to.be.true; });
}); });
}); });

View File

@ -20,6 +20,7 @@ describe('Contacts Controller unit test', function() {
scope.state = {}; scope.state = {};
contactsCtrl = $controller(ContactsCtrl, { contactsCtrl = $controller(ContactsCtrl, {
$scope: scope, $scope: scope,
$q: window.qMock,
keychain: keychainStub, keychain: keychainStub,
pgp: pgpStub, pgp: pgpStub,
dialog: dialogStub dialog: dialogStub
@ -36,28 +37,30 @@ describe('Contacts Controller unit test', function() {
}); });
describe('listKeys', function() { describe('listKeys', function() {
it('should fail due to error in keychain.listLocalPublicKeys', function() { it('should fail due to error in keychain.listLocalPublicKeys', function(done) {
keychainStub.listLocalPublicKeys.yields(42); keychainStub.listLocalPublicKeys.returns(rejects(42));
scope.listKeys(); scope.listKeys().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; done();
});
}); });
it('should work', function() { it('should work', function(done) {
keychainStub.listLocalPublicKeys.yields(null, [{ keychainStub.listLocalPublicKeys.returns(resolves([{
_id: '12345' _id: '12345'
}]); }]));
pgpStub.getKeyParams.returns({ pgpStub.getKeyParams.returns({
fingerprint: 'asdf' fingerprint: 'asdf'
}); });
expect(scope.keys).to.not.exist; expect(scope.keys).to.not.exist;
scope.listKeys(); scope.listKeys().then(function() {
expect(scope.keys.length).to.equal(1);
expect(scope.keys.length).to.equal(1); expect(scope.keys[0]._id).to.equal('12345');
expect(scope.keys[0]._id).to.equal('12345'); expect(scope.keys[0].fingerprint).to.equal('asdf');
expect(scope.keys[0].fingerprint).to.equal('asdf'); done();
});
}); });
}); });
@ -89,13 +92,13 @@ describe('Contacts Controller unit test', function() {
userIds: [], userIds: [],
publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----', publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----',
imported: true imported: true
}).yields(); }).returns(resolves());
scope.listKeys = function() { scope.listKeys = function() {};
scope.importKey(keyArmored).then(function() {
done(); done();
}; });
scope.importKey(keyArmored);
}); });
it('should fail due to invalid armored key', function() { it('should fail due to invalid armored key', function() {
@ -115,7 +118,7 @@ describe('Contacts Controller unit test', function() {
expect(dialogStub.error.calledOnce).to.be.true; expect(dialogStub.error.calledOnce).to.be.true;
}); });
it('should fail due to error in keychain.saveLocalPublicKey', function() { it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----'; var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpStub.getKeyParams.returns({ pgpStub.getKeyParams.returns({
@ -123,11 +126,12 @@ describe('Contacts Controller unit test', function() {
userId: 'max@example.com' userId: 'max@example.com'
}); });
keychainStub.saveLocalPublicKey.yields(42); keychainStub.saveLocalPublicKey.returns(rejects(42));
scope.importKey(keyArmored); scope.importKey(keyArmored).then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; done();
});
}); });
}); });
@ -137,25 +141,26 @@ describe('Contacts Controller unit test', function() {
_id: '12345' _id: '12345'
}; };
keychainStub.removeLocalPublicKey.withArgs('12345').yields(); keychainStub.removeLocalPublicKey.withArgs('12345').returns(resolves());
scope.listKeys = function() { scope.listKeys = function() {};
scope.removeKey(key).then(function() {
done(); done();
}; });
scope.removeKey(key);
}); });
it('should fail due to error in keychain.removeLocalPublicKey', function() { it('should fail due to error in keychain.removeLocalPublicKey', function(done) {
var key = { var key = {
_id: '12345' _id: '12345'
}; };
keychainStub.removeLocalPublicKey.withArgs('12345').yields(42); keychainStub.removeLocalPublicKey.withArgs('12345').returns(rejects(42));
scope.removeKey(key); scope.removeKey(key).then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; done();
});
}); });
}); });
}); });

View File

@ -44,6 +44,7 @@ describe('Mail List controller unit test', function() {
ctrl = $controller(MailListCtrl, { ctrl = $controller(MailListCtrl, {
$scope: scope, $scope: scope,
$location: location, $location: location,
$q: window.qMock,
status: statusMock, status: statusMock,
notification: notificationMock, notification: notificationMock,
email: emailMock, email: emailMock,
@ -207,15 +208,17 @@ describe('Mail List controller unit test', function() {
}); });
describe('getBody', function() { describe('getBody', function() {
it('should get the mail content', function() { it('should get the mail content', function(done) {
scope.state.nav = { scope.state.nav = {
currentFolder: { currentFolder: {
type: 'asd', type: 'asd',
} }
}; };
scope.getBody(); scope.getBody().then(function() {
expect(emailMock.getBody.calledOnce).to.be.true; expect(emailMock.getBody.calledOnce).to.be.true;
done();
});
}); });
}); });
@ -245,7 +248,7 @@ describe('Mail List controller unit test', function() {
}); });
describe('select', function() { describe('select', function() {
it('should decrypt, focus mark an unread mail as read', function() { it('should decrypt, focus mark an unread mail as read', function(done) {
scope.pendingNotifications = ['asd']; scope.pendingNotifications = ['asd'];
sinon.stub(notificationMock, 'close'); sinon.stub(notificationMock, 'close');
@ -272,20 +275,21 @@ describe('Mail List controller unit test', function() {
keychainMock.refreshKeyForUserId.withArgs({ keychainMock.refreshKeyForUserId.withArgs({
userId: mail.from[0].address userId: mail.from[0].address
}).yields(); }).returns(resolves());
scope.select(mail); scope.select(mail).then(function() {
expect(emailMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail);
expect(notificationMock.close.calledWith('asd')).to.be.true;
expect(notificationMock.close.calledOnce).to.be.true;
expect(emailMock.decryptBody.calledOnce).to.be.true; notificationMock.close.restore();
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; done();
expect(scope.state.mailList.selected).to.equal(mail); });
expect(notificationMock.close.calledWith('asd')).to.be.true;
expect(notificationMock.close.calledOnce).to.be.true;
notificationMock.close.restore();
}); });
it('should decrypt and focus a read mail', function() { it('should decrypt and focus a read mail', function(done) {
var mail = { var mail = {
from: [{ from: [{
address: 'asd' address: 'asd'
@ -307,13 +311,14 @@ describe('Mail List controller unit test', function() {
keychainMock.refreshKeyForUserId.withArgs({ keychainMock.refreshKeyForUserId.withArgs({
userId: mail.from[0].address userId: mail.from[0].address
}).yields(); }).returns(resolves());
scope.select(mail); scope.select(mail).then(function() {
expect(emailMock.decryptBody.calledOnce).to.be.true;
expect(emailMock.decryptBody.calledOnce).to.be.true; expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(scope.state.mailList.selected).to.equal(mail);
expect(scope.state.mailList.selected).to.equal(mail); done();
});
}); });
}); });

View File

@ -41,6 +41,7 @@ describe('Navigation Controller unit test', function() {
ctrl = $controller(NavigationCtrl, { ctrl = $controller(NavigationCtrl, {
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
account: accountMock, account: accountMock,
email: emailDaoMock, email: emailDaoMock,
outbox: outboxBoMock, outbox: outboxBoMock,

View File

@ -23,6 +23,7 @@ describe('Private Key Upload Controller unit test', function() {
ctrl = $controller(PrivateKeyUploadCtrl, { ctrl = $controller(PrivateKeyUploadCtrl, {
$location: location, $location: location,
$scope: scope, $scope: scope,
$q: window.qMock,
keychain: keychainMock, keychain: keychainMock,
pgp: pgpStub, pgp: pgpStub,
dialog: dialogStub, dialog: dialogStub,
@ -41,14 +42,15 @@ describe('Private Key Upload Controller unit test', function() {
_id: 'keyId', _id: 'keyId',
}; };
it('should fail', function() { it('should fail', function(done) {
pgpStub.getKeyParams.returns(keyParams); pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.yields(42); keychainMock.hasPrivateKey.returns(rejects(42));
scope.checkServerForKey(); scope.checkServerForKey().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; expect(keychainMock.hasPrivateKey.calledOnce).to.be.true;
expect(keychainMock.hasPrivateKey.calledOnce).to.be.true; done();
});
}); });
it('should return true', function(done) { it('should return true', function(done) {
@ -56,9 +58,9 @@ describe('Private Key Upload Controller unit test', function() {
keychainMock.hasPrivateKey.withArgs({ keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId, userId: keyParams.userId,
keyId: keyParams._id keyId: keyParams._id
}).yields(null, true); }).returns(resolves(true));
scope.checkServerForKey(function(privateKeySynced) { scope.checkServerForKey().then(function(privateKeySynced) {
expect(privateKeySynced).to.be.true; expect(privateKeySynced).to.be.true;
done(); done();
}); });
@ -69,9 +71,9 @@ describe('Private Key Upload Controller unit test', function() {
keychainMock.hasPrivateKey.withArgs({ keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId, userId: keyParams.userId,
keyId: keyParams._id keyId: keyParams._id
}).yields(null, false); }).returns(resolves(false));
scope.checkServerForKey(function(privateKeySynced) { scope.checkServerForKey().then(function(privateKeySynced) {
expect(privateKeySynced).to.be.undefined; expect(privateKeySynced).to.be.undefined;
done(); done();
}); });
@ -110,27 +112,27 @@ describe('Private Key Upload Controller unit test', function() {
describe('setDeviceName', function() { describe('setDeviceName', function() {
it('should work', function(done) { it('should work', function(done) {
keychainMock.setDeviceName.yields(); keychainMock.setDeviceName.returns(resolves());
scope.setDeviceName(done); scope.setDeviceName().then(done);
}); });
}); });
describe('encryptAndUploadKey', function() { describe('encryptAndUploadKey', function() {
it('should fail due to keychain.registerDevice', function() { it('should fail due to keychain.registerDevice', function(done) {
keychainMock.registerDevice.yields(42); keychainMock.registerDevice.returns(rejects(42));
scope.encryptAndUploadKey(); scope.encryptAndUploadKey().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; expect(keychainMock.registerDevice.calledOnce).to.be.true;
expect(keychainMock.registerDevice.calledOnce).to.be.true; done();
});
}); });
it('should work', function(done) { it('should work', function(done) {
keychainMock.registerDevice.yields(); keychainMock.registerDevice.returns(resolves());
keychainMock.uploadPrivateKey.yields(); keychainMock.uploadPrivateKey.returns(resolves());
scope.encryptAndUploadKey(function(err) { scope.encryptAndUploadKey().then(function() {
expect(err).to.not.exist;
expect(keychainMock.registerDevice.calledOnce).to.be.true; expect(keychainMock.registerDevice.calledOnce).to.be.true;
expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true; expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true;
done(); done();
@ -185,36 +187,39 @@ describe('Private Key Upload Controller unit test', function() {
expect(scope.step).to.equal(2); expect(scope.step).to.equal(2);
}); });
it('should fail for 3 due to error in setDeviceName', function() { it('should fail for 3 due to error in setDeviceName', function(done) {
scope.step = 3; scope.step = 3;
setDeviceNameStub.yields(42); setDeviceNameStub.returns(rejects(42));
scope.goForward(); scope.goForward().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; expect(scope.step).to.equal(3);
expect(scope.step).to.equal(3); done();
});
}); });
it('should fail for 3 due to error in encryptAndUploadKey', function() { it('should fail for 3 due to error in encryptAndUploadKey', function(done) {
scope.step = 3; scope.step = 3;
setDeviceNameStub.yields(); setDeviceNameStub.returns(resolves());
encryptAndUploadKeyStub.yields(42); encryptAndUploadKeyStub.returns(rejects(42));
scope.goForward(); scope.goForward().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true; expect(scope.step).to.equal(4);
expect(scope.step).to.equal(4); done();
});
}); });
it('should work for 3', function() { it('should work for 3', function(done) {
scope.step = 3; scope.step = 3;
setDeviceNameStub.yields(); setDeviceNameStub.returns(resolves());
encryptAndUploadKeyStub.yields(); encryptAndUploadKeyStub.returns(resolves());
scope.goForward(); scope.goForward().then(function() {
expect(dialogStub.info.calledOnce).to.be.true;
expect(dialogStub.info.calledOnce).to.be.true; expect(scope.step).to.equal(4);
expect(scope.step).to.equal(4); done();
});
}); });
}); });
}); });

View File

@ -11,8 +11,7 @@ var Keychain = require('../../../../src/js/service/keychain'),
Download = require('../../../../src/js/util/download'); Download = require('../../../../src/js/util/download');
describe('Read Controller unit test', function() { describe('Read Controller unit test', function() {
var scope, ctrl, keychainMock, invitationMock, emailMock, pgpMock, outboxMock, dialogMock, authMock, downloadMock, var scope, ctrl, keychainMock, invitationMock, emailMock, pgpMock, outboxMock, dialogMock, authMock, downloadMock;
emailAddress = 'sender@example.com';
beforeEach(function() { beforeEach(function() {
keychainMock = sinon.createStubInstance(Keychain); keychainMock = sinon.createStubInstance(Keychain);
@ -31,6 +30,7 @@ describe('Read Controller unit test', function() {
scope.state = {}; scope.state = {};
ctrl = $controller(ReadCtrl, { ctrl = $controller(ReadCtrl, {
$scope: scope, $scope: scope,
$q: window.qMock,
email: emailMock, email: emailMock,
invitation: invitationMock, invitation: invitationMock,
outbox: outboxMock, outbox: outboxMock,
@ -66,33 +66,37 @@ describe('Read Controller unit test', function() {
describe('getKeyId', function() { describe('getKeyId', function() {
var address = 'asfd@asdf.com'; var address = 'asfd@asdf.com';
it('should show searching on error', function() { it('should show searching on error', function(done) {
expect(scope.keyId).to.equal('No key found.'); expect(scope.keyId).to.equal('No key found.');
keychainMock.getReceiverPublicKey.yields(42); keychainMock.getReceiverPublicKey.returns(rejects(42));
scope.getKeyId(address); scope.getKeyId(address).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
expect(dialogMock.error.calledOnce).to.be.true; expect(scope.keyId).to.equal('Searching...');
expect(scope.keyId).to.equal('Searching...'); done();
});
it('should allow invitation on empty key', function() {
keychainMock.getReceiverPublicKey.yields();
scope.getKeyId(address);
expect(scope.keyId).to.equal('User has no key. Click to invite.');
});
it('should show searching on error', function() {
keychainMock.getReceiverPublicKey.yields(null, {
publicKey: 'PUBLIC KEY'
}); });
});
it('should allow invitation on empty key', function(done) {
keychainMock.getReceiverPublicKey.returns(resolves());
scope.getKeyId(address).then(function() {
expect(scope.keyId).to.equal('User has no key. Click to invite.');
done();
});
});
it('should show searching on error', function(done) {
keychainMock.getReceiverPublicKey.returns(resolves({
publicKey: 'PUBLIC KEY'
}));
pgpMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); pgpMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
scope.getKeyId(address); scope.getKeyId(address).then(function() {
expect(scope.keyId).to.equal('PGP key: XXXXXXXX'); expect(scope.keyId).to.equal('PGP key: XXXXXXXX');
done();
});
}); });
}); });
@ -108,36 +112,39 @@ describe('Read Controller unit test', function() {
expect(scope.keyId).to.equal('No key found.'); expect(scope.keyId).to.equal('No key found.');
}); });
it('should show error on invitation dao invite error', function() { it('should show error on invitation dao invite error', function(done) {
invitationMock.invite.yields(42); invitationMock.invite.returns(rejects(42));
scope.invite({ scope.invite({
address: 'asdf@asdf.de' address: 'asdf@asdf.de'
}).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
done();
}); });
expect(dialogMock.error.calledOnce).to.be.true;
}); });
it('should show error on outbox put error', function() { it('should show error on outbox put error', function(done) {
invitationMock.invite.yields(); invitationMock.invite.returns(resolves());
outboxMock.put.yields(42); outboxMock.put.returns(rejects(42));
scope.invite({ scope.invite({
address: 'asdf@asdf.de' address: 'asdf@asdf.de'
}).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
done();
}); });
expect(dialogMock.error.calledOnce).to.be.true;
}); });
it('should work', function() { it('should work', function(done) {
invitationMock.invite.yields(); invitationMock.invite.returns(resolves());
outboxMock.put.yields(); outboxMock.put.returns(resolves());
scope.invite({ scope.invite({
address: 'asdf@asdf.de' address: 'asdf@asdf.de'
}).then(function() {
expect(dialogMock.error.calledOnce).to.be.false;
done();
}); });
expect(dialogMock.error.calledOnce).to.be.true;
}); });
}); });

View File

@ -40,6 +40,7 @@ describe('Set Passphrase Controller unit test', function() {
scope.state = {}; scope.state = {};
setPassphraseCtrl = $controller(SetPassphraseCtrl, { setPassphraseCtrl = $controller(SetPassphraseCtrl, {
$scope: scope, $scope: scope,
$q: window.qMock,
pgp: pgpStub, pgp: pgpStub,
keychain: keychainStub, keychain: keychainStub,
dialog: dialogStub dialog: dialogStub
@ -49,31 +50,32 @@ describe('Set Passphrase Controller unit test', function() {
afterEach(function() {}); afterEach(function() {});
describe('setPassphrase', function() { describe('setPassphrase', function(done) {
it('should work', function() { it('should work', function() {
scope.oldPassphrase = 'old'; scope.oldPassphrase = 'old';
scope.newPassphrase = 'new'; scope.newPassphrase = 'new';
keychainStub.lookupPrivateKey.withArgs(dummyKeyId).yields(null, { keychainStub.lookupPrivateKey.withArgs(dummyKeyId).returns(resolves({
encryptedKey: 'encrypted' encryptedKey: 'encrypted'
}); }));
pgpStub.changePassphrase.withArgs({ pgpStub.changePassphrase.withArgs({
privateKeyArmored: 'encrypted', privateKeyArmored: 'encrypted',
oldPassphrase: 'old', oldPassphrase: 'old',
newPassphrase: 'new' newPassphrase: 'new'
}).yields(null, 'newArmoredKey'); }).returns(resolves('newArmoredKey'));
keychainStub.saveLocalPrivateKey.withArgs({ keychainStub.saveLocalPrivateKey.withArgs({
_id: dummyKeyId, _id: dummyKeyId,
userId: emailAddress, userId: emailAddress,
userIds: [], userIds: [],
encryptedKey: 'newArmoredKey' encryptedKey: 'newArmoredKey'
}).yields(); }).returns(resolves());
scope.setPassphrase(); scope.setPassphrase().then(function() {
expect(dialogStub.info.calledOnce).to.be.true;
expect(dialogStub.info.calledOnce).to.be.true; done();
});
}); });
}); });

View File

@ -36,6 +36,7 @@ describe('Write controller unit test', function() {
scope.state = {}; scope.state = {};
ctrl = $controller(WriteCtrl, { ctrl = $controller(WriteCtrl, {
$scope: scope, $scope: scope,
$q: window.qMock,
auth: authMock, auth: authMock,
keychain: keychainMock, keychain: keychainMock,
pgp: pgpMock, pgp: pgpMock,
@ -183,24 +184,25 @@ describe('Write controller unit test', function() {
expect(keychainMock.getReceiverPublicKey.called).to.be.false; expect(keychainMock.getReceiverPublicKey.called).to.be.false;
}); });
it('should not work for error in keychain', function() { it('should not work for error in keychain', function(done) {
var recipient = { var recipient = {
address: 'asds@example.com' address: 'asds@example.com'
}; };
keychainMock.refreshKeyForUserId.withArgs({ keychainMock.refreshKeyForUserId.withArgs({
userId: recipient.address userId: recipient.address
}).yields({ }).returns(rejects({
errMsg: '404 not found yadda yadda' errMsg: '404 not found yadda yadda'
}));
scope.verify(recipient).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
expect(recipient.key).to.be.undefined;
expect(recipient.secure).to.be.false;
expect(scope.checkSendStatus.callCount).to.equal(1);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done();
}); });
scope.verify(recipient);
expect(dialogMock.error.calledOnce).to.be.true;
expect(recipient.key).to.be.undefined;
expect(recipient.secure).to.be.false;
expect(scope.checkSendStatus.callCount).to.equal(1);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
}); });
it('should work for main userId', function(done) { it('should work for main userId', function(done) {
@ -210,11 +212,11 @@ describe('Write controller unit test', function() {
keychainMock.refreshKeyForUserId.withArgs({ keychainMock.refreshKeyForUserId.withArgs({
userId: recipient.address userId: recipient.address
}).yields(null, { }).returns(resolves({
userId: 'asdf@example.com' userId: 'asdf@example.com'
}); }));
scope.$digest = function() { scope.verify(recipient).then(function() {
expect(recipient.key).to.deep.equal({ expect(recipient.key).to.deep.equal({
userId: 'asdf@example.com' userId: 'asdf@example.com'
}); });
@ -222,9 +224,7 @@ describe('Write controller unit test', function() {
expect(scope.checkSendStatus.callCount).to.equal(2); expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done(); done();
}; });
scope.verify(recipient);
}); });
it('should work for secondary userId', function(done) { it('should work for secondary userId', function(done) {
@ -240,17 +240,15 @@ describe('Write controller unit test', function() {
keychainMock.refreshKeyForUserId.withArgs({ keychainMock.refreshKeyForUserId.withArgs({
userId: recipient.address userId: recipient.address
}).yields(null, key); }).returns(resolves(key));
scope.$digest = function() { scope.verify(recipient).then(function() {
expect(recipient.key).to.deep.equal(key); expect(recipient.key).to.deep.equal(key);
expect(recipient.secure).to.be.true; expect(recipient.secure).to.be.true;
expect(scope.checkSendStatus.callCount).to.equal(2); expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done(); done();
}; });
scope.verify(recipient);
}); });
}); });
@ -311,7 +309,7 @@ describe('Write controller unit test', function() {
}); });
describe('send to outbox', function() { describe('send to outbox', function() {
it('should work', function() { it('should work', function(done) {
scope.to = [{ scope.to = [{
address: 'pity@dafool' address: 'pity@dafool'
}]; }];
@ -341,25 +339,26 @@ describe('Write controller unit test', function() {
expect(mail.sentDate).to.exist; expect(mail.sentDate).to.exist;
return true; return true;
})).yields(); })).returns(resolves());
emailMock.setFlags.yields(); emailMock.setFlags.returns(resolves());
scope.sendToOutbox(); scope.sendToOutbox().then(function() {
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true;
expect(statusMock.setReading.withArgs(false).calledOnce).to.be.true; expect(outboxMock.put.calledOnce).to.be.true;
expect(outboxMock.put.calledOnce).to.be.true; expect(emailMock.setFlags.calledOnce).to.be.true;
expect(emailMock.setFlags.calledOnce).to.be.true; expect(scope.state.lightbox).to.be.undefined;
expect(scope.state.lightbox).to.be.undefined; expect(scope.replyTo.answered).to.be.true;
expect(scope.replyTo.answered).to.be.true; done();
});
}); });
}); });
describe('lookupAddressBook', function() { describe('lookupAddressBook', function() {
it('should work', function(done) { it('should work', function(done) {
keychainMock.listLocalPublicKeys.yields(null, [{ keychainMock.listLocalPublicKeys.returns(resolves([{
userId: 'test@asdf.com', userId: 'test@asdf.com',
publicKey: 'KEY' publicKey: 'KEY'
}]); }]));
var result = scope.lookupAddressBook('test'); var result = scope.lookupAddressBook('test');
@ -369,7 +368,6 @@ describe('Write controller unit test', function() {
}]); }]);
done(); done();
}); });
scope.$digest();
}); });
it('should work with cache', function(done) { it('should work with cache', function(done) {
@ -387,7 +385,6 @@ describe('Write controller unit test', function() {
}]); }]);
done(); done();
}); });
scope.$digest();
}); });
}); });

View File

@ -116,16 +116,17 @@ describe('Add Account Controller unit test', function() {
setCredentialsStub.restore(); setCredentialsStub.restore();
}); });
it('should use oauth', function() { it('should use oauth', function(done) {
dialogStub.confirm = function(options) { dialogStub.confirm = function(options) {
options.callback(true); options.callback(true).then(function() {
expect(setCredentialsStub.calledOnce).to.be.true;
expect(authStub.getOAuthToken.calledOnce).to.be.true;
done();
});
}; };
authStub.getOAuthToken.yields(); authStub.getOAuthToken.returns(resolves());
scope.oauthPossible(); scope.oauthPossible();
expect(setCredentialsStub.calledOnce).to.be.true;
expect(authStub.getOAuthToken.calledOnce).to.be.true;
}); });
it('should not use oauth', function() { it('should not use oauth', function() {
@ -139,25 +140,29 @@ describe('Add Account Controller unit test', function() {
expect(authStub.getOAuthToken.called).to.be.false; expect(authStub.getOAuthToken.called).to.be.false;
}); });
it('should not forward to login when oauth fails', function() { it('should not forward to login when oauth fails', function(done) {
dialogStub.error = function(err) {
expect(err).to.exist;
expect(setCredentialsStub.called).to.be.false;
done();
};
dialogStub.confirm = function(options) { dialogStub.confirm = function(options) {
options.callback(true); options.callback(true);
}; };
authStub.getOAuthToken.yields(new Error()); authStub.getOAuthToken.returns(rejects(new Error()));
scope.oauthPossible(); scope.oauthPossible();
expect(dialogStub.error.calledOnce).to.be.true;
expect(setCredentialsStub.called).to.be.false;
}); });
}); });
describe('setCredentials', function() { describe('setCredentials', function() {
it('should work', function() { it('should work', inject(function($timeout) {
scope.setCredentials(); scope.setCredentials().then();
$timeout.flush();
expect(location.path.calledWith('/login-set-credentials')).to.be.true; expect(location.path.calledWith('/login-set-credentials')).to.be.true;
}); }));
}); });
}); });

View File

@ -29,6 +29,7 @@ describe('Create Account Controller unit test', function() {
$location: location, $location: location,
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
auth: authStub, auth: authStub,
admin: adminStub admin: adminStub
}); });
@ -54,16 +55,15 @@ describe('Create Account Controller unit test', function() {
scope.form.$invalid = false; scope.form.$invalid = false;
scope.betaCode = 'asfd'; scope.betaCode = 'asfd';
scope.phone = '12345'; scope.phone = '12345';
adminStub.createUser.yieldsAsync(new Error('asdf')); adminStub.createUser.returns(rejects(new Error('asdf')));
scope.$apply = function() { scope.createWhiteoutAccount().then(function() {
expect(scope.busy).to.be.false; expect(scope.busy).to.be.false;
expect(scope.errMsg).to.equal('asdf'); expect(scope.errMsg).to.equal('asdf');
expect(adminStub.createUser.calledOnce).to.be.true; expect(adminStub.createUser.calledOnce).to.be.true;
done(); done();
}; });
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true; expect(scope.busy).to.be.true;
}); });
@ -71,16 +71,15 @@ describe('Create Account Controller unit test', function() {
scope.form.$invalid = false; scope.form.$invalid = false;
scope.betaCode = 'asfd'; scope.betaCode = 'asfd';
scope.phone = '12345'; scope.phone = '12345';
adminStub.createUser.yieldsAsync(); adminStub.createUser.returns(resolves());
scope.$apply = function() { scope.createWhiteoutAccount().then(function() {
expect(scope.busy).to.be.false; expect(scope.busy).to.be.false;
expect(scope.errMsg).to.be.undefined; expect(scope.errMsg).to.be.undefined;
expect(adminStub.createUser.calledOnce).to.be.true; expect(adminStub.createUser.calledOnce).to.be.true;
done(); done();
}; });
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true; expect(scope.busy).to.be.true;
}); });
}); });

View File

@ -26,17 +26,13 @@ describe('Login Controller unit test', function() {
}; };
authMock.emailAddress = emailAddress; authMock.emailAddress = emailAddress;
});
function createController() {
angular.module('login-test', ['woServices', 'woEmail', 'woUtil']); angular.module('login-test', ['woServices', 'woEmail', 'woUtil']);
angular.mock.module('login-test'); angular.mock.module('login-test');
angular.mock.inject(function($rootScope, $controller) { angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new(); scope = $rootScope.$new();
scope.state = {}; scope.state = {};
scope.form = {}; scope.form = {};
scope.goTo = function() {};
goToStub = sinon.stub(scope, 'goTo');
ctrl = $controller(LoginCtrl, { ctrl = $controller(LoginCtrl, {
$scope: scope, $scope: scope,
@ -46,151 +42,174 @@ describe('Login Controller unit test', function() {
auth: authMock, auth: authMock,
email: emailMock, email: emailMock,
keychain: keychainMock, keychain: keychainMock,
dialog: dialogMock dialog: dialogMock,
appConfig: {
preventAutoStart: true
}
}); });
}); });
}
scope.goTo = function() {};
goToStub = sinon.stub(scope, 'goTo');
});
afterEach(function() {}); afterEach(function() {});
it('should fail for auth.getEmailAddress', function() { it('should fail for auth.getEmailAddress', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(new Error()); authMock.getEmailAddress.returns(rejects(new Error()));
createController(); scope.init().then(function() {
expect(updateHandlerMock.checkForUpdate.calledOnce).to.be.true;
expect(updateHandlerMock.checkForUpdate.calledOnce).to.be.true; expect(authMock.init.calledOnce).to.be.true;
expect(authMock.init.calledOnce).to.be.true; expect(dialogMock.error.calledOnce).to.be.true;
expect(dialogMock.error.calledOnce).to.be.true; done();
});
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() { it('should fail for auth.init', function(done) {
authMock.init.yields(); authMock.init.returns(rejects(new Error()));
authMock.getEmailAddress.yields(null, {}); authMock.getEmailAddress.returns(resolves({
createController();
expect(goToStub.withArgs('/add-account').calledOnce).to.be.true;
});
it('should redirect to /login-existing', function() {
authMock.init.yields();
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress emailAddress: emailAddress
}));
scope.init().then(function() {
expect(authMock.init.calledOnce).to.be.true;
expect(accountMock.init.called).to.be.false;
expect(dialogMock.error.calledOnce).to.be.true;
done();
}); });
accountMock.init.yields(null, { });
it('should redirect to /add-account', function(done) {
authMock.init.returns(resolves());
authMock.getEmailAddress.returns(resolves({}));
scope.init().then(function() {
expect(goToStub.withArgs('/add-account').called).to.be.true;
expect(goToStub.calledOnce).to.be.true;
done();
});
});
it('should redirect to /login-existing', function(done) {
authMock.init.returns(resolves());
authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress
}));
accountMock.init.returns(resolves({
publicKey: 'publicKey', publicKey: 'publicKey',
privateKey: 'privateKey' privateKey: 'privateKey'
}));
emailMock.unlock.returns(rejects(new Error()));
scope.init().then(function() {
expect(goToStub.withArgs('/login-existing').called).to.be.true;
expect(goToStub.calledOnce).to.be.true;
expect(authMock.storeCredentials.called).to.be.false;
done();
}); });
emailMock.unlock.yields(new Error());
createController();
expect(goToStub.withArgs('/login-existing').calledOnce).to.be.true;
}); });
it('should fail for auth.storeCredentials', function() { it('should fail for auth.storeCredentials', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(null, { authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress emailAddress: emailAddress
}); }));
accountMock.init.yields(null, { accountMock.init.returns(resolves({
publicKey: 'publicKey', publicKey: 'publicKey',
privateKey: 'privateKey' privateKey: 'privateKey'
}));
emailMock.unlock.returns(resolves());
authMock.storeCredentials.returns(rejects(new Error()));
scope.init().then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
done();
}); });
emailMock.unlock.yields();
authMock.storeCredentials.yields(new Error());
createController();
expect(dialogMock.error.calledOnce).to.be.true;
}); });
it('should redirect to /account', function() { it('should redirect to /account', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(null, { authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress emailAddress: emailAddress
}); }));
accountMock.init.yields(null, { accountMock.init.returns(resolves({
publicKey: 'publicKey', publicKey: 'publicKey',
privateKey: 'privateKey' privateKey: 'privateKey'
}));
emailMock.unlock.returns(resolves());
authMock.storeCredentials.returns(resolves());
scope.init().then(function() {
expect(goToStub.withArgs('/account').called).to.be.true;
expect(goToStub.calledOnce).to.be.true;
done();
}); });
emailMock.unlock.yields();
authMock.storeCredentials.yields();
createController();
expect(goToStub.withArgs('/account').calledOnce).to.be.true;
}); });
it('should fail for keychain.requestPrivateKeyDownload', function() { it('should fail for keychain.requestPrivateKeyDownload', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(null, { authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress emailAddress: emailAddress
}); }));
accountMock.init.yields(null, { accountMock.init.returns(resolves({
publicKey: 'publicKey' publicKey: 'publicKey'
}));
keychainMock.requestPrivateKeyDownload.returns(rejects(new Error()));
scope.init().then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
done();
}); });
keychainMock.requestPrivateKeyDownload.yields(new Error());
createController();
expect(dialogMock.error.calledOnce).to.be.true;
}); });
it('should redirect to /login-privatekey-download', function() { it('should redirect to /login-privatekey-download', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(null, { authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress emailAddress: emailAddress
}); }));
accountMock.init.yields(null, { accountMock.init.returns(resolves({
publicKey: 'publicKey' publicKey: 'publicKey'
}));
keychainMock.requestPrivateKeyDownload.returns(resolves(true));
scope.init().then(function() {
expect(goToStub.withArgs('/login-privatekey-download').called).to.be.true;
expect(goToStub.calledOnce).to.be.true;
done();
}); });
keychainMock.requestPrivateKeyDownload.yields(null, true);
createController();
expect(goToStub.withArgs('/login-privatekey-download').calledOnce).to.be.true;
}); });
it('should redirect to /login-new-device', function() { it('should redirect to /login-new-device', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(null, { authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress emailAddress: emailAddress
}); }));
accountMock.init.yields(null, { accountMock.init.returns(resolves({
publicKey: 'publicKey' publicKey: 'publicKey'
}));
keychainMock.requestPrivateKeyDownload.returns(resolves());
scope.init().then(function() {
expect(goToStub.withArgs('/login-new-device').called).to.be.true;
expect(goToStub.calledOnce).to.be.true;
done();
}); });
keychainMock.requestPrivateKeyDownload.yields();
createController();
expect(goToStub.withArgs('/login-new-device').calledOnce).to.be.true;
}); });
it('should redirect to /login-initial', function() { it('should redirect to /login-initial', function(done) {
authMock.init.yields(); authMock.init.returns(resolves());
authMock.getEmailAddress.yields(null, { authMock.getEmailAddress.returns(resolves({
emailAddress: emailAddress emailAddress: emailAddress
}));
accountMock.init.returns(resolves({}));
scope.init().then(function() {
expect(goToStub.withArgs('/login-initial').called).to.be.true;
expect(goToStub.calledOnce).to.be.true;
done();
}); });
accountMock.init.yields(null, {});
createController();
expect(goToStub.withArgs('/login-initial').calledOnce).to.be.true;
}); });
}); });

View File

@ -28,6 +28,7 @@ describe('Login (existing user) Controller unit test', function() {
ctrl = $controller(LoginExistingCtrl, { ctrl = $controller(LoginExistingCtrl, {
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
email: emailDaoMock, email: emailDaoMock,
auth: authMock, auth: authMock,
keychain: keychainMock keychain: keychainMock
@ -44,32 +45,35 @@ describe('Login (existing user) Controller unit test', function() {
}); });
describe('confirm passphrase', function() { describe('confirm passphrase', function() {
it('should unlock crypto and start', function() { it('should unlock crypto and start', function(done) {
var keypair = {}, var keypair = {},
pathSpy = sinon.spy(location, 'path'); pathSpy = sinon.spy(location, 'path');
scope.passphrase = passphrase; scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair); keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves(keypair));
emailDaoMock.unlock.withArgs({ emailDaoMock.unlock.withArgs({
keypair: keypair, keypair: keypair,
passphrase: passphrase passphrase: passphrase
}).yields(); }).returns(resolves());
authMock.storeCredentials.yields(); authMock.storeCredentials.returns(resolves());
scope.confirmPassphrase(); scope.confirmPassphrase().then(function() {
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true; expect(pathSpy.calledOnce).to.be.true;
expect(pathSpy.calledOnce).to.be.true; expect(pathSpy.calledWith('/account')).to.be.true;
expect(pathSpy.calledWith('/account')).to.be.true; done();
});
}); });
it('should not work when keypair unavailable', function() { it('should not work when keypair unavailable', function(done) {
scope.passphrase = passphrase; scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd')); keychainMock.getUserKeyPair.withArgs(emailAddress).returns(rejects(new Error('asd')));
scope.confirmPassphrase(); scope.confirmPassphrase().then(function() {
expect(scope.errMsg).to.exist; expect(scope.errMsg).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
});
}); });
}); });
}); });

View File

@ -30,6 +30,7 @@ describe('Login (initial user) Controller unit test', function() {
ctrl = $controller(LoginInitialCtrl, { ctrl = $controller(LoginInitialCtrl, {
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
newsletter: newsletter, newsletter: newsletter,
email: emailMock, email: emailMock,
auth: authMock auth: authMock
@ -74,36 +75,38 @@ describe('Login (initial user) Controller unit test', function() {
expect(newsletterStub.called).to.be.false; expect(newsletterStub.called).to.be.false;
}); });
it('should fail due to error in emailDao.unlock', function() { it('should fail due to error in emailDao.unlock', function(done) {
scope.agree = true; scope.agree = true;
emailMock.unlock.withArgs({ emailMock.unlock.withArgs({
passphrase: undefined passphrase: undefined
}).yields(new Error('asdf')); }).returns(rejects(new Error('asdf')));
authMock.storeCredentials.yields(); authMock.storeCredentials.returns(resolves());
scope.generateKey(); scope.generateKey().then(function() {
expect(scope.errMsg).to.exist;
expect(scope.errMsg).to.exist; expect(scope.state.ui).to.equal(1);
expect(scope.state.ui).to.equal(1); expect(newsletterStub.called).to.be.true;
expect(newsletterStub.called).to.be.true; done();
});
}); });
it('should unlock crypto', function() { it('should unlock crypto', function(done) {
scope.agree = true; scope.agree = true;
emailMock.unlock.withArgs({ emailMock.unlock.withArgs({
passphrase: undefined passphrase: undefined
}).yields(); }).returns(resolves());
authMock.storeCredentials.yields(); authMock.storeCredentials.returns(resolves());
scope.generateKey(); scope.generateKey().then(function() {
expect(scope.errMsg).to.not.exist;
expect(scope.errMsg).to.not.exist; expect(scope.state.ui).to.equal(2);
expect(scope.state.ui).to.equal(2); expect(newsletterStub.called).to.be.true;
expect(newsletterStub.called).to.be.true; expect(location.$$path).to.equal('/account');
expect(location.$$path).to.equal('/account'); expect(emailMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true; done();
});
}); });
}); });
}); });

View File

@ -35,6 +35,7 @@ describe('Login (new device) Controller unit test', function() {
ctrl = $controller(LoginNewDeviceCtrl, { ctrl = $controller(LoginNewDeviceCtrl, {
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
email: emailMock, email: emailMock,
auth: authMock, auth: authMock,
pgp: pgpMock, pgp: pgpMock,
@ -53,7 +54,7 @@ describe('Login (new device) Controller unit test', function() {
}); });
describe('confirm passphrase', function() { describe('confirm passphrase', function() {
it('should unlock crypto with a public key on the server', function() { it('should unlock crypto with a public key on the server', function(done) {
scope.passphrase = passphrase; scope.passphrase = passphrase;
scope.key = { scope.key = {
privateKeyArmored: 'b' privateKeyArmored: 'b'
@ -64,20 +65,21 @@ describe('Login (new device) Controller unit test', function() {
userIds: [] userIds: []
}); });
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, { keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves({
_id: keyId, _id: keyId,
publicKey: 'a' publicKey: 'a'
}));
emailMock.unlock.withArgs(sinon.match.any, passphrase).returns(resolves());
keychainMock.putUserKeyPair.returns(resolves());
scope.confirmPassphrase().then(function() {
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
}); });
emailMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
}); });
it('should unlock crypto with no key on the server', function() { it('should unlock crypto with no key on the server', function(done) {
scope.passphrase = passphrase; scope.passphrase = passphrase;
scope.key = { scope.key = {
privateKeyArmored: 'b', privateKeyArmored: 'b',
@ -89,17 +91,18 @@ describe('Login (new device) Controller unit test', function() {
userIds: [] userIds: []
}); });
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(); keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves());
emailMock.unlock.withArgs(sinon.match.any, passphrase).yields(); emailMock.unlock.withArgs(sinon.match.any, passphrase).returns(resolves());
keychainMock.putUserKeyPair.yields(); keychainMock.putUserKeyPair.returns(resolves());
scope.confirmPassphrase(); scope.confirmPassphrase().then(function() {
expect(emailMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true; expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; done();
});
}); });
it('should not work when keypair upload fails', function() { it('should not work when keypair upload fails', function(done) {
scope.passphrase = passphrase; scope.passphrase = passphrase;
scope.key = { scope.key = {
privateKeyArmored: 'b' privateKeyArmored: 'b'
@ -110,24 +113,25 @@ describe('Login (new device) Controller unit test', function() {
userIds: [] userIds: []
}); });
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, { keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves({
_id: keyId, _id: keyId,
publicKey: 'a' publicKey: 'a'
}); }));
emailMock.unlock.yields(); emailMock.unlock.returns(resolves());
keychainMock.putUserKeyPair.yields({ keychainMock.putUserKeyPair.returns(rejects({
errMsg: 'yo mamma.' errMsg: 'yo mamma.'
}));
scope.confirmPassphrase().then(function() {
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
done();
}); });
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
}); });
it('should not work when unlock fails', function() { it('should not work when unlock fails', function(done) {
scope.passphrase = passphrase; scope.passphrase = passphrase;
scope.key = { scope.key = {
privateKeyArmored: 'b' privateKeyArmored: 'b'
@ -138,36 +142,38 @@ describe('Login (new device) Controller unit test', function() {
userIds: [] userIds: []
}); });
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, { keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves({
_id: keyId, _id: keyId,
publicKey: 'a' publicKey: 'a'
}); }));
emailMock.unlock.yields({ emailMock.unlock.returns(rejects({
errMsg: 'yo mamma.' errMsg: 'yo mamma.'
}));
scope.confirmPassphrase().then(function() {
expect(scope.incorrect).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
done();
}); });
scope.confirmPassphrase();
expect(scope.incorrect).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
}); });
it('should not work when keypair retrieval', function() { it('should not work when keypair retrieval', function(done) {
scope.passphrase = passphrase; scope.passphrase = passphrase;
scope.key = { scope.key = {
privateKeyArmored: 'b' privateKeyArmored: 'b'
}; };
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({ keychainMock.getUserKeyPair.withArgs(emailAddress).returns(rejects({
errMsg: 'yo mamma.' errMsg: 'yo mamma.'
}));
scope.confirmPassphrase().then(function() {
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
done();
}); });
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
}); });
}); });
}); });

View File

@ -29,6 +29,7 @@ describe('Login Private Key Download Controller unit test', function() {
$location: location, $location: location,
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
auth: authMock, auth: authMock,
email: emailDaoMock, email: emailDaoMock,
keychain: keychainMock keychain: keychainMock
@ -46,74 +47,57 @@ describe('Login Private Key Download Controller unit test', function() {
}); });
describe('checkToken', function() { describe('checkToken', function() {
var verifyRecoveryTokenStub;
beforeEach(function() {
verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
});
afterEach(function() {
verifyRecoveryTokenStub.restore();
});
it('should fail for empty recovery token', function() {
scope.tokenForm.$invalid = true;
scope.checkToken();
expect(verifyRecoveryTokenStub.calledOnce).to.be.false;
expect(scope.errMsg).to.exist;
});
it('should work', function() {
verifyRecoveryTokenStub.yields();
scope.checkToken();
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
expect(scope.step).to.equal(2);
});
});
describe('verifyRecoveryToken', function() {
var testKeypair = { var testKeypair = {
publicKey: { publicKey: {
_id: 'id' _id: 'id'
} }
}; };
it('should fail in keychain.getUserKeyPair', function() { it('should fail for empty recovery token', function() {
keychainMock.getUserKeyPair.yields(new Error('asdf')); scope.tokenForm.$invalid = true;
scope.verifyRecoveryToken(); scope.checkToken();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.false;
expect(scope.errMsg).to.exist; expect(scope.errMsg).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
}); });
it('should fail in keychain.downloadPrivateKey', function() { it('should fail in keychain.getUserKeyPair', function(done) {
keychainMock.getUserKeyPair.yields(null, testKeypair); keychainMock.getUserKeyPair.returns(rejects(new Error('asdf')));
keychainMock.downloadPrivateKey.yields(new Error('asdf'));
scope.recoveryToken = 'token';
scope.verifyRecoveryToken(); scope.checkToken().then(function() {
expect(scope.errMsg).to.exist;
expect(scope.errMsg).to.exist; expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true; done();
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true; });
}); });
it('should work', function() { it('should fail in keychain.downloadPrivateKey', function(done) {
keychainMock.getUserKeyPair.yields(null, testKeypair); keychainMock.getUserKeyPair.returns(resolves(testKeypair));
keychainMock.downloadPrivateKey.yields(null, 'encryptedPrivateKey'); keychainMock.downloadPrivateKey.returns(rejects(new Error('asdf')));
scope.recoveryToken = 'token'; scope.recoveryToken = 'token';
scope.verifyRecoveryToken(function() {}); scope.checkToken().then(function() {
expect(scope.errMsg).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
done();
});
});
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey'); it('should work', function(done) {
keychainMock.getUserKeyPair.returns(resolves(testKeypair));
keychainMock.downloadPrivateKey.returns(resolves('encryptedPrivateKey'));
scope.recoveryToken = 'token';
scope.checkToken().then(function() {
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
done();
});
}); });
}); });
describe('decryptAndStorePrivateKeyLocally', function() { describe('checkCode', function() {
beforeEach(function() { beforeEach(function() {
scope.code = '012345'; scope.code = '012345';
@ -125,48 +109,50 @@ describe('Login Private Key Download Controller unit test', function() {
_id: 'keyId' _id: 'keyId'
} }
}; };
sinon.stub(scope, 'goTo');
});
afterEach(function() {
scope.goTo.restore();
}); });
it('should fail on decryptAndStorePrivateKeyLocally', function() { it('should fail on decryptAndStorePrivateKeyLocally', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(new Error('asdf')); keychainMock.decryptAndStorePrivateKeyLocally.returns(rejects(new Error('asdf')));
scope.decryptAndStorePrivateKeyLocally(); scope.checkCode().then(function() {
expect(scope.errMsg).to.exist;
expect(scope.errMsg).to.exist; expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true; done();
});
}); });
it('should goto /login-existing on emailDao.unlock fail', function(done) { it('should goto /login-existing on emailDao.unlock fail', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, { keychainMock.decryptAndStorePrivateKeyLocally.returns(resolves({
encryptedKey: 'keyArmored' encryptedKey: 'keyArmored'
}); }));
emailDaoMock.unlock.yields(new Error('asdf')); emailDaoMock.unlock.returns(rejects(new Error('asdf')));
scope.goTo = function(location) { scope.checkCode().then(function() {
expect(location).to.equal('/login-existing'); expect(scope.goTo.withArgs('/login-existing').calledOnce).to.be.true;
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true; expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true; expect(emailDaoMock.unlock.calledOnce).to.be.true;
done(); done();
}; });
scope.decryptAndStorePrivateKeyLocally();
}); });
it('should goto /account on emailDao.unlock success', function(done) { it('should goto /account on emailDao.unlock success', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, { keychainMock.decryptAndStorePrivateKeyLocally.returns(resolves({
encryptedKey: 'keyArmored' encryptedKey: 'keyArmored'
}); }));
emailDaoMock.unlock.yields(); emailDaoMock.unlock.returns(resolves());
authMock.storeCredentials.yields(); authMock.storeCredentials.returns(resolves());
scope.goTo = function(location) { scope.checkCode().then(function() {
expect(location).to.equal('/account'); expect(scope.goTo.withArgs('/account').calledOnce).to.be.true;
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true; expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true; expect(emailDaoMock.unlock.calledOnce).to.be.true;
done(); done();
}; });
scope.decryptAndStorePrivateKeyLocally();
}); });
}); });

View File

@ -40,6 +40,7 @@ describe('Login (Set Credentials) Controller unit test', function() {
setCredentialsCtrl = $controller(SetCredentialsCtrl, { setCredentialsCtrl = $controller(SetCredentialsCtrl, {
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
auth: auth, auth: auth,
connectionDoctor: doctor connectionDoctor: doctor
}); });
@ -49,7 +50,7 @@ describe('Login (Set Credentials) Controller unit test', function() {
afterEach(function() {}); afterEach(function() {});
describe('set credentials', function() { describe('set credentials', function() {
it('should work', function() { it('should work', function(done) {
scope.emailAddress = 'emailemailemailemail'; scope.emailAddress = 'emailemailemailemail';
scope.password = 'passwdpasswdpasswdpasswd'; scope.password = 'passwdpasswdpasswdpasswd';
scope.smtpHost = 'hosthosthost'; scope.smtpHost = 'hosthosthost';
@ -80,14 +81,16 @@ describe('Login (Set Credentials) Controller unit test', function() {
} }
}; };
doctor.check.yields(); // synchronous yields! doctor.check.returns(resolves()); // synchronous yields!
scope.test(); scope.test().then(function() {
expect(doctor.check.calledOnce).to.be.true;
expect(doctor.configure.calledOnce).to.be.true;
expect(doctor.configure.calledWith(expectedCredentials)).to.be.true;
expect(auth.setCredentials.calledOnce).to.be.true;
done();
});
expect(doctor.check.calledOnce).to.be.true;
expect(doctor.configure.calledOnce).to.be.true;
expect(doctor.configure.calledWith(expectedCredentials)).to.be.true;
expect(auth.setCredentials.calledOnce).to.be.true;
}); });
}); });
}); });

View File

@ -34,6 +34,7 @@ describe('Validate Phone Controller unit test', function() {
$location: location, $location: location,
$scope: scope, $scope: scope,
$routeParams: {}, $routeParams: {},
$q: window.qMock,
mailConfig: mailConfigMock, mailConfig: mailConfigMock,
auth: authStub, auth: authStub,
admin: adminStub admin: adminStub
@ -59,55 +60,53 @@ describe('Validate Phone Controller unit test', function() {
it('should fail to error creating user', function(done) { it('should fail to error creating user', function(done) {
scope.form.$invalid = false; scope.form.$invalid = false;
scope.token = 'asfd'; scope.token = 'asfd';
adminStub.validateUser.yieldsAsync(new Error('asdf')); adminStub.validateUser.returns(rejects(new Error('asdf')));
scope.$apply = function() { scope.validateUser().then(function() {
expect(scope.busy).to.be.false; expect(scope.busy).to.be.false;
expect(scope.errMsg).to.equal('asdf'); expect(scope.errMsg).to.equal('asdf');
expect(adminStub.validateUser.calledOnce).to.be.true; expect(adminStub.validateUser.calledOnce).to.be.true;
done(); done();
}; });
scope.validateUser();
expect(scope.busy).to.be.true; expect(scope.busy).to.be.true;
}); });
it('should work', function(done) { it('should work', function(done) {
scope.form.$invalid = false; scope.form.$invalid = false;
scope.token = 'asfd'; scope.token = 'asfd';
adminStub.validateUser.yieldsAsync(); adminStub.validateUser.returns(resolves());
scope.login = function() { scope.login = function() {};
scope.validateUser().then(function() {
expect(scope.busy).to.be.true; expect(scope.busy).to.be.true;
expect(scope.errMsg).to.be.undefined; expect(scope.errMsg).to.be.undefined;
expect(adminStub.validateUser.calledOnce).to.be.true; expect(adminStub.validateUser.calledOnce).to.be.true;
done(); done();
}; });
scope.validateUser();
expect(scope.busy).to.be.true; expect(scope.busy).to.be.true;
}); });
}); });
describe('login', function() { describe('login', function() {
it('should work', inject(function($q, $rootScope) { it('should work', function(done) {
scope.form.$invalid = false; scope.form.$invalid = false;
authStub.setCredentials.returns(); authStub.setCredentials.returns();
var deferred = $q.defer(); sinon.stub(mailConfigMock, 'get').returns(resolves({
sinon.stub(mailConfigMock, 'get').returns(deferred.promise);
deferred.resolve({
imap: {}, imap: {},
smtp: {} smtp: {}
}));
scope.login().then(function() {
expect(mailConfigMock.get.calledOnce).to.be.true;
expect(authStub.setCredentials.calledOnce).to.be.true;
expect(location.path.calledWith('/login')).to.be.true;
done();
}); });
});
scope.login();
$rootScope.$apply();
expect(mailConfigMock.get.calledOnce).to.be.true;
expect(authStub.setCredentials.calledOnce).to.be.true;
expect(location.path.calledWith('/login')).to.be.true;
}));
}); });
}); });

View File

@ -24,16 +24,14 @@ describe('Crypto unit tests', function() {
var key = util.random(keySize); var key = util.random(keySize);
var iv = util.random(ivSize); var iv = util.random(ivSize);
crypto.encrypt(plaintext, key, iv, function(err, ciphertext) { crypto.encrypt(plaintext, key, iv).then(function(ciphertext) {
expect(err).to.not.exist;
expect(ciphertext).to.exist; expect(ciphertext).to.exist;
crypto.decrypt(ciphertext, key, iv, function(err, decrypted) { return crypto.decrypt(ciphertext, key, iv);
expect(err).to.not.exist; }).then(function(decrypted) {
expect(decrypted).to.equal(plaintext); expect(decrypted).to.equal(plaintext);
done(); done();
});
}); });
}); });
}); });
@ -42,14 +40,11 @@ describe('Crypto unit tests', function() {
it('should work', function(done) { it('should work', function(done) {
var salt = util.random(keySize); var salt = util.random(keySize);
crypto.deriveKey(password, salt, keySize, function(err, key) { crypto.deriveKey(password, salt, keySize).then(function(key) {
expect(err).to.not.exist;
expect(util.base642Str(key).length * 8).to.equal(keySize); expect(util.base642Str(key).length * 8).to.equal(keySize);
done(); done();
}); });
}); });
}); });
}); });

View File

@ -50,9 +50,8 @@ describe('PGP Crypto Api unit tests', function() {
emailAddress: 'whiteout.test@t-onlinede', emailAddress: 'whiteout.test@t-onlinede',
keySize: keySize, keySize: keySize,
passphrase: passphrase passphrase: passphrase
}, function(err, keys) { }).catch(function(err) {
expect(err).to.exist; expect(err.message).to.match(/options/);
expect(keys).to.not.exist;
done(); done();
}); });
}); });
@ -61,76 +60,69 @@ describe('PGP Crypto Api unit tests', function() {
emailAddress: 'whiteout.testt-online.de', emailAddress: 'whiteout.testt-online.de',
keySize: keySize, keySize: keySize,
passphrase: passphrase passphrase: passphrase
}, function(err, keys) { }).catch(function(err) {
expect(err).to.exist; expect(err.message).to.match(/options/);
expect(keys).to.not.exist;
done(); done();
}); });
}); });
it('should work with passphrase', function(done) { it('should work with passphrase', function(done) {
var keyObject;
pgp.generateKeys({ pgp.generateKeys({
emailAddress: user, emailAddress: user,
keySize: keySize, keySize: keySize,
passphrase: passphrase passphrase: passphrase
}, function(err, keys) { }).then(function(keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.exist; expect(keys.keyId).to.exist;
expect(keys.privateKeyArmored).to.exist; expect(keys.privateKeyArmored).to.exist;
expect(keys.publicKeyArmored).to.exist; expect(keys.publicKeyArmored).to.exist;
keyObject = keys;
// test encrypt/decrypt // test encrypt/decrypt
pgp.importKeys({ return pgp.importKeys({
passphrase: passphrase, passphrase: passphrase,
privateKeyArmored: keys.privateKeyArmored, privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored publicKeyArmored: keys.publicKeyArmored
}, function(err) {
expect(err).to.not.exist;
pgp.encrypt('secret', [keys.publicKeyArmored], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
pgp.decrypt(ct, keys.publicKeyArmored, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal('secret');
expect(signValid).to.be.true;
done();
});
});
}); });
}).then(function() {
return pgp.encrypt('secret', [keyObject.publicKeyArmored]);
}).then(function(ct) {
expect(ct).to.exist;
return pgp.decrypt(ct, keyObject.publicKeyArmored);
}).then(function(pt) {
expect(pt.decrypted).to.equal('secret');
expect(pt.signaturesValid).to.be.true;
done();
}); });
}); });
it('should work without passphrase', function(done) { it('should work without passphrase', function(done) {
var keyObject;
pgp.generateKeys({ pgp.generateKeys({
emailAddress: user, emailAddress: user,
keySize: keySize, keySize: keySize,
passphrase: '' passphrase: ''
}, function(err, keys) { }).then(function(keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.exist; expect(keys.keyId).to.exist;
expect(keys.privateKeyArmored).to.exist; expect(keys.privateKeyArmored).to.exist;
expect(keys.publicKeyArmored).to.exist; expect(keys.publicKeyArmored).to.exist;
keyObject = keys;
// test encrypt/decrypt // test encrypt/decrypt
pgp.importKeys({ return pgp.importKeys({
passphrase: undefined, passphrase: passphrase,
privateKeyArmored: keys.privateKeyArmored, privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored publicKeyArmored: keys.publicKeyArmored
}, function(err) {
expect(err).to.not.exist;
pgp.encrypt('secret', [keys.publicKeyArmored], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
pgp.decrypt(ct, keys.publicKeyArmored, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal('secret');
expect(signValid).to.be.true;
done();
});
});
}); });
}).then(function() {
return pgp.encrypt('secret', [keyObject.publicKeyArmored]);
}).then(function(ct) {
expect(ct).to.exist;
return pgp.decrypt(ct, keyObject.publicKeyArmored);
}).then(function(pt) {
expect(pt.decrypted).to.equal('secret');
expect(pt.signaturesValid).to.be.true;
done();
}); });
}); });
}); });
@ -141,15 +133,13 @@ describe('PGP Crypto Api unit tests', function() {
passphrase: 'asd', passphrase: 'asd',
privateKeyArmored: privkey, privateKeyArmored: privkey,
publicKeyArmored: pubkey publicKeyArmored: pubkey
}, function(err) { }).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(err.message).to.equal('Incorrect passphrase!'); expect(err.message).to.equal('Incorrect passphrase!');
return pgp.exportKeys();
pgp.exportKeys(function(err, keys) { }).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(keys).to.not.exist; done();
done();
});
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
@ -157,16 +147,13 @@ describe('PGP Crypto Api unit tests', function() {
passphrase: passphrase, passphrase: passphrase,
privateKeyArmored: privkey, privateKeyArmored: privkey,
publicKeyArmored: pubkey publicKeyArmored: pubkey
}, function(err) { }).then(function() {
expect(err).to.not.exist; return pgp.exportKeys();
}).then(function(keys) {
pgp.exportKeys(function(err, keys) { expect(keys.keyId).to.equal(keyId);
expect(err).to.not.exist; expect(keys.privateKeyArmored.replace(/\r/g, '')).to.equal(privkey.replace(/\r/g, ''));
expect(keys.keyId).to.equal(keyId); expect(keys.publicKeyArmored.replace(/\r/g, '')).to.equal(pubkey.replace(/\r/g, ''));
expect(keys.privateKeyArmored.replace(/\r/g, '')).to.equal(privkey.replace(/\r/g, '')); done();
expect(keys.publicKeyArmored.replace(/\r/g, '')).to.equal(pubkey.replace(/\r/g, ''));
done();
});
}); });
}); });
}); });
@ -177,47 +164,38 @@ describe('PGP Crypto Api unit tests', function() {
privateKeyArmored: privkey, privateKeyArmored: privkey,
oldPassphrase: passphrase, oldPassphrase: passphrase,
newPassphrase: 'yxcv' newPassphrase: 'yxcv'
}, function(err, reEncryptedKey) { }).then(function(reEncryptedKey) {
expect(err).to.not.exist;
expect(reEncryptedKey).to.exist; expect(reEncryptedKey).to.exist;
pgp.importKeys({ return pgp.importKeys({
passphrase: 'yxcv', passphrase: 'yxcv',
privateKeyArmored: reEncryptedKey, privateKeyArmored: reEncryptedKey,
publicKeyArmored: pubkey publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
}); });
}); }).then(done);
}); });
it('should work with empty passphrase', function(done) { it('should work with empty passphrase', function(done) {
pgp.changePassphrase({ pgp.changePassphrase({
privateKeyArmored: privkey, privateKeyArmored: privkey,
oldPassphrase: passphrase, oldPassphrase: passphrase,
newPassphrase: undefined newPassphrase: undefined
}, function(err, reEncryptedKey) { }).then(function(reEncryptedKey) {
expect(err).to.not.exist;
expect(reEncryptedKey).to.exist; expect(reEncryptedKey).to.exist;
pgp.importKeys({ return pgp.importKeys({
passphrase: undefined, passphrase: undefined,
privateKeyArmored: reEncryptedKey, privateKeyArmored: reEncryptedKey,
publicKeyArmored: pubkey publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
}); });
}); }).then(done);
}); });
it('should fail when passphrases are equal', function(done) { it('should fail when passphrases are equal', function(done) {
pgp.changePassphrase({ pgp.changePassphrase({
privateKeyArmored: privkey, privateKeyArmored: privkey,
oldPassphrase: passphrase, oldPassphrase: passphrase,
newPassphrase: passphrase newPassphrase: passphrase
}, function(err, reEncryptedKey) { }).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(reEncryptedKey).to.not.exist;
done(); done();
}); });
}); });
@ -226,9 +204,8 @@ describe('PGP Crypto Api unit tests', function() {
privateKeyArmored: privkey, privateKeyArmored: privkey,
oldPassphrase: 'asd', oldPassphrase: 'asd',
newPassphrase: 'yxcv' newPassphrase: 'yxcv'
}, function(err, reEncryptedKey) { }).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(reEncryptedKey).to.not.exist;
done(); done();
}); });
}); });
@ -247,10 +224,7 @@ describe('PGP Crypto Api unit tests', function() {
passphrase: passphrase, passphrase: passphrase,
privateKeyArmored: privkey, privateKeyArmored: privkey,
publicKeyArmored: pubkey publicKeyArmored: pubkey
}, function(err) { }).then(done);
expect(err).to.not.exist;
done();
});
}); });
describe('Get KeyId', function() { describe('Get KeyId', function() {
@ -311,22 +285,19 @@ describe('PGP Crypto Api unit tests', function() {
it('should fail', function(done) { it('should fail', function(done) {
var input = null; var input = null;
pgp.encrypt(input, [pubkey], function(err, ct) { pgp.encrypt(input, [pubkey]).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(ct).to.not.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) { pgp.encrypt(message, [pubkey]).then(function(ct) {
expect(err).to.not.exist;
expect(ct).to.exist; expect(ct).to.exist;
done(); done();
}); });
}); });
it('should encrypt to myself if public keys are empty', function(done) { it('should encrypt to myself if public keys are empty', function(done) {
pgp.encrypt(message, undefined, function(err, ct) { pgp.encrypt(message, undefined).then(function(ct) {
expect(err).to.not.exist;
expect(ct).to.exist; expect(ct).to.exist;
done(); done();
}); });
@ -337,8 +308,7 @@ describe('PGP Crypto Api unit tests', function() {
var ciphertext; var ciphertext;
beforeEach(function(done) { beforeEach(function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) { pgp.encrypt(message, [pubkey]).then(function(ct) {
expect(err).to.not.exist;
expect(ct).to.exist; expect(ct).to.exist;
ciphertext = ct; ciphertext = ct;
done(); done();
@ -348,45 +318,40 @@ describe('PGP Crypto Api unit tests', function() {
it('should fail', function(done) { it('should fail', function(done) {
var input = 'asdfa\rsdf'; var input = 'asdfa\rsdf';
pgp.decrypt(input, pubkey, function(err, pt) { pgp.decrypt(input, pubkey).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(pt).to.not.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
pgp.decrypt(ciphertext, pubkey, function(err, pt, signValid) { pgp.decrypt(ciphertext, pubkey).then(function(pt) {
expect(err).to.not.exist; expect(pt.decrypted).to.equal(message);
expect(pt).to.equal(message); expect(pt.signaturesValid).to.be.true;
expect(signValid).to.be.true;
done(); done();
}); });
}); });
it('should work without signature', function(done) { it('should work without signature', function(done) {
openpgp.encryptMessage([pgp._publicKey], message).then(function(ct) { openpgp.encryptMessage([pgp._publicKey], message).then(function(ct) {
pgp.decrypt(ct, undefined, function(err, pt, signValid) { return pgp.decrypt(ct, undefined);
expect(err).to.not.exist; }).then(function(pt) {
expect(pt).to.equal(message); expect(pt.decrypted).to.equal(message);
expect(signValid).to.be.undefined; expect(pt.signaturesValid).to.be.undefined;
done(); done();
});
}); });
}); });
it('should fail to verify if public keys are empty', function(done) { it('should fail to verify if public keys are empty', function(done) {
// setup another public key so that signature verification fails // setup another public key so that signature verification fails
pgp._publicKey = openpgp.key.readArmored(wrongPubkey).keys[0]; pgp._publicKey = openpgp.key.readArmored(wrongPubkey).keys[0];
pgp.decrypt(ciphertext, undefined, function(err, pt, signValid) { pgp.decrypt(ciphertext, undefined).then(function(pt) {
expect(err).to.not.exist; expect(pt.decrypted).to.equal(message);
expect(pt).to.equal(message); expect(pt.signaturesValid).to.be.null;
expect(signValid).to.be.null;
done(); done();
}); });
}); });
it('should decrypt but signValid should be null for wrong public key', function(done) { it('should decrypt but signValid should be null for wrong public key', function(done) {
pgp.decrypt(ciphertext, wrongPubkey, function(err, pt, signValid) { pgp.decrypt(ciphertext, wrongPubkey).then(function(pt) {
expect(err).to.not.exist; expect(pt.decrypted).to.equal(message);
expect(pt).to.equal(message); expect(pt.signaturesValid).to.be.null;
expect(signValid).to.be.null;
done(); done();
}); });
}); });
@ -403,23 +368,20 @@ describe('PGP Crypto Api unit tests', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
pgp.verifyClearSignedMessage(clearsigned, pubkey, function(err, signaturesValid) { pgp.verifyClearSignedMessage(clearsigned, pubkey).then(function(signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.true; expect(signaturesValid).to.be.true;
done(); done();
}); });
}); });
it('should fail', function(done) { it('should fail', function(done) {
pgp.verifyClearSignedMessage(clearsigned.replace('clearsigned', 'invalid'), pubkey, function(err, signaturesValid) { pgp.verifyClearSignedMessage(clearsigned.replace('clearsigned', 'invalid'), pubkey).then(function(signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.false; expect(signaturesValid).to.be.false;
done(); done();
}); });
}); });
it('should be null for wrong public key', function(done) { it('should be null for wrong public key', function(done) {
pgp.verifyClearSignedMessage(clearsigned, wrongPubkey, function(err, signaturesValid) { pgp.verifyClearSignedMessage(clearsigned, wrongPubkey).then(function(signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.null; expect(signaturesValid).to.be.null;
done(); done();
}); });
@ -439,23 +401,20 @@ describe('PGP Crypto Api unit tests', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
pgp.verifySignedMessage(signedMessage, signature, pubkey, function(err, signaturesValid) { pgp.verifySignedMessage(signedMessage, signature, pubkey).then(function(signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.true; expect(signaturesValid).to.be.true;
done(); done();
}); });
}); });
it('should fail', function(done) { it('should fail', function(done) {
pgp.verifySignedMessage(signedMessage.replace('signed', 'invalid'), signature, pubkey, function(err, signaturesValid) { pgp.verifySignedMessage(signedMessage.replace('signed', 'invalid'), signature, pubkey).then(function(signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.false; expect(signaturesValid).to.be.false;
done(); done();
}); });
}); });
it('should be null for wrong public key', function(done) { it('should be null for wrong public key', function(done) {
pgp.verifySignedMessage(signedMessage, signature, wrongPubkey, function(err, signaturesValid) { pgp.verifySignedMessage(signedMessage, signature, wrongPubkey).then(function(signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.null; expect(signaturesValid).to.be.null;
done(); done();
}); });

View File

@ -58,55 +58,45 @@ describe('Account Service unit test', function() {
account.init({ account.init({
emailAddress: dummyUser.replace('@'), emailAddress: dummyUser.replace('@'),
realname: realname realname: realname
}, onInit); }).catch(function onInit(err) {
function onInit(err, keys) {
expect(err).to.exist; expect(err).to.exist;
expect(keys).to.not.exist; });
}
}); });
it('should fail for _accountStore.init', function() { it('should fail for _accountStore.init', function() {
devicestorageStub.init.yields(new Error('asdf')); devicestorageStub.init.returns(rejects(new Error('asdf')));
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }).catch(function onInit(err) {
function onInit(err, keys) {
expect(err.message).to.match(/asdf/); expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist; });
}
}); });
it('should fail for _updateHandler.update', function() { it('should fail for _updateHandler.update', function() {
updateHandlerStub.update.yields(new Error('asdf')); devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(rejects(new Error('asdf')));
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }).catch(function onInit(err) {
function onInit(err, keys) {
expect(err.message).to.match(/Updating/); expect(err.message).to.match(/Updating/);
expect(keys).to.not.exist; });
}
}); });
it('should fail for _keychain.getUserKeyPair', function() { it('should fail for _keychain.getUserKeyPair', function() {
updateHandlerStub.update.yields(); devicestorageStub.init.returns(resolves());
keychainStub.getUserKeyPair.yields(new Error('asdf')); updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(rejects(new Error('asdf')));
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }).catch(function(err) {
function onInit(err, keys) {
expect(err.message).to.match(/asdf/); expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist; });
}
}); });
it('should fail for _keychain.refreshKeyForUserId', function() { it('should fail for _keychain.refreshKeyForUserId', function() {
@ -114,19 +104,17 @@ describe('Account Service unit test', function() {
publicKey: 'publicKey' publicKey: 'publicKey'
}; };
updateHandlerStub.update.yields(); devicestorageStub.init.returns(resolves());
keychainStub.getUserKeyPair.yields(null, storedKeys); updateHandlerStub.update.returns(resolves());
keychainStub.refreshKeyForUserId.yields(new Error('asdf')); keychainStub.getUserKeyPair.returns(resolves(storedKeys));
keychainStub.refreshKeyForUserId.returns(rejects(new Error('asdf')));
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }).catch(function(err) {
function onInit(err, keys) {
expect(err.message).to.match(/asdf/); expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist; });
}
}); });
it('should fail for _emailDao.init after _keychain.refreshKeyForUserId', function() { it('should fail for _emailDao.init after _keychain.refreshKeyForUserId', function() {
@ -134,20 +122,18 @@ describe('Account Service unit test', function() {
publicKey: 'publicKey' publicKey: 'publicKey'
}; };
updateHandlerStub.update.yields(); devicestorageStub.init.returns(resolves());
keychainStub.getUserKeyPair.yields(null, storedKeys); updateHandlerStub.update.returns(resolves());
keychainStub.refreshKeyForUserId.yields(null, storedKeys); keychainStub.getUserKeyPair.returns(resolves(storedKeys));
emailStub.init.yields(new Error('asdf')); keychainStub.refreshKeyForUserId.returns(resolves(storedKeys));
emailStub.init.returns(rejects(new Error('asdf')));
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }).catch(function(err) {
function onInit(err, keys) {
expect(err.message).to.match(/asdf/); expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist; });
}
}); });
it('should fail for _emailDao.init', function() { it('should fail for _emailDao.init', function() {
@ -156,19 +142,17 @@ describe('Account Service unit test', function() {
privateKey: 'privateKey' privateKey: 'privateKey'
}; };
updateHandlerStub.update.yields(); devicestorageStub.init.returns(resolves());
keychainStub.getUserKeyPair.yields(null, storedKeys); updateHandlerStub.update.returns(resolves());
emailStub.init.yields(new Error('asdf')); keychainStub.getUserKeyPair.returns(resolves(storedKeys));
emailStub.init.returns(rejects(new Error('asdf')));
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }).catch(function(err) {
function onInit(err, keys) {
expect(err.message).to.match(/asdf/); expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist; });
}
}); });
it('should work after _keychain.refreshKeyForUserId', function() { it('should work after _keychain.refreshKeyForUserId', function() {
@ -176,20 +160,20 @@ describe('Account Service unit test', function() {
publicKey: 'publicKey' publicKey: 'publicKey'
}; };
updateHandlerStub.update.yields(); devicestorageStub.init.returns(resolves());
keychainStub.getUserKeyPair.yields(null, storedKeys); updateHandlerStub.update.returns(resolves());
keychainStub.refreshKeyForUserId.yields(null, 'publicKey'); keychainStub.getUserKeyPair.returns(resolves(storedKeys));
emailStub.init.yields(); keychainStub.refreshKeyForUserId.returns(resolves('publicKey'));
emailStub.init.returns(resolves());
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }, function onInit(keys) {
function onInit(err, keys) {
expect(err).to.not.exist;
expect(keys).to.deep.equal(storedKeys); expect(keys).to.deep.equal(storedKeys);
} expect(keychainStub.refreshKeyForUserId.calledOnce).to.be.true;
expect(emailStub.init.calledOnce).to.be.true;
});
}); });
it('should work', function() { it('should work', function() {
@ -198,19 +182,20 @@ describe('Account Service unit test', function() {
privateKey: 'privateKey' privateKey: 'privateKey'
}; };
updateHandlerStub.update.yields(); devicestorageStub.init.returns(resolves());
keychainStub.getUserKeyPair.yields(null, storedKeys); updateHandlerStub.update.returns(resolves());
emailStub.init.yields(); keychainStub.getUserKeyPair.returns(resolves(storedKeys));
emailStub.init.returns(resolves());
account.init({ account.init({
emailAddress: dummyUser, emailAddress: dummyUser,
realname: realname realname: realname
}, onInit); }, function onInit(keys) {
function onInit(err, keys) {
expect(err).to.not.exist;
expect(keys).to.equal(storedKeys); expect(keys).to.equal(storedKeys);
} expect(keychainStub.refreshKeyForUserId.called).to.be.false;
expect(emailStub.init.calledOnce).to.be.true;
expect(account._accounts.length).to.equal(1);
});
}); });
}); });
@ -227,48 +212,66 @@ describe('Account Service unit test', function() {
account.isOnline.restore(); account.isOnline.restore();
}); });
it('should fail due to _auth.getCredentials', function() { it('should fail due to _auth.getCredentials', function(done) {
authStub.getCredentials.yields(new Error('asdf')); authStub.getCredentials.returns(rejects(new Error('asdf')));
dialogStub.error = function(err) {
expect(err.message).to.match(/asdf/);
done();
};
account.onConnect(); account.onConnect();
expect(dialogStub.error.calledOnce).to.be.true;
}); });
it('should work', function() { it('should fail due to _auth.getCredentials', function(done) {
authStub.getCredentials.yields(null, credentials); authStub.getCredentials.returns(rejects(new Error('asdf')));
emailStub.onConnect.yields();
account.onConnect(); account.onConnect(function(err) {
expect(err.message).to.match(/asdf/);
expect(dialogStub.error.called).to.be.false;
done();
});
});
expect(emailStub.onConnect.calledOnce).to.be.true; it('should work', function(done) {
expect(dialogStub.error.calledOnce).to.be.true; authStub.getCredentials.returns(resolves(credentials));
authStub.handleCertificateUpdate.returns(resolves());
emailStub.onConnect.returns(resolves());
account.onConnect(function(err) {
expect(err).to.not.exist;
expect(dialogStub.error.called).to.be.false;
expect(emailStub.onConnect.calledOnce).to.be.true;
done();
});
}); });
}); });
describe('onDisconnect', function() { describe('onDisconnect', function() {
it('should work', function() { it('should work', function(done) {
account.onDisconnect(); emailStub.onDisconnect.returns(resolves());
expect(emailStub.onDisconnect.calledOnce).to.be.true; account.onDisconnect().then(done);
}); });
}); });
describe('logout', function() { describe('logout', function() {
it('should fail due to _auth.logout', function() { it('should fail due to _auth.logout', function(done) {
authStub.logout.yields(new Error()); authStub.logout.returns(rejects(new Error('asdf')));
account.logout(); account.logout().catch(function(err) {
expect(err.message).to.match(/asdf/);
expect(dialogStub.error.calledOnce).to.be.true; done();
});
}); });
it('should fail due to _emailDao.onDisconnect', function() { it('should fail due to _emailDao.onDisconnect', function(done) {
authStub.logout.yields(); authStub.logout.returns(resolves());
emailStub.onDisconnect.yields(new Error()); emailStub.onDisconnect.returns(rejects(new Error('asdf')));
account.logout(); account.logout().catch(function(err) {
expect(err.message).to.match(/asdf/);
expect(dialogStub.error.calledOnce).to.be.true; done();
});
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ var OutboxBO = require('../../../src/js/email/outbox'),
EmailDAO = require('../../../src/js/email/email'), EmailDAO = require('../../../src/js/email/email'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage'); DeviceStorageDAO = require('../../../src/js/service/devicestorage');
describe('Outbox Business Object unit test', function() { describe('Outbox unit test', function() {
var outbox, emailDaoStub, devicestorageStub, keychainStub, var outbox, emailDaoStub, devicestorageStub, keychainStub,
dummyUser = 'spiderpig@springfield.com'; dummyUser = 'spiderpig@springfield.com';
@ -42,6 +42,13 @@ describe('Outbox Business Object unit test', function() {
}); });
describe('put', function() { describe('put', function() {
beforeEach(function() {
sinon.stub(outbox, '_processOutbox');
});
afterEach(function() {
outbox._processOutbox.restore();
});
it('should not encrypt and store a mail', function(done) { it('should not encrypt and store a mail', function(done) {
var mail, senderKey, receiverKey; var mail, senderKey, receiverKey;
@ -67,15 +74,13 @@ describe('Outbox Business Object unit test', function() {
bcc: [] bcc: []
}; };
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey); keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).returns(resolves(senderKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey); keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).returns(resolves(receiverKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync(); keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).returns(resolves());
devicestorageStub.storeList.withArgs([mail]).yieldsAsync(); devicestorageStub.storeList.withArgs([mail]).returns(resolves());
outbox.put(mail, function(error) {
expect(error).to.not.exist;
outbox.put(mail).then(function() {
expect(mail.publicKeysArmored.length).to.equal(2); expect(mail.publicKeysArmored.length).to.equal(2);
expect(emailDaoStub.encrypt.called).to.be.false; expect(emailDaoStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.calledOnce).to.be.true; expect(devicestorageStub.storeList.calledOnce).to.be.true;
@ -103,11 +108,9 @@ describe('Outbox Business Object unit test', function() {
}] }]
}; };
devicestorageStub.storeList.withArgs([mail]).yieldsAsync(); devicestorageStub.storeList.withArgs([mail]).returns(resolves());
outbox.put(mail, function(error) {
expect(error).to.not.exist;
outbox.put(mail).then(function() {
expect(mail.publicKeysArmored.length).to.equal(0); expect(mail.publicKeysArmored.length).to.equal(0);
expect(keychainStub.getReceiverPublicKey.called).to.be.false; expect(keychainStub.getReceiverPublicKey.called).to.be.false;
expect(emailDaoStub.encrypt.called).to.be.false; expect(emailDaoStub.encrypt.called).to.be.false;
@ -142,20 +145,18 @@ describe('Outbox Business Object unit test', function() {
bcc: [] bcc: []
}; };
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey); keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).returns(resolves(senderKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey); keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).returns(resolves(receiverKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync(null, receiverKey); keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).returns(resolves(receiverKey));
emailDaoStub.encrypt.withArgs({ emailDaoStub.encrypt.withArgs({
mail: mail, mail: mail,
publicKeysArmored: [senderKey.publicKey, receiverKey.publicKey, receiverKey.publicKey] publicKeysArmored: [senderKey.publicKey, receiverKey.publicKey, receiverKey.publicKey]
}).yieldsAsync(); }).returns(resolves());
devicestorageStub.storeList.withArgs([mail]).yieldsAsync(); devicestorageStub.storeList.withArgs([mail]).returns(resolves());
outbox.put(mail, function(error) {
expect(error).to.not.exist;
outbox.put(mail).then(function() {
expect(mail.publicKeysArmored.length).to.equal(3); expect(mail.publicKeysArmored.length).to.equal(3);
expect(emailDaoStub.encrypt.calledOnce).to.be.true; expect(emailDaoStub.encrypt.calledOnce).to.be.true;
expect(devicestorageStub.storeList.calledOnce).to.be.true; expect(devicestorageStub.storeList.calledOnce).to.be.true;
@ -230,19 +231,19 @@ describe('Outbox Business Object unit test', function() {
dummyMails = [member, invited, notinvited, newlyjoined]; dummyMails = [member, invited, notinvited, newlyjoined];
devicestorageStub.listItems.yieldsAsync(null, dummyMails); devicestorageStub.listItems.returns(resolves(dummyMails));
emailDaoStub.sendPlaintext.yieldsAsync(); emailDaoStub.sendPlaintext.returns(resolves());
emailDaoStub.sendEncrypted.withArgs({ emailDaoStub.sendEncrypted.withArgs({
email: newlyjoined email: newlyjoined
}).yieldsAsync(); }).returns(resolves());
emailDaoStub.sendEncrypted.withArgs({ emailDaoStub.sendEncrypted.withArgs({
email: member email: member
}).yieldsAsync(); }).returns(resolves());
devicestorageStub.removeList.yieldsAsync(); devicestorageStub.removeList.returns(resolves());
function onOutboxUpdate(err, count) { function onOutboxUpdate(err, count) {
expect(err).to.not.exist; expect(err).to.not.exist;
@ -263,7 +264,7 @@ describe('Outbox Business Object unit test', function() {
it('should not process outbox in offline mode', function(done) { it('should not process outbox in offline mode', function(done) {
emailDaoStub._account.online = false; emailDaoStub._account.online = false;
devicestorageStub.listItems.yieldsAsync(null, [{}]); devicestorageStub.listItems.returns(resolves([{}]));
outbox._processOutbox(function(err, count) { outbox._processOutbox(function(err, count) {
expect(err).to.not.exist; expect(err).to.not.exist;

View File

@ -23,7 +23,7 @@ describe('Admin DAO unit tests', function() {
emailAddress: emailAddress emailAddress: emailAddress
}; };
adminDao.createUser(opt, function(err) { adminDao.createUser(opt).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -36,11 +36,11 @@ describe('Admin DAO unit tests', function() {
phone: '12345' phone: '12345'
}; };
restDaoStub.post.withArgs(opt, '/user').yields({ restDaoStub.post.withArgs(opt, '/user').returns(rejects({
code: 409 code: 409
}); }));
adminDao.createUser(opt, function(err) { adminDao.createUser(opt).catch(function(err) {
expect(err.message).to.contain('already taken'); expect(err.message).to.contain('already taken');
expect(restDaoStub.post.calledOnce).to.be.true; expect(restDaoStub.post.calledOnce).to.be.true;
done(); done();
@ -54,9 +54,9 @@ describe('Admin DAO unit tests', function() {
phone: '12345' phone: '12345'
}; };
restDaoStub.post.withArgs(opt, '/user').yields(new Error()); restDaoStub.post.withArgs(opt, '/user').returns(rejects(new Error()));
adminDao.createUser(opt, function(err) { adminDao.createUser(opt).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(restDaoStub.post.calledOnce).to.be.true; expect(restDaoStub.post.calledOnce).to.be.true;
done(); done();
@ -70,10 +70,9 @@ describe('Admin DAO unit tests', function() {
phone: '12345' phone: '12345'
}; };
restDaoStub.post.withArgs(opt, '/user').yields(); restDaoStub.post.withArgs(opt, '/user').returns(resolves());
adminDao.createUser(opt, function(err) { adminDao.createUser(opt).then(function() {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true; expect(restDaoStub.post.calledOnce).to.be.true;
done(); done();
}); });
@ -86,7 +85,7 @@ describe('Admin DAO unit tests', function() {
emailAddress: emailAddress emailAddress: emailAddress
}; };
adminDao.validateUser(opt, function(err) { adminDao.validateUser(opt).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -98,9 +97,9 @@ describe('Admin DAO unit tests', function() {
token: 'H45Z6D' token: 'H45Z6D'
}; };
restDaoStub.post.withArgs(opt, '/user/validate').yields(new Error()); restDaoStub.post.withArgs(opt, '/user/validate').returns(rejects(new Error()));
adminDao.validateUser(opt, function(err) { adminDao.validateUser(opt).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(restDaoStub.post.calledOnce).to.be.true; expect(restDaoStub.post.calledOnce).to.be.true;
done(); done();
@ -113,10 +112,9 @@ describe('Admin DAO unit tests', function() {
token: 'H45Z6D' token: 'H45Z6D'
}; };
restDaoStub.post.withArgs(opt, '/user/validate').yields(); restDaoStub.post.withArgs(opt, '/user/validate').returns(resolves());
adminDao.validateUser(opt, function(err) { adminDao.validateUser(opt).then(function() {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true; expect(restDaoStub.post.calledOnce).to.be.true;
done(); done();
}); });
@ -128,12 +126,11 @@ describe('Admin DAO unit tests', function() {
token: 'H45Z6D' token: 'H45Z6D'
}; };
restDaoStub.post.withArgs(opt, '/user/validate').yields({ restDaoStub.post.withArgs(opt, '/user/validate').returns(rejects({
code: 202 code: 202
}); }));
adminDao.validateUser(opt, function(err) { adminDao.validateUser(opt).then(function() {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true; expect(restDaoStub.post.calledOnce).to.be.true;
done(); done();
}); });

View File

@ -50,16 +50,15 @@ describe('Auth unit tests', function() {
describe('#init', function() { describe('#init', function() {
it('should initialize a user db', function(done) { it('should initialize a user db', function(done) {
storageStub.init.withArgs(APP_CONFIG_DB_NAME).yields(); storageStub.init.withArgs(APP_CONFIG_DB_NAME).returns(resolves());
auth.init(function(err) { auth.init().then(function() {
expect(err).to.not.exist;
expect(auth._initialized).to.be.true; expect(auth._initialized).to.be.true;
done(); done();
}); });
}); });
it('should initialize a user db', function(done) { it('should initialize a user db', function(done) {
storageStub.init.withArgs(APP_CONFIG_DB_NAME).yields(new Error()); storageStub.init.withArgs(APP_CONFIG_DB_NAME).returns(rejects(new Error()));
auth.init(function(err) { auth.init().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(auth._initialized).to.be.false; expect(auth._initialized).to.be.false;
done(); done();
@ -69,17 +68,18 @@ describe('Auth unit tests', function() {
describe('#getCredentials', function() { describe('#getCredentials', function() {
it('should load credentials and retrieve credentials from cfg', function(done) { it('should load credentials and retrieve credentials from cfg', function(done) {
storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]); storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).returns(resolves([emailAddress]));
storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).yieldsAsync(null, [encryptedPassword]); storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).returns(resolves([encryptedPassword]));
storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).yieldsAsync(null, [username]); storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).returns(resolves([username]));
storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).yieldsAsync(null, [realname]); storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).returns(resolves([realname]));
storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).yieldsAsync(null, [imap]); storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).returns(resolves([imap]));
storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).yieldsAsync(null, [smtp]); storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).returns(resolves([smtp]));
pgpStub.decrypt.withArgs(encryptedPassword, undefined).yields(null, password); pgpStub.decrypt.withArgs(encryptedPassword, undefined).returns(resolves({
decrypted: password,
auth.getCredentials(function(err, cred) { signaturesValid: true
expect(err).to.not.exist; }));
auth.getCredentials().then(function(cred) {
expect(auth.emailAddress).to.equal(emailAddress); expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.password).to.equal(password); expect(auth.password).to.equal(password);
@ -136,17 +136,15 @@ describe('Auth unit tests', function() {
auth.smtp = smtp; auth.smtp = smtp;
auth.imap = imap; auth.imap = imap;
storageStub.storeList.withArgs([encryptedPassword], PASSWD_DB_KEY).yieldsAsync(); storageStub.storeList.withArgs([encryptedPassword], PASSWD_DB_KEY).returns(resolves());
storageStub.storeList.withArgs([emailAddress], EMAIL_ADDR_DB_KEY).yieldsAsync(); storageStub.storeList.withArgs([emailAddress], EMAIL_ADDR_DB_KEY).returns(resolves());
storageStub.storeList.withArgs([username], USERNAME_DB_KEY).yieldsAsync(); storageStub.storeList.withArgs([username], USERNAME_DB_KEY).returns(resolves());
storageStub.storeList.withArgs([realname], REALNAME_DB_KEY).yieldsAsync(); storageStub.storeList.withArgs([realname], REALNAME_DB_KEY).returns(resolves());
storageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync(); storageStub.storeList.withArgs([imap], IMAP_DB_KEY).returns(resolves());
storageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync(); storageStub.storeList.withArgs([smtp], SMTP_DB_KEY).returns(resolves());
pgpStub.encrypt.withArgs(password).yields(null, encryptedPassword); pgpStub.encrypt.withArgs(password).returns(resolves(encryptedPassword));
auth.storeCredentials(function(err) {
expect(err).to.not.exist;
auth.storeCredentials().then(function() {
expect(storageStub.storeList.callCount).to.equal(6); expect(storageStub.storeList.callCount).to.equal(6);
expect(pgpStub.encrypt.calledOnce).to.be.true; expect(pgpStub.encrypt.calledOnce).to.be.true;
@ -186,13 +184,11 @@ describe('Auth unit tests', function() {
oauthStub.refreshToken.withArgs({ oauthStub.refreshToken.withArgs({
emailAddress: emailAddress, emailAddress: emailAddress,
oldToken: 'oldToken' oldToken: 'oldToken'
}).yieldsAsync(null, oauthToken); }).returns(resolves(oauthToken));
auth.getOAuthToken(function(err) { auth.getOAuthToken().then(function() {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress); expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken); expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.refreshToken.calledOnce).to.be.true; expect(oauthStub.refreshToken.calledOnce).to.be.true;
done(); done();
@ -201,13 +197,11 @@ describe('Auth unit tests', function() {
it('should fetch token with known email address', function(done) { it('should fetch token with known email address', function(done) {
auth.emailAddress = emailAddress; auth.emailAddress = emailAddress;
oauthStub.getOAuthToken.withArgs(emailAddress).yieldsAsync(null, oauthToken); oauthStub.getOAuthToken.withArgs(emailAddress).returns(resolves(oauthToken));
auth.getOAuthToken(function(err) { auth.getOAuthToken().then(function() {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress); expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken); expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.getOAuthToken.calledOnce).to.be.true; expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
done(); done();
@ -215,14 +209,12 @@ describe('Auth unit tests', function() {
}); });
it('should fetch token with unknown email address', function(done) { it('should fetch token with unknown email address', function(done) {
oauthStub.getOAuthToken.withArgs(undefined).yieldsAsync(null, oauthToken); oauthStub.getOAuthToken.withArgs(undefined).returns(resolves(oauthToken));
oauthStub.queryEmailAddress.withArgs(oauthToken).yieldsAsync(null, emailAddress); oauthStub.queryEmailAddress.withArgs(oauthToken).returns(resolves(emailAddress));
auth.getOAuthToken(function(err) { auth.getOAuthToken().then(function() {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress); expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken); expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.getOAuthToken.calledOnce).to.be.true; expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.calledOnce).to.be.true; expect(oauthStub.queryEmailAddress.calledOnce).to.be.true;
@ -231,14 +223,13 @@ describe('Auth unit tests', function() {
}); });
it('should fail when email address fetch fails', function(done) { it('should fail when email address fetch fails', function(done) {
oauthStub.getOAuthToken.yieldsAsync(null, oauthToken); oauthStub.getOAuthToken.returns(resolves(oauthToken));
oauthStub.queryEmailAddress.yieldsAsync(new Error()); oauthStub.queryEmailAddress.returns(rejects(new Error()));
auth.getOAuthToken(function(err) { auth.getOAuthToken().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(auth.emailAddress).to.not.exist; expect(auth.emailAddress).to.not.exist;
expect(auth.oauthToken).to.not.exist; expect(auth.oauthToken).to.not.exist;
expect(oauthStub.getOAuthToken.calledOnce).to.be.true; expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.calledOnce).to.be.true; expect(oauthStub.queryEmailAddress.calledOnce).to.be.true;
@ -247,13 +238,12 @@ describe('Auth unit tests', function() {
}); });
it('should fail when oauth fetch fails', function(done) { it('should fail when oauth fetch fails', function(done) {
oauthStub.getOAuthToken.yieldsAsync(new Error()); oauthStub.getOAuthToken.returns(rejects(new Error()));
auth.getOAuthToken(function(err) { auth.getOAuthToken().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(auth.emailAddress).to.not.exist; expect(auth.emailAddress).to.not.exist;
expect(auth.oauthToken).to.not.exist; expect(auth.oauthToken).to.not.exist;
expect(oauthStub.getOAuthToken.calledOnce).to.be.true; expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.called).to.be.false; expect(oauthStub.queryEmailAddress.called).to.be.false;
@ -264,15 +254,14 @@ describe('Auth unit tests', function() {
describe('#_loadCredentials', function() { describe('#_loadCredentials', function() {
it('should work', function(done) { it('should work', function(done) {
storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]); storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).returns(resolves([emailAddress]));
storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).yieldsAsync(null, [encryptedPassword]); storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).returns(resolves([encryptedPassword]));
storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).yieldsAsync(null, [username]); storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).returns(resolves([username]));
storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).yieldsAsync(null, [realname]); storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).returns(resolves([realname]));
storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).yieldsAsync(null, [imap]); storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).returns(resolves([imap]));
storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).yieldsAsync(null, [smtp]); storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).returns(resolves([smtp]));
auth._loadCredentials(function(err) { auth._loadCredentials().then(function() {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress); expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.password).to.equal(encryptedPassword); expect(auth.password).to.equal(encryptedPassword);
expect(auth.imap).to.equal(imap); expect(auth.imap).to.equal(imap);
@ -289,9 +278,9 @@ describe('Auth unit tests', function() {
}); });
it('should fail', function(done) { it('should fail', function(done) {
storageStub.listItems.yieldsAsync(new Error()); storageStub.listItems.returns(rejects(new Error()));
auth._loadCredentials(function(err) { auth._loadCredentials().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(auth.emailAddress).to.not.exist; expect(auth.emailAddress).to.not.exist;
expect(auth.password).to.not.exist; expect(auth.password).to.not.exist;
@ -319,13 +308,13 @@ describe('Auth unit tests', function() {
it('should work for Trust on first use', function(done) { it('should work for Trust on first use', function(done) {
auth.imap = {}; auth.imap = {};
storeCredentialsStub.yields(); storeCredentialsStub.returns(resolves());
function callback(err) { function callback() {
expect(err).to.not.exist;
expect(storeCredentialsStub.callCount).to.equal(1); expect(storeCredentialsStub.callCount).to.equal(1);
done(); done();
} }
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert); auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
}); });
@ -333,9 +322,12 @@ describe('Auth unit tests', function() {
auth.imap = { auth.imap = {
ca: dummyCert ca: dummyCert
}; };
storeCredentialsStub.yields(); storeCredentialsStub.returns(resolves());
function callback() {}
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
auth.handleCertificateUpdate('imap', onConnectDummy, onConnectDummy, dummyCert);
expect(storeCredentialsStub.callCount).to.equal(0); expect(storeCredentialsStub.callCount).to.equal(0);
}); });
@ -344,7 +336,7 @@ describe('Auth unit tests', function() {
ca: 'other', ca: 'other',
pinned: true pinned: true
}; };
storeCredentialsStub.yields(); storeCredentialsStub.returns(resolves());
function callback(err) { function callback(err) {
expect(err).to.exist; expect(err).to.exist;
@ -352,6 +344,7 @@ describe('Auth unit tests', function() {
expect(storeCredentialsStub.callCount).to.equal(0); expect(storeCredentialsStub.callCount).to.equal(0);
done(); done();
} }
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert); auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
}); });
@ -359,7 +352,7 @@ describe('Auth unit tests', function() {
auth.imap = { auth.imap = {
ca: 'other' ca: 'other'
}; };
storeCredentialsStub.yields(); storeCredentialsStub.returns(resolves());
function callback(err) { function callback(err) {
if (err && err.callback) { if (err && err.callback) {
@ -373,8 +366,8 @@ describe('Auth unit tests', function() {
} }
} }
function onConnect(callback) { function onConnect(cb) {
callback(); cb();
} }
auth.handleCertificateUpdate('imap', onConnect, callback, dummyCert); auth.handleCertificateUpdate('imap', onConnect, callback, dummyCert);
@ -383,19 +376,18 @@ describe('Auth unit tests', function() {
describe('#logout', function() { describe('#logout', function() {
it('should fail to to error in calling db clear', function(done) { it('should fail to to error in calling db clear', function(done) {
storageStub.clear.yields(new Error()); storageStub.clear.returns(rejects(new Error()));
auth.logout(function(err) { auth.logout().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
storageStub.clear.yields(); storageStub.clear.returns(resolves());
auth.logout(function(err) { auth.logout().then(function() {
expect(err).to.not.exist;
expect(auth.password).to.be.undefined; expect(auth.password).to.be.undefined;
expect(auth.initialized).to.be.undefined; expect(auth.initialized).to.be.undefined;
expect(auth.credentialsDirty).to.be.undefined; expect(auth.credentialsDirty).to.be.undefined;

View File

@ -17,21 +17,22 @@ describe('Device Storage DAO unit tests', function() {
afterEach(function() {}); afterEach(function() {});
describe('init', function() { describe('init', function() {
it('should work', function() { it('should work', function(done) {
lawnchairDaoStub.init.yields(); lawnchairDaoStub.init.returns(resolves());
storageDao.init(testUser, function(err) { storageDao.init(testUser).then(function() {
expect(err).to.not.exist;
expect(lawnchairDaoStub.init.calledOnce).to.be.true; expect(lawnchairDaoStub.init.calledOnce).to.be.true;
done();
}); });
}); });
it('should fail', function() { it('should fail', function(done) {
lawnchairDaoStub.init.yields(new Error()); lawnchairDaoStub.init.returns(rejects(new Error()));
storageDao.init(testUser, function(err) { storageDao.init(testUser).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(lawnchairDaoStub.init.calledOnce).to.be.true; expect(lawnchairDaoStub.init.calledOnce).to.be.true;
done();
}); });
}); });
}); });
@ -40,7 +41,7 @@ describe('Device Storage DAO unit tests', function() {
it('should fail', function(done) { it('should fail', function(done) {
var list = [{}]; var list = [{}];
storageDao.storeList(list, '', function(err) { storageDao.storeList(list, '').catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -49,21 +50,17 @@ describe('Device Storage DAO unit tests', function() {
it('should work with empty list', function(done) { it('should work with empty list', function(done) {
var list = []; var list = [];
storageDao.storeList(list, 'email', function(err) { storageDao.storeList(list, 'email').then(done);
expect(err).to.not.exist;
done();
});
}); });
it('should work', function(done) { it('should work', function(done) {
lawnchairDaoStub.batch.yields(); lawnchairDaoStub.batch.returns(resolves());
var list = [{ var list = [{
foo: 'bar' foo: 'bar'
}]; }];
storageDao.storeList(list, 'email', function(err) { storageDao.storeList(list, 'email').then(function() {
expect(err).to.not.exist;
expect(lawnchairDaoStub.batch.calledOnce).to.be.true; expect(lawnchairDaoStub.batch.calledOnce).to.be.true;
done(); done();
}); });
@ -72,10 +69,9 @@ describe('Device Storage DAO unit tests', function() {
describe('remove list', function() { describe('remove list', function() {
it('should work', function(done) { it('should work', function(done) {
lawnchairDaoStub.removeList.yields(); lawnchairDaoStub.removeList.returns(resolves());
storageDao.removeList('email', function(err) { storageDao.removeList('email').then(function() {
expect(err).to.not.exist;
expect(lawnchairDaoStub.removeList.calledOnce).to.be.true; expect(lawnchairDaoStub.removeList.calledOnce).to.be.true;
done(); done();
}); });
@ -84,10 +80,9 @@ describe('Device Storage DAO unit tests', function() {
describe('list items', function() { describe('list items', function() {
it('should work', function(done) { it('should work', function(done) {
lawnchairDaoStub.list.yields(); lawnchairDaoStub.list.returns(resolves());
storageDao.listItems('email', 0, null, function(err) { storageDao.listItems('email', 0, null).then(function() {
expect(err).to.not.exist;
expect(lawnchairDaoStub.list.calledOnce).to.be.true; expect(lawnchairDaoStub.list.calledOnce).to.be.true;
done(); done();
}); });
@ -96,10 +91,9 @@ describe('Device Storage DAO unit tests', function() {
describe('clear', function() { describe('clear', function() {
it('should work', function(done) { it('should work', function(done) {
lawnchairDaoStub.clear.yields(); lawnchairDaoStub.clear.returns(resolves());
storageDao.clear(function(err) { storageDao.clear().then(function() {
expect(err).to.not.exist;
expect(lawnchairDaoStub.clear.calledOnce).to.be.true; expect(lawnchairDaoStub.clear.calledOnce).to.be.true;
done(); done();
}); });

View File

@ -1,8 +1,7 @@
'use strict'; 'use strict';
var RestDAO = require('../../../src/js/service/rest'), var RestDAO = require('../../../src/js/service/rest'),
InvitationDAO = require('../../../src/js/service/invitation'), InvitationDAO = require('../../../src/js/service/invitation');
appConfig = require('../../../src/js/app-config');
describe('Invitation DAO unit tests', function() { describe('Invitation DAO unit tests', function() {
var restDaoStub, invitationDao, var restDaoStub, invitationDao,
@ -12,94 +11,78 @@ describe('Invitation DAO unit tests', function() {
beforeEach(function() { beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO); restDaoStub = sinon.createStubInstance(RestDAO);
invitationDao = new InvitationDAO(restDaoStub, appConfig); invitationDao = new InvitationDAO(restDaoStub);
}); });
describe('initialization', function() { describe('initialization', function() {
it('should wire up correctly', function() { it('should wire up correctly', function() {
expect(invitationDao._restDao).to.equal(restDaoStub); expect(invitationDao._restDao).to.equal(restDaoStub);
expect(invitationDao.invite).to.exist; expect(invitationDao.invite).to.exist;
expect(InvitationDAO.INVITE_MISSING).to.equal(1);
expect(InvitationDAO.INVITE_PENDING).to.equal(2);
expect(InvitationDAO.INVITE_SUCCESS).to.equal(4);
}); });
}); });
describe('invite', function() { describe('invite', function() {
it('should invite the recipient', function(done) { it('should invite the recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 201); restDaoStub.put.returns(resolves());
invitationDao.invite({ invitationDao.invite({
recipient: alice, recipient: alice,
sender: bob sender: bob
}, function(err, status) { }).then(function() {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_SUCCESS);
expect(restDaoStub.put.calledWith({}, expectedUri)).to.be.true; expect(restDaoStub.put.calledWith({}, expectedUri)).to.be.true;
done(); done();
}); });
}); });
it('should point out already invited recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 304);
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_PENDING);
done();
});
});
it('should not work for http error', function(done) { it('should not work for http error', function(done) {
restDaoStub.put.yieldsAsync({ restDaoStub.put.returns(rejects(new Error()));
errMsg: 'jawollja.'
});
invitationDao.invite({ invitationDao.invite({
recipient: alice, recipient: alice,
sender: bob sender: bob
}, function(err, status) { }).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(status).to.not.exist;
done(); done();
}); });
}); });
it('should not work for unexpected response', function(done) { it('should report erroneous usage', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 1337);
invitationDao.invite({ invitationDao.invite({
recipient: alice,
sender: bob sender: bob
}, function(err, status) { }, expectError);
invitationDao.invite('asd').catch(expectError);
function expectError(err) {
expect(err).to.exist; expect(err).to.exist;
expect(status).to.not.exist;
done(); done();
}); }
}); });
it('should report erroneous usage', function() { it('should report erroneous usage', function(done) {
invitationDao.invite({
sender: bob
}, expectError);
invitationDao.invite({ invitationDao.invite({
recipient: alice, recipient: alice,
}, expectError); }, expectError);
invitationDao.invite('asd').catch(expectError);
function expectError(err) {
expect(err).to.exist;
done();
}
});
it('should report erroneous usage', function(done) {
invitationDao.invite({ invitationDao.invite({
recipient: 123, recipient: 123,
sender: 123 sender: 123
}, expectError); }, expectError);
invitationDao.invite('asd', expectError); invitationDao.invite('asd').catch(expectError);
function expectError(err, status) { function expectError(err) {
expect(err).to.exist; expect(err).to.exist;
expect(status).to.not.exist; done();
} }
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -22,23 +22,19 @@ describe('Lawnchair DAO unit tests', function() {
beforeEach(function(done) { beforeEach(function(done) {
lawnchairDao = new LawnchairDAO(); lawnchairDao = new LawnchairDAO();
lawnchairDao.init(dbName, function(err) { lawnchairDao.init(dbName).then(function() {
expect(err).to.not.exist;
expect(lawnchairDao._db).to.exist; expect(lawnchairDao._db).to.exist;
done(); done();
}); });
}); });
afterEach(function(done) { afterEach(function(done) {
lawnchairDao.clear(function(err) { lawnchairDao.clear().then(done);
expect(err).to.not.exist;
done();
});
}); });
describe('read', function() { describe('read', function() {
it('should fail', function(done) { it('should fail', function(done) {
lawnchairDao.read(undefined, function(err) { lawnchairDao.read(undefined).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -47,7 +43,7 @@ describe('Lawnchair DAO unit tests', function() {
describe('list', function() { describe('list', function() {
it('should fail', function(done) { it('should fail', function(done) {
lawnchairDao.list(undefined, 0, null, function(err) { lawnchairDao.list(undefined, 0, null).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -56,7 +52,7 @@ describe('Lawnchair DAO unit tests', function() {
describe('remove list', function() { describe('remove list', function() {
it('should fail', function(done) { it('should fail', function(done) {
lawnchairDao.removeList(undefined, function(err) { lawnchairDao.removeList(undefined).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -65,46 +61,36 @@ describe('Lawnchair DAO unit tests', function() {
describe('persist/read/remove', function() { describe('persist/read/remove', function() {
it('should fail', function(done) { it('should fail', function(done) {
lawnchairDao.persist(undefined, data, function(err) { lawnchairDao.persist(undefined, data).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should fail', function(done) { it('should fail', function(done) {
lawnchairDao.persist('1234', undefined, function(err) { lawnchairDao.persist('1234', undefined).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
lawnchairDao.persist(key, data, function(err) { lawnchairDao.persist(key, data).then(function() {
expect(err).to.not.exist; return lawnchairDao.read(key);
lawnchairDao.read(key, onRead); }).then(function(fetched) {
});
function onRead(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.deep.equal(data); expect(fetched).to.deep.equal(data);
lawnchairDao.remove(key, onRemove); return lawnchairDao.remove(key);
} }).then(function() {
return lawnchairDao.read(key);
function onRemove(err) { }).then(function(fetched) {
expect(err).to.not.exist;
lawnchairDao.read(key, onReadAgain);
}
function onReadAgain(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.not.exist; expect(fetched).to.not.exist;
done(); done();
} });
}); });
}); });
describe('batch/list/removeList', function() { describe('batch/list/removeList', function() {
it('should fails', function(done) { it('should fails', function(done) {
lawnchairDao.batch({}, function(err) { lawnchairDao.batch({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -119,29 +105,19 @@ describe('Lawnchair DAO unit tests', function() {
object: data2 object: data2
}]; }];
lawnchairDao.batch(list, function(err) { lawnchairDao.batch(list).then(function() {
expect(err).to.not.exist; return lawnchairDao.list('type', 0, null);
lawnchairDao.list('type', 0, null, onList); }).then(function(fetched) {
});
function onList(err, fetched) {
expect(err).to.not.exist;
expect(fetched.length).to.equal(2); expect(fetched.length).to.equal(2);
expect(fetched[0]).to.deep.equal(list[0].object); expect(fetched[0]).to.deep.equal(list[0].object);
lawnchairDao.removeList('type', onRemoveList); return lawnchairDao.removeList('type');
} }).then(function() {
return lawnchairDao.list('type', 0, null);
function onRemoveList(err) { }).then(function(fetched) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onListAgain);
}
function onListAgain(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.exist; expect(fetched).to.exist;
expect(fetched.length).to.equal(0); expect(fetched.length).to.equal(0);
done(); done();
} });
}); });
}); });

View File

@ -29,45 +29,43 @@ describe('Newsletter Service unit test', function() {
xhrMock.restore(); xhrMock.restore();
}); });
it('should not signup if user has not agreed', inject(function($rootScope) { it('should not signup if user has not agreed', function(done) {
newsletter.signup('text@example.com', false).then(function(result) { newsletter.signup('text@example.com', false).then(function(result) {
expect(result).to.be.false; expect(result).to.be.false;
expect(requests.length).to.equal(0);
done();
}); });
});
$rootScope.$apply(); it('should not signup due to invalid email address', function(done) {
expect(requests.length).to.equal(0);
}));
it('should not signup due to invalid email address', inject(function($rootScope) {
newsletter.signup('textexample.com', true).catch(function(err) { newsletter.signup('textexample.com', true).catch(function(err) {
expect(err.message).to.contain('Invalid'); expect(err.message).to.contain('Invalid');
expect(requests.length).to.equal(0);
done();
}); });
});
$rootScope.$apply(); it('should fail', function(done) {
expect(requests.length).to.equal(0);
}));
it('should fail', inject(function($rootScope) {
newsletter.signup('text@example.com', true).catch(function(err) { newsletter.signup('text@example.com', true).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(requests.length).to.equal(1);
done();
}); });
requests[0].onerror('err'); requests[0].onerror('err');
$rootScope.$apply(); });
expect(requests.length).to.equal(1);
}));
it('should work', inject(function($rootScope) { it('should work', function(done) {
newsletter.signup('text@example.com', true).then(function(result) { newsletter.signup('text@example.com', true).then(function(result) {
expect(result).to.exist; expect(result).to.exist;
expect(requests.length).to.equal(1);
done();
}); });
requests[0].respond(200, { requests[0].respond(200, {
"Content-Type": "text/plain" "Content-Type": "text/plain"
}, 'foobar!'); }, 'foobar!');
$rootScope.$apply(); });
expect(requests.length).to.equal(1);
}));
}); });
}); });

View File

@ -56,15 +56,14 @@ describe('OAuth unit tests', function() {
it('should work', function() { it('should work', function() {
removeCachedStub.withArgs({ removeCachedStub.withArgs({
token: 'oldToken' token: 'oldToken'
}).yields(); }).returns(resolves());
getOAuthTokenStub.withArgs(testEmail).yields(); getOAuthTokenStub.withArgs(testEmail).returns(resolves());
oauth.refreshToken({ oauth.refreshToken({
oldToken: 'oldToken', oldToken: 'oldToken',
emailAddress: testEmail emailAddress: testEmail
}, function(err) { }).then(function() {
expect(err).to.not.exist;
expect(removeCachedStub.calledOnce).to.be.true; expect(removeCachedStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledOnce).to.be.true; expect(getOAuthTokenStub.calledOnce).to.be.true;
}); });
@ -73,14 +72,13 @@ describe('OAuth unit tests', function() {
it('should work without email', function() { it('should work without email', function() {
removeCachedStub.withArgs({ removeCachedStub.withArgs({
token: 'oldToken' token: 'oldToken'
}).yields(); }).returns(resolves());
getOAuthTokenStub.withArgs(undefined).yields(); getOAuthTokenStub.withArgs(undefined).returns(resolves());
oauth.refreshToken({ oauth.refreshToken({
oldToken: 'oldToken', oldToken: 'oldToken',
}, function(err) { }).then(function() {
expect(err).to.not.exist;
expect(removeCachedStub.calledOnce).to.be.true; expect(removeCachedStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledOnce).to.be.true; expect(getOAuthTokenStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledWith(undefined)).to.be.true; expect(getOAuthTokenStub.calledWith(undefined)).to.be.true;
@ -90,7 +88,7 @@ describe('OAuth unit tests', function() {
it('should fail without all options', function() { it('should fail without all options', function() {
oauth.refreshToken({ oauth.refreshToken({
emailAddress: testEmail emailAddress: testEmail
}, function(err) { }).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(removeCachedStub.called).to.be.false; expect(removeCachedStub.called).to.be.false;
expect(getOAuthTokenStub.called).to.be.false; expect(getOAuthTokenStub.called).to.be.false;
@ -107,8 +105,7 @@ describe('OAuth unit tests', function() {
interactive: true interactive: true
}).yields('token'); }).yields('token');
oauth.getOAuthToken(undefined, function(err, token) { oauth.getOAuthToken(undefined).then(function(token) {
expect(err).to.not.exist;
expect(token).to.equal('token'); expect(token).to.equal('token');
done(); done();
}); });
@ -123,8 +120,7 @@ describe('OAuth unit tests', function() {
accountHint: testEmail accountHint: testEmail
}).yields('token'); }).yields('token');
oauth.getOAuthToken(testEmail, function(err, token) { oauth.getOAuthToken(testEmail).then(function(token) {
expect(err).to.not.exist;
expect(token).to.equal('token'); expect(token).to.equal('token');
done(); done();
}); });
@ -138,8 +134,7 @@ describe('OAuth unit tests', function() {
interactive: true interactive: true
}).yields('token'); }).yields('token');
oauth.getOAuthToken(testEmail, function(err, token) { oauth.getOAuthToken(testEmail).then(function(token) {
expect(err).to.not.exist;
expect(token).to.equal('token'); expect(token).to.equal('token');
done(); done();
}); });
@ -151,9 +146,8 @@ describe('OAuth unit tests', function() {
}); });
identityStub.yields(); identityStub.yields();
oauth.getOAuthToken(testEmail, function(err, token) { oauth.getOAuthToken(testEmail).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(token).to.not.exist;
done(); done();
}); });
}); });
@ -163,21 +157,19 @@ describe('OAuth unit tests', function() {
it('should work', function(done) { it('should work', function(done) {
googleApiStub.get.withArgs({ googleApiStub.get.withArgs({
uri: '/oauth2/v3/userinfo?access_token=token' uri: '/oauth2/v3/userinfo?access_token=token'
}).yields(null, { }).returns(resolves({
email: 'asdf@example.com' email: 'asdf@example.com'
}); }));
oauth.queryEmailAddress('token', function(err, emailAddress) { oauth.queryEmailAddress('token').then(function(emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com'); expect(emailAddress).to.equal('asdf@example.com');
done(); done();
}); });
}); });
it('should fail due to invalid token', function(done) { it('should fail due to invalid token', function(done) {
oauth.queryEmailAddress('', function(err, emailAddress) { oauth.queryEmailAddress('').catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(emailAddress).to.not.exist;
done(); done();
}); });
}); });
@ -187,9 +179,8 @@ describe('OAuth unit tests', function() {
uri: '/oauth2/v3/userinfo?access_token=token' uri: '/oauth2/v3/userinfo?access_token=token'
}).yields(new Error()); }).yields(new Error());
oauth.queryEmailAddress('token', function(err, emailAddress) { oauth.queryEmailAddress('token').catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(emailAddress).to.not.exist;
done(); done();
}); });
}); });

View File

@ -19,23 +19,21 @@ describe('Private Key DAO unit tests', function() {
describe('requestDeviceRegistration', function() { describe('requestDeviceRegistration', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.requestDeviceRegistration({}, function(err, sessionKey) { privkeyDao.requestDeviceRegistration({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(sessionKey).to.not.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
restDaoStub.post.yields(null, { restDaoStub.post.returns(resolves({
encryptedRegSessionKey: 'asdf' encryptedRegSessionKey: 'asdf'
}); }));
privkeyDao.requestDeviceRegistration({ privkeyDao.requestDeviceRegistration({
userId: emailAddress, userId: emailAddress,
deviceName: deviceName deviceName: deviceName
}, function(err, sessionKey) { }).then(function(sessionKey) {
expect(err).to.not.exist;
expect(sessionKey).to.exist; expect(sessionKey).to.exist;
done(); done();
}); });
@ -44,50 +42,44 @@ describe('Private Key DAO unit tests', function() {
describe('uploadDeviceSecret', function() { describe('uploadDeviceSecret', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.uploadDeviceSecret({}, function(err) { privkeyDao.uploadDeviceSecret({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
restDaoStub.put.yields(); restDaoStub.put.returns(resolves());
privkeyDao.uploadDeviceSecret({ privkeyDao.uploadDeviceSecret({
userId: emailAddress, userId: emailAddress,
deviceName: deviceName, deviceName: deviceName,
encryptedDeviceSecret: 'asdf', encryptedDeviceSecret: 'asdf',
iv: 'iv' iv: 'iv'
}, function(err) { }).then(done);
expect(err).to.not.exist;
done();
});
}); });
}); });
describe('requestAuthSessionKey', function() { describe('requestAuthSessionKey', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.requestAuthSessionKey({}, function(err) { privkeyDao.requestAuthSessionKey({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields(); restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).returns(resolves());
privkeyDao.requestAuthSessionKey({ privkeyDao.requestAuthSessionKey({
userId: emailAddress userId: emailAddress
}, function(err) { }).then(done);
expect(err).to.not.exist;
done();
});
}); });
}); });
describe('verifyAuthentication', function() { describe('verifyAuthentication', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.verifyAuthentication({}, function(err) { privkeyDao.verifyAuthentication({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -104,18 +96,15 @@ describe('Private Key DAO unit tests', function() {
iv: ' iv' iv: ' iv'
}; };
restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields(); restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).returns(resolves());
privkeyDao.verifyAuthentication(options, function(err) { privkeyDao.verifyAuthentication(options).then(done);
expect(err).to.not.exist;
done();
});
}); });
}); });
describe('upload', function() { describe('upload', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.upload({}, function(err) { privkeyDao.upload({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -131,35 +120,49 @@ describe('Private Key DAO unit tests', function() {
iv: 'iv' iv: 'iv'
}; };
restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).yields(); restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).returns(resolves());
privkeyDao.upload(options, function(err) { privkeyDao.upload(options).then(done);
expect(err).to.not.exist;
done();
});
}); });
}); });
describe('requestDownload', function() { describe('requestDownload', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.requestDownload({}, function(err) { privkeyDao.requestDownload({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should not find a key', function(done) {
var keyId = '12345';
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId
}).returns(rejects({
code: 404
}));
privkeyDao.requestDownload({
userId: emailAddress,
keyId: keyId
}).then(function(found) {
expect(found).to.be.false;
done();
});
});
it('should work', function(done) { it('should work', function(done) {
var keyId = '12345'; var keyId = '12345';
restDaoStub.get.withArgs({ restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId uri: '/privatekey/user/' + emailAddress + '/key/' + keyId
}).yields(); }).returns(resolves());
privkeyDao.requestDownload({ privkeyDao.requestDownload({
userId: emailAddress, userId: emailAddress,
keyId: keyId keyId: keyId
}, function(err, found) { }).then(function(found) {
expect(err).to.not.exist;
expect(found).to.be.true; expect(found).to.be.true;
done(); done();
}); });
@ -168,24 +171,41 @@ describe('Private Key DAO unit tests', function() {
describe('hasPrivateKey', function() { describe('hasPrivateKey', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.hasPrivateKey({}, function(err) { privkeyDao.hasPrivateKey({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should not find a key', function(done) {
var keyId = '12345';
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true'
}).returns(rejects({
code: 404
}));
privkeyDao.hasPrivateKey({
userId: emailAddress,
keyId: keyId
}).then(function(found) {
expect(found).to.be.false;
done();
});
});
it('should work', function(done) { it('should work', function(done) {
var keyId = '12345'; var keyId = '12345';
restDaoStub.get.withArgs({ restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true' uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true'
}).yields(); }).returns(resolves());
privkeyDao.hasPrivateKey({ privkeyDao.hasPrivateKey({
userId: emailAddress, userId: emailAddress,
keyId: keyId keyId: keyId
}, function(err, found) { }).then(function(found) {
expect(err).to.not.exist;
expect(found).to.be.true; expect(found).to.be.true;
done(); done();
}); });
@ -194,7 +214,7 @@ describe('Private Key DAO unit tests', function() {
describe('download', function() { describe('download', function() {
it('should fail due to invalid args', function(done) { it('should fail due to invalid args', function(done) {
privkeyDao.download({}, function(err) { privkeyDao.download({}).catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
@ -207,16 +227,13 @@ describe('Private Key DAO unit tests', function() {
restDaoStub.get.withArgs({ restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token' uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token'
}).yields(); }).returns(resolves());
privkeyDao.download({ privkeyDao.download({
userId: emailAddress, userId: emailAddress,
keyId: key._id, keyId: key._id,
recoveryToken: 'token' recoveryToken: 'token'
}, function(err) { }).then(done);
expect(err).to.not.exist;
done();
});
}); });
}); });

View File

@ -1,8 +1,7 @@
'use strict'; 'use strict';
var RestDAO = require('../../../src/js/service/rest'), var RestDAO = require('../../../src/js/service/rest'),
PublicKeyDAO = require('../../../src/js/service/publickey'), PublicKeyDAO = require('../../../src/js/service/publickey');
appConfig = require('../../../src/js/app-config');
describe('Public Key DAO unit tests', function() { describe('Public Key DAO unit tests', function() {
@ -10,32 +9,29 @@ describe('Public Key DAO unit tests', function() {
beforeEach(function() { beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO); restDaoStub = sinon.createStubInstance(RestDAO);
pubkeyDao = new PublicKeyDAO(restDaoStub, appConfig); pubkeyDao = new PublicKeyDAO(restDaoStub);
}); });
afterEach(function() {}); afterEach(function() {});
describe('get', function() { describe('get', function() {
it('should fail', function(done) { it('should fail', function(done) {
restDaoStub.get.yields(42); restDaoStub.get.returns(rejects(42));
pubkeyDao.get('id', function(err, key) { pubkeyDao.get('id').catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true; expect(restDaoStub.get.calledOnce).to.be.true;
done(); done();
}); });
}); });
it('should work', function(done) { it('should work', function(done) {
restDaoStub.get.yields(null, { restDaoStub.get.returns(resolves({
_id: '12345', _id: '12345',
publicKey: 'asdf' publicKey: 'asdf'
}); }));
pubkeyDao.get('id', function(err, key) { pubkeyDao.get('id').then(function(key) {
expect(err).to.not.exist;
expect(key).to.exist;
expect(key._id).to.exist; expect(key._id).to.exist;
expect(key.publicKey).to.exist; expect(key.publicKey).to.exist;
expect(restDaoStub.get.calledOnce).to.be.true; expect(restDaoStub.get.calledOnce).to.be.true;
@ -46,31 +42,27 @@ describe('Public Key DAO unit tests', function() {
describe('verify', function() { describe('verify', function() {
it('should fail', function(done) { it('should fail', function(done) {
restDaoStub.get.yields(42); restDaoStub.get.returns(rejects(42));
pubkeyDao.verify('id', function(err) { pubkeyDao.verify('id').catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
done(); done();
}); });
}); });
it('should not error for 400', function(done) { it('should not error for 400', function(done) {
restDaoStub.get.yields({ restDaoStub.get.returns(rejects({
code: 400 code: 400
}); }));
pubkeyDao.verify('id', function(err) { pubkeyDao.verify('id').then(done);
expect(err).to.not.exist;
done();
});
}); });
it('should work', function(done) { it('should work', function(done) {
var uuid = 'c621e328-8548-40a1-8309-adf1955e98a9'; var uuid = 'c621e328-8548-40a1-8309-adf1955e98a9';
restDaoStub.get.yields(null); restDaoStub.get.returns(resolves());
pubkeyDao.verify(uuid, function(err) { pubkeyDao.verify(uuid).then(function() {
expect(err).to.not.exist;
expect(restDaoStub.get.calledWith(sinon.match(function(arg) { expect(restDaoStub.get.calledWith(sinon.match(function(arg) {
return arg.uri === '/verify/' + uuid && arg.type === 'text'; return arg.uri === '/verify/' + uuid && arg.type === 'text';
}))).to.be.true; }))).to.be.true;
@ -81,23 +73,21 @@ describe('Public Key DAO unit tests', function() {
describe('get by userId', function() { describe('get by userId', function() {
it('should fail', function(done) { it('should fail', function(done) {
restDaoStub.get.yields(42); restDaoStub.get.returns(rejects(42));
pubkeyDao.getByUserId('userId', function(err, key) { pubkeyDao.getByUserId('userId').catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true; expect(restDaoStub.get.calledOnce).to.be.true;
done(); done();
}); });
}); });
it('should react to 404', function(done) { it('should react to 404', function(done) {
restDaoStub.get.yields({ restDaoStub.get.returns(resolves({
code: 404 code: 404
}); }));
pubkeyDao.getByUserId('userId', function(err, key) { pubkeyDao.getByUserId('userId').then(function(key) {
expect(err).to.not.exist;
expect(key).to.not.exist; expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true; expect(restDaoStub.get.calledOnce).to.be.true;
done(); done();
@ -105,10 +95,9 @@ describe('Public Key DAO unit tests', function() {
}); });
it('should return empty array', function(done) { it('should return empty array', function(done) {
restDaoStub.get.yields(null, []); restDaoStub.get.returns(resolves([]));
pubkeyDao.getByUserId('userId', function(err, key) { pubkeyDao.getByUserId('userId').then(function(key) {
expect(err).to.not.exist;
expect(key).to.not.exist; expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true; expect(restDaoStub.get.calledOnce).to.be.true;
done(); done();
@ -116,14 +105,12 @@ describe('Public Key DAO unit tests', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
restDaoStub.get.yields(null, [{ restDaoStub.get.returns(resolves([{
_id: '12345', _id: '12345',
publicKey: 'asdf' publicKey: 'asdf'
}]); }]));
pubkeyDao.getByUserId('userId', function(err, key) { pubkeyDao.getByUserId('userId').then(function(key) {
expect(err).to.not.exist;
expect(key).to.exist;
expect(key._id).to.exist; expect(key._id).to.exist;
expect(key.publicKey).to.exist; expect(key.publicKey).to.exist;
expect(restDaoStub.get.calledOnce).to.be.true; expect(restDaoStub.get.calledOnce).to.be.true;
@ -134,13 +121,12 @@ describe('Public Key DAO unit tests', function() {
describe('put', function() { describe('put', function() {
it('should fail', function(done) { it('should fail', function(done) {
restDaoStub.put.yields(); restDaoStub.put.returns(resolves());
pubkeyDao.put({ pubkeyDao.put({
_id: '12345', _id: '12345',
publicKey: 'asdf' publicKey: 'asdf'
}, function(err) { }).then(function() {
expect(err).to.not.exist;
expect(restDaoStub.put.calledOnce).to.be.true; expect(restDaoStub.put.calledOnce).to.be.true;
done(); done();
}); });
@ -149,10 +135,9 @@ describe('Public Key DAO unit tests', function() {
describe('remove', function() { describe('remove', function() {
it('should fail', function(done) { it('should fail', function(done) {
restDaoStub.remove.yields(); restDaoStub.remove.returns(resolves());
pubkeyDao.remove('12345', function(err) { pubkeyDao.remove('12345').then(function(err) {
expect(err).to.not.exist;
expect(restDaoStub.remove.calledOnce).to.be.true; expect(restDaoStub.remove.calledOnce).to.be.true;
done(); done();
}); });

View File

@ -7,7 +7,7 @@ describe('Rest DAO unit tests', function() {
var restDao, xhrMock, requests; var restDao, xhrMock, requests;
beforeEach(function() { beforeEach(function() {
restDao = new RestDAO(); restDao = new RestDAO(window.qMock);
xhrMock = sinon.useFakeXMLHttpRequest(); xhrMock = sinon.useFakeXMLHttpRequest();
requests = []; requests = [];
@ -31,15 +31,14 @@ describe('Rest DAO unit tests', function() {
}); });
describe('get', function() { describe('get', function() {
it('should work with json as default type', function() { it('should work with json as default type', function(done) {
restDao.get({ restDao.get({
uri: '/asdf' uri: '/asdf'
}, function(err, data, status) { }).then(function(data) {
expect(err).to.not.exist;
expect(data.foo).to.equal('bar'); expect(data.foo).to.equal('bar');
var req = requests[0]; var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/json'); expect(req.requestHeaders.Accept).to.equal('application/json');
expect(status).to.equal(200); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -48,16 +47,15 @@ describe('Rest DAO unit tests', function() {
}, '{"foo": "bar"}'); }, '{"foo": "bar"}');
}); });
it('should work with jsonz', function() { it('should work with jsonz', function(done) {
restDao.get({ restDao.get({
uri: '/asdf', uri: '/asdf',
type: 'json' type: 'json'
}, function(err, data, status) { }).then(function(data) {
expect(err).to.not.exist;
expect(data.foo).to.equal('bar'); expect(data.foo).to.equal('bar');
var req = requests[0]; var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/json'); expect(req.requestHeaders.Accept).to.equal('application/json');
expect(status).to.equal(200); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -66,16 +64,15 @@ describe('Rest DAO unit tests', function() {
}, '{"foo": "bar"}'); }, '{"foo": "bar"}');
}); });
it('should work with plain text', function() { it('should work with plain text', function(done) {
restDao.get({ restDao.get({
uri: '/asdf', uri: '/asdf',
type: 'text' type: 'text'
}, function(err, data, status) { }).then(function(data) {
expect(err).to.not.exist;
expect(data).to.equal('foobar!'); expect(data).to.equal('foobar!');
var req = requests[0]; var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('text/plain'); expect(req.requestHeaders.Accept).to.equal('text/plain');
expect(status).to.equal(200); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -84,16 +81,15 @@ describe('Rest DAO unit tests', function() {
}, 'foobar!'); }, 'foobar!');
}); });
it('should work with xml', function() { it('should work with xml', function(done) {
restDao.get({ restDao.get({
uri: '/asdf', uri: '/asdf',
type: 'xml' type: 'xml'
}, function(err, data, status) { }).then(function(data) {
expect(err).to.not.exist;
expect(data).to.equal('<foo>bar</foo>'); expect(data).to.equal('<foo>bar</foo>');
var req = requests[0]; var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/xml'); expect(req.requestHeaders.Accept).to.equal('application/xml');
expect(status).to.equal(200); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -102,32 +98,29 @@ describe('Rest DAO unit tests', function() {
}, '<foo>bar</foo>'); }, '<foo>bar</foo>');
}); });
it('should fail for missing uri parameter', function() { it('should fail for missing uri parameter', function(done) {
restDao.get({}, function(err, data) { restDao.get({}).catch(function(err) {
expect(err).to.exist;
expect(err.code).to.equal(400); expect(err.code).to.equal(400);
expect(data).to.not.exist; done();
}); });
}); });
it('should fail for unhandled data type', function() { it('should fail for unhandled data type', function(done) {
restDao.get({ restDao.get({
uri: '/asdf', uri: '/asdf',
type: 'snafu' type: 'snafu'
}, function(err, data) { }).catch(function(err) {
expect(err).to.exist;
expect(err.code).to.equal(400); expect(err.code).to.equal(400);
expect(data).to.not.exist; done();
}); });
}); });
it('should fail for server error', function() { it('should fail for server error', function(done) {
restDao.get({ restDao.get({
uri: '/asdf' uri: '/asdf'
}, function(err, data) { }).catch(function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500); expect(err.code).to.equal(500);
expect(data).to.not.exist; done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -138,10 +131,10 @@ describe('Rest DAO unit tests', function() {
}); });
describe('post', function() { describe('post', function() {
it('should fail', function() { it('should fail', function(done) {
restDao.post('/asdf', {}, function(err) { restDao.post('/asdf', {}).catch(function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500); expect(err.code).to.equal(500);
done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -150,11 +143,10 @@ describe('Rest DAO unit tests', function() {
}, 'Internal error'); }, 'Internal error');
}); });
it('should work', function() { it('should work', function(done) {
restDao.post('/asdf', {}, function(err, res, status) { restDao.post('/asdf', {}).then(function(res) {
expect(err).to.not.exist;
expect(res).to.equal(''); expect(res).to.equal('');
expect(status).to.equal(201); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -163,10 +155,10 @@ describe('Rest DAO unit tests', function() {
}); });
describe('put', function() { describe('put', function() {
it('should fail', function() { it('should fail', function(done) {
restDao.put('/asdf', {}, function(err) { restDao.put('/asdf', {}).catch(function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500); expect(err.code).to.equal(500);
done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -175,11 +167,10 @@ describe('Rest DAO unit tests', function() {
}, 'Internal error'); }, 'Internal error');
}); });
it('should work', function() { it('should work', function(done) {
restDao.put('/asdf', {}, function(err, res, status) { restDao.put('/asdf', {}).then(function(res) {
expect(err).to.not.exist;
expect(res).to.equal(''); expect(res).to.equal('');
expect(status).to.equal(201); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -188,10 +179,10 @@ describe('Rest DAO unit tests', function() {
}); });
describe('remove', function() { describe('remove', function() {
it('should fail', function() { it('should fail', function(done) {
restDao.remove('/asdf', function(err) { restDao.remove('/asdf').catch(function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500); expect(err.code).to.equal(500);
done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);
@ -200,11 +191,10 @@ describe('Rest DAO unit tests', function() {
}, 'Internal error'); }, 'Internal error');
}); });
it('should work', function() { it('should work', function(done) {
restDao.remove('/asdf', function(err, res, status) { restDao.remove('/asdf').then(function(res) {
expect(err).to.not.exist;
expect(res).to.equal(''); expect(res).to.equal('');
expect(status).to.equal(200); done();
}); });
expect(requests.length).to.equal(1); expect(requests.length).to.equal(1);

View File

@ -64,15 +64,14 @@ describe('Connection Doctor', function() {
describe('#_checkOnline', function() { describe('#_checkOnline', function() {
it('should check if browser is online', function(done) { it('should check if browser is online', function(done) {
doctor._checkOnline(function(error) { if (navigator.onLine) {
if (navigator.onLine) { doctor._checkOnline().then(done);
expect(error).to.not.exist; } else {
} else { doctor._checkOnline().catch(function(err) {
expect(error).to.exist; expect(err.code).to.equal(ConnectionDoctor.OFFLINE);
expect(error.code).to.equal(ConnectionDoctor.OFFLINE); done();
} });
done(); }
});
}); });
}); });
@ -80,8 +79,7 @@ describe('Connection Doctor', function() {
it('should be able to reach the host w/o cert', function(done) { it('should be able to reach the host w/o cert', function(done) {
credentials.imap.ca = undefined; credentials.imap.ca = undefined;
doctor._checkReachable(credentials.imap, function(error) { doctor._checkReachable(credentials.imap).then(function() {
expect(error).to.not.exist;
expect(TCPSocket.open.calledOnce).to.be.true; expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, { expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer', binaryType: 'arraybuffer',
@ -105,8 +103,7 @@ describe('Connection Doctor', function() {
} }
}); });
doctor._checkReachable(credentials.imap, function(error) { doctor._checkReachable(credentials.imap).then(function() {
expect(error).to.not.exist;
expect(TCPSocket.open.calledOnce).to.be.true; expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, { expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer', binaryType: 'arraybuffer',
@ -122,8 +119,7 @@ describe('Connection Doctor', function() {
}); });
it('should fail w/ wrong cert', function(done) { it('should fail w/ wrong cert', function(done) {
doctor._checkReachable(credentials.imap, function(error) { doctor._checkReachable(credentials.imap).catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.TLS_WRONG_CERT); expect(error.code).to.equal(ConnectionDoctor.TLS_WRONG_CERT);
expect(TCPSocket.open.calledOnce).to.be.true; expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, { expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
@ -142,8 +138,7 @@ describe('Connection Doctor', function() {
}); });
it('should fail w/ host unreachable', function(done) { it('should fail w/ host unreachable', function(done) {
doctor._checkReachable(credentials.imap, function(error) { doctor._checkReachable(credentials.imap).catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.HOST_UNREACHABLE); expect(error.code).to.equal(ConnectionDoctor.HOST_UNREACHABLE);
expect(TCPSocket.open.calledOnce).to.be.true; expect(TCPSocket.open.calledOnce).to.be.true;
@ -160,8 +155,7 @@ describe('Connection Doctor', function() {
var origTimeout = cfg.connDocTimeout; // remember timeout from the config to reset it on done var origTimeout = cfg.connDocTimeout; // remember timeout from the config to reset it on done
cfg.connDocTimeout = 20; // set to 20ms for the test cfg.connDocTimeout = 20; // set to 20ms for the test
doctor._checkReachable(credentials.imap, function(error) { doctor._checkReachable(credentials.imap).catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.HOST_TIMEOUT); expect(error.code).to.equal(ConnectionDoctor.HOST_TIMEOUT);
expect(TCPSocket.open.calledOnce).to.be.true; expect(TCPSocket.open.calledOnce).to.be.true;
cfg.connDocTimeout = origTimeout; cfg.connDocTimeout = origTimeout;
@ -179,8 +173,7 @@ describe('Connection Doctor', function() {
}); });
imapStub.logout.yieldsAsync(); imapStub.logout.yieldsAsync();
doctor._checkImap(function(error) { doctor._checkImap().then(function() {
expect(error).to.not.exist;
expect(imapStub.login.calledOnce).to.be.true; expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true; expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.calledOnce).to.be.true; expect(imapStub.logout.calledOnce).to.be.true;
@ -195,8 +188,7 @@ describe('Connection Doctor', function() {
Inbox: [{}] Inbox: [{}]
}); });
doctor._checkImap(function(error) { doctor._checkImap().catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR); expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist; expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true; expect(imapStub.login.calledOnce).to.be.true;
@ -218,8 +210,7 @@ describe('Connection Doctor', function() {
Inbox: [] Inbox: []
}); });
doctor._checkImap(function(error) { doctor._checkImap().catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.NO_INBOX); expect(error.code).to.equal(ConnectionDoctor.NO_INBOX);
expect(imapStub.login.calledOnce).to.be.true; expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true; expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
@ -233,8 +224,7 @@ describe('Connection Doctor', function() {
imapStub.login.yieldsAsync(); imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(new Error()); imapStub.listWellKnownFolders.yieldsAsync(new Error());
doctor._checkImap(function(error) { doctor._checkImap().catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR); expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist; expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true; expect(imapStub.login.calledOnce).to.be.true;
@ -246,8 +236,7 @@ describe('Connection Doctor', function() {
}); });
it('should fail w/ auth rejected', function(done) { it('should fail w/ auth rejected', function(done) {
doctor._checkImap(function(error) { doctor._checkImap().catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED); expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist; expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true; expect(imapStub.login.calledOnce).to.be.true;
@ -266,8 +255,7 @@ describe('Connection Doctor', function() {
describe('#_checkSmtp', function() { describe('#_checkSmtp', function() {
it('should perform SMTP login, logout', function(done) { it('should perform SMTP login, logout', function(done) {
doctor._checkSmtp(function(error) { doctor._checkSmtp().then(function() {
expect(error).to.not.exist;
expect(smtpStub.connect.calledOnce).to.be.true; expect(smtpStub.connect.calledOnce).to.be.true;
expect(smtpStub.quit.calledOnce).to.be.true; expect(smtpStub.quit.calledOnce).to.be.true;
@ -279,8 +267,7 @@ describe('Connection Doctor', function() {
}); });
it('should fail w/ auth rejected', function(done) { it('should fail w/ auth rejected', function(done) {
doctor._checkSmtp(function(error) { doctor._checkSmtp().catch(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED); expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist; expect(error.underlyingError).to.exist;
expect(smtpStub.connect.calledOnce).to.be.true; expect(smtpStub.connect.calledOnce).to.be.true;
@ -302,14 +289,13 @@ describe('Connection Doctor', function() {
}); });
it('should perform all tests', function(done) { it('should perform all tests', function(done) {
doctor._checkOnline.yieldsAsync(); doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(); doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(); doctor._checkReachable.withArgs(credentials.smtp).returns(resolves());
doctor._checkImap.yieldsAsync(); doctor._checkImap.returns(resolves());
doctor._checkSmtp.yieldsAsync(); doctor._checkSmtp.returns(resolves());
doctor.check(function(err) { doctor.check().then(function() {
expect(err).to.not.exist;
expect(doctor._checkOnline.calledOnce).to.be.true; expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true; expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true; expect(doctor._checkImap.calledOnce).to.be.true;
@ -320,13 +306,13 @@ describe('Connection Doctor', function() {
}); });
it('should fail for smtp', function(done) { it('should fail for smtp', function(done) {
doctor._checkOnline.yieldsAsync(); doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(); doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(); doctor._checkReachable.withArgs(credentials.smtp).returns(resolves());
doctor._checkImap.yieldsAsync(); doctor._checkImap.returns(resolves());
doctor._checkSmtp.yieldsAsync(new Error()); doctor._checkSmtp.returns(rejects(new Error()));
doctor.check(function(err) { doctor.check().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true; expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true; expect(doctor._checkReachable.calledTwice).to.be.true;
@ -338,12 +324,12 @@ describe('Connection Doctor', function() {
}); });
it('should fail for imap', function(done) { it('should fail for imap', function(done) {
doctor._checkOnline.yieldsAsync(); doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(); doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(); doctor._checkReachable.withArgs(credentials.smtp).returns(resolves());
doctor._checkImap.yieldsAsync(new Error()); doctor._checkImap.returns(rejects(new Error()));
doctor.check(function(err) { doctor.check().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true; expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true; expect(doctor._checkReachable.calledTwice).to.be.true;
@ -355,11 +341,11 @@ describe('Connection Doctor', function() {
}); });
it('should fail for smtp reachability', function(done) { it('should fail for smtp reachability', function(done) {
doctor._checkOnline.yieldsAsync(); doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(); doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(new Error()); doctor._checkReachable.withArgs(credentials.smtp).returns(rejects(new Error()));
doctor.check(function(err) { doctor.check().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true; expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true; expect(doctor._checkReachable.calledTwice).to.be.true;
@ -371,10 +357,10 @@ describe('Connection Doctor', function() {
}); });
it('should fail for imap reachability', function(done) { it('should fail for imap reachability', function(done) {
doctor._checkOnline.yieldsAsync(); doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(new Error()); doctor._checkReachable.withArgs(credentials.imap).returns(rejects(new Error()));
doctor.check(function(err) { doctor.check().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true; expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledOnce).to.be.true; expect(doctor._checkReachable.calledOnce).to.be.true;
@ -386,9 +372,9 @@ describe('Connection Doctor', function() {
}); });
it('should fail for offline', function(done) { it('should fail for offline', function(done) {
doctor._checkOnline.yieldsAsync(new Error()); doctor._checkOnline.returns(rejects(new Error()));
doctor.check(function(err) { doctor.check().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true; expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.called).to.be.false; expect(doctor._checkReachable.called).to.be.false;
@ -402,7 +388,7 @@ describe('Connection Doctor', function() {
it('should fail w/o config', function(done) { it('should fail w/o config', function(done) {
doctor.credentials = doctor._imap = doctor._smtp = undefined; doctor.credentials = doctor._imap = doctor._smtp = undefined;
doctor.check(function(err) { doctor.check().catch(function(err) {
expect(err).to.exist; expect(err).to.exist;
expect(doctor._checkOnline.called).to.be.false; expect(doctor._checkOnline.called).to.be.false;
expect(doctor._checkReachable.called).to.be.false; expect(doctor._checkReachable.called).to.be.false;

View File

@ -40,10 +40,9 @@ describe('UpdateHandler', function() {
it('should not update when up to date', function(done) { it('should not update when up to date', function(done) {
cfg.dbVersion = 10; // app requires database version 10 cfg.dbVersion = 10; // app requires database version 10
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, ['10']); // database version is 10 appConfigStorageStub.listItems.withArgs(versionDbType).returns(resolves(['10'])); // database version is 10
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(appConfigStorageStub.listItems.calledOnce).to.be.true; expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
done(); done();
@ -55,7 +54,7 @@ describe('UpdateHandler', function() {
beforeEach(function() { beforeEach(function() {
updateCounter = 0; updateCounter = 0;
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, ['2']); // database version is 0 appConfigStorageStub.listItems.withArgs(versionDbType).returns(resolves(['2'])); // database version is 0
}); });
afterEach(function() { afterEach(function() {
@ -67,17 +66,16 @@ describe('UpdateHandler', function() {
cfg.dbVersion = 4; // app requires database version 4 cfg.dbVersion = 4; // app requires database version 4
// a simple dummy update to executed that only increments the update counter // a simple dummy update to executed that only increments the update counter
function dummyUpdate(options, callback) { function dummyUpdate() {
updateCounter++; updateCounter++;
callback(); return resolves();
} }
// inject the dummy updates instead of live ones // inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, dummyUpdate, dummyUpdate]; updateHandler._updateScripts = [dummyUpdate, dummyUpdate, dummyUpdate, dummyUpdate];
// execute test // execute test
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(updateCounter).to.equal(2); expect(updateCounter).to.equal(2);
done(); done();
@ -87,20 +85,20 @@ describe('UpdateHandler', function() {
it('should fail while updating to v3', function(done) { it('should fail while updating to v3', function(done) {
cfg.dbVersion = 4; // app requires database version 4 cfg.dbVersion = 4; // app requires database version 4
function dummyUpdate(options, callback) { function dummyUpdate() {
updateCounter++; updateCounter++;
callback(); return resolves();
} }
function failingUpdate(options, callback) { function failingUpdate() {
callback({}); return rejects({});
} }
// inject the dummy updates instead of live ones // inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, failingUpdate, dummyUpdate]; updateHandler._updateScripts = [dummyUpdate, dummyUpdate, failingUpdate, dummyUpdate];
// execute test // execute test
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(updateCounter).to.equal(0); expect(updateCounter).to.equal(0);
@ -115,7 +113,7 @@ describe('UpdateHandler', function() {
beforeEach(function() { beforeEach(function() {
cfg.dbVersion = 1; // app requires database version 1 cfg.dbVersion = 1; // app requires database version 1
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(); // database version is 0 appConfigStorageStub.listItems.withArgs(versionDbType).returns(resolves()); // database version is 0
}); });
afterEach(function() { afterEach(function() {
@ -125,11 +123,10 @@ describe('UpdateHandler', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync(); userStorageStub.removeList.withArgs(emailDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs([1], versionDbType).yieldsAsync(); appConfigStorageStub.storeList.withArgs([1], versionDbType).returns(resolves());
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -138,10 +135,10 @@ describe('UpdateHandler', function() {
}); });
it('should fail when persisting database version fails', function(done) { it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync(); userStorageStub.removeList.returns(resolves());
appConfigStorageStub.storeList.yieldsAsync(new Error()); appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -151,9 +148,9 @@ describe('UpdateHandler', function() {
}); });
it('should fail when wiping emails from database fails', function(done) { it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error()); userStorageStub.removeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false; expect(appConfigStorageStub.storeList.called).to.be.false;
@ -168,7 +165,7 @@ describe('UpdateHandler', function() {
beforeEach(function() { beforeEach(function() {
cfg.dbVersion = 2; // app requires database version 2 cfg.dbVersion = 2; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [1]); // database version is 0 appConfigStorageStub.listItems.withArgs(versionDbType).returns(resolves([1])); // database version is 0
}); });
afterEach(function() { afterEach(function() {
@ -178,11 +175,10 @@ describe('UpdateHandler', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync(); userStorageStub.removeList.withArgs(emailDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs([2], versionDbType).yieldsAsync(); appConfigStorageStub.storeList.withArgs([2], versionDbType).returns(resolves());
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -191,10 +187,10 @@ describe('UpdateHandler', function() {
}); });
it('should fail when persisting database version fails', function(done) { it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync(); userStorageStub.removeList.returns(resolves());
appConfigStorageStub.storeList.yieldsAsync(new Error()); appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -204,9 +200,9 @@ describe('UpdateHandler', function() {
}); });
it('should fail when wiping emails from database fails', function(done) { it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error()); userStorageStub.removeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false; expect(appConfigStorageStub.storeList.called).to.be.false;
@ -221,7 +217,7 @@ describe('UpdateHandler', function() {
beforeEach(function() { beforeEach(function() {
cfg.dbVersion = 3; // app requires database version 2 cfg.dbVersion = 3; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [2]); // database version is 0 appConfigStorageStub.listItems.withArgs(versionDbType).returns(resolves([2])); // database version is 0
}); });
afterEach(function() { afterEach(function() {
@ -231,11 +227,10 @@ describe('UpdateHandler', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync(); userStorageStub.removeList.withArgs(emailDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs([3], versionDbType).yieldsAsync(); appConfigStorageStub.storeList.withArgs([3], versionDbType).returns(resolves());
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -244,10 +239,10 @@ describe('UpdateHandler', function() {
}); });
it('should fail when persisting database version fails', function(done) { it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync(); userStorageStub.removeList.returns(resolves());
appConfigStorageStub.storeList.yieldsAsync(new Error()); appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -257,9 +252,9 @@ describe('UpdateHandler', function() {
}); });
it('should fail when wiping emails from database fails', function(done) { it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error()); userStorageStub.removeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true; expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false; expect(appConfigStorageStub.storeList.called).to.be.false;
@ -290,22 +285,21 @@ describe('UpdateHandler', function() {
beforeEach(function() { beforeEach(function() {
cfg.dbVersion = 4; // app requires database version 4 cfg.dbVersion = 4; // app requires database version 4
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [3]); // database version is 3 appConfigStorageStub.listItems.withArgs(versionDbType).returns(resolves([3])); // database version is 3
}); });
it('should add gmail as mail service provider with email address and no provider present in db', function(done) { it('should add gmail as mail service provider with email address and no provider present in db', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, [emailaddress]); appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).returns(resolves([emailaddress]));
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []); appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).returns(resolves([]));
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync(); appConfigStorageStub.storeList.withArgs([4], versionDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs(['gmail'], PROVIDER_DB_KEY).yieldsAsync(); appConfigStorageStub.storeList.withArgs(['gmail'], PROVIDER_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([emailaddress], USERNAME_DB_KEY).yieldsAsync(); appConfigStorageStub.storeList.withArgs([emailaddress], USERNAME_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync(); appConfigStorageStub.storeList.withArgs([imap], IMAP_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync(); appConfigStorageStub.storeList.withArgs([smtp], SMTP_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([''], REALNAME_DB_KEY).yieldsAsync(); appConfigStorageStub.storeList.withArgs([''], REALNAME_DB_KEY).returns(resolves());
authStub._loadCredentials.yieldsAsync(); authStub._loadCredentials.returns(resolves());
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(appConfigStorageStub.storeList.callCount).to.equal(6); expect(appConfigStorageStub.storeList.callCount).to.equal(6);
expect(appConfigStorageStub.listItems.calledThrice).to.be.true; expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
expect(authStub._loadCredentials.calledOnce).to.be.true; expect(authStub._loadCredentials.calledOnce).to.be.true;
@ -315,12 +309,11 @@ describe('UpdateHandler', function() {
}); });
it('should not add a provider when no email adress is in db', function(done) { it('should not add a provider when no email adress is in db', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, []); appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).returns(resolves([]));
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []); appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).returns(resolves([]));
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync(); appConfigStorageStub.storeList.withArgs([4], versionDbType).returns(resolves());
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.listItems.calledThrice).to.be.true; expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
@ -329,10 +322,10 @@ describe('UpdateHandler', function() {
}); });
it('should fail when appConfigStore write fails', function(done) { it('should fail when appConfigStore write fails', function(done) {
appConfigStorageStub.listItems.yieldsAsync(null, []); appConfigStorageStub.listItems.returns(resolves([]));
appConfigStorageStub.storeList.yieldsAsync(new Error()); appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledThrice).to.be.true; expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -342,10 +335,10 @@ describe('UpdateHandler', function() {
}); });
it('should fail when appConfigStore read fails', function(done) { it('should fail when appConfigStore read fails', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(new Error()); appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).returns(rejects(new Error()));
appConfigStorageStub.storeList.yieldsAsync(new Error()); appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledTwice).to.be.true; expect(appConfigStorageStub.listItems.calledTwice).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false; expect(appConfigStorageStub.storeList.called).to.be.false;
@ -368,7 +361,7 @@ describe('UpdateHandler', function() {
beforeEach(function() { beforeEach(function() {
cfg.dbVersion = 5; // app requires database version 4 cfg.dbVersion = 5; // app requires database version 4
appConfigStorageStub.listItems.withArgs(VERSION_DB_TYPE).yieldsAsync(null, [4]); // database version is 4 appConfigStorageStub.listItems.withArgs(VERSION_DB_TYPE).returns(resolves([4])); // database version is 4
}); });
afterEach(function() { afterEach(function() {
@ -378,7 +371,7 @@ describe('UpdateHandler', function() {
}); });
it('should work', function(done) { it('should work', function(done) {
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).yieldsAsync(null, [ userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).returns(resolves([
[{ [{
name: 'inbox1', name: 'inbox1',
type: FOLDER_TYPE_INBOX type: FOLDER_TYPE_INBOX
@ -404,7 +397,7 @@ describe('UpdateHandler', function() {
name: 'trash2', name: 'trash2',
type: FOLDER_TYPE_TRASH type: FOLDER_TYPE_TRASH
}] }]
]); ]));
userStorageStub.storeList.withArgs([ userStorageStub.storeList.withArgs([
[{ [{
@ -420,12 +413,11 @@ describe('UpdateHandler', function() {
name: 'trash1', name: 'trash1',
type: FOLDER_TYPE_TRASH type: FOLDER_TYPE_TRASH
}] }]
], FOLDER_DB_TYPE).yieldsAsync(); ], FOLDER_DB_TYPE).returns(resolves());
appConfigStorageStub.storeList.withArgs([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE).yieldsAsync(); appConfigStorageStub.storeList.withArgs([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE).returns(resolves());
updateHandler.update(function(error) { updateHandler.update().then(function() {
expect(error).to.not.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true; expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true; expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true; expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
@ -435,11 +427,11 @@ describe('UpdateHandler', function() {
}); });
it('should fail when persisting database version fails', function(done) { it('should fail when persisting database version fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []); userStorageStub.listItems.returns(resolves([]));
userStorageStub.storeList.yieldsAsync(); userStorageStub.storeList.returns(resolves());
appConfigStorageStub.storeList.yieldsAsync(new Error()); appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true; expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true; expect(userStorageStub.storeList.calledOnce).to.be.true;
@ -450,10 +442,10 @@ describe('UpdateHandler', function() {
}); });
it('should fail when persisting folders fails', function(done) { it('should fail when persisting folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []); userStorageStub.listItems.returns(resolves([]));
userStorageStub.storeList.yieldsAsync(new Error()); userStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true; expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true; expect(userStorageStub.storeList.calledOnce).to.be.true;
@ -464,9 +456,9 @@ describe('UpdateHandler', function() {
}); });
it('should fail when listing folders fails', function(done) { it('should fail when listing folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(new Error()); userStorageStub.listItems.returns(rejects(new Error()));
updateHandler.update(function(error) { updateHandler.update().catch(function(error) {
expect(error).to.exist; expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true; expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.called).to.be.false; expect(userStorageStub.storeList.called).to.be.false;