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

[WO-300] Wrap chrome notifications and identity apis in modules

This commit is contained in:
Tankred Hase 2014-04-01 13:16:39 +02:00
parent 0143db7f0b
commit a2f3e86545
17 changed files with 673 additions and 434 deletions

View File

@ -4,22 +4,24 @@
define(function(require) {
'use strict';
var ImapClient = require('imap-client'),
mailreader = require('mailreader'),
var Auth = require('js/bo/auth'),
PGP = require('js/crypto/pgp'),
PgpMailer = require('pgpmailer'),
EmailDAO = require('js/dao/email-dao'),
EmailSync = require('js/dao/email-sync'),
OAuth = require('js/util/oauth'),
PgpBuilder = require('pgpbuilder'),
OutboxBO = require('js/bo/outbox'),
mailreader = require('mailreader'),
ImapClient = require('imap-client'),
RestDAO = require('js/dao/rest-dao'),
EmailDAO = require('js/dao/email-dao'),
config = require('js/app-config').config,
EmailSync = require('js/dao/email-sync'),
KeychainDAO = require('js/dao/keychain-dao'),
PublicKeyDAO = require('js/dao/publickey-dao'),
LawnchairDAO = require('js/dao/lawnchair-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
InvitationDAO = require('js/dao/invitation-dao'),
OutboxBO = require('js/bo/outbox'),
PGP = require('js/crypto/pgp'),
PgpBuilder = require('pgpbuilder'),
UpdateHandler = require('js/util/update/update-handler'),
config = require('js/app-config').config;
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
UpdateHandler = require('js/util/update/update-handler');
var self = {};
@ -41,64 +43,74 @@ define(function(require) {
function onDeviceReady() {
console.log('Starting app.');
self.buildModules();
// Handle offline and online gracefully
window.addEventListener('online', self.onConnect.bind(self, options.onError));
window.addEventListener('offline', self.onDisconnect.bind(self, options.onError));
// init app config storage
self._appConfigStore = new DeviceStorageDAO(new LawnchairDAO());
self._appConfigStore.init('app-config', callback);
}
};
self.onDisconnect = function(callback) {
if (!self._emailDao) {
// the following code only makes sense if the email dao has been initialized
return;
}
self.buildModules = function() {
var lawnchairDao, restDao, pubkeyDao, emailDao, emailSync, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore;
// start the mailreader's worker thread
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
// init objects and inject dependencies
restDao = new RestDAO();
lawnchairDao = new LawnchairDAO();
pubkeyDao = new PublicKeyDAO(restDao);
oauth = new OAuth(new RestDAO('https://www.googleapis.com'));
self._appConfigStore = appConfigStore = new DeviceStorageDAO(new LawnchairDAO());
self._auth = new Auth(appConfigStore, oauth, new RestDAO('/ca'));
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
self._invitationDao = new InvitationDAO(restDao);
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
self._crypto = pgp = new PGP();
self._pgpbuilder = pgpbuilder = new PgpBuilder();
emailSync = new EmailSync(keychain, userStorage);
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader, emailSync);
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
self._updateHandler = new UpdateHandler(appConfigStore, userStorage);
};
self.isOnline = function() {
return navigator.onLine;
};
self.onDisconnect = function(callback) {
self._emailDao.onDisconnect(null, callback);
};
self.onConnect = function(callback) {
if (!self._emailDao) {
// the following code only makes sense if the email dao has been initialized
return;
}
if (!self.isOnline()) {
if (!self.isOnline() || !self._emailDao._account) {
// prevent connection infinite loop
console.log('Not connecting since user agent is offline.');
callback();
return;
}
// fetch pinned local ssl certificate
self.getCertficate(function(err, certificate) {
self._auth.getCredentials({}, function(err, credentials) {
if (err) {
callback(err);
return;
}
// get a fresh oauth token
self.fetchOAuthToken(function(err, oauth) {
if (err) {
callback(err);
return;
}
initClients(oauth, certificate);
});
initClients(credentials);
});
function initClients(oauth, certificate) {
function initClients(credentials) {
var auth, imapOptions, imapClient, smtpOptions, pgpMailer;
auth = {
XOAuth2: {
user: oauth.emailAddress,
user: credentials.emailAddress,
clientId: config.gmail.clientId,
accessToken: oauth.token
accessToken: credentials.oauthToken
}
};
imapOptions = {
@ -106,7 +118,7 @@ define(function(require) {
port: config.gmail.imap.port,
host: config.gmail.imap.host,
auth: auth,
ca: [certificate]
ca: [credentials.sslCert]
};
smtpOptions = {
secureConnection: config.gmail.smtp.secure,
@ -114,15 +126,23 @@ define(function(require) {
host: config.gmail.smtp.host,
auth: auth,
tls: {
ca: [certificate]
ca: [credentials.sslCert]
},
onError: console.error
};
imapClient = new ImapClient(imapOptions, mailreader);
pgpMailer = new PgpMailer(smtpOptions, self._pgpbuilder);
imapClient = new ImapClient(imapOptions, mailreader);
imapClient.onError = onImapError;
imapClient.onError = function(err) {
// connect to clients
self._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer
}, callback);
}
function onImapError(err) {
console.log('IMAP error.', err);
console.log('IMAP reconnecting...');
// re-init client modules on error
@ -134,50 +154,11 @@ define(function(require) {
console.log('IMAP reconnect attempt complete.');
});
};
// connect to clients
self._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer
}, callback);
}
};
self.getCertficate = function(localCallback) {
if (self.certificate) {
localCallback(null, self.certificate);
return;
}
// fetch pinned local ssl certificate
var ca = new RestDAO({
baseUri: '/ca'
});
ca.get({
uri: '/Google_Internet_Authority_G2.pem',
type: 'text'
}, function(err, cert) {
if (err || !cert) {
localCallback({
errMsg: 'Could not fetch pinned certificate!'
});
return;
}
self.certificate = cert;
localCallback(null, self.certificate);
return;
});
};
self.isOnline = function() {
return navigator.onLine;
};
self.checkForUpdate = function() {
if (!chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) {
if (!window.chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) {
return;
}
@ -197,167 +178,10 @@ define(function(require) {
});
};
/**
* Gracefully try to fetch the user's email address from local storage.
* If not yet stored, handle online/offline cases on first use.
*/
self.getEmailAddress = function(callback) {
// try to fetch email address from local storage
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
if (err) {
callback(err);
return;
}
if (!cachedEmailAddress && !self.isOnline()) {
// first time login... must be online
callback({
errMsg: 'The app must be online on first use!'
});
return;
}
callback(null, cachedEmailAddress);
});
};
/**
* Get the user's email address from local storage
*/
self.getEmailAddressFromConfig = function(callback) {
self._appConfigStore.listItems('emailaddress', 0, null, function(err, cachedItems) {
if (err) {
callback(err);
return;
}
// no email address is cached yet
if (!cachedItems || cachedItems.length < 1) {
callback();
return;
}
callback(null, cachedItems[0]);
});
};
/**
* Lookup the user's email address. Check local cache if available
* otherwise query google's token info api to learn the user's email address
*/
self.queryEmailAddress = function(token, callback) {
var itemKey = 'emailaddress';
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
if (err) {
callback(err);
return;
}
// do roundtrip to google api if no email address is cached yet
if (!cachedEmailAddress) {
queryGoogleApi();
return;
}
callback(null, cachedEmailAddress);
});
function queryGoogleApi() {
if (!token) {
callback({
errMsg: 'Invalid OAuth token!'
});
return;
}
// fetch gmail user's email address from the Google Authorization Server endpoint
var googleEndpoint = new RestDAO({
baseUri: 'https://www.googleapis.com'
});
googleEndpoint.get({
uri: '/oauth2/v1/tokeninfo?access_token=' + token
}, function(err, info) {
if (err || !info || !info.email) {
callback({
errMsg: 'Error looking up email address on google api!'
});
return;
}
// cache the email address on the device
self._appConfigStore.storeList([info.email], itemKey, function(err) {
callback(err, info.email);
});
});
}
};
/**
* Request an OAuth token from chrome for gmail users
*/
self.fetchOAuthToken = function(callback) {
// get OAuth Token from chrome
chrome.identity.getAuthToken({
'interactive': true
}, onToken);
function onToken(token) {
if ((chrome && chrome.runtime && chrome.runtime.lastError) || !token) {
callback({
errMsg: 'Error fetching an OAuth token for the user!'
});
return;
}
// get email address for the token
self.queryEmailAddress(token, function(err, emailAddress) {
if (err || !emailAddress) {
callback({
errMsg: 'Error looking up email address on login!',
err: err
});
return;
}
// init the email dao
callback(null, {
emailAddress: emailAddress,
token: token
});
});
}
};
self.buildModules = function() {
var lawnchairDao, restDao, pubkeyDao, emailDao, emailSync, keychain, pgp, userStorage, pgpbuilder;
// start the mailreader's worker thread
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
// init objects and inject dependencies
restDao = new RestDAO();
pubkeyDao = new PublicKeyDAO(restDao);
lawnchairDao = new LawnchairDAO();
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
self._invitationDao = new InvitationDAO(restDao);
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
self._crypto = pgp = new PGP();
self._pgpbuilder = pgpbuilder = new PgpBuilder();
emailSync = new EmailSync(keychain, userStorage);
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader, emailSync);
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
self._updateHandler = new UpdateHandler(self._appConfigStore, userStorage);
};
/**
* Instanciate the mail email data access object and its dependencies. Login to imap on init.
*/
self.init = function(options, callback) {
self.buildModules();
// init user's local database
self._userStorage.init(options.emailAddress, function(err) {
if (err) {

139
src/js/bo/auth.js Normal file
View File

@ -0,0 +1,139 @@
define(function() {
'use strict';
var emailItemKey = 'emailaddress';
var Auth = function(appConfigStore, oauth, ca) {
this._appConfigStore = appConfigStore;
this._oauth = oauth;
this._ca = ca;
};
Auth.prototype.getCredentials = function(options, callback) {
var self = this;
// fetch pinned local ssl certificate
self.getCertificate(function(err, certificate) {
if (err) {
callback(err);
return;
}
// get a fresh oauth token
self._oauth.getOAuthToken(function(err, token) {
if (err) {
callback(err);
return;
}
// get email address for the token
self.queryEmailAddress(token, function(err, emailAddress) {
if (err) {
callback(err);
return;
}
callback(null, {
emailAddress: emailAddress,
oauthToken: token,
sslCert: certificate
});
});
});
});
};
/**
* Get the pinned ssl certificate for the corresponding mail server.
*/
Auth.prototype.getCertificate = function(callback) {
this._ca.get({
uri: '/Google_Internet_Authority_G2.pem',
type: 'text'
}, function(err, cert) {
if (err || !cert) {
callback({
errMsg: 'Could not fetch pinned certificate!'
});
return;
}
callback(null, cert);
});
};
/**
* Gracefully try to fetch the user's email address from local storage.
* If not yet stored, handle online/offline cases on first use.
*/
Auth.prototype.getEmailAddress = function(callback) {
// try to fetch email address from local storage
this.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
if (err) {
callback(err);
return;
}
callback(null, cachedEmailAddress);
});
};
/**
* Get the user's email address from local storage
*/
Auth.prototype.getEmailAddressFromConfig = function(callback) {
this._appConfigStore.listItems(emailItemKey, 0, null, function(err, cachedItems) {
if (err) {
callback(err);
return;
}
// no email address is cached yet
if (!cachedItems || cachedItems.length < 1) {
callback();
return;
}
callback(null, cachedItems[0]);
});
};
/**
* Lookup the user's email address. Check local cache if available
* otherwise query google's token info api to learn the user's email address
*/
Auth.prototype.queryEmailAddress = function(token, callback) {
var self = this;
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
if (err) {
callback(err);
return;
}
// do roundtrip to google api if no email address is cached yet
if (!cachedEmailAddress) {
queryOAuthApi();
return;
}
callback(null, cachedEmailAddress);
});
function queryOAuthApi() {
self._oauth.queryEmailAddress(token, function(err, emailAddress) {
if (err) {
callback(err);
return;
}
// cache the email address on the device
self._appConfigStore.storeList([emailAddress], emailItemKey, function(err) {
callback(err, emailAddress);
});
});
}
};
return Auth;
});

View File

@ -11,7 +11,7 @@ define(function(require) {
errorUtil.attachHandler($scope);
$scope.connectToGoogle = function() {
appController.fetchOAuthToken(function(err) {
appController._auth.getCredentials({}, function(err) {
if (err) {
$scope.onError(err);
return;

View File

@ -32,7 +32,7 @@ define(function(require) {
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
appController._emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
handleError(err);
return;

View File

@ -27,7 +27,7 @@ define(function(require) {
function initializeUser() {
// get OAuth token from chrome
appController.getEmailAddress(function(err, emailAddress) {
appController._auth.getEmailAddress(function(err, emailAddress) {
if (err) {
$scope.onError(err);
return;

View File

@ -6,7 +6,7 @@ define(function(require) {
appController = require('js/app-controller'),
IScroll = require('iscroll'),
str = require('js/app-config').string,
cfg = require('js/app-config').config,
notification = require('js/util/notification'),
emailDao, outboxBo;
var MailListCtrl = function($scope) {
@ -26,7 +26,7 @@ define(function(require) {
notificationForEmail(email);
});
};
chrome.notifications.onClicked.addListener(notificationClicked);
notification.setOnClickedListener(notificationClicked);
}
//
@ -38,7 +38,7 @@ define(function(require) {
folder: getFolder().path,
message: email
}, function(err) {
if (err) {
if (err && err.code !== 42) {
$scope.onError(err);
return;
}
@ -244,11 +244,10 @@ define(function(require) {
}
function notificationForEmail(email) {
chrome.notifications.create('' + email.uid, {
type: 'basic',
notification.create({
id: '' + email.uid,
title: email.from[0].name || email.from[0].address,
message: email.subject.replace(str.subjectPrefix, ''),
iconUrl: chrome.runtime.getURL(cfg.iconPath)
message: email.subject.replace(str.subjectPrefix, '')
}, function() {});
}

View File

@ -3,9 +3,9 @@ define(function(require) {
var angular = require('angular'),
str = require('js/app-config').string,
cfg = require('js/app-config').config,
appController = require('js/app-controller'),
errorUtil = require('js/util/error'),
notification = require('js/util/notification'),
_ = require('underscore'),
emailDao, outboxBo;
@ -126,11 +126,10 @@ define(function(require) {
}
function sentNotification(email) {
chrome.notifications.create('o' + email.id, {
type: 'basic',
notification.create({
id: 'o' + email.id,
title: 'Message sent',
message: email.subject.replace(str.subjectPrefix, ''),
iconUrl: chrome.runtime.getURL(cfg.iconPath)
message: email.subject.replace(str.subjectPrefix, '')
}, function() {});
}
};

View File

@ -3,9 +3,9 @@ define(function(require) {
var config = require('js/app-config').config;
var RestDAO = function(options) {
if (options && options.baseUri) {
this._baseUri = options.baseUri;
var RestDAO = function(baseUri) {
if (baseUri) {
this._baseUri = baseUri;
} else {
this._baseUri = config.cloudUrl;
}

View File

@ -0,0 +1,26 @@
define(function(require) {
'use strict';
var cfg = require('js/app-config').config;
var self = {};
self.create = function(options, callback) {
if (window.chrome && chrome.notifications) {
chrome.notifications.create(options.id, {
type: 'basic',
title: options.title,
message: options.message,
iconUrl: chrome.runtime.getURL(cfg.iconPath)
}, callback);
}
};
self.setOnClickedListener = function(listener) {
if (window.chrome && chrome.notifications) {
chrome.notifications.onClicked.addListener(listener);
}
};
return self;
});

55
src/js/util/oauth.js Normal file
View File

@ -0,0 +1,55 @@
define(function() {
'use strict';
var OAuth = function(googleApi) {
this._googleApi = googleApi;
};
OAuth.prototype.isSupported = function() {
return !!(window.chrome && chrome.identity);
};
/**
* Request an OAuth token from chrome for gmail users
*/
OAuth.prototype.getOAuthToken = function(callback) {
// get OAuth Token from chrome
chrome.identity.getAuthToken({
'interactive': true
}, function(token) {
if ((chrome && chrome.runtime && chrome.runtime.lastError) || !token) {
callback({
errMsg: 'Error fetching an OAuth token for the user!'
});
return;
}
callback(null, token);
});
};
OAuth.prototype.queryEmailAddress = function(token, callback) {
if (!token) {
callback({
errMsg: 'Invalid OAuth token!'
});
return;
}
// fetch gmail user's email address from the Google Authorization Server
this._googleApi.get({
uri: '/oauth2/v1/tokeninfo?access_token=' + token
}, function(err, info) {
if (err || !info || !info.email) {
callback({
errMsg: 'Error looking up email address on google api!'
});
return;
}
callback(null, info.email);
});
};
return OAuth;
});

View File

@ -5,22 +5,21 @@ define(function(require) {
angular = require('angular'),
mocks = require('angularMocks'),
AddAccountCtrl = require('js/controller/add-account'),
Auth = require('js/bo/auth'),
appController = require('js/app-controller');
describe('Add Account Controller unit test', function() {
var scope, location, ctrl,
fetchOAuthTokenStub;
var scope, location, ctrl, authStub;
describe('connectToGoogle', function() {
beforeEach(function() {
// remember original module to restore later, then replace it
fetchOAuthTokenStub = sinon.stub(appController, 'fetchOAuthToken');
appController._auth = authStub = sinon.createStubInstance(Auth);
});
afterEach(function() {
// restore the app controller module
location && location.path && location.path.restore && location.path.restore();
fetchOAuthTokenStub.restore();
});
it('should fail on fetchOAuthToken error', function(done) {
@ -37,10 +36,10 @@ define(function(require) {
scope.onError = function(err) {
expect(err).to.equal(42);
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
expect(authStub.getCredentials.calledOnce).to.be.true;
done();
};
fetchOAuthTokenStub.yields(42);
authStub.getCredentials.yields(42);
scope.connectToGoogle();
});
@ -55,7 +54,7 @@ define(function(require) {
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login');
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
expect(authStub.getCredentials.calledOnce).to.be.true;
location.path.restore();
scope.$apply.restore();
@ -70,7 +69,7 @@ define(function(require) {
});
});
fetchOAuthTokenStub.yields();
authStub.getCredentials.yields();
scope.connectToGoogle();
});

View File

@ -6,11 +6,11 @@ define(function(require) {
OutboxBO = require('js/bo/outbox'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
UpdateHandler = require('js/util/update/update-handler'),
Auth = require('js/bo/auth'),
expect = chai.expect;
describe('App Controller unit tests', function() {
var emailDaoStub, outboxStub, updateHandlerStub, appConfigStoreStub, devicestorageStub, isOnlineStub,
identityStub;
var emailDaoStub, outboxStub, updateHandlerStub, appConfigStoreStub, devicestorageStub, isOnlineStub, authStub;
beforeEach(function() {
controller._emailDao = emailDaoStub = sinon.createStubInstance(EmailDAO);
@ -18,22 +18,31 @@ define(function(require) {
controller._appConfigStore = appConfigStoreStub = sinon.createStubInstance(DeviceStorageDAO);
controller._userStorage = devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
controller._updateHandler = updateHandlerStub = sinon.createStubInstance(UpdateHandler);
controller._auth = authStub = sinon.createStubInstance(Auth);
isOnlineStub = sinon.stub(controller, 'isOnline');
window.chrome = window.chrome || {};
window.chrome.identity = window.chrome.identity || {};
if (typeof window.chrome.identity.getAuthToken !== 'function') {
window.chrome.identity.getAuthToken = function() {};
}
identityStub = sinon.stub(window.chrome.identity, 'getAuthToken');
});
afterEach(function() {
identityStub.restore();
isOnlineStub.restore();
});
describe('buildModules', function() {
it('should work', function() {
controller.buildModules();
expect(controller._appConfigStore).to.exist;
expect(controller._auth).to.exist;
expect(controller._userStorage).to.exist;
expect(controller._invitationDao).to.exist;
expect(controller._keychain).to.exist;
expect(controller._crypto).to.exist;
expect(controller._pgpbuilder).to.exist;
expect(controller._emailDao).to.exist;
expect(controller._outboxBo).to.exist;
expect(controller._updateHandler).to.exist;
});
});
describe('start', function() {
it('should not explode', function(done) {
controller.start({
@ -57,17 +66,8 @@ define(function(require) {
});
describe('onConnect', function() {
var fetchOAuthTokenStub, getCertficateStub;
beforeEach(function() {
// buildModules
fetchOAuthTokenStub = sinon.stub(controller, 'fetchOAuthToken');
getCertficateStub = sinon.stub(controller, 'getCertficate');
});
afterEach(function() {
fetchOAuthTokenStub.restore();
getCertficateStub.restore();
controller._emailDao._account = {};
});
it('should not connect if offline', function(done) {
@ -79,171 +79,55 @@ define(function(require) {
});
});
it('should fail due to error in certificate', function(done) {
isOnlineStub.returns(true);
getCertficateStub.yields({});
it('should not connect if account is not initialized', function(done) {
controller._emailDao._account = null;
controller.onConnect(function(err) {
expect(err).to.exist;
expect(getCertficateStub.calledOnce).to.be.true;
expect(err).to.not.exist;
done();
});
});
it('should fail due to error in fetch oauth', function(done) {
it('should fail due to error in auth.getCredentials', function(done) {
isOnlineStub.returns(true);
getCertficateStub.yields(null, 'PEM');
fetchOAuthTokenStub.yields({});
authStub.getCredentials.withArgs({}).yields(new Error());
controller.onConnect(function(err) {
expect(err).to.exist;
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
expect(getCertficateStub.calledOnce).to.be.true;
expect(authStub.getCredentials.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
isOnlineStub.returns(true);
fetchOAuthTokenStub.yields(null, {
emailAddress: 'asfd@example.com'
authStub.getCredentials.withArgs({}).yields(null, {
emailAddress: 'asdf@example.com',
oauthToken: 'token',
sslCert: 'cert'
});
getCertficateStub.yields(null, 'PEM');
emailDaoStub.onConnect.yields();
controller.onConnect(function(err) {
expect(err).to.not.exist;
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
expect(getCertficateStub.calledOnce).to.be.true;
expect(authStub.getCredentials.calledOnce).to.be.true;
expect(emailDaoStub.onConnect.calledOnce).to.be.true;
done();
});
});
});
describe('getEmailAddress', function() {
var fetchOAuthTokenStub;
beforeEach(function() {
// buildModules
fetchOAuthTokenStub = sinon.stub(controller, 'fetchOAuthToken');
});
afterEach(function() {
fetchOAuthTokenStub.restore();
});
it('should fail due to error in config list items', function(done) {
appConfigStoreStub.listItems.yields({});
controller.getEmailAddress(function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should work if address is already cached', function(done) {
appConfigStoreStub.listItems.yields(null, ['asdf']);
controller.getEmailAddress(function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.exist;
done();
});
});
it('should fail first time if app is offline', function(done) {
appConfigStoreStub.listItems.yields(null, []);
isOnlineStub.returns(false);
controller.getEmailAddress(function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
expect(isOnlineStub.calledOnce).to.be.true;
done();
});
});
});
describe('fetchOAuthToken', function() {
var queryEmailAddressStub;
beforeEach(function() {
// buildModules
queryEmailAddressStub = sinon.stub(controller, 'queryEmailAddress');
});
afterEach(function() {
queryEmailAddressStub.restore();
});
it('should work', function(done) {
identityStub.yields('token42');
queryEmailAddressStub.yields(null, 'bob@asdf.com');
controller.fetchOAuthToken(function(err, res) {
expect(err).to.not.exist;
expect(res.emailAddress).to.equal('bob@asdf.com');
expect(res.token).to.equal('token42');
expect(queryEmailAddressStub.calledOnce).to.be.true;
expect(identityStub.calledOnce).to.be.true;
done();
});
});
it('should fail due to chrome api error', function(done) {
identityStub.yields();
controller.fetchOAuthToken(function(err) {
expect(err).to.exist;
expect(identityStub.calledOnce).to.be.true;
done();
});
});
it('should fail due error querying email address', function(done) {
identityStub.yields('token42');
queryEmailAddressStub.yields();
controller.fetchOAuthToken(function(err) {
expect(err).to.exist;
expect(queryEmailAddressStub.calledOnce).to.be.true;
expect(identityStub.calledOnce).to.be.true;
done();
});
});
});
describe('buildModules', function() {
it('should work', function() {
controller.buildModules();
expect(controller._userStorage).to.exist;
expect(controller._invitationDao).to.exist;
expect(controller._keychain).to.exist;
expect(controller._crypto).to.exist;
expect(controller._pgpbuilder).to.exist;
expect(controller._emailDao).to.exist;
expect(controller._outboxBo).to.exist;
expect(controller._updateHandler).to.exist;
});
});
describe('init', function() {
var buildModulesStub, onConnectStub, emailAddress;
var onConnectStub, emailAddress;
beforeEach(function() {
emailAddress = 'alice@bob.com';
// buildModules
buildModulesStub = sinon.stub(controller, 'buildModules');
buildModulesStub.returns();
// onConnect
onConnectStub = sinon.stub(controller, 'onConnect');
});
afterEach(function() {
buildModulesStub.restore();
onConnectStub.restore();
});

224
test/new-unit/auth-test.js Normal file
View File

@ -0,0 +1,224 @@
define(function(require) {
'use strict';
var Auth = require('js/bo/auth'),
OAuth = require('js/util/oauth'),
RestDAO = require('js/dao/rest-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
expect = chai.expect;
describe('Auth unit tests', function() {
var auth, appConfigStoreStub, oauthStub, caStub;
beforeEach(function() {
appConfigStoreStub = sinon.createStubInstance(DeviceStorageDAO);
oauthStub = sinon.createStubInstance(OAuth);
caStub = sinon.createStubInstance(RestDAO);
auth = new Auth(appConfigStoreStub, oauthStub, caStub);
});
afterEach(function() {});
describe('getCredentials', function() {
var getCertificateStub, queryEmailAddressStub;
beforeEach(function() {
getCertificateStub = sinon.stub(auth, 'getCertificate');
queryEmailAddressStub = sinon.stub(auth, 'queryEmailAddress');
});
it('should work', function(done) {
getCertificateStub.yields(null, 'cert');
queryEmailAddressStub.withArgs('token').yields(null, 'asdf@example.com');
oauthStub.getOAuthToken.yields(null, 'token');
auth.getCredentials({}, function(err, credentials) {
expect(err).to.not.exist;
expect(credentials.emailAddress).to.equal('asdf@example.com');
expect(credentials.oauthToken).to.equal('token');
expect(credentials.sslCert).to.equal('cert');
done();
});
});
it('should fail due to error in getCertificate', function(done) {
getCertificateStub.yields(new Error());
auth.getCredentials({}, function(err, credentials) {
expect(err).to.exist;
expect(credentials).to.not.exist;
done();
});
});
it('should fail due to error in getOAuthToken', function(done) {
getCertificateStub.yields(null, 'cert');
oauthStub.getOAuthToken.yields(new Error());
auth.getCredentials({}, function(err, credentials) {
expect(err).to.exist;
expect(credentials).to.not.exist;
done();
});
});
it('should fail due to error in queryEmailAddress', function(done) {
getCertificateStub.yields(null, 'cert');
queryEmailAddressStub.withArgs('token').yields(new Error());
oauthStub.getOAuthToken.yields(null, 'token');
auth.getCredentials({}, function(err, credentials) {
expect(err).to.exist;
expect(credentials).to.not.exist;
done();
});
});
});
describe('getCertificate', function() {
it('should work', function(done) {
caStub.get.yields(null, 'cert');
auth.getCertificate(function(err, cert) {
expect(err).to.not.exist;
expect(cert).to.equal('cert');
done();
});
});
it('should fail', function(done) {
caStub.get.yields(null, '');
auth.getCertificate(function(err, cert) {
expect(err).to.exist;
expect(cert).to.not.exist;
done();
});
});
});
describe('getEmailAddress', function() {
var getEmailAddressFromConfigStub;
beforeEach(function() {
getEmailAddressFromConfigStub = sinon.stub(auth, 'getEmailAddressFromConfig');
});
it('should work', function(done) {
getEmailAddressFromConfigStub.yields(null, 'asdf@example.com');
auth.getEmailAddress(function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should fail', function(done) {
getEmailAddressFromConfigStub.yields(new Error());
auth.getEmailAddress(function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
});
describe('getEmailAddressFromConfig', function() {
it('should work', function(done) {
appConfigStoreStub.listItems.withArgs('emailaddress', 0, null).yields(null, ['asdf@example.com']);
auth.getEmailAddressFromConfig(function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should return empty result', function(done) {
appConfigStoreStub.listItems.withArgs('emailaddress', 0, null).yields(null, []);
auth.getEmailAddressFromConfig(function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should fail', function(done) {
appConfigStoreStub.listItems.withArgs('emailaddress', 0, null).yields(new Error());
auth.getEmailAddressFromConfig(function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
});
describe('queryEmailAddress', function() {
var getEmailAddressFromConfigStub;
beforeEach(function() {
getEmailAddressFromConfigStub = sinon.stub(auth, 'getEmailAddressFromConfig');
});
it('should if already cached', function(done) {
getEmailAddressFromConfigStub.yields(null, 'asdf@example.com');
auth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should when querying oauth api', function(done) {
getEmailAddressFromConfigStub.yields();
oauthStub.queryEmailAddress.withArgs('token').yields(null, 'asdf@example.com');
appConfigStoreStub.storeList.withArgs(['asdf@example.com'], 'emailaddress').yields();
auth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should fail due to error in cache lookup', function(done) {
getEmailAddressFromConfigStub.yields(new Error());
auth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should fail due to error in oauth api', function(done) {
getEmailAddressFromConfigStub.yields();
oauthStub.queryEmailAddress.withArgs('token').yields(new Error());
auth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should fail due to error in oauth api', function(done) {
getEmailAddressFromConfigStub.yields();
oauthStub.queryEmailAddress.withArgs('token').yields(null, 'asdf@example.com');
appConfigStoreStub.storeList.withArgs(['asdf@example.com'], 'emailaddress').yields(new Error());
auth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.exist;
done();
});
});
});
});
});

View File

@ -6,6 +6,7 @@ define(function(require) {
mocks = require('angularMocks'),
LoginCtrl = require('js/controller/login'),
EmailDAO = require('js/dao/email-dao'),
Auth = require('js/bo/auth'),
appController = require('js/app-controller');
describe('Login Controller unit test', function() {
@ -13,7 +14,7 @@ define(function(require) {
emailAddress = 'fred@foo.com',
startAppStub,
checkForUpdateStub,
getEmailAddressStub,
authStub,
initStub;
describe('initialization', function() {
@ -27,12 +28,11 @@ define(function(require) {
// remember original module to restore later, then replace it
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._auth = authStub = sinon.createStubInstance(Auth);
startAppStub = sinon.stub(appController, 'start');
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
getEmailAddressStub = sinon.stub(appController, 'getEmailAddress');
initStub = sinon.stub(appController, 'init');
});
@ -50,13 +50,11 @@ define(function(require) {
appController._emailDao = origEmailDao;
appController.start.restore && appController.start.restore();
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
appController.fetchOAuthToken.restore && appController.fetchOAuthToken.restore();
appController.init.restore && appController.init.restore();
location.path.restore && location.path.restore();
startAppStub.restore();
checkForUpdateStub.restore();
getEmailAddressStub.restore();
initStub.restore();
});
@ -67,7 +65,7 @@ define(function(require) {
};
startAppStub.yields();
getEmailAddressStub.yields(null, emailAddress);
authStub.getEmailAddress.yields(null, emailAddress);
initStub.yields(null, testKeys);
emailDaoMock.unlock.withArgs({
@ -83,7 +81,7 @@ define(function(require) {
expect(path).to.equal('/desktop');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(getEmailAddressStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
@ -102,7 +100,7 @@ define(function(require) {
};
startAppStub.yields();
getEmailAddressStub.yields(null, emailAddress);
authStub.getEmailAddress.yields(null, emailAddress);
initStub.yields(null, testKeys);
emailDaoMock.unlock.withArgs({
@ -118,7 +116,7 @@ define(function(require) {
expect(path).to.equal('/login-existing');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(getEmailAddressStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
@ -132,7 +130,7 @@ define(function(require) {
it('should forward to new device login', function(done) {
startAppStub.yields();
getEmailAddressStub.yields(null, emailAddress);
authStub.getEmailAddress.yields(null, emailAddress);
initStub.yields(null, {
publicKey: 'b'
});
@ -145,7 +143,7 @@ define(function(require) {
expect(path).to.equal('/login-new-device');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(getEmailAddressStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
@ -159,7 +157,7 @@ define(function(require) {
it('should forward to initial login', function(done) {
startAppStub.yields();
getEmailAddressStub.yields(null, emailAddress);
authStub.getEmailAddress.yields(null, emailAddress);
initStub.yields();
angular.module('logintest', []);
@ -170,7 +168,7 @@ define(function(require) {
expect(path).to.equal('/login-initial');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(getEmailAddressStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();

View File

@ -28,6 +28,8 @@ function startTests() {
require(
[
'test/new-unit/oauth-test',
'test/new-unit/auth-test',
'test/new-unit/email-dao-test',
'test/new-unit/email-sync-test',
'test/new-unit/app-controller-test',

View File

@ -0,0 +1,92 @@
define(function(require) {
'use strict';
var OAuth = require('js/util/oauth'),
RestDAO = require('js/dao/rest-dao'),
expect = chai.expect;
describe('OAuth unit tests', function() {
var oauth, googleApiStub, identityStub;
beforeEach(function() {
googleApiStub = sinon.createStubInstance(RestDAO);
oauth = new OAuth(googleApiStub);
window.chrome = window.chrome || {};
window.chrome.identity = window.chrome.identity || {};
if (typeof window.chrome.identity.getAuthToken !== 'function') {
window.chrome.identity.getAuthToken = function() {};
}
identityStub = sinon.stub(window.chrome.identity, 'getAuthToken');
});
afterEach(function() {
identityStub.restore();
});
describe('isSupported', function() {
it('should work', function() {
expect(oauth.isSupported()).to.be.true;
});
});
describe('getOAuthToken', function() {
it('should work', function(done) {
identityStub.yields('token');
oauth.getOAuthToken(function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should fail', function(done) {
identityStub.yields();
oauth.getOAuthToken(function(err, token) {
expect(err).to.exist;
expect(token).to.not.exist;
done();
});
});
});
describe('queryEmailAddress', function() {
it('should work', function(done) {
googleApiStub.get.withArgs({
uri: '/oauth2/v1/tokeninfo?access_token=token'
}).yields(null, {
email: 'asdf@example.com'
});
oauth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should fail due to invalid token', function(done) {
oauth.queryEmailAddress('', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should fail due to error in rest api', function(done) {
googleApiStub.get.withArgs({
uri: '/oauth2/v1/tokeninfo?access_token=token'
}).yields(new Error());
oauth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
});
});
});

View File

@ -32,9 +32,7 @@ define(function(require) {
it('should accept default base uri', function() {
var baseUri = 'http://custom.com';
restDao = new RestDAO({
baseUri: baseUri
});
restDao = new RestDAO(baseUri);
expect(restDao).to.exist;
expect(restDao._baseUri).to.equal(baseUri);
});