diff --git a/src/js/email/account.js b/src/js/email/account.js index 171ac8e..9d72bfc 100644 --- a/src/js/email/account.js +++ b/src/js/email/account.js @@ -4,12 +4,9 @@ var ngModule = angular.module('woEmail'); ngModule.service('account', Account); module.exports = Account; -var axe = require('axe-logger'), - util = require('crypto-lib').util, - PgpMailer = require('pgpmailer'), - ImapClient = require('imap-client'); +var util = require('crypto-lib').util; -function Account(appConfig, auth, accountStore, email, outbox, keychain, updateHandler, pgpbuilder, dialog) { +function Account(appConfig, auth, accountStore, email, outbox, keychain, updateHandler, dialog) { this._appConfig = appConfig; this._auth = auth; this._accountStore = accountStore; @@ -17,7 +14,6 @@ function Account(appConfig, auth, accountStore, email, outbox, keychain, updateH this._outbox = outbox; this._keychain = keychain; this._updateHandler = updateHandler; - this._pgpbuilder = pgpbuilder; this._dialog = dialog; this._accounts = []; // init accounts list } @@ -102,68 +98,16 @@ Account.prototype.init = function(options) { }); }; -/** - * Check if the user agent is online. - */ -Account.prototype.isOnline = function() { - return navigator.onLine; -}; - /** * Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server. */ Account.prototype.onConnect = function(callback) { - var self = this; - var config = self._appConfig.config; - - callback = callback || self._dialog.error; - - if (!self.isOnline() || !self._emailDao || !self._emailDao._account) { + if (!this._emailDao || !this._emailDao._account) { // prevent connection infinite loop return; } - // 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; - - // tls socket worker path for multithreaded tls in non-native tls environments - credentials.imap.tlsWorkerPath = credentials.smtp.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js'; - - var pgpMailer = new PgpMailer(credentials.smtp, self._pgpbuilder); - var imapClient = new ImapClient(credentials.imap); - imapClient.onError = onConnectionError; - pgpMailer.onError = onConnectionError; - - // certificate update handling - imapClient.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'imap', self.onConnect.bind(self), self._dialog.error); - pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self.onConnect.bind(self), self._dialog.error); - - // connect to clients - return self._emailDao.onConnect({ - imapClient: imapClient, - pgpMailer: pgpMailer, - ignoreUploadOnSent: self._emailDao.checkIgnoreUploadOnSent(credentials.imap.host) - }); - }).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) : '')); - - setTimeout(function() { - axe.debug('Reconnecting...'); - // re-init client modules on error - self.onConnect(function(err) { - if (err) { - axe.error('Reconnect attempt failed! ' + (err.errMsg || err.message) + (err.stack ? ('\n' + err.stack) : '')); - return; - } - - axe.debug('Reconnect attempt complete.'); - }); - }, config.reconnectInterval); - } + this._emailDao.onConnect().then(callback).catch(callback); }; /** diff --git a/src/js/email/email.js b/src/js/email/email.js index 03b9aa6..709c71d 100644 --- a/src/js/email/email.js +++ b/src/js/email/email.js @@ -5,7 +5,10 @@ ngModule.service('email', Email); module.exports = Email; var config = require('../app-config').config, - str = require('../app-config').string; + str = require('../app-config').string, + axe = require('axe-logger'), + PgpMailer = require('pgpmailer'), + ImapClient = require('imap-client'); // // @@ -50,13 +53,15 @@ var MSG_PART_TYPE_HTML = 'html'; * @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages * @param {Object} mailreader Parses MIME messages received from IMAP */ -function Email(keychain, pgp, accountStore, pgpbuilder, mailreader, dialog) { +function Email(keychain, pgp, accountStore, pgpbuilder, mailreader, dialog, appConfig, auth) { this._keychain = keychain; this._pgp = pgp; this._devicestorage = accountStore; this._pgpbuilder = pgpbuilder; this._mailreader = mailreader; this._dialog = dialog; + this._appConfig = appConfig; + this._auth = auth; } @@ -896,37 +901,40 @@ Email.prototype.decryptBody = function(options) { * Encrypted (if necessary) and sends a message with a predefined clear text greeting. * * @param {Object} options.email The message to be sent + * @param {Object} mailer an instance of the pgpmailer to be used for testing purposes only */ -Email.prototype.sendEncrypted = function(options) { +Email.prototype.sendEncrypted = function(options, mailer) { // mime encode, sign, encrypt and send email via smtp return this._sendGeneric({ encrypt: true, smtpclient: options.smtpclient, // filled solely in the integration test, undefined in normal usage mail: options.email, publicKeysArmored: options.email.publicKeysArmored - }); + }, mailer); }; /** * Sends a signed message in the plain * * @param {Object} options.email The message to be sent + * @param {Object} mailer an instance of the pgpmailer to be used for testing purposes only */ -Email.prototype.sendPlaintext = function(options) { +Email.prototype.sendPlaintext = function(options, mailer) { // add suffix to plaintext mail options.email.body += str.signature + config.cloudUrl + '/' + this._account.emailAddress; // mime encode, sign and send email via smtp return this._sendGeneric({ smtpclient: options.smtpclient, // filled solely in the integration test, undefined in normal usage mail: options.email - }); + }, mailer); }; /** * This funtion wraps error handling for sending via pgpMailer and uploading to imap. * @param {Object} options.email The message to be sent + * @param {Object} mailer an instance of the pgpmailer to be used for testing purposes only */ -Email.prototype._sendGeneric = function(options) { +Email.prototype._sendGeneric = function(options, mailer) { var self = this; self.busy(); return new Promise(function(resolve) { @@ -934,8 +942,33 @@ Email.prototype._sendGeneric = function(options) { resolve(); }).then(function() { - return self._mailerSend(options); + // get the smtp credentials + return self._auth.getCredentials(); + }).then(function(credentials) { + // gmail does not require you to upload to the sent items folder after successful sending, whereas most other providers do + self.ignoreUploadOnSent = self.checkIgnoreUploadOnSent(credentials.smtp.host); + + // tls socket worker path for multithreaded tls in non-native tls environments + credentials.smtp.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js'; + + // create a new pgpmailer + self._pgpMailer = (mailer || new PgpMailer(credentials.smtp, self._pgpbuilder)); + + // certificate update retriggers sending after cert update is persisted + self._pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self._sendGeneric.bind(self, options), self._dialog.error); + }).then(function() { + + // send the email + return new Promise(function(resolve, reject) { + self._pgpMailer.send(options, function(err, rfcText) { + if (err) { + reject(err); + } else { + resolve(rfcText); + } + }); + }); }).then(function(rfcText) { // try to upload to sent, but we don't actually care if the upload failed or not // this should not negatively impact the process of sending @@ -987,29 +1020,45 @@ Email.prototype.encrypt = function(options) { * given instance of the imap client. If the connection attempt was successful, it will * update the locally available folders with the newly received IMAP folder listing. * - * @param {Object} options.imapClient The IMAP client used to receive messages - * @param {Object} options.pgpMailer The SMTP client used to send messages + * @param {Object} imap an instance of the imap-client to be used for testing purposes only */ -Email.prototype.onConnect = function(options) { +Email.prototype.onConnect = function(imap) { var self = this; self._account.loggingIn = true; - self._imapClient = options.imapClient; - self._pgpMailer = options.pgpMailer; + // init imap/smtp clients + return self._auth.getCredentials().then(function(credentials) { + // add the maximum update batch size for imap folders to the imap configuration + credentials.imap.maxUpdateSize = config.imapUpdateBatchSize; - // gmail does not require you to upload to the sent items folder after successful sending, whereas most other providers do - self.ignoreUploadOnSent = !!options.ignoreUploadOnSent; + // tls socket worker path for multithreaded tls in non-native tls environments + credentials.imap.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js'; - return imapLogin().then(function() { + self._imapClient = (imap || new ImapClient(credentials.imap)); + + self._imapClient.onError = onConnectionError; // connection error handling + self._imapClient.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'imap', self.onConnect.bind(self), self._dialog.error); // certificate update handling + self._imapClient.onSyncUpdate = self._onSyncUpdate.bind(self); // attach sync update handler + + }).then(function() { + // imap login + return new Promise(function(resolve, reject) { + self._imapClient.login(function(err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + + }).then(function() { self._account.loggingIn = false; // init folders return self._initFoldersFromImap(); }).then(function() { - // attach sync update handler - self._imapClient.onSyncUpdate = self._onSyncUpdate.bind(self); - // fill the imap mailboxCache with information we have locally available: // - highest locally available moseq (NB! JavaScript can't handle 64 bit uints, so modseq values are strings) // - list of locally available uids @@ -1071,16 +1120,20 @@ Email.prototype.onConnect = function(options) { }); }); - function imapLogin() { - return new Promise(function(resolve, reject) { - self._imapClient.login(function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); + function onConnectionError(error) { + axe.debug('IMAP connection error, disconnected. Reason: ' + error.message + (error.stack ? ('\n' + error.stack) : '')); + + if (!self.isOnline()) { + return; + } + + axe.debug('Attempting reconnect in ' + config.reconnectInterval / 1000 + ' seconds.'); + + setTimeout(function() { + axe.debug('Reconnecting the IMAP stack'); + // re-init client modules on error + self.onConnect().catch(self._dialog.error); + }, config.reconnectInterval); } }; @@ -1667,24 +1720,6 @@ Email.prototype._parse = function(options) { }); }; -/** - * Send email via smtp - * @param {Object} options The options to be passed to the pgpMailer - * @return {Promise} - */ -Email.prototype._mailerSend = function(options) { - var self = this; - return new Promise(function(resolve, reject) { - self._pgpMailer.send(options, function(err, rfcText) { - if (err) { - reject(err); - } else { - resolve(rfcText); - } - }); - }); -}; - /** * Uploads a message to the sent folder, if necessary. * Calls back immediately if ignoreUploadOnSent == true or not sent folder was found. @@ -1758,6 +1793,13 @@ Email.prototype.checkIgnoreUploadOnSent = function(hostname) { }; +/** + * Check if the user agent is online. + */ +Email.prototype.isOnline = function() { + return navigator.onLine; +}; + // // // Helper Functions diff --git a/src/js/service/auth.js b/src/js/service/auth.js index b4f9041..140b700 100644 --- a/src/js/service/auth.js +++ b/src/js/service/auth.js @@ -240,16 +240,8 @@ Auth.prototype.useOAuth = function(hostname) { Auth.prototype.getOAuthToken = function() { var self = this; - if (self.oauthToken) { - // removed cached token and get a new one - return self._oauth.refreshToken({ - emailAddress: self.emailAddress, - oldToken: self.oauthToken - }).then(onToken); - } else { - // get a fresh oauth token - return self._oauth.getOAuthToken(self.emailAddress).then(onToken); - } + // get a fresh oauth token + return self._oauth.getOAuthToken(self.emailAddress).then(onToken); function onToken(oauthToken) { // shortcut if the email address is already known @@ -317,7 +309,7 @@ Auth.prototype._loadCredentials = function() { * @param {Function} callback The error handler * @param {[type]} pemEncodedCert The PEM encoded SSL certificate */ -Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback, pemEncodedCert) { +Auth.prototype.handleCertificateUpdate = function(component, reconnectCallback, callback, pemEncodedCert) { var self = this; axe.debug('new ssl certificate received: ' + pemEncodedCert); @@ -351,7 +343,7 @@ Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback self[component].ca = pemEncodedCert; self.credentialsDirty = true; self.storeCredentials().then(function() { - onConnect(callback); + reconnectCallback(callback); }).catch(callback); } }); diff --git a/test/integration/email-dao-test.js b/test/integration/email-dao-test.js index c4ca728..82dc9a3 100644 --- a/test/integration/email-dao-test.js +++ b/test/integration/email-dao-test.js @@ -14,7 +14,7 @@ var ImapClient = require('imap-client'), describe('Email DAO integration tests', function() { this.timeout(100000); - var accountService, emailDao, imapClient, imapMessages, imapFolders, imapServer, smtpServer, smtpClient, userStorage, auth, + var accountService, emailDao, imapClient, pgpMailer, imapMessages, imapFolders, imapServer, smtpServer, smtpClient, userStorage, auth, mockKeyPair, inbox, spam; var testAccount = { @@ -209,6 +209,7 @@ describe('Email DAO integration tests', function() { }); function initAccountService() { + // create imap/smtp clients with stubbed tcp sockets imapClient = new ImapClient({ auth: { @@ -231,25 +232,10 @@ describe('Email DAO integration tests', function() { user: testAccount.user, xoauth2: testAccount.xoauth2 }, - secure: true, - ca: ['random string'], - onError: console.error + secure: true }); - smtpClient._TCPSocket = smtpServer.createTCPSocket(); - // stub the onConnect function to inject the test imap/smtp clients - sinon.stub(accountService, 'onConnect', function(cb) { - accountService._emailDao.onConnect({ - imapClient: imapClient, - pgpMailer: new PgpMailer({ - tls: { - ca: 'random string' - } - }, accountService._pgpbuilder) - }).then(cb).catch(cb); - }); - // clear the local database before each test var cleanup = new DeviceStorageDAO(new LawnchairDAO()); cleanup.init(testAccount.user).then(function() { @@ -260,12 +246,22 @@ describe('Email DAO integration tests', function() { userStorage = accountService._accountStore; auth = accountService._auth; + auth.setCredentials({ + emailAddress: testAccount.user, + password: 'asd', + smtp: {}, // host and port don't matter here since we're using + imap: {} // a preconfigured smtpclient with mocked tcp sockets + }); + auth.init().then(function() { accountService.init({ emailAddress: testAccount.user }).then(function() { emailDao = accountService._emailDao; + // retrieve the pgpbuilder from the emaildao and initialize the pgpmailer with the existing pgpbuilder + pgpMailer = new PgpMailer({}, emailDao._pgpbuilder); + // stub rest request to key server sinon.stub(emailDao._keychain._publicKeyDao, 'get').returns(resolves(mockKeyPair.publicKey)); sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').returns(resolves(mockKeyPair.publicKey)); @@ -297,9 +293,7 @@ describe('Email DAO integration tests', function() { passphrase: testAccount.pass, keypair: mockKeyPair }).then(function() { - accountService.onConnect(function(err) { - expect(err).to.not.exist; - }); + accountService._emailDao.onConnect(imapClient); }); }); }); @@ -310,7 +304,6 @@ describe('Email DAO integration tests', function() { afterEach(function(done) { openpgp.initWorker.restore(); mailreader.startWorker.restore(); - accountService.onConnect.restore(); imapClient.stopListeningForChanges(function() { imapClient.logout(function() { @@ -701,7 +694,7 @@ describe('Email DAO integration tests', function() { subject: 'plaintext test', body: 'hello world!' } - }).then(function() { + }, pgpMailer).then(function() { expect(smtpServer.onmail.callCount).to.equal(1); done(); }); @@ -726,7 +719,7 @@ describe('Email DAO integration tests', function() { body: 'hello world!', publicKeysArmored: [mockKeyPair.publicKey.publicKey] } - }).then(function() { + }, pgpMailer).then(function() { expect(smtpServer.onmail.callCount).to.equal(1); done(); }); @@ -771,7 +764,41 @@ describe('Email DAO integration tests', function() { subject: 'plaintext test', body: expectedBody } - }).then(function() {}); + }, pgpMailer).then(function() {}); + }); + + it('should send & receive a signed encrypted message', function(done) { + var expectedBody = "asdasdasdasdasdasdasdasdasdasdasdasd asdasdasdasdasdasdasdasdasdasdasdasd"; + + emailDao.onIncomingMessage = function(messages) { + emailDao.getBody({ + folder: inbox, + message: messages[0] + }).then(function(message) { + return emailDao.decryptBody({ + message: message + }); + }).then(function(message) { + expect(message.encrypted).to.be.true; + expect(message.signed).to.be.true; + expect(message.signaturesValid).to.be.true; + expect(message.attachments.length).to.equal(0); + expect(message.body).to.equal(expectedBody); + done(); + }); + }; + + emailDao.sendEncrypted({ + smtpclient: smtpClient, + + email: { + from: [testAccount.user], + to: [testAccount.user], + subject: 'plaintext test', + body: expectedBody, + publicKeysArmored: [mockKeyPair.publicKey.publicKey] + } + }, pgpMailer).then(function() {}); }); }); }); \ No newline at end of file diff --git a/test/unit/email/account-test.js b/test/unit/email/account-test.js index ff18481..36b38d7 100644 --- a/test/unit/email/account-test.js +++ b/test/unit/email/account-test.js @@ -11,7 +11,7 @@ var Account = require('../../../src/js/email/account'), Dialog = require('../../../src/js/util/dialog'); describe('Account Service unit test', function() { - var account, authStub, outboxStub, emailStub, devicestorageStub, keychainStub, updateHandlerStub, pgpbuilderStub, dialogStub, + var account, authStub, outboxStub, emailStub, devicestorageStub, keychainStub, updateHandlerStub, dialogStub, realname = 'John Doe', dummyUser = 'spiderpig@springfield.com'; @@ -25,9 +25,8 @@ describe('Account Service unit test', function() { outboxStub = sinon.createStubInstance(Outbox); keychainStub = sinon.createStubInstance(Keychain); updateHandlerStub = sinon.createStubInstance(UpdateHandler); - pgpbuilderStub = {}; dialogStub = sinon.createStubInstance(Dialog); - account = new Account(appConfig, authStub, devicestorageStub, emailStub, outboxStub, keychainStub, updateHandlerStub, pgpbuilderStub, dialogStub); + account = new Account(appConfig, authStub, devicestorageStub, emailStub, outboxStub, keychainStub, updateHandlerStub, dialogStub); }); afterEach(function() {}); @@ -200,47 +199,15 @@ describe('Account Service unit test', function() { }); describe('onConnect', function() { - var credentials = { - imap: {}, - smtp: {} - }; beforeEach(function() { emailStub._account = {}; - sinon.stub(account, 'isOnline').returns(true); - }); - afterEach(function() { - account.isOnline.restore(); - }); - - 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(); - }); - - it('should fail due to _auth.getCredentials', function(done) { - authStub.getCredentials.returns(rejects(new Error('asdf'))); - - 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; done(); }); diff --git a/test/unit/email/email-dao-test.js b/test/unit/email/email-dao-test.js index 0fa909c..cb9c161 100644 --- a/test/unit/email/email-dao-test.js +++ b/test/unit/email/email-dao-test.js @@ -9,6 +9,8 @@ var mailreader = require('mailreader'), KeychainDAO = require('../../../src/js/service/keychain'), PGP = require('../../../src/js/crypto/pgp'), DeviceStorageDAO = require('../../../src/js/service/devicestorage'), + appConfig = require('../../../src/js/app-config'), + Auth = require('../../../src/js/service/auth'), Dialog = require('../../../src/js/util/dialog'); @@ -20,7 +22,7 @@ describe('Email DAO unit tests', function() { var dao; // mocks - var keychainStub, imapClientStub, pgpMailerStub, pgpBuilderStub, pgpStub, devicestorageStub, parseStub, dialogStub; + var keychainStub, imapClientStub, pgpMailerStub, pgpBuilderStub, pgpStub, devicestorageStub, parseStub, dialogStub, authStub; // config var emailAddress, passphrase, asymKeySize, account; @@ -118,11 +120,12 @@ describe('Email DAO unit tests', function() { parseStub = sinon.stub(mailreader, 'parse'); devicestorageStub = sinon.createStubInstance(DeviceStorageDAO); dialogStub = sinon.createStubInstance(Dialog); + authStub = sinon.createStubInstance(Auth); // // setup the SUT // - dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader, dialogStub); + dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader, dialogStub, appConfig, authStub); dao._account = account; dao._pgpMailer = pgpMailerStub; dao._imapClient = imapClientStub; @@ -1616,11 +1619,23 @@ describe('Email DAO unit tests', function() { }); describe('#sendEncrypted', function() { - var publicKeys = ["PUBLIC KEY"], + var credentials, + publicKeys, + dummyMail, + msg; + + beforeEach(function() { + credentials = { + smtp: { + host: 'foo.io' + } + }; + publicKeys = ["PUBLIC KEY"]; dummyMail = { publicKeysArmored: publicKeys - }, + }; msg = 'wow. such message. much rfc2822.'; + }); it('should send encrypted and upload to sent', function(done) { imapClientStub.uploadMessage.withArgs({ @@ -1628,6 +1643,7 @@ describe('Email DAO unit tests', function() { message: msg }).yields(); + authStub.getCredentials.returns(resolves(credentials)); pgpMailerStub.send.withArgs({ encrypt: true, mail: dummyMail, @@ -1637,17 +1653,20 @@ describe('Email DAO unit tests', function() { dao.sendEncrypted({ email: dummyMail - }).then(function() { + }, pgpMailerStub).then(function() { + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.calledOnce).to.be.true; + expect(dao.ignoreUploadOnSent).to.be.false; done(); }); }); it('should send encrypted and not upload to sent', function(done) { - dao.ignoreUploadOnSent = true; + credentials.smtp.host = 'smtp.gmail.com'; + authStub.getCredentials.returns(resolves(credentials)); pgpMailerStub.send.withArgs({ encrypt: true, mail: dummyMail, @@ -1657,9 +1676,11 @@ describe('Email DAO unit tests', function() { dao.sendEncrypted({ email: dummyMail - }).then(function() { + }, pgpMailerStub).then(function() { + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.called).to.be.false; + expect(dao.ignoreUploadOnSent).to.be.true; done(); }); @@ -1668,12 +1689,14 @@ describe('Email DAO unit tests', function() { it('should send encrypted and ignore error on upload', function(done) { imapClientStub.uploadMessage.yields(new Error()); pgpMailerStub.send.yieldsAsync(null, msg); + authStub.getCredentials.returns(resolves(credentials)); dao.sendEncrypted({ email: dummyMail - }).then(function() { + }, pgpMailerStub).then(function() { expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.calledOnce).to.be.true; + expect(authStub.getCredentials.calledOnce).to.be.true; done(); }); @@ -1681,12 +1704,14 @@ describe('Email DAO unit tests', function() { it('should not send when pgpmailer fails', function(done) { pgpMailerStub.send.yieldsAsync({}); + authStub.getCredentials.returns(resolves(credentials)); dao.sendEncrypted({ email: dummyMail - }).catch(function(err) { + }, pgpMailerStub).catch(function(err) { expect(err).to.exist; + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.called).to.be.false; @@ -1699,8 +1724,9 @@ describe('Email DAO unit tests', function() { dao.sendEncrypted({ email: dummyMail - }).catch(function(err) { + }, pgpMailerStub).catch(function(err) { expect(err.code).to.equal(42); + expect(authStub.getCredentials.called).to.be.false; expect(pgpMailerStub.send.called).to.be.false; expect(imapClientStub.uploadMessage.called).to.be.false; done(); @@ -1710,14 +1736,26 @@ describe('Email DAO unit tests', function() { }); describe('#sendPlaintext', function() { - var dummyMail = {}; - var msg = 'wow. such message. much rfc2822.'; + var credentials, + dummyMail, + msg; + + beforeEach(function() { + credentials = { + smtp: { + host: 'foo.io' + } + }; + dummyMail = {}; + msg = 'wow. such message. much rfc2822.'; + }); it('should send in the plain and upload to sent', function(done) { pgpMailerStub.send.withArgs({ smtpclient: undefined, mail: dummyMail }).yieldsAsync(null, msg); + authStub.getCredentials.returns(resolves(credentials)); imapClientStub.uploadMessage.withArgs({ path: sentFolder.path, @@ -1726,7 +1764,8 @@ describe('Email DAO unit tests', function() { dao.sendPlaintext({ email: dummyMail - }).then(function() { + }, pgpMailerStub).then(function() { + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.calledOnce).to.be.true; done(); @@ -1735,15 +1774,18 @@ describe('Email DAO unit tests', function() { it('should send in the plain and not upload to sent', function(done) { dao.ignoreUploadOnSent = true; + credentials.smtp.host = 'smtp.gmail.com'; pgpMailerStub.send.withArgs({ smtpclient: undefined, mail: dummyMail }).yieldsAsync(null, msg); + authStub.getCredentials.returns(resolves(credentials)); dao.sendPlaintext({ email: dummyMail - }).then(function() { + }, pgpMailerStub).then(function() { + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.called).to.be.false; done(); @@ -1753,10 +1795,12 @@ describe('Email DAO unit tests', function() { it('should send and ignore error on upload', function(done) { imapClientStub.uploadMessage.yields(new Error()); pgpMailerStub.send.yieldsAsync(null, msg); + authStub.getCredentials.returns(resolves(credentials)); - dao.sendEncrypted({ + dao.sendPlaintext({ email: dummyMail - }).then(function() { + }, pgpMailerStub).then(function() { + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.calledOnce).to.be.true; @@ -1766,11 +1810,13 @@ describe('Email DAO unit tests', function() { it('should not send due to error', function(done) { pgpMailerStub.send.yieldsAsync({}); + authStub.getCredentials.returns(resolves(credentials)); dao.sendPlaintext({ email: dummyMail - }).catch(function(err) { + }, pgpMailerStub).catch(function(err) { expect(err).to.exist; + expect(authStub.getCredentials.calledOnce).to.be.true; expect(pgpMailerStub.send.calledOnce).to.be.true; expect(imapClientStub.uploadMessage.called).to.be.false; done(); @@ -1782,8 +1828,9 @@ describe('Email DAO unit tests', function() { dao.sendPlaintext({ email: dummyMail - }).catch(function(err) { + }, pgpMailerStub).catch(function(err) { expect(err.code).to.equal(42); + expect(authStub.getCredentials.called).to.be.false; expect(pgpMailerStub.send.called).to.be.false; expect(imapClientStub.uploadMessage.called).to.be.false; done(); @@ -1805,12 +1852,15 @@ describe('Email DAO unit tests', function() { describe('event handlers', function() { describe('#onConnect', function() { - var initFoldersStub; + var initFoldersStub, credentials; beforeEach(function() { initFoldersStub = sinon.stub(dao, '_initFoldersFromImap'); delete dao._imapClient; - delete dao._pgpMailer; + + credentials = { + imap: {} + }; }); it('should connect', function(done) { @@ -1818,16 +1868,13 @@ describe('Email DAO unit tests', function() { uid: 123, modseq: '123' }]; + authStub.getCredentials.returns(resolves(credentials)); imapClientStub.login.yieldsAsync(); imapClientStub.selectMailbox.yields(); imapClientStub.listenForChanges.yields(); initFoldersStub.returns(resolves()); - dao.onConnect({ - imapClient: imapClientStub, - pgpMailer: pgpMailerStub - }).then(function() { - expect(dao.ignoreUploadOnSent).to.be.false; + dao.onConnect(imapClientStub).then(function() { expect(imapClientStub.login.calledOnce).to.be.true; expect(imapClientStub.selectMailbox.calledOnce).to.be.true; expect(initFoldersStub.calledOnce).to.be.true; diff --git a/test/unit/service/auth-test.js b/test/unit/service/auth-test.js index 9c57655..34ca988 100644 --- a/test/unit/service/auth-test.js +++ b/test/unit/service/auth-test.js @@ -177,24 +177,6 @@ describe('Auth unit tests', function() { }); describe('#getOAuthToken', function() { - it('should refresh token with known email address', function(done) { - auth.emailAddress = emailAddress; - auth.oauthToken = 'oldToken'; - - oauthStub.refreshToken.withArgs({ - emailAddress: emailAddress, - oldToken: 'oldToken' - }).returns(resolves(oauthToken)); - - auth.getOAuthToken().then(function() { - expect(auth.emailAddress).to.equal(emailAddress); - expect(auth.oauthToken).to.equal(oauthToken); - expect(oauthStub.refreshToken.calledOnce).to.be.true; - - done(); - }); - }); - it('should fetch token with known email address', function(done) { auth.emailAddress = emailAddress; oauthStub.getOAuthToken.withArgs(emailAddress).returns(resolves(oauthToken));