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

@ -21,6 +21,8 @@
"$",
"inject",
"Promise",
"resolves",
"rejects",
"self",
"importScripts",
"console",
@ -46,6 +48,5 @@
"openpgp"
],
"globals": {
}
"globals": {}
}

View File

@ -1,6 +1,6 @@
'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;
if (!userId) {
return;
@ -34,12 +34,13 @@ var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dia
//
$scope.exportKeyFile = function() {
keychain.getUserKeyPair(userId, function(err, keys) {
if (err) {
dialog.error(err);
return;
}
return $q(function(resolve) {
resolve();
}).then(function() {
return keychain.getUserKeyPair(userId);
}).then(function(keys) {
var keyId = keys.publicKey._id;
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',
contentType: 'text/plain'
});
});
}).catch(dialog.error);
};
};

View File

@ -2,7 +2,7 @@
var JUNK_FOLDER_TYPE = 'Junk';
var ActionBarCtrl = function($scope, email, dialog, status) {
var ActionBarCtrl = function($scope, $q, email, dialog, status) {
//
// scope functions
@ -23,12 +23,20 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
// show message
status.update('Moving message...');
email.moveMessage({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.moveMessage({
folder: currentFolder(),
destination: destination,
message: message
}, function(err) {
if (err) {
});
}).then(function() {
status.update('Online');
}).catch(function(err) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
@ -36,11 +44,7 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
return;
}
status.update('Error during move!');
dialog.error(err);
return;
}
status.update('Message moved.');
$scope.$apply();
return dialog.error(err);
});
};
@ -83,11 +87,19 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
status.setReading(false);
status.update('Deleting message...');
email.deleteMessage({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.deleteMessage({
folder: currentFolder(),
message: message
}, function(err) {
if (err) {
});
}).then(function() {
status.update('Online');
}).catch(function(err) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
@ -95,11 +107,7 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
return;
}
status.update('Error during delete!');
dialog.error(err);
return;
}
status.update('Message deleted.');
$scope.$apply();
return dialog.error(err);
});
};
@ -130,25 +138,29 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
var originalState = message.unread;
message.unread = unread;
email.setFlags({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.setFlags({
folder: currentFolder(),
message: message
}, function(err) {
if (err && err.code === 42) {
});
}).then(function() {
status.update('Online');
}).catch(function(err) {
if (err.code === 42) {
// offline, restore
message.unread = originalState;
status.update('Unable to mark message in offline mode!');
return;
}
if (err) {
status.update('Error on sync!');
dialog.error(err);
return;
}
status.update('Online');
$scope.$apply();
return dialog.error(err);
});
};
@ -176,25 +188,29 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
var originalState = message.flagged;
message.flagged = flagged;
email.setFlags({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.setFlags({
folder: currentFolder(),
message: message
}, function(err) {
if (err && err.code === 42) {
});
}).then(function() {
status.update('Online');
}).catch(function(err) {
if (err.code === 42) {
// offline, restore
message.unread = originalState;
status.update('Unable to ' + (flagged ? 'add star to' : 'remove star from') + ' message in offline mode!');
return;
}
if (err) {
status.update('Error on sync!');
dialog.error(err);
return;
}
status.update('Online');
$scope.$apply();
return dialog.error(err);
});
};
@ -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
$scope.state.actionBar = {
markMessage: $scope.markMessage,
flagMessage: $scope.flagMessage
};
//
// Helper functions
//
function currentFolder() {
return $scope.state.nav.currentFolder;
}
@ -223,13 +254,6 @@ var ActionBarCtrl = function($scope, email, dialog, status) {
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;

View File

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

View File

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

View File

@ -11,7 +11,7 @@ var INIT_DISPLAY_LEN = 50,
FOLDER_TYPE_INBOX = 'Inbox',
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
@ -55,23 +55,26 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
//
$scope.getBody = function(message) {
email.getBody({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.getBody({
folder: currentFolder(),
message: message
}, function(err) {
if (err && err.code !== 42) {
dialog.error(err);
return;
}
// display fetched body
$scope.$digest();
});
}).then(function() {
// automatically decrypt if it's the selected message
if (message === currentMessage()) {
email.decryptBody({
return email.decryptBody({
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;
}
keychain.refreshKeyForUserId({
return $q(function(resolve) {
resolve();
}).then(function() {
return keychain.refreshKeyForUserId({
userId: message.from[0].address
}, onKeyRefreshed);
});
function onKeyRefreshed(err) {
if (err) {
dialog.error(err);
}
email.decryptBody({
}).then(function() {
return email.decryptBody({
message: message
}, dialog.error);
});
}).then(function() {
// if the message is unread, please sync the new state.
// otherweise forget about it.
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.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
openCurrentFolder();
return openCurrentFolder();
});
$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) {
// wait one cycle for the status display controllers to init
$timeout(function() {
return $timeout(function() {
if (isOnline) {
status.update('Online');
openCurrentFolder();
return openCurrentFolder();
} else {
status.update('Offline mode');
}
@ -265,18 +270,24 @@ var MailListCtrl = function($scope, $timeout, $location, $filter, status, notifi
return;
}
email.openFolder({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.openFolder({
folder: currentFolder()
}, function(error) {
}).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
$scope.loadVisibleBodies();
// don't display error for offline case
if (error && error.code === 42) {
return;
}
dialog.error(error);
});
}).catch(dialog.error);
}
function currentFolder() {

View File

@ -11,7 +11,7 @@ var NOTIFICATION_SENT_TIMEOUT = 2000;
// 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()) {
$location.path('/'); // init app
return;
@ -102,18 +102,24 @@ var NavigationCtrl = function($scope, $location, account, email, outbox, notific
});
ob.count = count;
email.refreshFolder({
return $q(function(resolve) {
resolve();
}).then(function() {
return email.refreshFolder({
folder: ob
}, dialog.error);
});
}).catch(dialog.error);
};
$scope.logout = function() {
dialog.confirm({
return dialog.confirm({
title: str.logoutTitle,
message: str.logoutMessage,
callback: function(confirm) {
if (confirm) {
account.logout();
account.logout().catch(dialog.error);
}
}
});

View File

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

View File

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

View File

@ -1,6 +1,6 @@
'use strict';
var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
var SetPassphraseCtrl = function($scope, $q, pgp, keychain, dialog) {
//
// scope variables
@ -76,27 +76,25 @@ var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
$scope.setPassphrase = function() {
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,
oldPassphrase: $scope.oldPassphrase,
newPassphrase: $scope.newPassphrase
}, onPassphraseChanged);
});
};
function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) {
}).catch(function(err) {
err.showBugReporter = false;
dialog.error(err);
return;
}
throw err;
});
}).then(function(newPrivateKeyArmored) {
// persist new armored key
var keyParams = pgp.getKeyParams(newPrivateKeyArmored);
var privateKey = {
@ -105,23 +103,17 @@ var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
userIds: keyParams.userIds,
encryptedKey: newPrivateKeyArmored
};
return keychain.saveLocalPrivateKey(privateKey);
keychain.saveLocalPrivateKey(privateKey, onKeyPersisted);
}
function onKeyPersisted(err) {
if (err) {
dialog.error(err);
return;
}
}).then(function() {
$scope.state.setPassphrase.toggle(false);
$scope.$apply();
dialog.info({
return dialog.info({
title: 'Success',
message: 'Passphrase change complete.'
});
}
}).catch(dialog.error);
};
};
module.exports = SetPassphraseCtrl;

View File

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

View File

@ -1,6 +1,6 @@
'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
$scope.getAccountSettings = function() {
@ -9,10 +9,15 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
return;
}
return $q(function(resolve) {
$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.state.login = {
mailConfig: config,
@ -22,10 +27,10 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
var hostname = config.imap.hostname;
if (auth.useOAuth(hostname)) {
// check for oauth support
$scope.oauthPossible();
return $scope.oauthPossible();
} else {
// use standard password login
$scope.setCredentials();
return $scope.setCredentials();
}
}).catch(function() {
@ -36,7 +41,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
$scope.oauthPossible = function() {
// ask user to use the platform's native OAuth api
dialog.confirm({
return dialog.confirm({
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?',
positiveBtnStr: 'Google sign in',
@ -46,29 +51,28 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth,
callback: function(granted) {
if (granted) {
// query oauth token
getOAuthToken();
return getOAuthToken();
} else {
// use normal user/password login
$scope.setCredentials();
$scope.$apply();
}
}
});
function getOAuthToken() {
// fetches the email address from the chrome identity api
auth.getOAuthToken(function(err) {
if (err) {
return dialog.error(err);
}
$scope.setCredentials();
$scope.$apply();
});
return auth.getOAuthToken().then(function() {
// continue to setting credentials
return $scope.setCredentials();
}).catch(dialog.error);
}
};
$scope.setCredentials = function() {
return $timeout(function() {
$location.path('/login-set-credentials');
});
};
};

View File

@ -1,6 +1,6 @@
'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
$scope.createWhiteoutAccount = function() {
@ -9,10 +9,14 @@ var CreateAccountCtrl = function($scope, $location, $routeParams, auth, admin, a
return;
}
$scope.busy = true;
$scope.errMsg = undefined; // reset error msg
var emailAddress = $scope.user + '@' + appConfig.config.wmailDomain;
return $q(function(resolve) {
$scope.busy = true;
$scope.errMsg = undefined; // reset error msg
resolve();
}).then(function() {
// set to state for next view
auth.setCredentials({
emailAddress: emailAddress,
@ -21,23 +25,21 @@ var CreateAccountCtrl = function($scope, $location, $routeParams, auth, admin, a
});
// call REST api
admin.createUser({
return admin.createUser({
emailAddress: emailAddress,
password: $scope.pass,
phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number
betaCode: $scope.betaCode.toUpperCase()
}, function(err) {
});
}).then(function() {
$scope.busy = false;
if (err) {
$scope.errMsg = err.errMsg || err.message;
$scope.$apply();
return;
}
// proceed to login and keygen
$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';
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
$scope.confirmPassphrase = function() {
@ -9,50 +9,39 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, k
return;
}
return $q(function(resolve) {
$scope.busy = true;
$scope.errMsg = undefined;
$scope.incorrect = false;
resolve();
unlockCrypto();
};
function unlockCrypto() {
}).then(function() {
// key keypair
var userId = auth.emailAddress;
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
displayError(err);
return;
}
return keychain.getUserKeyPair(userId);
email.unlock({
}).then(function(keypair) {
// unlock email service
return email.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, onUnlock);
});
}
function onUnlock(err) {
if (err) {
displayError(err);
return;
}
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;
}
}).then(function() {
// persist credentials locally
return auth.storeCredentials();
}).then(function() {
// go to main account screen
$location.path('/account');
$scope.$apply();
});
}
}).catch(displayError);
};
function displayError(err) {
$scope.busy = false;
$scope.incorrect = true;
$scope.errMsg = err.errMsg || err.message;
$scope.$apply();
}
};

View File

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

View File

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

View File

@ -1,6 +1,6 @@
'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
$scope.step = 1;
@ -15,42 +15,32 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth
return;
}
var userId = auth.emailAddress;
return $q(function(resolve) {
$scope.busy = true;
$scope.errMsg = undefined;
resolve();
$scope.verifyRecoveryToken(function() {
$scope.busy = false;
$scope.errMsg = undefined;
$scope.step++;
$scope.$apply();
});
};
$scope.verifyRecoveryToken = function(callback) {
var userId = auth.emailAddress;
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
displayError(err);
return;
}
}).then(function() {
// get public key id for reference
return keychain.getUserKeyPair(userId);
}).then(function(keypair) {
// remember for storage later
$scope.cachedKeypair = keypair;
keychain.downloadPrivateKey({
return keychain.downloadPrivateKey({
userId: userId,
keyId: keypair.publicKey._id,
recoveryToken: $scope.recoveryToken.toUpperCase()
}, function(err, encryptedPrivateKey) {
if (err) {
displayError(err);
return;
}
});
}).then(function(encryptedPrivateKey) {
$scope.encryptedPrivateKey = encryptedPrivateKey;
callback();
});
});
$scope.busy = false;
$scope.step++;
}).catch(displayError);
};
//
@ -63,47 +53,39 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth
return;
}
$scope.busy = true;
$scope.errMsg = undefined;
$scope.decryptAndStorePrivateKeyLocally();
};
$scope.decryptAndStorePrivateKeyLocally = function() {
var options = $scope.encryptedPrivateKey;
options.code = $scope.code.toUpperCase();
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
if (err) {
displayError(err);
return;
}
return $q(function(resolve) {
$scope.busy = true;
$scope.errMsg = undefined;
resolve();
}).then(function() {
return keychain.decryptAndStorePrivateKeyLocally(options);
}).then(function(privateKey) {
// add private key to cached keypair object
$scope.cachedKeypair.privateKey = privateKey;
// try empty passphrase
email.unlock({
return email.unlock({
keypair: $scope.cachedKeypair,
passphrase: undefined
}, function(err) {
if (err) {
// go to passphrase login screen
}).catch(function(err) {
// passphrase incorrct ... go to passphrase login screen
$scope.goTo('/login-existing');
return;
}
throw err;
});
// passphrase is corrent ... go to main app
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;
}
}).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) {
$location.path(location);
$scope.$apply();
};
function displayError(err) {
$scope.busy = false;
$scope.incorrect = true;
$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_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
//
@ -82,19 +82,23 @@ var SetCredentialsCtrl = function($scope, $location, $routeParams, auth, connect
connectionDoctor.configure(credentials);
// run connection doctor test suite
return $q(function(resolve) {
$scope.busy = true;
connectionDoctor.check(function(err) {
if (err) {
// display the error in the settings UI
$scope.connectionError = err;
} else {
resolve();
}).then(function() {
return connectionDoctor.check();
}).then(function() {
// persists the credentials and forwards to /login
auth.setCredentials(credentials);
$location.path('/login');
}
$scope.busy = false;
$scope.$apply();
$location.path('/login');
}).catch(function(err) {
// display the error in the settings UI
$scope.connectionError = err;
$scope.busy = false;
});
};
};

View File

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

View File

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

View File

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

View File

@ -17,30 +17,39 @@ function PGP() {
/**
* Generate a key pair for the user
* @return {Promise}
*/
PGP.prototype.generateKeys = function(options, callback) {
PGP.prototype.generateKeys = function(options) {
return new Promise(function(resolve) {
var userId, passphrase;
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
callback(new Error('Crypto init failed. Not all options set!'));
return;
throw new Error('Crypto init failed. Not all options set!');
}
// generate keypair
userId = 'Whiteout User <' + options.emailAddress + '>';
passphrase = (options.passphrase) ? options.passphrase : undefined;
openpgp.generateKeyPair({
keyType: 1, // (keytype 1=RSA)
numBits: options.keySize,
resolve({
userId: userId,
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) {
callback(null, {
return {
keyId: keys.key.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
};
});
}).catch(callback);
};
/**
@ -141,14 +150,16 @@ PGP.prototype.extractPublicKey = function(privateKeyArmored) {
/**
* Import the user's key pair
* @return {Promise}
*/
PGP.prototype.importKeys = function(options, callback) {
var pubKeyId, privKeyId, self = this;
PGP.prototype.importKeys = function(options) {
var self = this;
return new Promise(function(resolve) {
var pubKeyId, privKeyId;
// check options
if (!options.privateKeyArmored || !options.publicKeyArmored) {
callback(new Error('Importing keys failed. Not all options set!'));
return;
throw new Error('Importing keys failed. Not all options set!');
}
function resetKeys() {
@ -158,81 +169,80 @@ PGP.prototype.importKeys = function(options, callback) {
// read armored keys
try {
this._publicKey = openpgp.key.readArmored(options.publicKeyArmored).keys[0];
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
self._publicKey = openpgp.key.readArmored(options.publicKeyArmored).keys[0];
self._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
resetKeys();
callback(new Error('Importing keys failed. Parsing error!'));
return;
throw new Error('Importing keys failed. Parsing error!');
}
// decrypt private key with passphrase
if (!this._privateKey.decrypt(options.passphrase)) {
if (!self._privateKey.decrypt(options.passphrase)) {
resetKeys();
callback(new Error('Incorrect passphrase!'));
return;
throw new Error('Incorrect passphrase!');
}
// check if keys have the same id
pubKeyId = this._publicKey.primaryKey.getKeyId().toHex();
privKeyId = this._privateKey.primaryKey.getKeyId().toHex();
pubKeyId = self._publicKey.primaryKey.getKeyId().toHex();
privKeyId = self._privateKey.primaryKey.getKeyId().toHex();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
resetKeys();
callback(new Error('Key IDs dont match!'));
return;
throw new Error('Key IDs dont match!');
}
callback();
resolve();
});
};
/**
* Export the user's key pair
* @return {Promise}
*/
PGP.prototype.exportKeys = function(callback) {
if (!this._publicKey || !this._privateKey) {
callback(new Error('Could not export keys!'));
return;
PGP.prototype.exportKeys = function() {
var self = this;
return new Promise(function(resolve) {
if (!self._publicKey || !self._privateKey) {
throw new Error('Could not export keys!');
}
callback(null, {
keyId: this._publicKey.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: this._privateKey.armor(),
publicKeyArmored: this._publicKey.armor()
resolve({
keyId: self._publicKey.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: self._privateKey.armor(),
publicKeyArmored: self._publicKey.armor()
});
});
};
/**
* Change the passphrase of an ascii armored private key.
* @return {Promise}
*/
PGP.prototype.changePassphrase = function(options, callback) {
PGP.prototype.changePassphrase = function(options) {
return new Promise(function(resolve) {
var privKey, packets, newPassphrase, newKeyArmored;
// set undefined instead of empty string as passphrase
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
if (!options.privateKeyArmored) {
callback(new Error('Private key must be specified to change passphrase!'));
return;
throw new Error('Private key must be specified to change passphrase!');
}
if (options.oldPassphrase === newPassphrase ||
(!options.oldPassphrase && !newPassphrase)) {
callback(new Error('New and old passphrase are the same!'));
return;
throw new Error('New and old passphrase are the same!');
}
// read armored key
try {
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
callback(new Error('Importing key failed. Parsing error!'));
return;
throw new Error('Importing key failed. Parsing error!');
}
// decrypt private key with passphrase
if (!privKey.decrypt(options.oldPassphrase)) {
callback(new Error('Old passphrase incorrect!'));
return;
throw new Error('Old passphrase incorrect!');
}
// encrypt key with new passphrase
@ -243,31 +253,31 @@ PGP.prototype.changePassphrase = function(options, callback) {
}
newKeyArmored = privKey.armor();
} catch (e) {
callback(new Error('Setting new passphrase failed!'));
return;
throw new Error('Setting new passphrase failed!');
}
// check if new passphrase really works
if (!privKey.decrypt(newPassphrase)) {
callback(new Error('Decrypting key with new passphrase failed!'));
return;
throw new Error('Decrypting key with new passphrase failed!');
}
callback(null, newKeyArmored);
resolve(newKeyArmored);
});
};
/**
* 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 self = this;
return new Promise(function(resolve) {
var publicKeys;
// check keys
if (!this._privateKey) {
callback(new Error('Error encrypting. Keys must be set!'));
return;
if (!self._privateKey) {
throw new Error('Error encrypting. Keys must be set!');
}
// parse armored public keys
try {
if (publicKeysArmored && publicKeysArmored.length) {
@ -277,17 +287,19 @@ PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) {
});
}
} catch (err) {
callback(new Error('Error encrypting plaintext!'));
return;
throw new Error('Error encrypting plaintext!');
}
resolve(publicKeys);
}).then(function(publicKeys) {
if (publicKeys) {
// encrypt and sign the plaintext
openpgp.signAndEncryptMessage(publicKeys, this._privateKey, plaintext).then(callback.bind(null, null)).catch(callback);
return openpgp.signAndEncryptMessage(publicKeys, self._privateKey, plaintext);
} else {
// if no public keys are available encrypt for myself
openpgp.signAndEncryptMessage([this._publicKey], this._privateKey, plaintext).then(callback.bind(null, null)).catch(callback);
return openpgp.signAndEncryptMessage([self._publicKey], self._privateKey, plaintext);
}
});
};
/**
@ -295,16 +307,17 @@ PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) {
* @param {String} ciphertext The encrypted PGP message block
* @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.
* @return {Promise}
*/
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored) {
var self = this;
return new Promise(function(resolve) {
var publicKeys, message;
// check keys
if (!this._privateKey) {
callback(new Error('Error decrypting. Keys must be set!'));
return;
if (!self._privateKey) {
throw new Error('Error decrypting. Keys must be set!');
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
@ -312,19 +325,27 @@ PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
publicKeys = [self._publicKey];
}
message = openpgp.message.readArmored(ciphertext);
} catch (err) {
callback(new Error('Error parsing encrypted PGP message!'));
return;
throw new Error('Error parsing encrypted PGP message!');
}
resolve({
publicKeys: publicKeys,
message: message
});
}).then(function(res) {
// decrypt and verify pgp message
openpgp.decryptAndVerifyMessage(this._privateKey, publicKeys, message).then(function(decrypted) {
return openpgp.decryptAndVerifyMessage(self._privateKey, res.publicKeys, res.message);
}).then(function(decrypted) {
// return decrypted plaintext
callback(null, decrypted.text, checkSignatureValidity(decrypted.signatures));
}).catch(callback);
return {
decrypted: decrypted.text,
signaturesValid: checkSignatureValidity(decrypted.signatures)
};
});
};
/**
@ -332,17 +353,17 @@ PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
* @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 {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) {
var publicKeys,
message;
PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmored) {
var self = this;
return new Promise(function(resolve) {
var publicKeys, message;
// check keys
if (!this._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!'));
return;
if (!self._privateKey) {
throw new Error('Error verifying signed PGP message. Keys must be set!');
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
@ -350,17 +371,22 @@ PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmo
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
publicKeys = [self._publicKey];
}
message = openpgp.cleartext.readArmored(clearSignedText);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
throw new Error('Error verifying signed PGP message!');
}
resolve({
publicKeys: publicKeys,
message: message
});
openpgp.verifyClearSignedMessage(publicKeys, message).then(function(result) {
callback(null, checkSignatureValidity(result.signatures));
}).catch(callback);
}).then(function(res) {
return openpgp.verifyClearSignedMessage(res.publicKeys, res.message);
}).then(function(result) {
return checkSignatureValidity(result.signatures);
});
};
/**
@ -369,16 +395,17 @@ PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmo
* @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 {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) {
var publicKeys;
PGP.prototype.verifySignedMessage = function(message, pgpSignature, publicKeyArmored) {
var self = this;
return new Promise(function(resolve) {
var publicKeys, signatures;
// check keys
if (!this._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!'));
return;
if (!self._privateKey) {
throw new Error('Error verifying signed PGP message. Keys must be set!');
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
@ -386,23 +413,21 @@ PGP.prototype.verifySignedMessage = function(message, pgpSignature, publicKeyArm
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
publicKeys = [self._publicKey];
}
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
throw new Error('Error verifying signed PGP message!');
}
var signatures;
// check signatures
try {
var msg = openpgp.message.readSignedContent(message, pgpSignature);
signatures = msg.verify(publicKeys);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
throw new Error('Error verifying signed PGP message!');
}
callback(null, checkSignatureValidity(signatures));
resolve(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
*/
Account.prototype.init = function(options, callback) {
Account.prototype.init = function(options) {
var self = this;
// 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
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
function prepareDatabase() {
self._accountStore.init(options.emailAddress, function(err) {
if (err) {
return callback(err);
}
return self._accountStore.init(options.emailAddress).then(function() {
// Migrate the databases if necessary
self._updateHandler.update(function(err) {
if (err) {
return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message));
}
prepareKeys();
return self._updateHandler.update().catch(function(err) {
throw new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message);
});
});
}
}).then(function() {
// 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);
}
return self._keychain.getUserKeyPair(options.emailAddress);
}).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) {
self._keychain.refreshKeyForUserId({
return self._keychain.refreshKeyForUserId({
userId: options.emailAddress,
overridePermission: true
}, function(err, publicKey) {
if (err) {
return callback(err);
}
initEmailDao({
}).then(function(publicKey) {
return {
publicKey: publicKey
};
});
});
return;
}
// either signup was complete or no pubkey is available, so we're good here.
initEmailDao(keys);
});
}
return keys;
function initEmailDao(keys) {
self._emailDao.init({
}).then(function(keys) {
// init the email data access object
return self._emailDao.init({
account: account
}, function(err) {
if (err) {
return callback(err);
}
}).then(function() {
// Handle offline and online gracefully ... arm dom event
window.addEventListener('online', self.onConnect.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
self._accounts.push(account);
callback(null, keys);
return keys;
});
});
}
};
/**
@ -148,16 +123,8 @@ Account.prototype.onConnect = function(callback) {
return;
}
self._auth.getCredentials(function(err, credentials) {
if (err) {
callback(err);
return;
}
initClients(credentials);
});
function initClients(credentials) {
// init imap/smtp clients
self._auth.getCredentials().then(function(credentials) {
// add the maximum update batch size for imap folders to the imap configuration
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);
// connect to clients
self._emailDao.onConnect({
return self._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer,
ignoreUploadOnSent: self._emailDao.checkIgnoreUploadOnSent(credentials.imap.host)
}, callback);
}
});
}).then(callback).catch(callback);
function onConnectionError(error) {
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.
*/
Account.prototype.onDisconnect = function() {
this._emailDao.onDisconnect();
return this._emailDao.onDisconnect();
};
/**
@ -211,21 +178,12 @@ Account.prototype.onDisconnect = function() {
*/
Account.prototype.logout = function() {
var self = this;
// clear app config store
self._auth.logout(function(err) {
if (err) {
self._dialog.error(err);
return;
}
return self._auth.logout().then(function() {
// delete instance of imap-client and pgp-mailer
self._emailDao.onDisconnect(function(err) {
if (err) {
self._dialog.error(err);
return;
}
return self._emailDao.onDisconnect();
}).then(function() {
if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) {
// reload chrome app
chrome.runtime.reload();
@ -234,5 +192,4 @@ Account.prototype.logout = function() {
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
* @param {Object} mail The Email DTO
* @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,
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
if (mail.bcc.length > 0) {
storeAndForward(mail);
return;
return storeAndForward(mail);
}
checkRecipients(allReaders);
return checkRecipients(allReaders).then(checkEncrypt);
// check if there are unregistered recipients
function checkRecipients(recipients) {
var after = _.after(recipients.length, function() {
checkEncrypt();
});
// find out if there are unregistered users
var pubkeyJobs = [];
recipients.forEach(function(recipient) {
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
if (err) {
callback(err);
return;
}
var promise = self._keychain.getReceiverPublicKey(recipient.address).then(function(key) {
// 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
if (key) {
mail.publicKeysArmored.push(key.publicKey);
}
});
pubkeyJobs.push(promise);
});
after();
});
});
return Promise.all(pubkeyJobs);
}
function checkEncrypt() {
// only encrypt if all recipients have public keys
if (mail.publicKeysArmored.length < allReaders.length) {
storeAndForward(mail);
return;
return storeAndForward(mail);
}
// encrypts the body and attachments and persists the mail object
self._emailDao.encrypt({
return self._emailDao.encrypt({
mail: mail,
publicKeysArmored: mail.publicKeysArmored
}, function(err) {
if (err) {
callback(err);
return;
}
storeAndForward(mail);
}).then(function() {
return storeAndForward(mail);
});
}
function storeAndForward(mail) {
// store in outbox
self._devicestorage.storeList([mail], outboxDb, function(err) {
if (err) {
callback(err);
return;
}
callback();
return self._devicestorage.storeList([mail], outboxDb).then(function() {
// don't wait for next round
self._processOutbox(self._onUpdate);
});
@ -149,83 +129,63 @@ Outbox.prototype._processOutbox = function(callback) {
self._outboxBusy = true;
// get pending mails from the outbox
self._devicestorage.listItems(outboxDb, 0, null, function(err, pendingMails) {
// error, we're done here
if (err) {
self._outboxBusy = false;
callback(err);
self._devicestorage.listItems(outboxDb, 0, null).then(function(pendingMails) {
// if we're not online, don't even bother sending mails.
if (!self._emailDao._account.online || _.isEmpty(pendingMails)) {
unsentMails = pendingMails.length;
return;
}
// if we're not online, don't even bother sending mails.
if (!self._emailDao._account.online || _.isEmpty(pendingMails)) {
self._outboxBusy = false;
callback(null, pendingMails.length);
return;
}
var sendJobs = [];
// send pending mails if possible
pendingMails.forEach(function(mail) {
sendJobs.push(send(mail));
});
// we're done after all the mails have been handled
// update the outbox count...
var after = _.after(pendingMails.length, function() {
return Promise.all(sendJobs);
}).then(function() {
self._outboxBusy = false;
callback(null, unsentMails);
});
// send pending mails if possible
pendingMails.forEach(function(mail) {
send(mail, after);
});
}).catch(function(err) {
self._outboxBusy = false;
callback(err);
});
// send the message
function send(mail, done) {
function send(mail) {
// check is email is to be sent encrypted or as plaintex
if (mail.encrypted === true) {
// 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
}, onSend);
}).then(onSend).catch(sendFailed);
} else {
// send email as plaintext
self._emailDao.sendPlaintext({
return self._emailDao.sendPlaintext({
email: mail
}, onSend);
}).then(onSend).catch(sendFailed);
}
function onSend(err) {
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);
function onSend() {
// fire sent notification
if (typeof self.onSent === 'function') {
self.onSent(mail);
}
}
}
// removes the mail object from disk after successfully sending it
function removeFromStorage(mail, done) {
self._devicestorage.removeList(outboxDb + '_' + mail.uid, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
return self._devicestorage.removeList(outboxDb + '_' + mail.uid);
}
done();
});
function sendFailed(err) {
if (err.code === 42) {
// offline. resolve promise and try again later
return;
}
throw err;
}
}
};

View File

@ -13,27 +13,24 @@ function Admin(adminRestDao) {
* @param {String} options.emailAddress The desired email address
* @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 {Function} callback(error)
*/
Admin.prototype.createUser = function(options, callback) {
var uri;
Admin.prototype.createUser = function(options) {
var self = this;
return new Promise(function(resolve) {
if (!options.emailAddress || !options.password || !options.phone) {
callback(new Error('Incomplete arguments!'));
return;
throw new Error('Incomplete arguments!');
}
resolve();
uri = '/user';
this._restDao.post(options, uri, function(err) {
}).then(function() {
return self._restDao.post(options, '/user');
}).catch(function(err) {
if (err && err.code === 409) {
callback(new Error('User name is already taken!'));
return;
} else if (err) {
callback(new Error('Error creating new user!'));
return;
throw new Error('User name is already taken!');
}
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.
* @param {String} options.emailAddress The desired email address
* @param {String} options.token The validation token.
* @param {Function} callback(error)
*/
Admin.prototype.validateUser = function(options, callback) {
var uri;
Admin.prototype.validateUser = function(options) {
var self = this;
return new Promise(function(resolve) {
if (!options.emailAddress || !options.token) {
callback(new Error('Incomplete arguments!'));
throw new Error('Incomplete arguments!');
}
resolve();
}).then(function() {
var uri = '/user/validate';
return self._restDao.post(options, uri);
}).catch(function(err) {
if (err && err.code === 202) {
// success
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!'));
}
throw new Error('Validation failed!');
});
};

View File

@ -30,17 +30,17 @@ function Auth(appConfigStore, oauth, pgp) {
this._appConfigStore = appConfigStore;
this._oauth = oauth;
this._pgp = pgp;
this._initialized = false;
}
/**
* Initialize the service
*/
Auth.prototype.init = function(callback) {
Auth.prototype.init = function() {
var self = this;
self._appConfigStore.init(APP_CONFIG_DB_NAME, function(error) {
self._initialized = !error;
callback(error);
return self._appConfigStore.init(APP_CONFIG_DB_NAME).then(function() {
self._initialized = true;
});
};
@ -57,58 +57,40 @@ Auth.prototype.isInitialized = function() {
* 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.
* 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;
if (!self.emailAddress) {
// we're not yet initialized, so let's load our stuff from disk
self._loadCredentials(function(err) {
if (err) {
return callback(err);
return self._loadCredentials().then(chooseLogin);
}
chooseLogin();
});
return;
}
chooseLogin();
return chooseLogin();
function chooseLogin() {
if (self.useOAuth(self.imap.host) && !self.password) {
// oauth login
self.getOAuthToken(function(err) {
if (err) {
return callback(err);
}
done();
});
return;
return self.getOAuthToken().then(done);
}
if (self.passwordNeedsDecryption) {
// decrypt password
self._pgp.decrypt(self.password, undefined, function(err, cleartext) {
if (err) {
return callback(err);
return self._pgp.decrypt(self.password, undefined).then(function(pt) {
if (!pt.signaturesValid) {
throw new Error('Verifying PGP signature of encrypted password failed!');
}
self.passwordNeedsDecryption = false;
self.password = cleartext;
done();
});
return;
self.password = pt.decrypted;
}).then(done);
}
done();
return done();
}
function done() {
return new Promise(function(resolve) {
var credentials = {
imap: {
secure: self.imap.secure,
@ -133,8 +115,8 @@ Auth.prototype.getCredentials = function(callback) {
}
}
};
callback(null, credentials);
resolve(credentials);
});
}
};
@ -158,100 +140,69 @@ Auth.prototype.setCredentials = function(options) {
this.imap = options.imap; // host, port, secure, ca
};
Auth.prototype.storeCredentials = function(callback) {
Auth.prototype.storeCredentials = function() {
var self = this;
if (!self.credentialsDirty) {
return callback();
// nothing to store if credentials not dirty
return new Promise(function(resolve) {
resolve();
});
}
// persist the config
self._appConfigStore.storeList([self.smtp], SMTP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.imap], IMAP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.emailAddress], EMAIL_ADDR_DB_KEY, function(err) {
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);
}
var storeSmtp = self._appConfigStore.storeList([self.smtp], SMTP_DB_KEY);
var storeImap = self._appConfigStore.storeList([self.imap], IMAP_DB_KEY);
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) {
self.credentialsDirty = false;
return callback();
resolve();
return;
}
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);
return self._appConfigStore.storeList([self.password], PASSWD_DB_KEY).then(resolve);
}
return self._pgp.encrypt(self.password, undefined).then(function(ciphertext) {
return self._appConfigStore.storeList([ciphertext], PASSWD_DB_KEY).then(resolve);
});
});
return Promise.all([
storeSmtp,
storeImap,
storeEmailAddress,
storeUsername,
storeRealname,
storePassword
]).then(function() {
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();
});
});
});
});
});
});
});
};
/**
* Returns the email address. Loads it from disk, if necessary
*/
Auth.prototype.getEmailAddress = function(callback) {
Auth.prototype.getEmailAddress = function() {
var self = this;
if (self.emailAddress) {
return callback(null, {
return new Promise(function(resolve) {
resolve({
emailAddress: self.emailAddress,
realname: self.realname
});
});
}
self._loadCredentials(function(err) {
if (err) {
return callback(err);
}
callback(null, {
return self._loadCredentials().then(function() {
return {
emailAddress: self.emailAddress,
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
* 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;
if (self.oauthToken) {
// removed cached token and get a new one
self._oauth.refreshToken({
return self._oauth.refreshToken({
emailAddress: self.emailAddress,
oldToken: self.oauthToken
}, onToken);
}).then(onToken);
} else {
// get a fresh oauth token
self._oauth.getOAuthToken(self.emailAddress, onToken);
}
function onToken(err, oauthToken) {
if (err) {
return callback(err);
return self._oauth.getOAuthToken(self.emailAddress).then(onToken);
}
function onToken(oauthToken) {
// shortcut if the email address is already known
if (self.emailAddress) {
self.oauthToken = oauthToken;
return callback();
return;
}
// query the email address
self._oauth.queryEmailAddress(oauthToken, function(err, emailAddress) {
if (err) {
return callback(err);
}
return self._oauth.queryEmailAddress(oauthToken).then(function(emailAddress) {
self.oauthToken = oauthToken;
self.emailAddress = emailAddress;
callback();
});
}
};
@ -327,67 +269,44 @@ Auth.prototype.getOAuthToken = function(callback) {
/**
* Loads email address, password, ... from disk and sets them on `this`
*/
Auth.prototype._loadCredentials = function(callback) {
Auth.prototype._loadCredentials = function() {
var self = this;
if (self.initialized) {
callback();
return new Promise(function(resolve) {
resolve();
});
}
loadFromDB(SMTP_DB_KEY, function(err, smtp) {
if (err) {
return callback(err);
}
return loadFromDB(SMTP_DB_KEY).then(function(smtp) {
self.smtp = smtp;
return loadFromDB(IMAP_DB_KEY);
}).then(function(imap) {
self.imap = imap;
return loadFromDB(USERNAME_DB_KEY);
loadFromDB(IMAP_DB_KEY, function(err, imap) {
if (err) {
return callback(err);
}
}).then(function(username) {
self.username = username;
return loadFromDB(REALNAME_DB_KEY);
}).then(function(realname) {
self.realname = realname;
return loadFromDB(EMAIL_ADDR_DB_KEY);
loadFromDB(USERNAME_DB_KEY, function(err, username) {
if (err) {
return callback(err);
}
loadFromDB(REALNAME_DB_KEY, function(err, realname) {
if (err) {
return callback(err);
}
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);
}
}).then(function(emailAddress) {
self.emailAddress = emailAddress;
return loadFromDB(PASSWD_DB_KEY);
}).then(function(password) {
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) {
self._appConfigStore.listItems(key, 0, null, function(err, cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0]));
function loadFromDB(key) {
return self._appConfigStore.listItems(key, 0, null).then(function(cachedItems) {
return cachedItems && cachedItems[0];
});
}
};
@ -407,7 +326,7 @@ Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback
// no previous ssl cert, trust on first use
self[component].ca = pemEncodedCert;
self.credentialsDirty = true;
self.storeCredentials(callback);
self.storeCredentials().then(callback).catch(callback);
return;
}
@ -431,14 +350,9 @@ Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback
self[component].ca = pemEncodedCert;
self.credentialsDirty = true;
self.storeCredentials(function(err) {
if (err) {
callback(err);
return;
}
self.storeCredentials().then(function() {
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
*/
Auth.prototype.logout = function(callback) {
Auth.prototype.logout = function() {
var self = this;
// clear app config db
self._appConfigStore.clear(function(err) {
if (err) {
callback(err);
return;
}
return self._appConfigStore.clear().then(function() {
// clear in memory cache
self.setCredentials({});
self.initialized = undefined;
self.credentialsDirty = undefined;
self.passwordNeedsDecryption = undefined;
callback();
});
};

View File

@ -28,30 +28,27 @@ function DeviceStorage(lawnchairDAO) {
/**
* Initialize the lawnchair database
* @param {String} dbName The name of the database
* @return {Promise}
*/
DeviceStorage.prototype.init = function(dbName, callback) {
this._lawnchairDAO.init(dbName, callback);
DeviceStorage.prototype.init = function(dbName) {
return this._lawnchairDAO.init(dbName);
};
/**
* Stores a list of encrypted items in the object store
* @param list [Array] The list of items to be persisted
* @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 self = this;
return new Promise(function(resolve) {
var key, items = [];
list = list || [];
// nothing to store
if (!list || list.length === 0) {
callback();
return;
}
// validate type
if (!type) {
callback({
errMsg: 'Type is not set!'
});
return;
throw new Error('Type is not set!');
}
// format items for batch storing in dao
@ -64,14 +61,24 @@ DeviceStorage.prototype.storeList = function(list, type, callback) {
});
});
this._lawnchairDAO.batch(items, callback);
resolve(items);
}).then(function(items) {
// nothing to store
if (items.length === 0) {
return;
}
return self._lawnchairDAO.batch(items);
});
};
/**
* Deletes items of a certain type from storage
* @return {Promise}
*/
DeviceStorage.prototype.removeList = function(type, callback) {
this._lawnchairDAO.removeList(type, callback);
DeviceStorage.prototype.removeList = function(type) {
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 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)
* @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
this._lawnchairDAO.list(type, offset, num, callback);
return this._lawnchairDAO.list(type, offset, num);
};
/**
* Clear the whole device data-store
* @return {Promise}
*/
DeviceStorage.prototype.clear = function(callback) {
this._lawnchairDAO.clear(callback);
DeviceStorage.prototype.clear = function() {
return this._lawnchairDAO.clear();
};
//

View File

@ -12,14 +12,6 @@ function Invitation(invitationRestDao) {
this._restDao = invitationRestDao;
}
//
// Constants
//
Invitation.INVITE_MISSING = 1;
Invitation.INVITE_PENDING = 2;
Invitation.INVITE_SUCCESS = 4;
//
// API
//
@ -28,35 +20,18 @@ Invitation.INVITE_SUCCESS = 4;
* 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.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) {
if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.recipient !== 'string') {
callback({
errMsg: 'erroneous usage of api: incorrect parameters!'
});
return;
Invitation.prototype.invite = function(options) {
var self = this;
return new Promise(function(resolve) {
if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.sender !== 'string') {
throw new Error('erroneous usage of api: incorrect parameters!');
}
resolve();
}).then(function() {
var uri = '/invitation/recipient/' + options.recipient + '/sender/' + options.sender;
this._restDao.put({}, uri, completed);
function completed(error, res, status) {
if (error) {
callback(error);
return;
}
if (status === 201) {
callback(null, Invitation.INVITE_SUCCESS);
return;
} else if (status === 304) {
callback(null, Invitation.INVITE_PENDING);
return;
}
callback({
errMsg: 'unexpected invitation state'
return self._restDao.put({}, uri);
});
}
};

File diff suppressed because it is too large Load Diff

View File

@ -13,88 +13,92 @@ function LawnchairDAO() {}
/**
* Initialize the lawnchair 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;
return new Promise(function(resolve, reject) {
if (!dbName) {
return callback(new Error('Lawnchair DB name must be specified!'));
throw new Error('Lawnchair DB name must be specified!');
}
self._db = new Lawnchair({
name: dbName
}, function(success) {
callback(success ? undefined : new Error('Lawnchair initialization ' + dbName + ' failed!'));
if (success) {
resolve();
} else {
reject(new Error('Lawnchair initialization ' + dbName + ' failed!'));
}
});
});
};
/**
* Create or update an object
* @return {Promise}
*/
LawnchairDAO.prototype.persist = function(key, object, callback) {
LawnchairDAO.prototype.persist = function(key, object) {
var self = this;
return new Promise(function(resolve, reject) {
if (!key || !object) {
callback({
errMsg: 'Key and Object must be set!'
});
return;
throw new Error('Key and Object must be set!');
}
this._db.save({
self._db.save({
key: key,
object: object
}, function(persisted) {
if (persisted.key !== key) {
callback({
errMsg: 'Persisting failed!'
});
reject(new Error('Persisting failed!'));
return;
}
callback();
resolve();
});
});
};
/**
* Persist a bunch of items at once
* @return {Promise}
*/
LawnchairDAO.prototype.batch = function(list, callback) {
LawnchairDAO.prototype.batch = function(list) {
var self = this;
return new Promise(function(resolve, reject) {
if (!(list instanceof Array)) {
callback({
errMsg: 'Input must be of type Array!'
});
return;
throw new Error('Input must be of type Array!');
}
this._db.batch(list, function(res) {
self._db.batch(list, function(res) {
if (!res) {
callback({
errMsg: 'Persisting batch failed!'
});
return;
reject(new Error('Persisting batch failed!'));
} else {
resolve();
}
callback();
});
});
};
/**
* Read a single item by its key
* @return {Promise}
*/
LawnchairDAO.prototype.read = function(key, callback) {
LawnchairDAO.prototype.read = function(key) {
var self = this;
return new Promise(function(resolve) {
if (!key) {
callback({
errMsg: 'Key must be specified!'
});
return;
throw new Error('Key must be specified!');
}
this._db.get(key, function(o) {
self._db.get(key, function(o) {
if (o) {
callback(null, o.object);
resolve(o.object);
} else {
callback();
resolve();
}
});
});
};
/**
@ -102,25 +106,23 @@ LawnchairDAO.prototype.read = function(key, callback) {
* @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 num [Number] The number of items to fetch (null means fetch all)
* @return {Promise}
*/
LawnchairDAO.prototype.list = function(type, offset, num, callback) {
var self = this,
i, from, to,
LawnchairDAO.prototype.list = function(type, offset, num) {
var self = this;
return new Promise(function(resolve) {
var i, from, to,
matchingKeys = [],
intervalKeys = [],
list = [];
// validate input
if (!type || typeof offset === 'undefined' || typeof num === 'undefined') {
callback({
errMsg: 'Args not is not set!'
});
return;
throw new Error('Args not is not set!');
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
@ -144,7 +146,7 @@ LawnchairDAO.prototype.list = function(type, offset, num, callback) {
// return if there are no matching keys
if (intervalKeys.length === 0) {
callback(null, list);
resolve(list);
return;
}
@ -155,33 +157,42 @@ LawnchairDAO.prototype.list = function(type, offset, num, callback) {
});
// return only the interval between offset and num
callback(null, list);
resolve(list);
});
});
});
};
/**
* Removes an object liter from local storage by its key (delete)
* @return {Promise}
*/
LawnchairDAO.prototype.remove = function(key, callback) {
this._db.remove(key, callback);
LawnchairDAO.prototype.remove = function(key) {
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)
* @return {Promise}
*/
LawnchairDAO.prototype.removeList = function(type, callback) {
var self = this,
matchingKeys = [],
LawnchairDAO.prototype.removeList = function(type) {
var self = this;
return new Promise(function(resolve) {
var matchingKeys = [],
after;
// validate type
if (!type) {
callback({
errMsg: 'Type is not set!'
});
return;
throw new Error('Type is not set!');
}
// get all keys
@ -194,21 +205,32 @@ LawnchairDAO.prototype.removeList = function(type, callback) {
});
if (matchingKeys.length < 1) {
callback();
resolve();
return;
}
// remove all matching keys
after = _.after(matchingKeys.length, callback);
after = _.after(matchingKeys.length, resolve);
_.each(matchingKeys, function(key) {
self._db.remove(key, after);
});
});
});
};
/**
* Clears the whole local storage cache
* @return {Promise}
*/
LawnchairDAO.prototype.clear = function(callback) {
this._db.nuke(callback);
LawnchairDAO.prototype.clear = function() {
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);
module.exports = Newsletter;
function Newsletter($q) {
this._q = $q;
}
function Newsletter() {}
/**
* Sign up to the whiteout newsletter
*/
Newsletter.prototype.signup = function(emailAddress, agree) {
return this._q(function(resolve, reject) {
return new Promise(function(resolve, reject) {
// validate email address
if (emailAddress.indexOf('@') < 0) {
reject(new Error('Invalid email address!'));

View File

@ -20,7 +20,8 @@ OAuth.prototype.isSupported = function() {
* Request an OAuth token from chrome for gmail users
* @param {String} emailAddress The user's email address (optional)
*/
OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
OAuth.prototype.getOAuthToken = function(emailAddress) {
return new Promise(function(resolve, reject) {
var idOptions = {
interactive: true
};
@ -28,7 +29,7 @@ OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
// check which runtime the app is running under
chrome.runtime.getPlatformInfo(function(platformInfo) {
if (chrome.runtime.lastError || !platformInfo) {
callback(new Error('Error getting chrome platform info!'));
reject(new Error('Error getting chrome platform info!'));
return;
}
@ -40,13 +41,12 @@ OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
// 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!'
});
reject(new Error('Error fetching an OAuth token for the user!'));
return;
}
callback(null, token);
resolve(token);
});
});
});
};
@ -56,12 +56,11 @@ OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
* @param {String} options.oldToken The old token to be removed
* @param {String} options.emailAddress The user's email address (optional)
*/
OAuth.prototype.refreshToken = function(options, callback) {
OAuth.prototype.refreshToken = function(options) {
var self = this;
return new Promise(function(resolve) {
if (!options.oldToken) {
callback(new Error('oldToken option not set!'));
return;
throw new Error('oldToken option not set!');
}
// remove cached token
@ -69,7 +68,8 @@ OAuth.prototype.refreshToken = function(options, callback) {
token: options.oldToken
}, function() {
// get a new token
self.getOAuthToken(options.emailAddress, callback);
self.getOAuthToken(options.emailAddress).then(resolve);
});
});
};
@ -77,25 +77,26 @@ OAuth.prototype.refreshToken = function(options, callback) {
* Get email address from google api
* @param {String} token The oauth token
*/
OAuth.prototype.queryEmailAddress = function(token, callback) {
OAuth.prototype.queryEmailAddress = function(token) {
var self = this;
return new Promise(function(resolve) {
if (!token) {
callback({
errMsg: 'Invalid OAuth token!'
});
return;
throw new Error('Invalid OAuth token!');
}
resolve();
}).then(function() {
// fetch gmail user's email address from the Google Authorization Server
this._googleApi.get({
return self._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;
}).then(function(info) {
if (!info || !info.email) {
throw new Error('Error looking up email address on google api!');
}
callback(null, info.email);
return info.email;
});
};

View File

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

View File

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

View File

@ -54,56 +54,57 @@ RestDAO.prototype.setBaseUri = function(baseUri) {
* @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.
*/
RestDAO.prototype.get = function(options, callback) {
RestDAO.prototype.get = function(options) {
options.method = 'GET';
this._processRequest(options, callback);
return this._processRequest(options);
};
/**
* POST (create) request
*/
RestDAO.prototype.post = function(item, uri, callback) {
this._processRequest({
RestDAO.prototype.post = function(item, uri) {
return this._processRequest({
method: 'POST',
payload: item,
uri: uri
}, callback);
});
};
/**
* PUT (update) request
*/
RestDAO.prototype.put = function(item, uri, callback) {
this._processRequest({
RestDAO.prototype.put = function(item, uri) {
return this._processRequest({
method: 'PUT',
payload: item,
uri: uri
}, callback);
});
};
/**
* DELETE (remove) request
*/
RestDAO.prototype.remove = function(uri, callback) {
this._processRequest({
RestDAO.prototype.remove = function(uri) {
return this._processRequest({
method: 'DELETE',
uri: uri
}, callback);
});
};
//
// helper functions
//
RestDAO.prototype._processRequest = function(options, callback) {
RestDAO.prototype._processRequest = function(options) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr, format;
if (typeof options.uri === 'undefined') {
callback({
throw {
code: 400,
errMsg: 'Bad Request! URI is a mandatory parameter.'
});
return;
message: 'Bad Request! URI is a mandatory parameter.'
};
}
options.type = options.type || 'json';
@ -115,15 +116,14 @@ RestDAO.prototype._processRequest = function(options, callback) {
} else if (options.type === 'text') {
format = 'text/plain';
} else {
callback({
throw {
code: 400,
errMsg: 'Bad Request! Unhandled data type.'
});
return;
message: 'Bad Request! Unhandled data type.'
};
}
xhr = new XMLHttpRequest();
xhr.open(options.method, this._baseUri + options.uri);
xhr.open(options.method, self._baseUri + options.uri);
xhr.setRequestHeader('Accept', format);
xhr.setRequestHeader('Content-Type', format);
@ -137,22 +137,23 @@ RestDAO.prototype._processRequest = function(options, callback) {
res = xhr.responseText;
}
callback(null, res, xhr.status);
resolve(res);
return;
}
callback({
reject({
code: xhr.status,
errMsg: xhr.statusText
message: xhr.statusText
});
};
xhr.onerror = function() {
callback({
reject({
code: 42,
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
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
* 4) Perform some basic commands (e.g. list folders)
* 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;
return new Promise(function(resolve) {
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);
}
self._checkReachable(self.credentials.imap, function(error) {
if (error) {
return callback(error);
}
self._checkReachable(self.credentials.smtp, function(error) {
if (error) {
return callback(error);
}
self._checkImap(function(error) {
if (error) {
return callback(error);
}
self._checkSmtp(callback);
});
});
});
}).then(function() {
return self._checkOnline();
}).then(function() {
return self._checkReachable(self.credentials.imap);
}).then(function() {
return self._checkReachable(self.credentials.smtp);
}).then(function() {
return self._checkImap();
}).then(function() {
return self._checkSmtp();
});
};
@ -126,15 +111,16 @@ ConnectionDoctor.prototype.check = function(callback) {
/**
* 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() {
var self = this;
return new Promise(function(resolve) {
if (navigator.onLine) {
callback();
resolve();
} else {
callback(createError(OFFLINE, this._appConfig.string.connDocOffline));
throw createError(OFFLINE, self._appConfig.string.connDocOffline);
}
});
};
/**
@ -143,27 +129,28 @@ ConnectionDoctor.prototype._checkOnline = function(callback) {
* @param {String} options.host
* @param {Number} options.port
* @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 self = this;
return new Promise(function(resolve, reject) {
var socket,
error, // remember the error message
timeout, // remember the timeout object
host = options.host + ':' + options.port,
hasTimedOut = false, // prevents multiple callbacks
cfg = this._appConfig.config,
str = this._appConfig.string;
cfg = self._appConfig.config,
str = self._appConfig.string;
timeout = setTimeout(function() {
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);
socket = TCPSocket.open(options.host, options.port, {
binaryType: 'arraybuffer',
useSecureTransport: options.secure,
ca: options.ca,
tlsWorkerPath: this._workerPath
tlsWorkerPath: self._workerPath
});
socket.ondata = function() {}; // we don't actually care about the data
@ -194,22 +181,26 @@ ConnectionDoctor.prototype._checkReachable = function(options, callback) {
socket.onclose = function() {
if (!hasTimedOut) {
clearTimeout(timeout);
callback(error);
if (error) {
reject(error);
} else {
resolve();
}
}
};
});
};
/**
* 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.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype._checkImap = function(callback) {
var self = this,
loggedIn = false,
ConnectionDoctor.prototype._checkImap = function() {
var self = this;
return new Promise(function(resolve, reject) {
var loggedIn = false,
host = self.credentials.imap.host + ':' + self.credentials.imap.port,
str = this._appConfig.string;
str = self._appConfig.string;
self._imap.onCert = function(pemEncodedCert) {
if (!self.credentials.imap.ca) {
@ -221,9 +212,9 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
// 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));
reject(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
} else {
callback(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
reject(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
}
};
@ -232,16 +223,19 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
self._imap.listWellKnownFolders(function(error, wellKnownFolders) {
if (error) {
return callback(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), 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
return callback(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host)));
reject(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host)));
return;
}
self._imap.logout(function() {
callback();
resolve();
});
});
});
});
@ -250,14 +244,13 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
/**
* Checks if an SMTP server is reachable and accepts the credentials and logs out.
* 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) {
var self = this,
host = self.credentials.smtp.host + ':' + self.credentials.smtp.port,
ConnectionDoctor.prototype._checkSmtp = function() {
var self = this;
return new Promise(function(resolve, reject) {
var host = self.credentials.smtp.host + ':' + self.credentials.smtp.port,
errored = false, // tracks if we need to invoke the callback at onclose or not
str = this._appConfig.string;
str = self._appConfig.string;
self._smtp.oncert = function(pemEncodedCert) {
if (!self.credentials.smtp.ca) {
@ -268,7 +261,7 @@ ConnectionDoctor.prototype._checkSmtp = function(callback) {
self._smtp.onerror = function(error) {
if (error) {
errored = true;
callback(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
reject(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
}
};
@ -278,11 +271,12 @@ ConnectionDoctor.prototype._checkSmtp = function(callback) {
self._smtp.onclose = function() {
if (!errored) {
callback();
resolve();
}
};
self._smtp.connect();
});
};

View File

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

View File

@ -25,43 +25,38 @@ function UpdateHandler(appConfigStore, accountStore, auth, dialog) {
/**
* 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,
currentVersion = 0,
targetVersion = cfg.dbVersion,
versionDbType = 'dbVersion';
self._appConfigStorage.listItems(versionDbType, 0, null, function(err, items) {
if (err) {
callback(err);
return;
}
return self._appConfigStorage.listItems(versionDbType, 0, null).then(function(items) {
// parse the database version number
if (items && items.length > 0) {
currentVersion = parseInt(items[0], 10);
}
self._applyUpdate({
return self._applyUpdate({
currentVersion: currentVersion,
targetVersion: targetVersion
}, callback);
});
});
};
/**
* Schedules necessary updates and executes thom in order
*/
UpdateHandler.prototype._applyUpdate = function(options, callback) {
var self = this,
scriptOptions,
UpdateHandler.prototype._applyUpdate = function(options) {
var self = this;
return new Promise(function(resolve, reject) {
var scriptOptions,
queue = [];
if (options.currentVersion >= options.targetVersion) {
// the current database version is up to date
callback();
resolve();
return;
}
@ -77,24 +72,20 @@ UpdateHandler.prototype._applyUpdate = function(options, callback) {
}
// takes the next update from the queue and executes it
function executeNextUpdate(err) {
if (err) {
callback(err);
return;
}
function executeNextUpdate() {
if (queue.length < 1) {
// we're done
callback();
resolve();
return;
}
// process next update
var script = queue.shift();
script(scriptOptions, executeNextUpdate);
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
* of the delta sync.
*/
function updateV1(options, callback) {
function updateV1(options) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 1;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
return options.userStorage.removeList(emailDbType).then(function() {
// 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
* new data model stores information about the email structure in the property 'bodyParts'.
*/
function updateV2(options, callback) {
function updateV2(options) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 2;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
return options.userStorage.removeList(emailDbType).then(function() {
// 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
* the outbox uses artificial uids
*/
function update(options, callback) {
function update(options) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 3;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
return options.userStorage.removeList(emailDbType).then(function() {
// 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,
* so let's add this...
*/
function update(options, callback) {
function update(options) {
var VERSION_DB_TYPE = 'dbVersion',
EMAIL_ADDR_DB_KEY = 'emailaddress',
USERNAME_DB_KEY = 'username',
@ -29,77 +29,45 @@ function update(options, callback) {
};
// load the email address (if existing)
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) {
if (err) {
return callback(err);
}
var emailAddress;
return loadFromDB(EMAIL_ADDR_DB_KEY).then(function(address) {
emailAddress = address;
// load the provider (if existing)
loadFromDB(PROVIDER_DB_KEY, function(err, provider) {
if (err) {
return callback(err);
}
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, 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);
}
var storeProvider = options.appConfigStorage.storeList(['gmail'], PROVIDER_DB_KEY);
// add the missing user name key
options.appConfigStorage.storeList([emailAddress], USERNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
var storeAdress = options.appConfigStorage.storeList([emailAddress], USERNAME_DB_KEY);
// add the missing imap host info key
options.appConfigStorage.storeList([imap], IMAP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
var storeImap = options.appConfigStorage.storeList([imap], IMAP_DB_KEY);
// add the missing empty real name
options.appConfigStorage.storeList([''], REALNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
var storeEmptyName = options.appConfigStorage.storeList([''], REALNAME_DB_KEY);
// add the missing smtp host info key
options.appConfigStorage.storeList([smtp], SMTP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
var storeSmtp = options.appConfigStorage.storeList([smtp], SMTP_DB_KEY);
return Promise.all([storeProvider, storeAdress, storeImap, storeEmptyName, storeSmtp]).then(function() {
// reload the credentials
options.auth.initialized = false;
options.auth._loadCredentials(function(err) {
if (err) {
return callback(err);
}
return options.auth._loadCredentials();
}).then(function() {
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
});
});
});
});
});
return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE);
});
});
function loadFromDB(key, callback) {
options.appConfigStorage.listItems(key, 0, null, function(err, cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0]));
function loadFromDB(key) {
return options.appConfigStorage.listItems(key, 0, null).then(function(cachedItems) {
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.
* This removes the "duplicate" folders.
*/
function update(options, callback) {
function update(options) {
// remove the emails
options.userStorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
if (err) {
return callback(err);
}
return options.userStorage.listItems(FOLDER_DB_TYPE, 0, null).then(function(stored) {
var folders = stored[0] || [];
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
var foldersForType = folders.filter(function(mbx) {
@ -39,15 +34,11 @@ function update(options, callback) {
folders.splice(folders.indexOf(foldersForType[i]), 1);
}
});
return options.userStorage.storeList([folders], FOLDER_DB_TYPE);
options.userStorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
if (err) {
return callback(err);
}
}).then(function() {
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE);
});
}

View File

@ -1,5 +1,5 @@
/**
* @license AngularJS v1.3.2
* @license AngularJS v1.3.7
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@ -19,7 +19,7 @@
* # Usage
*
* 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
* by using the `$animate` service.
*
@ -161,8 +161,8 @@
* ### Structural transition animations
*
* 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
* or .ng-move) class. This means that any active transition animations operating on the element
* 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
* will be cut off to make way for the enter, leave or move animation.
*
* ### Class-based transition animations
@ -245,7 +245,7 @@
* You then configure `$animate` to enforce this prefix:
*
* ```js
* $animateProvider.classNamePrefix(/animate-/);
* $animateProvider.classNameFilter(/animate-/);
* ```
* </div>
*
@ -479,11 +479,12 @@ angular.module('ngAnimate', ['ng'])
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
var $$jqLite;
$provide.decorator('$animate',
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
function($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, $$$jqLite) {
$$jqLite = $$$jqLite;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// Wait until all directive and route-related templates are downloaded and
@ -878,21 +879,21 @@ angular.module('ngAnimate', ['ng'])
* Below is a breakdown of each step that occurs during the `animate` animation:
*
* | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. $animate.animate(...) is called | class="my-animation" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 14. The returned promise is resolved. | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
*
* @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
@ -924,20 +925,20 @@ angular.module('ngAnimate', ['ng'])
* Below is a breakdown of each step that occurs during enter animation:
*
* | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. $animate.enter(...) is called | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. `$animate.enter(...)` is called | `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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
*
* @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
@ -970,18 +971,18 @@ angular.module('ngAnimate', ['ng'])
* Below is a breakdown of each step that occurs during leave animation:
*
* | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. $animate.leave(...) is called | class="my-animation" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. `$animate.leave(...)` is called | `class="my-animation"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 12. The element is removed from the DOM | ... |
* | 13. The returned promise is resolved. | ... |
*
@ -1015,20 +1016,20 @@ angular.module('ngAnimate', ['ng'])
* Below is a breakdown of each step that occurs during move animation:
*
* | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
* | 1. $animate.move(...) is called | 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" |
* | 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" |
* | 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 |
* | 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 |
* | 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" |
* | 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" |
* |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. `$animate.move(...)` is called | `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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
*
* @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
@ -1063,17 +1064,17 @@ angular.module('ngAnimate', ['ng'])
* Below is a breakdown of each step that occurs during addClass animation:
*
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
* | 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" |
* | 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" |
* | 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" |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation super super-add super-add-active" |
* | 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" |
* | 10. The returned promise is resolved. | class="my-animation super" |
* |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
* | 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"` |
* | 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"` |
* | 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 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"` |
* | 9. The super class is kept on the element | `class="my-animation super"` |
* | 10. The returned promise is resolved. | `class="my-animation super"` |
*
* @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
@ -1097,16 +1098,16 @@ angular.module('ngAnimate', ['ng'])
* Below is a breakdown of each step that occurs during removeClass animation:
*
* | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
* | 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" |
* | 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" |
* | 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" |
* | 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" |
* | 9. The returned promise is resolved. | class="my-animation" |
* |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
* | 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"` |
* | 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"` |
* | 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 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"` |
* | 9. The returned promise is resolved. | `class="my-animation"` |
*
*
* @param {DOMElement} element the element that will be animated
@ -1124,19 +1125,19 @@ angular.module('ngAnimate', ['ng'])
* @name $animate#setClass
*
* @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 |
* |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
* | 1. $animate.removeClass(element, on, off) is called | class="my-animation super off |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super 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 |
* | 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 |
* | 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" |
* | 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" |
* |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
* | 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 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"` |
* | 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"` |
* | 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"` |
* | 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"` |
*
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
@ -1274,7 +1275,7 @@ angular.module('ngAnimate', ['ng'])
all animations call this shared animation triggering function internally.
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
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.
*/
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
//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) {
forEach(options.tempClasses, function(className) {
element.addClass(className);
$$jqLite.addClass(element, className);
});
}
@ -1467,7 +1468,7 @@ angular.module('ngAnimate', ['ng'])
closeAnimation.hasBeenRun = true;
if (options && options.tempClasses) {
forEach(options.tempClasses, function(className) {
element.removeClass(className);
$$jqLite.removeClass(element, className);
});
}
@ -1529,7 +1530,7 @@ angular.module('ngAnimate', ['ng'])
}
if (removeAnimations || !data.totalActive) {
element.removeClass(NG_ANIMATE_CLASS_NAME);
$$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
}
}
@ -1770,14 +1771,14 @@ angular.module('ngAnimate', ['ng'])
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
var applyClasses = !lookupCache[staggerCacheKey];
applyClasses && element.addClass(staggerClassName);
applyClasses && $$jqLite.addClass(element, staggerClassName);
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 timings = getElementAnimationDetails(element, eventCacheKey);
@ -1785,7 +1786,7 @@ angular.module('ngAnimate', ['ng'])
var animationDuration = timings.animationDuration;
if (structural && transitionDuration === 0 && animationDuration === 0) {
element.removeClass(className);
$$jqLite.removeClass(element, className);
return false;
}
@ -1857,7 +1858,7 @@ angular.module('ngAnimate', ['ng'])
}
if (!staggerTime) {
element.addClass(activeClassName);
$$jqLite.addClass(element, activeClassName);
if (elementData.blockTransition) {
blockTransitions(node, false);
}
@ -1867,7 +1868,7 @@ angular.module('ngAnimate', ['ng'])
var timings = getElementAnimationDetails(element, eventCacheKey);
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
if (maxDuration === 0) {
element.removeClass(activeClassName);
$$jqLite.removeClass(element, activeClassName);
animateClose(element, className);
activeAnimationComplete();
return;
@ -1889,7 +1890,7 @@ angular.module('ngAnimate', ['ng'])
//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
var oldStyle = node.getAttribute('style') || '';
if (oldStyle.charAt(oldStyle.length-1) !== ';') {
if (oldStyle.charAt(oldStyle.length - 1) !== ';') {
oldStyle += ';';
}
node.setAttribute('style', oldStyle + ' ' + style);
@ -1902,7 +1903,7 @@ angular.module('ngAnimate', ['ng'])
var staggerTimeout;
if (staggerTime > 0) {
element.addClass(pendingClassName);
$$jqLite.addClass(element, pendingClassName);
staggerTimeout = $timeout(function() {
staggerTimeout = null;
@ -1913,8 +1914,8 @@ angular.module('ngAnimate', ['ng'])
blockAnimations(node, false);
}
element.addClass(activeClassName);
element.removeClass(pendingClassName);
$$jqLite.addClass(element, activeClassName);
$$jqLite.removeClass(element, pendingClassName);
if (styles) {
if (timings.transitionDuration === 0) {
@ -1941,8 +1942,8 @@ angular.module('ngAnimate', ['ng'])
// timeout done method.
function onEnd() {
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
element.removeClass(pendingClassName);
$$jqLite.removeClass(element, activeClassName);
$$jqLite.removeClass(element, pendingClassName);
if (staggerTimeout) {
$timeout.cancel(staggerTimeout);
}
@ -2030,7 +2031,7 @@ angular.module('ngAnimate', ['ng'])
}
function animateClose(element, className) {
element.removeClass(className);
$$jqLite.removeClass(element, className);
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if (data) {
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
* License: MIT
*/
@ -117,7 +117,7 @@ angular.mock.$Browser = function() {
self.defer.now += delay;
} else {
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 {
throw new Error('No deferred tasks to be flushed');
}
@ -428,7 +428,7 @@ angular.mock.$LogProvider = function() {
});
});
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:");
errors.push('');
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.
*/
angular.mock.$IntervalProvider = function() {
this.$get = ['$rootScope', '$q',
function($rootScope, $q) {
this.$get = ['$browser', '$rootScope', '$q', '$$q',
function($browser, $rootScope, $q, $$q) {
var repeatFns = [],
nextRepeatId = 0,
now = 0;
var $interval = function(fn, delay, count, invokeApply) {
var deferred = $q.defer(),
promise = deferred.promise,
iteration = 0,
skipApply = (angular.isDefined(invokeApply) && !invokeApply);
var iteration = 0,
skipApply = (angular.isDefined(invokeApply) && !invokeApply),
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise;
count = (angular.isDefined(count)) ? count : 0;
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({
@ -581,10 +585,10 @@ function jsonStringToDate(string) {
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour,
int(match[5]||0) - tzMin,
int(match[6]||0),
int(match[7]||0));
date.setUTCHours(int(match[4] || 0) - tzHour,
int(match[5] || 0) - tzMin,
int(match[6] || 0),
int(match[7] || 0));
return date;
}
return string;
@ -663,7 +667,7 @@ angular.mock.TzDate = function(offset, timestamp) {
}
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.getTime = function() {
@ -815,7 +819,7 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
animate.queue.push({
event: method,
element: arguments[0],
options: arguments[arguments.length-1],
options: arguments[arguments.length - 1],
args: arguments
});
return $delegate[method].apply($delegate, arguments);
@ -1115,7 +1119,7 @@ angular.mock.dump = function(object) {
```
*/
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
* @return {Object} Instance of $httpBackend mock
*/
function createHttpBackendMock($rootScope, $delegate, $browser) {
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var definitions = [],
expectations = [],
responses = [],
@ -1145,7 +1149,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
: [200, status, data];
: [200, status, data, headers];
};
}
@ -1162,7 +1166,9 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
}
function wrapResponse(wrapped) {
if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
if (!$browser && timeout) {
timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
}
return handleResponse;
@ -1770,7 +1776,7 @@ angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
}
var length = queue.length;
for (var i=0;i<length;i++) {
for (var i = 0; i < length; i++) {
queue[i]();
}
@ -2036,7 +2042,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
*/
angular.mock.e2e = {};
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:
*/
angular.mock.$RootScopeDecorator = function($delegate) {
angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
var $rootScopePrototype = Object.getPrototypeOf($delegate);
@ -2122,7 +2128,7 @@ angular.mock.$RootScopeDecorator = function($delegate) {
return count;
}
};
}];
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
* License: MIT
*/
@ -41,7 +41,7 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
*/
function $RouteProvider() {
function inherit(parent, extra) {
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra);
return angular.extend(Object.create(parent), extra);
}
var routes = {};
@ -151,6 +151,9 @@ function $RouteProvider() {
if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true;
}
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
}
routes[path] = angular.extend(
routeCopy,
path && pathRegExp(path, routeCopy)
@ -158,9 +161,9 @@ function $RouteProvider() {
// create redirection for trailing slashes
if (path) {
var redirectPath = (path[path.length-1] == '/')
? path.substr(0, path.length-1)
: path +'/';
var redirectPath = (path[path.length - 1] == '/')
? path.substr(0, path.length - 1)
: path + '/';
routes[redirectPath] = angular.extend(
{redirectTo: path},
@ -171,6 +174,17 @@ function $RouteProvider() {
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 opts {Object} options
@ -639,11 +653,11 @@ function $RouteProvider() {
*/
function interpolate(string, params) {
var result = [];
angular.forEach((string||'').split(':'), function(segment, i) {
angular.forEach((string || '').split(':'), function(segment, i) {
if (i === 0) {
result.push(segment);
} else {
var segmentMatch = segment.match(/(\w+)(.*)/);
var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
var key = segmentMatch[1];
result.push(params[key]);
result.push(segmentMatch[2] || '');
@ -774,7 +788,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
.view-animate-container {
position:relative;
height:100px!important;
position:relative;
background:white;
border:1px solid black;
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

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

View File

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

View File

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

View File

@ -44,6 +44,7 @@ describe('Mail List controller unit test', function() {
ctrl = $controller(MailListCtrl, {
$scope: scope,
$location: location,
$q: window.qMock,
status: statusMock,
notification: notificationMock,
email: emailMock,
@ -207,15 +208,17 @@ describe('Mail List controller unit test', function() {
});
describe('getBody', function() {
it('should get the mail content', function() {
it('should get the mail content', function(done) {
scope.state.nav = {
currentFolder: {
type: 'asd',
}
};
scope.getBody();
scope.getBody().then(function() {
expect(emailMock.getBody.calledOnce).to.be.true;
done();
});
});
});
@ -245,7 +248,7 @@ describe('Mail List controller unit test', 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'];
sinon.stub(notificationMock, 'close');
@ -272,10 +275,9 @@ describe('Mail List controller unit test', function() {
keychainMock.refreshKeyForUserId.withArgs({
userId: mail.from[0].address
}).yields();
scope.select(mail);
}).returns(resolves());
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);
@ -283,9 +285,11 @@ describe('Mail List controller unit test', function() {
expect(notificationMock.close.calledOnce).to.be.true;
notificationMock.close.restore();
done();
});
});
it('should decrypt and focus a read mail', function() {
it('should decrypt and focus a read mail', function(done) {
var mail = {
from: [{
address: 'asd'
@ -307,13 +311,14 @@ describe('Mail List controller unit test', function() {
keychainMock.refreshKeyForUserId.withArgs({
userId: mail.from[0].address
}).yields();
scope.select(mail);
}).returns(resolves());
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);
done();
});
});
});

View File

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

View File

@ -23,6 +23,7 @@ describe('Private Key Upload Controller unit test', function() {
ctrl = $controller(PrivateKeyUploadCtrl, {
$location: location,
$scope: scope,
$q: window.qMock,
keychain: keychainMock,
pgp: pgpStub,
dialog: dialogStub,
@ -41,14 +42,15 @@ describe('Private Key Upload Controller unit test', function() {
_id: 'keyId',
};
it('should fail', function() {
it('should fail', function(done) {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.yields(42);
scope.checkServerForKey();
keychainMock.hasPrivateKey.returns(rejects(42));
scope.checkServerForKey().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(keychainMock.hasPrivateKey.calledOnce).to.be.true;
done();
});
});
it('should return true', function(done) {
@ -56,9 +58,9 @@ describe('Private Key Upload Controller unit test', function() {
keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId,
keyId: keyParams._id
}).yields(null, true);
}).returns(resolves(true));
scope.checkServerForKey(function(privateKeySynced) {
scope.checkServerForKey().then(function(privateKeySynced) {
expect(privateKeySynced).to.be.true;
done();
});
@ -69,9 +71,9 @@ describe('Private Key Upload Controller unit test', function() {
keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId,
keyId: keyParams._id
}).yields(null, false);
}).returns(resolves(false));
scope.checkServerForKey(function(privateKeySynced) {
scope.checkServerForKey().then(function(privateKeySynced) {
expect(privateKeySynced).to.be.undefined;
done();
});
@ -110,27 +112,27 @@ describe('Private Key Upload Controller unit test', function() {
describe('setDeviceName', function() {
it('should work', function(done) {
keychainMock.setDeviceName.yields();
scope.setDeviceName(done);
keychainMock.setDeviceName.returns(resolves());
scope.setDeviceName().then(done);
});
});
describe('encryptAndUploadKey', function() {
it('should fail due to keychain.registerDevice', function() {
keychainMock.registerDevice.yields(42);
scope.encryptAndUploadKey();
it('should fail due to keychain.registerDevice', function(done) {
keychainMock.registerDevice.returns(rejects(42));
scope.encryptAndUploadKey().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
keychainMock.registerDevice.yields();
keychainMock.uploadPrivateKey.yields();
keychainMock.registerDevice.returns(resolves());
keychainMock.uploadPrivateKey.returns(resolves());
scope.encryptAndUploadKey(function(err) {
expect(err).to.not.exist;
scope.encryptAndUploadKey().then(function() {
expect(keychainMock.registerDevice.calledOnce).to.be.true;
expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true;
done();
@ -185,36 +187,39 @@ describe('Private Key Upload Controller unit test', function() {
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;
setDeviceNameStub.yields(42);
scope.goForward();
setDeviceNameStub.returns(rejects(42));
scope.goForward().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
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;
setDeviceNameStub.yields();
encryptAndUploadKeyStub.yields(42);
scope.goForward();
setDeviceNameStub.returns(resolves());
encryptAndUploadKeyStub.returns(rejects(42));
scope.goForward().then(function() {
expect(dialogStub.error.calledOnce).to.be.true;
expect(scope.step).to.equal(4);
done();
});
});
it('should work for 3', function() {
it('should work for 3', function(done) {
scope.step = 3;
setDeviceNameStub.yields();
encryptAndUploadKeyStub.yields();
scope.goForward();
setDeviceNameStub.returns(resolves());
encryptAndUploadKeyStub.returns(resolves());
scope.goForward().then(function() {
expect(dialogStub.info.calledOnce).to.be.true;
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');
describe('Read Controller unit test', function() {
var scope, ctrl, keychainMock, invitationMock, emailMock, pgpMock, outboxMock, dialogMock, authMock, downloadMock,
emailAddress = 'sender@example.com';
var scope, ctrl, keychainMock, invitationMock, emailMock, pgpMock, outboxMock, dialogMock, authMock, downloadMock;
beforeEach(function() {
keychainMock = sinon.createStubInstance(Keychain);
@ -31,6 +30,7 @@ describe('Read Controller unit test', function() {
scope.state = {};
ctrl = $controller(ReadCtrl, {
$scope: scope,
$q: window.qMock,
email: emailMock,
invitation: invitationMock,
outbox: outboxMock,
@ -66,33 +66,37 @@ describe('Read Controller unit test', function() {
describe('getKeyId', function() {
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.');
keychainMock.getReceiverPublicKey.yields(42);
scope.getKeyId(address);
keychainMock.getReceiverPublicKey.returns(rejects(42));
scope.getKeyId(address).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
expect(scope.keyId).to.equal('Searching...');
done();
});
});
it('should allow invitation on empty key', function() {
keychainMock.getReceiverPublicKey.yields();
scope.getKeyId(address);
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() {
keychainMock.getReceiverPublicKey.yields(null, {
it('should show searching on error', function(done) {
keychainMock.getReceiverPublicKey.returns(resolves({
publicKey: 'PUBLIC KEY'
});
}));
pgpMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
scope.getKeyId(address);
scope.getKeyId(address).then(function() {
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.');
});
it('should show error on invitation dao invite error', function() {
invitationMock.invite.yields(42);
it('should show error on invitation dao invite error', function(done) {
invitationMock.invite.returns(rejects(42));
scope.invite({
address: 'asdf@asdf.de'
});
}).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
done();
});
});
it('should show error on outbox put error', function() {
invitationMock.invite.yields();
outboxMock.put.yields(42);
it('should show error on outbox put error', function(done) {
invitationMock.invite.returns(resolves());
outboxMock.put.returns(rejects(42));
scope.invite({
address: 'asdf@asdf.de'
});
}).then(function() {
expect(dialogMock.error.calledOnce).to.be.true;
done();
});
});
it('should work', function() {
invitationMock.invite.yields();
outboxMock.put.yields();
it('should work', function(done) {
invitationMock.invite.returns(resolves());
outboxMock.put.returns(resolves());
scope.invite({
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 = {};
setPassphraseCtrl = $controller(SetPassphraseCtrl, {
$scope: scope,
$q: window.qMock,
pgp: pgpStub,
keychain: keychainStub,
dialog: dialogStub
@ -49,31 +50,32 @@ describe('Set Passphrase Controller unit test', function() {
afterEach(function() {});
describe('setPassphrase', function() {
describe('setPassphrase', function(done) {
it('should work', function() {
scope.oldPassphrase = 'old';
scope.newPassphrase = 'new';
keychainStub.lookupPrivateKey.withArgs(dummyKeyId).yields(null, {
keychainStub.lookupPrivateKey.withArgs(dummyKeyId).returns(resolves({
encryptedKey: 'encrypted'
});
}));
pgpStub.changePassphrase.withArgs({
privateKeyArmored: 'encrypted',
oldPassphrase: 'old',
newPassphrase: 'new'
}).yields(null, 'newArmoredKey');
}).returns(resolves('newArmoredKey'));
keychainStub.saveLocalPrivateKey.withArgs({
_id: dummyKeyId,
userId: emailAddress,
userIds: [],
encryptedKey: 'newArmoredKey'
}).yields();
scope.setPassphrase();
}).returns(resolves());
scope.setPassphrase().then(function() {
expect(dialogStub.info.calledOnce).to.be.true;
done();
});
});
});

View File

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

View File

@ -116,16 +116,17 @@ describe('Add Account Controller unit test', function() {
setCredentialsStub.restore();
});
it('should use oauth', function() {
it('should use oauth', function(done) {
dialogStub.confirm = function(options) {
options.callback(true);
};
authStub.getOAuthToken.yields();
scope.oauthPossible();
options.callback(true).then(function() {
expect(setCredentialsStub.calledOnce).to.be.true;
expect(authStub.getOAuthToken.calledOnce).to.be.true;
done();
});
};
authStub.getOAuthToken.returns(resolves());
scope.oauthPossible();
});
it('should not use oauth', function() {
@ -139,25 +140,29 @@ describe('Add Account Controller unit test', function() {
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) {
options.callback(true);
};
authStub.getOAuthToken.yields(new Error());
authStub.getOAuthToken.returns(rejects(new Error()));
scope.oauthPossible();
expect(dialogStub.error.calledOnce).to.be.true;
expect(setCredentialsStub.called).to.be.false;
});
});
describe('setCredentials', function() {
it('should work', function() {
scope.setCredentials();
it('should work', inject(function($timeout) {
scope.setCredentials().then();
$timeout.flush();
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,
$scope: scope,
$routeParams: {},
$q: window.qMock,
auth: authStub,
admin: adminStub
});
@ -54,16 +55,15 @@ describe('Create Account Controller unit test', function() {
scope.form.$invalid = false;
scope.betaCode = 'asfd';
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.errMsg).to.equal('asdf');
expect(adminStub.createUser.calledOnce).to.be.true;
done();
};
});
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true;
});
@ -71,16 +71,15 @@ describe('Create Account Controller unit test', function() {
scope.form.$invalid = false;
scope.betaCode = 'asfd';
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.errMsg).to.be.undefined;
expect(adminStub.createUser.calledOnce).to.be.true;
done();
};
});
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true;
});
});

View File

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

View File

@ -30,6 +30,7 @@ describe('Login (initial user) Controller unit test', function() {
ctrl = $controller(LoginInitialCtrl, {
$scope: scope,
$routeParams: {},
$q: window.qMock,
newsletter: newsletter,
email: emailMock,
auth: authMock
@ -74,36 +75,38 @@ describe('Login (initial user) Controller unit test', function() {
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;
emailMock.unlock.withArgs({
passphrase: undefined
}).yields(new Error('asdf'));
authMock.storeCredentials.yields();
scope.generateKey();
}).returns(rejects(new Error('asdf')));
authMock.storeCredentials.returns(resolves());
scope.generateKey().then(function() {
expect(scope.errMsg).to.exist;
expect(scope.state.ui).to.equal(1);
expect(newsletterStub.called).to.be.true;
done();
});
});
it('should unlock crypto', function() {
it('should unlock crypto', function(done) {
scope.agree = true;
emailMock.unlock.withArgs({
passphrase: undefined
}).yields();
authMock.storeCredentials.yields();
scope.generateKey();
}).returns(resolves());
authMock.storeCredentials.returns(resolves());
scope.generateKey().then(function() {
expect(scope.errMsg).to.not.exist;
expect(scope.state.ui).to.equal(2);
expect(newsletterStub.called).to.be.true;
expect(location.$$path).to.equal('/account');
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, {
$scope: scope,
$routeParams: {},
$q: window.qMock,
email: emailMock,
auth: authMock,
pgp: pgpMock,
@ -53,7 +54,7 @@ describe('Login (new device) Controller unit test', 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.key = {
privateKeyArmored: 'b'
@ -64,20 +65,21 @@ describe('Login (new device) Controller unit test', function() {
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves({
_id: keyId,
publicKey: 'a'
});
emailMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
}));
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();
});
});
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.key = {
privateKeyArmored: 'b',
@ -89,17 +91,18 @@ describe('Login (new device) Controller unit test', function() {
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields();
emailMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves());
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();
});
});
it('should not work when keypair upload fails', function() {
it('should not work when keypair upload fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
@ -110,24 +113,25 @@ describe('Login (new device) Controller unit test', function() {
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves({
_id: keyId,
publicKey: 'a'
});
emailMock.unlock.yields();
keychainMock.putUserKeyPair.yields({
}));
emailMock.unlock.returns(resolves());
keychainMock.putUserKeyPair.returns(rejects({
errMsg: 'yo mamma.'
});
scope.confirmPassphrase();
}));
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();
});
});
it('should not work when unlock fails', function() {
it('should not work when unlock fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
@ -138,36 +142,38 @@ describe('Login (new device) Controller unit test', function() {
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
keychainMock.getUserKeyPair.withArgs(emailAddress).returns(resolves({
_id: keyId,
publicKey: 'a'
});
emailMock.unlock.yields({
}));
emailMock.unlock.returns(rejects({
errMsg: 'yo mamma.'
});
scope.confirmPassphrase();
}));
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();
});
});
it('should not work when keypair retrieval', function() {
it('should not work when keypair retrieval', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({
keychainMock.getUserKeyPair.withArgs(emailAddress).returns(rejects({
errMsg: 'yo mamma.'
});
scope.confirmPassphrase();
}));
scope.confirmPassphrase().then(function() {
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
done();
});
});
});
});

View File

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

View File

@ -40,6 +40,7 @@ describe('Login (Set Credentials) Controller unit test', function() {
setCredentialsCtrl = $controller(SetCredentialsCtrl, {
$scope: scope,
$routeParams: {},
$q: window.qMock,
auth: auth,
connectionDoctor: doctor
});
@ -49,7 +50,7 @@ describe('Login (Set Credentials) Controller unit test', function() {
afterEach(function() {});
describe('set credentials', function() {
it('should work', function() {
it('should work', function(done) {
scope.emailAddress = 'emailemailemailemail';
scope.password = 'passwdpasswdpasswdpasswd';
scope.smtpHost = 'hosthosthost';
@ -80,14 +81,16 @@ describe('Login (Set Credentials) Controller unit test', function() {
}
};
doctor.check.yields(); // synchronous yields!
scope.test();
doctor.check.returns(resolves()); // synchronous yields!
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();
});
});
});
});

View File

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

View File

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

View File

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

View File

@ -58,55 +58,45 @@ describe('Account Service unit test', function() {
account.init({
emailAddress: dummyUser.replace('@'),
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function onInit(err) {
expect(err).to.exist;
expect(keys).to.not.exist;
}
});
});
it('should fail for _accountStore.init', function() {
devicestorageStub.init.yields(new Error('asdf'));
devicestorageStub.init.returns(rejects(new Error('asdf')));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function onInit(err) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
});
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({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function onInit(err) {
expect(err.message).to.match(/Updating/);
expect(keys).to.not.exist;
}
});
});
it('should fail for _keychain.getUserKeyPair', function() {
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(new Error('asdf'));
devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(rejects(new Error('asdf')));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function(err) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
});
it('should fail for _keychain.refreshKeyForUserId', function() {
@ -114,19 +104,17 @@ describe('Account Service unit test', function() {
publicKey: 'publicKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
keychainStub.refreshKeyForUserId.yields(new Error('asdf'));
devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(resolves(storedKeys));
keychainStub.refreshKeyForUserId.returns(rejects(new Error('asdf')));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function(err) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
});
it('should fail for _emailDao.init after _keychain.refreshKeyForUserId', function() {
@ -134,20 +122,18 @@ describe('Account Service unit test', function() {
publicKey: 'publicKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
keychainStub.refreshKeyForUserId.yields(null, storedKeys);
emailStub.init.yields(new Error('asdf'));
devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(resolves(storedKeys));
keychainStub.refreshKeyForUserId.returns(resolves(storedKeys));
emailStub.init.returns(rejects(new Error('asdf')));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function(err) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
});
it('should fail for _emailDao.init', function() {
@ -156,19 +142,17 @@ describe('Account Service unit test', function() {
privateKey: 'privateKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
emailStub.init.yields(new Error('asdf'));
devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(resolves(storedKeys));
emailStub.init.returns(rejects(new Error('asdf')));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
}).catch(function(err) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
});
it('should work after _keychain.refreshKeyForUserId', function() {
@ -176,20 +160,20 @@ describe('Account Service unit test', function() {
publicKey: 'publicKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
keychainStub.refreshKeyForUserId.yields(null, 'publicKey');
emailStub.init.yields();
devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(resolves(storedKeys));
keychainStub.refreshKeyForUserId.returns(resolves('publicKey'));
emailStub.init.returns(resolves());
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err).to.not.exist;
}, function onInit(keys) {
expect(keys).to.deep.equal(storedKeys);
}
expect(keychainStub.refreshKeyForUserId.calledOnce).to.be.true;
expect(emailStub.init.calledOnce).to.be.true;
});
});
it('should work', function() {
@ -198,19 +182,20 @@ describe('Account Service unit test', function() {
privateKey: 'privateKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
emailStub.init.yields();
devicestorageStub.init.returns(resolves());
updateHandlerStub.update.returns(resolves());
keychainStub.getUserKeyPair.returns(resolves(storedKeys));
emailStub.init.returns(resolves());
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err).to.not.exist;
}, function onInit(keys) {
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();
});
it('should fail due to _auth.getCredentials', function() {
authStub.getCredentials.yields(new Error('asdf'));
it('should fail due to _auth.getCredentials', function(done) {
authStub.getCredentials.returns(rejects(new Error('asdf')));
dialogStub.error = function(err) {
expect(err.message).to.match(/asdf/);
done();
};
account.onConnect();
expect(dialogStub.error.calledOnce).to.be.true;
});
it('should work', function() {
authStub.getCredentials.yields(null, credentials);
emailStub.onConnect.yields();
it('should fail due to _auth.getCredentials', function(done) {
authStub.getCredentials.returns(rejects(new Error('asdf')));
account.onConnect();
account.onConnect(function(err) {
expect(err.message).to.match(/asdf/);
expect(dialogStub.error.called).to.be.false;
done();
});
});
it('should work', function(done) {
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;
expect(dialogStub.error.calledOnce).to.be.true;
done();
});
});
});
describe('onDisconnect', function() {
it('should work', function() {
account.onDisconnect();
expect(emailStub.onDisconnect.calledOnce).to.be.true;
it('should work', function(done) {
emailStub.onDisconnect.returns(resolves());
account.onDisconnect().then(done);
});
});
describe('logout', function() {
it('should fail due to _auth.logout', function() {
authStub.logout.yields(new Error());
it('should fail due to _auth.logout', function(done) {
authStub.logout.returns(rejects(new Error('asdf')));
account.logout();
expect(dialogStub.error.calledOnce).to.be.true;
account.logout().catch(function(err) {
expect(err.message).to.match(/asdf/);
done();
});
});
it('should fail due to _emailDao.onDisconnect', function() {
authStub.logout.yields();
emailStub.onDisconnect.yields(new Error());
it('should fail due to _emailDao.onDisconnect', function(done) {
authStub.logout.returns(resolves());
emailStub.onDisconnect.returns(rejects(new Error('asdf')));
account.logout();
expect(dialogStub.error.calledOnce).to.be.true;
account.logout().catch(function(err) {
expect(err.message).to.match(/asdf/);
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'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage');
describe('Outbox Business Object unit test', function() {
describe('Outbox unit test', function() {
var outbox, emailDaoStub, devicestorageStub, keychainStub,
dummyUser = 'spiderpig@springfield.com';
@ -42,6 +42,13 @@ describe('Outbox Business Object unit test', 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) {
var mail, senderKey, receiverKey;
@ -67,15 +74,13 @@ describe('Outbox Business Object unit test', function() {
bcc: []
};
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync();
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).returns(resolves(senderKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).returns(resolves(receiverKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).returns(resolves());
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
devicestorageStub.storeList.withArgs([mail]).returns(resolves());
outbox.put(mail).then(function() {
expect(mail.publicKeysArmored.length).to.equal(2);
expect(emailDaoStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
@ -103,11 +108,9 @@ describe('Outbox Business Object unit test', function() {
}]
};
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
devicestorageStub.storeList.withArgs([mail]).returns(resolves());
outbox.put(mail).then(function() {
expect(mail.publicKeysArmored.length).to.equal(0);
expect(keychainStub.getReceiverPublicKey.called).to.be.false;
expect(emailDaoStub.encrypt.called).to.be.false;
@ -142,20 +145,18 @@ describe('Outbox Business Object unit test', function() {
bcc: []
};
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).returns(resolves(senderKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).returns(resolves(receiverKey));
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).returns(resolves(receiverKey));
emailDaoStub.encrypt.withArgs({
mail: mail,
publicKeysArmored: [senderKey.publicKey, receiverKey.publicKey, receiverKey.publicKey]
}).yieldsAsync();
}).returns(resolves());
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
devicestorageStub.storeList.withArgs([mail]).returns(resolves());
outbox.put(mail).then(function() {
expect(mail.publicKeysArmored.length).to.equal(3);
expect(emailDaoStub.encrypt.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];
devicestorageStub.listItems.yieldsAsync(null, dummyMails);
devicestorageStub.listItems.returns(resolves(dummyMails));
emailDaoStub.sendPlaintext.yieldsAsync();
emailDaoStub.sendPlaintext.returns(resolves());
emailDaoStub.sendEncrypted.withArgs({
email: newlyjoined
}).yieldsAsync();
}).returns(resolves());
emailDaoStub.sendEncrypted.withArgs({
email: member
}).yieldsAsync();
}).returns(resolves());
devicestorageStub.removeList.yieldsAsync();
devicestorageStub.removeList.returns(resolves());
function onOutboxUpdate(err, count) {
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) {
emailDaoStub._account.online = false;
devicestorageStub.listItems.yieldsAsync(null, [{}]);
devicestorageStub.listItems.returns(resolves([{}]));
outbox._processOutbox(function(err, count) {
expect(err).to.not.exist;

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
'use strict';
var RestDAO = require('../../../src/js/service/rest'),
InvitationDAO = require('../../../src/js/service/invitation'),
appConfig = require('../../../src/js/app-config');
InvitationDAO = require('../../../src/js/service/invitation');
describe('Invitation DAO unit tests', function() {
var restDaoStub, invitationDao,
@ -12,94 +11,78 @@ describe('Invitation DAO unit tests', function() {
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
invitationDao = new InvitationDAO(restDaoStub, appConfig);
invitationDao = new InvitationDAO(restDaoStub);
});
describe('initialization', function() {
it('should wire up correctly', function() {
expect(invitationDao._restDao).to.equal(restDaoStub);
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() {
it('should invite the recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 201);
restDaoStub.put.returns(resolves());
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_SUCCESS);
}).then(function() {
expect(restDaoStub.put.calledWith({}, expectedUri)).to.be.true;
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) {
restDaoStub.put.yieldsAsync({
errMsg: 'jawollja.'
});
restDaoStub.put.returns(rejects(new Error()));
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
}).catch(function(err) {
expect(err).to.exist;
expect(status).to.not.exist;
done();
});
});
it('should not work for unexpected response', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 1337);
it('should report erroneous usage', function(done) {
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
}, expectError);
invitationDao.invite('asd').catch(expectError);
function expectError(err) {
expect(err).to.exist;
expect(status).to.not.exist;
done();
});
}
});
it('should report erroneous usage', function() {
invitationDao.invite({
sender: bob
}, expectError);
it('should report erroneous usage', function(done) {
invitationDao.invite({
recipient: alice,
}, expectError);
invitationDao.invite('asd').catch(expectError);
function expectError(err) {
expect(err).to.exist;
done();
}
});
it('should report erroneous usage', function(done) {
invitationDao.invite({
recipient: 123,
sender: 123
}, expectError);
invitationDao.invite('asd', expectError);
invitationDao.invite('asd').catch(expectError);
function expectError(err, status) {
function expectError(err) {
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) {
lawnchairDao = new LawnchairDAO();
lawnchairDao.init(dbName, function(err) {
expect(err).to.not.exist;
lawnchairDao.init(dbName).then(function() {
expect(lawnchairDao._db).to.exist;
done();
});
});
afterEach(function(done) {
lawnchairDao.clear(function(err) {
expect(err).to.not.exist;
done();
});
lawnchairDao.clear().then(done);
});
describe('read', function() {
it('should fail', function(done) {
lawnchairDao.read(undefined, function(err) {
lawnchairDao.read(undefined).catch(function(err) {
expect(err).to.exist;
done();
});
@ -47,7 +43,7 @@ describe('Lawnchair DAO unit tests', function() {
describe('list', function() {
it('should fail', function(done) {
lawnchairDao.list(undefined, 0, null, function(err) {
lawnchairDao.list(undefined, 0, null).catch(function(err) {
expect(err).to.exist;
done();
});
@ -56,7 +52,7 @@ describe('Lawnchair DAO unit tests', function() {
describe('remove list', function() {
it('should fail', function(done) {
lawnchairDao.removeList(undefined, function(err) {
lawnchairDao.removeList(undefined).catch(function(err) {
expect(err).to.exist;
done();
});
@ -65,46 +61,36 @@ describe('Lawnchair DAO unit tests', function() {
describe('persist/read/remove', function() {
it('should fail', function(done) {
lawnchairDao.persist(undefined, data, function(err) {
lawnchairDao.persist(undefined, data).catch(function(err) {
expect(err).to.exist;
done();
});
});
it('should fail', function(done) {
lawnchairDao.persist('1234', undefined, function(err) {
lawnchairDao.persist('1234', undefined).catch(function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
lawnchairDao.persist(key, data, function(err) {
expect(err).to.not.exist;
lawnchairDao.read(key, onRead);
});
function onRead(err, fetched) {
expect(err).to.not.exist;
lawnchairDao.persist(key, data).then(function() {
return lawnchairDao.read(key);
}).then(function(fetched) {
expect(fetched).to.deep.equal(data);
lawnchairDao.remove(key, onRemove);
}
function onRemove(err) {
expect(err).to.not.exist;
lawnchairDao.read(key, onReadAgain);
}
function onReadAgain(err, fetched) {
expect(err).to.not.exist;
return lawnchairDao.remove(key);
}).then(function() {
return lawnchairDao.read(key);
}).then(function(fetched) {
expect(fetched).to.not.exist;
done();
}
});
});
});
describe('batch/list/removeList', function() {
it('should fails', function(done) {
lawnchairDao.batch({}, function(err) {
lawnchairDao.batch({}).catch(function(err) {
expect(err).to.exist;
done();
});
@ -119,29 +105,19 @@ describe('Lawnchair DAO unit tests', function() {
object: data2
}];
lawnchairDao.batch(list, function(err) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onList);
});
function onList(err, fetched) {
expect(err).to.not.exist;
lawnchairDao.batch(list).then(function() {
return lawnchairDao.list('type', 0, null);
}).then(function(fetched) {
expect(fetched.length).to.equal(2);
expect(fetched[0]).to.deep.equal(list[0].object);
lawnchairDao.removeList('type', onRemoveList);
}
function onRemoveList(err) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onListAgain);
}
function onListAgain(err, fetched) {
expect(err).to.not.exist;
return lawnchairDao.removeList('type');
}).then(function() {
return lawnchairDao.list('type', 0, null);
}).then(function(fetched) {
expect(fetched).to.exist;
expect(fetched.length).to.equal(0);
done();
}
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -64,15 +64,14 @@ describe('Connection Doctor', function() {
describe('#_checkOnline', function() {
it('should check if browser is online', function(done) {
doctor._checkOnline(function(error) {
if (navigator.onLine) {
expect(error).to.not.exist;
doctor._checkOnline().then(done);
} else {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.OFFLINE);
}
doctor._checkOnline().catch(function(err) {
expect(err.code).to.equal(ConnectionDoctor.OFFLINE);
done();
});
}
});
});
@ -80,8 +79,7 @@ describe('Connection Doctor', function() {
it('should be able to reach the host w/o cert', function(done) {
credentials.imap.ca = undefined;
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.not.exist;
doctor._checkReachable(credentials.imap).then(function() {
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
@ -105,8 +103,7 @@ describe('Connection Doctor', function() {
}
});
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.not.exist;
doctor._checkReachable(credentials.imap).then(function() {
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
@ -122,8 +119,7 @@ describe('Connection Doctor', function() {
});
it('should fail w/ wrong cert', function(done) {
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
doctor._checkReachable(credentials.imap).catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.TLS_WRONG_CERT);
expect(TCPSocket.open.calledOnce).to.be.true;
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) {
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
doctor._checkReachable(credentials.imap).catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.HOST_UNREACHABLE);
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
cfg.connDocTimeout = 20; // set to 20ms for the test
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
doctor._checkReachable(credentials.imap).catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.HOST_TIMEOUT);
expect(TCPSocket.open.calledOnce).to.be.true;
cfg.connDocTimeout = origTimeout;
@ -179,8 +173,7 @@ describe('Connection Doctor', function() {
});
imapStub.logout.yieldsAsync();
doctor._checkImap(function(error) {
expect(error).to.not.exist;
doctor._checkImap().then(function() {
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.calledOnce).to.be.true;
@ -195,8 +188,7 @@ describe('Connection Doctor', function() {
Inbox: [{}]
});
doctor._checkImap(function(error) {
expect(error).to.exist;
doctor._checkImap().catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
@ -218,8 +210,7 @@ describe('Connection Doctor', function() {
Inbox: []
});
doctor._checkImap(function(error) {
expect(error).to.exist;
doctor._checkImap().catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.NO_INBOX);
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
@ -233,8 +224,7 @@ describe('Connection Doctor', function() {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(new Error());
doctor._checkImap(function(error) {
expect(error).to.exist;
doctor._checkImap().catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
@ -246,8 +236,7 @@ describe('Connection Doctor', function() {
});
it('should fail w/ auth rejected', function(done) {
doctor._checkImap(function(error) {
expect(error).to.exist;
doctor._checkImap().catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
@ -266,8 +255,7 @@ describe('Connection Doctor', function() {
describe('#_checkSmtp', function() {
it('should perform SMTP login, logout', function(done) {
doctor._checkSmtp(function(error) {
expect(error).to.not.exist;
doctor._checkSmtp().then(function() {
expect(smtpStub.connect.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) {
doctor._checkSmtp(function(error) {
expect(error).to.exist;
doctor._checkSmtp().catch(function(error) {
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist;
expect(smtpStub.connect.calledOnce).to.be.true;
@ -302,14 +289,13 @@ describe('Connection Doctor', function() {
});
it('should perform all tests', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync();
doctor._checkSmtp.yieldsAsync();
doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).returns(resolves());
doctor._checkImap.returns(resolves());
doctor._checkSmtp.returns(resolves());
doctor.check(function(err) {
expect(err).to.not.exist;
doctor.check().then(function() {
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).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) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync();
doctor._checkSmtp.yieldsAsync(new Error());
doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).returns(resolves());
doctor._checkImap.returns(resolves());
doctor._checkSmtp.returns(rejects(new Error()));
doctor.check(function(err) {
doctor.check().catch(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).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) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync(new Error());
doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).returns(resolves());
doctor._checkImap.returns(rejects(new Error()));
doctor.check(function(err) {
doctor.check().catch(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).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) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(new Error());
doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).returns(resolves());
doctor._checkReachable.withArgs(credentials.smtp).returns(rejects(new Error()));
doctor.check(function(err) {
doctor.check().catch(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).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) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(new Error());
doctor._checkOnline.returns(resolves());
doctor._checkReachable.withArgs(credentials.imap).returns(rejects(new Error()));
doctor.check(function(err) {
doctor.check().catch(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.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) {
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(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.called).to.be.false;
@ -402,7 +388,7 @@ describe('Connection Doctor', function() {
it('should fail w/o config', function(done) {
doctor.credentials = doctor._imap = doctor._smtp = undefined;
doctor.check(function(err) {
doctor.check().catch(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.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) {
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) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
done();
@ -55,7 +54,7 @@ describe('UpdateHandler', function() {
beforeEach(function() {
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() {
@ -67,17 +66,16 @@ describe('UpdateHandler', function() {
cfg.dbVersion = 4; // app requires database version 4
// a simple dummy update to executed that only increments the update counter
function dummyUpdate(options, callback) {
function dummyUpdate() {
updateCounter++;
callback();
return resolves();
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, dummyUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(updateCounter).to.equal(2);
done();
@ -87,20 +85,20 @@ describe('UpdateHandler', function() {
it('should fail while updating to v3', function(done) {
cfg.dbVersion = 4; // app requires database version 4
function dummyUpdate(options, callback) {
function dummyUpdate() {
updateCounter++;
callback();
return resolves();
}
function failingUpdate(options, callback) {
callback({});
function failingUpdate() {
return rejects({});
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, failingUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(updateCounter).to.equal(0);
@ -115,7 +113,7 @@ describe('UpdateHandler', function() {
beforeEach(function() {
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() {
@ -125,11 +123,10 @@ describe('UpdateHandler', function() {
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([1], versionDbType).yieldsAsync();
userStorageStub.removeList.withArgs(emailDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs([1], versionDbType).returns(resolves());
updateHandler.update(function(error) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(userStorageStub.removeList.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) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
userStorageStub.removeList.returns(resolves());
appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.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) {
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(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
@ -168,7 +165,7 @@ describe('UpdateHandler', function() {
beforeEach(function() {
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() {
@ -178,11 +175,10 @@ describe('UpdateHandler', function() {
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([2], versionDbType).yieldsAsync();
userStorageStub.removeList.withArgs(emailDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs([2], versionDbType).returns(resolves());
updateHandler.update(function(error) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(userStorageStub.removeList.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) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
userStorageStub.removeList.returns(resolves());
appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.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) {
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(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
@ -221,7 +217,7 @@ describe('UpdateHandler', function() {
beforeEach(function() {
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() {
@ -231,11 +227,10 @@ describe('UpdateHandler', function() {
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([3], versionDbType).yieldsAsync();
userStorageStub.removeList.withArgs(emailDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs([3], versionDbType).returns(resolves());
updateHandler.update(function(error) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(userStorageStub.removeList.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) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
userStorageStub.removeList.returns(resolves());
appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.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) {
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(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
@ -290,22 +285,21 @@ describe('UpdateHandler', function() {
beforeEach(function() {
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) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, [emailaddress]);
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs(['gmail'], PROVIDER_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([emailaddress], USERNAME_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([''], REALNAME_DB_KEY).yieldsAsync();
authStub._loadCredentials.yieldsAsync();
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).returns(resolves([emailaddress]));
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).returns(resolves([]));
appConfigStorageStub.storeList.withArgs([4], versionDbType).returns(resolves());
appConfigStorageStub.storeList.withArgs(['gmail'], PROVIDER_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([emailaddress], USERNAME_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([imap], IMAP_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([smtp], SMTP_DB_KEY).returns(resolves());
appConfigStorageStub.storeList.withArgs([''], REALNAME_DB_KEY).returns(resolves());
authStub._loadCredentials.returns(resolves());
updateHandler.update(function(error) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(appConfigStorageStub.storeList.callCount).to.equal(6);
expect(appConfigStorageStub.listItems.calledThrice).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) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync();
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).returns(resolves([]));
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).returns(resolves([]));
appConfigStorageStub.storeList.withArgs([4], versionDbType).returns(resolves());
updateHandler.update(function(error) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(appConfigStorageStub.storeList.calledOnce).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) {
appConfigStorageStub.listItems.yieldsAsync(null, []);
appConfigStorageStub.storeList.yieldsAsync(new Error());
appConfigStorageStub.listItems.returns(resolves([]));
appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledThrice).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) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(new Error());
appConfigStorageStub.storeList.yieldsAsync(new Error());
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).returns(rejects(new Error()));
appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledTwice).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
@ -368,7 +361,7 @@ describe('UpdateHandler', function() {
beforeEach(function() {
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() {
@ -378,7 +371,7 @@ describe('UpdateHandler', function() {
});
it('should work', function(done) {
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).yieldsAsync(null, [
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).returns(resolves([
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
@ -404,7 +397,7 @@ describe('UpdateHandler', function() {
name: 'trash2',
type: FOLDER_TYPE_TRASH
}]
]);
]));
userStorageStub.storeList.withArgs([
[{
@ -420,12 +413,11 @@ describe('UpdateHandler', function() {
name: 'trash1',
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) {
expect(error).to.not.exist;
updateHandler.update().then(function() {
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.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) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
userStorageStub.listItems.returns(resolves([]));
userStorageStub.storeList.returns(resolves());
appConfigStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.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) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync(new Error());
userStorageStub.listItems.returns(resolves([]));
userStorageStub.storeList.returns(rejects(new Error()));
updateHandler.update(function(error) {
updateHandler.update().catch(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.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) {
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(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.called).to.be.false;