From 3da5a552515df9b67e8366b65d5b02b13ef21dd7 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Thu, 12 Feb 2015 16:10:46 +0100 Subject: [PATCH 1/5] [WO-795] Port to promises --- package.json | 6 +- src/js/email/email.js | 174 ++++++----------------- src/js/util/connection-doctor.js | 30 ++-- test/integration/email-dao-test.js | 10 +- test/unit/email/email-dao-test.js | 101 +++++++------ test/unit/util/connection-doctor-test.js | 41 +++--- 6 files changed, 139 insertions(+), 223 deletions(-) diff --git a/package.json b/package.json index b12c4cf..2bdae6c 100644 --- a/package.json +++ b/package.json @@ -62,13 +62,13 @@ "grunt-string-replace": "~1.0.0", "grunt-svgmin": "~1.0.0", "grunt-svgstore": "~0.3.4", - "imap-client": "~0.10.0", + "imap-client": "https://github.com/whiteout-io/imap-client/tarball/dev/WO-794", "jquery": "~2.1.1", "mailreader": "~0.4.0", "mocha": "^1.21.4", "ng-infinite-scroll": "~1.1.2", - "pgpbuilder": "~0.5.0", - "pgpmailer": "~0.8.0", + "pgpbuilder": "https://github.com/whiteout-io/pgpbuilder/tarball/dev/WO-795", + "pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/dev/WO-795", "sinon": "~1.7.3", "tcp-socket": "~0.5.0", "time-grunt": "^1.0.0", diff --git a/src/js/email/email.js b/src/js/email/email.js index 709c71d..b0a75fa 100644 --- a/src/js/email/email.js +++ b/src/js/email/email.js @@ -188,23 +188,16 @@ Email.prototype.unlock = function(options) { */ Email.prototype.openFolder = function(options) { var self = this; - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { self.checkOnline(); + resolve(); - if (options.folder.path === config.outboxMailboxPath) { - resolve(); - return; + }).then(function() { + if (options.folder.path !== config.outboxMailboxPath) { + return self._imapClient.selectMailbox({ + path: options.folder.path + }); } - - self._imapClient.selectMailbox({ - path: options.folder.path - }, function(err, folder) { - if (err) { - reject(err); - } else { - resolve(folder); - } - }); }); }; /** @@ -960,15 +953,7 @@ Email.prototype._sendGeneric = function(options, mailer) { }).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); - } - }); - }); + return self._pgpMailer.send(options); }).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 @@ -990,20 +975,14 @@ Email.prototype._sendGeneric = function(options, mailer) { * Signs and encrypts a message * * @param {Object} options.email The message to be encrypted - * @param {Function} callback(error, message) Invoked when the message was encrypted, or an error occurred + * @param {Function} callback(message) Invoked when the message was encrypted, or an error occurred */ Email.prototype.encrypt = function(options) { var self = this; self.busy(); - return new Promise(function(resolve, reject) { - self._pgpbuilder.encrypt(options, function(err, message) { - self.done(); - if (err) { - reject(err); - } else { - resolve(message); - } - }); + return self._pgpbuilder.encrypt(options).then(function(message) { + self.done(); + return message; }); }; @@ -1043,15 +1022,7 @@ Email.prototype.onConnect = function(imap) { }).then(function() { // imap login - return new Promise(function(resolve, reject) { - self._imapClient.login(function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); + return self._imapClient.login(); }).then(function() { self._account.loggingIn = false; @@ -1185,7 +1156,7 @@ Email.prototype._onSyncUpdate = function(options) { folder: folder, firstUid: Math.min.apply(null, options.list), lastUid: Math.max.apply(null, options.list) - }, self._dialog.error); + }).then(self._dialog.error).catch(self._dialog.error); } else if (options.type === SYNC_TYPE_DELETED) { // messages have been deleted, remove from local storage and memory options.list.forEach(function(uid) { @@ -1201,7 +1172,7 @@ Email.prototype._onSyncUpdate = function(options) { folder: folder, message: message, localOnly: true - }, self._dialog.error); + }).then(self._dialog.error).catch(self._dialog.error); }); } else if (options.type === SYNC_TYPE_MSGS) { // NB! several possible reasons why this could be called. @@ -1228,7 +1199,7 @@ Email.prototype._onSyncUpdate = function(options) { folder: folder, message: message, localOnly: true - }, self._dialog.error); + }).then(self._dialog.error).catch(self._dialog.error); }); } }; @@ -1276,7 +1247,7 @@ Email.prototype._initFoldersFromImap = function() { self.busy(); // start the spinner // fetch list from imap server - return listWellknownFolder().then(function(wellKnownFolders) { + return self._imapClient.listWellKnownFolders().then(function(wellKnownFolders) { var foldersChanged = false, // indicates if we need to persist anything to disk imapFolders = []; // aggregate all the imap folders @@ -1409,18 +1380,6 @@ Email.prototype._initFoldersFromImap = function() { self.done(); // stop the spinner throw err; }); - - function listWellknownFolder() { - return new Promise(function(resolve, reject) { - self._imapClient.listWellKnownFolders(function(err, wellKnownFolders) { - if (err) { - reject(err); - } else { - resolve(wellKnownFolders); - } - }); - }); - } }; /** @@ -1475,17 +1434,13 @@ Email.prototype.done = function() { */ Email.prototype._imapMark = function(options) { var self = this; - return new Promise(function(resolve, reject) { - self.checkOnline(); + return new Promise(function(resolve) { + self.checkOnline(); + resolve(); + }).then(function() { options.path = options.folder.path; - self._imapClient.updateFlags(options, function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); + return self._imapClient.updateFlags(options); }); }; @@ -1510,7 +1465,10 @@ Email.prototype._imapDeleteMessage = function(options) { // there's no known trash folder to move the mail to or we're in the trash folder, so we can purge the message if (!trash || options.folder === trash) { - return imapDelete(); + return self._imapClient.deleteMessage({ + path: options.folder.path, + uid: options.uid + }); } return self._imapMoveMessage({ @@ -1519,21 +1477,6 @@ Email.prototype._imapDeleteMessage = function(options) { uid: options.uid }); }); - - function imapDelete() { - return new Promise(function(resolve, reject) { - self._imapClient.deleteMessage({ - path: options.folder.path, - uid: options.uid - }, function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } }; /** @@ -1546,19 +1489,14 @@ Email.prototype._imapDeleteMessage = function(options) { */ Email.prototype._imapMoveMessage = function(options) { var self = this; - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { self.checkOnline(); - - self._imapClient.moveMessage({ + resolve(); + }).then(function() { + return self._imapClient.moveMessage({ path: options.folder.path, destination: options.destination.path, uid: options.uid - }, function(err) { - if (err) { - reject(err); - } else { - resolve(); - } }); }); }; @@ -1575,17 +1513,12 @@ Email.prototype._imapMoveMessage = function(options) { */ Email.prototype._imapListMessages = function(options) { var self = this; - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { self.checkOnline(); - + resolve(); + }).then(function() { options.path = options.folder.path; - self._imapClient.listMessages(options, function(err, messages) { - if (err) { - reject(err); - } else { - resolve(messages); - } - }); + return self._imapClient.listMessages(options); }); }; @@ -1597,17 +1530,10 @@ Email.prototype._imapListMessages = function(options) { */ Email.prototype._imapUploadMessage = function(options) { var self = this; - return new Promise(function(resolve, reject) { - self._imapClient.uploadMessage({ - path: options.folder.path, - message: options.message - }, function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); + + return self._imapClient.uploadMessage({ + path: options.folder.path, + message: options.message }); }; @@ -1619,26 +1545,14 @@ Email.prototype._imapUploadMessage = function(options) { */ Email.prototype._getBodyParts = function(options) { var self = this; - return new Promise(function(resolve, reject) { + return new Promise(function(resolve) { self.checkOnline(); - + resolve(); + }).then(function() { options.path = options.folder.path; - self._imapClient.getBodyParts(options, function(err) { - if (err) { - reject(err); - return; - } - - // interpret the raw content of the email - self._mailreader.parse(options, function(err, message) { - if (err) { - reject(err); - return; - } - - resolve(message); - }); - }); + return self._imapClient.getBodyParts(options); + }).then(function() { + return self._parse(options); }); }; diff --git a/src/js/util/connection-doctor.js b/src/js/util/connection-doctor.js index eada553..538b20a 100644 --- a/src/js/util/connection-doctor.js +++ b/src/js/util/connection-doctor.js @@ -218,25 +218,21 @@ ConnectionDoctor.prototype._checkImap = function() { } }; - self._imap.login(function() { + self._imap.login().then(function() { loggedIn = true; + return self._imap.listWellKnownFolders(); + }).then(function(wellKnownFolders) { + if (wellKnownFolders.Inbox.length === 0) { + // the client needs at least an inbox folder to work properly + reject(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host))); + return; + } - self._imap.listWellKnownFolders(function(error, wellKnownFolders) { - if (error) { - reject(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error)); - return; - } - - if (wellKnownFolders.Inbox.length === 0) { - // the client needs at least an inbox folder to work properly - reject(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host))); - return; - } - - self._imap.logout(function() { - resolve(); - }); - }); + return self._imap.logout(); + }).then(function(){ + resolve(); + }).catch(function(error) { + reject(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error)); }); }); }; diff --git a/test/integration/email-dao-test.js b/test/integration/email-dao-test.js index 82dc9a3..d2b0d00 100644 --- a/test/integration/email-dao-test.js +++ b/test/integration/email-dao-test.js @@ -305,11 +305,11 @@ describe('Email DAO integration tests', function() { openpgp.initWorker.restore(); mailreader.startWorker.restore(); - imapClient.stopListeningForChanges(function() { - imapClient.logout(function() { - userStorage.clear().then(done); - }); - }); + imapClient.stopListeningForChanges().then(function() { + return imapClient.logout(); + }).then(function() { + return userStorage.clear(); + }).then(done); }); describe('IMAP Integration Tests', function() { diff --git a/test/unit/email/email-dao-test.js b/test/unit/email/email-dao-test.js index cb9c161..4ef5b81 100644 --- a/test/unit/email/email-dao-test.js +++ b/test/unit/email/email-dao-test.js @@ -294,7 +294,7 @@ describe('Email DAO unit tests', function() { it('should open an imap mailbox', function(done) { imapClientStub.selectMailbox.withArgs({ path: inboxFolder.path - }).yieldsAsync(); + }).returns(resolves()); dao.openFolder({ folder: inboxFolder @@ -534,7 +534,7 @@ describe('Email DAO unit tests', function() { content: '' + cfg.cloudUrl + cfg.verificationUrl + validUuid }])); - keychainStub.verifyPublicKey.withArgs(validUuid).yieldsAsync({}); + keychainStub.verifyPublicKey.withArgs(validUuid).returns(rejects({})); localStoreStub.withArgs({ folder: inboxFolder, @@ -1641,7 +1641,7 @@ describe('Email DAO unit tests', function() { imapClientStub.uploadMessage.withArgs({ path: sentFolder.path, message: msg - }).yields(); + }).returns(resolves()); authStub.getCredentials.returns(resolves(credentials)); pgpMailerStub.send.withArgs({ @@ -1649,7 +1649,7 @@ describe('Email DAO unit tests', function() { mail: dummyMail, smtpclient: undefined, publicKeysArmored: publicKeys - }).yieldsAsync(null, msg); + }).returns(resolves(msg)); dao.sendEncrypted({ email: dummyMail @@ -1672,7 +1672,7 @@ describe('Email DAO unit tests', function() { mail: dummyMail, smtpclient: undefined, publicKeysArmored: publicKeys - }).yieldsAsync(null, msg); + }).returns(resolves(msg)); dao.sendEncrypted({ email: dummyMail @@ -1687,8 +1687,8 @@ 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); + imapClientStub.uploadMessage.returns(rejects(new Error())); + pgpMailerStub.send.returns(resolves(msg)); authStub.getCredentials.returns(resolves(credentials)); dao.sendEncrypted({ @@ -1703,7 +1703,7 @@ describe('Email DAO unit tests', function() { }); it('should not send when pgpmailer fails', function(done) { - pgpMailerStub.send.yieldsAsync({}); + pgpMailerStub.send.returns(rejects({})); authStub.getCredentials.returns(resolves(credentials)); dao.sendEncrypted({ @@ -1754,13 +1754,13 @@ describe('Email DAO unit tests', function() { pgpMailerStub.send.withArgs({ smtpclient: undefined, mail: dummyMail - }).yieldsAsync(null, msg); + }).returns(resolves(msg)); authStub.getCredentials.returns(resolves(credentials)); imapClientStub.uploadMessage.withArgs({ path: sentFolder.path, message: msg - }).yields(); + }).returns(resolves()); dao.sendPlaintext({ email: dummyMail @@ -1779,7 +1779,7 @@ describe('Email DAO unit tests', function() { pgpMailerStub.send.withArgs({ smtpclient: undefined, mail: dummyMail - }).yieldsAsync(null, msg); + }).returns(resolves(msg)); authStub.getCredentials.returns(resolves(credentials)); dao.sendPlaintext({ @@ -1793,8 +1793,8 @@ 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); + imapClientStub.uploadMessage.returns(rejects(new Error())); + pgpMailerStub.send.returns(resolves(msg)); authStub.getCredentials.returns(resolves(credentials)); dao.sendPlaintext({ @@ -1809,7 +1809,7 @@ describe('Email DAO unit tests', function() { }); it('should not send due to error', function(done) { - pgpMailerStub.send.yieldsAsync({}); + pgpMailerStub.send.returns(rejects({})); authStub.getCredentials.returns(resolves(credentials)); dao.sendPlaintext({ @@ -1840,7 +1840,7 @@ describe('Email DAO unit tests', function() { describe('#encrypt', function() { it('should encrypt', function(done) { - pgpBuilderStub.encrypt.yieldsAsync(); + pgpBuilderStub.encrypt.returns(resolves()); dao.encrypt({}).then(function() { expect(pgpBuilderStub.encrypt.calledOnce).to.be.true; @@ -1869,9 +1869,9 @@ describe('Email DAO unit tests', function() { modseq: '123' }]; authStub.getCredentials.returns(resolves(credentials)); - imapClientStub.login.yieldsAsync(); - imapClientStub.selectMailbox.yields(); - imapClientStub.listenForChanges.yields(); + imapClientStub.login.returns(resolves()); + imapClientStub.selectMailbox.returns(resolves()); + imapClientStub.listenForChanges.returns(resolves()); initFoldersStub.returns(resolves()); dao.onConnect(imapClientStub).then(function() { @@ -1894,8 +1894,8 @@ describe('Email DAO unit tests', function() { describe('#onDisconnect', function() { it('should discard imapClient and pgpMailer', function(done) { - imapClientStub.stopListeningForChanges.yields(); - imapClientStub.logout.yields(); + imapClientStub.stopListeningForChanges.returns(resolves()); + imapClientStub.logout.returns(resolves()); dao.onDisconnect().then(function() { expect(imapClientStub.stopListeningForChanges.calledOnce).to.be.true; @@ -1923,12 +1923,12 @@ describe('Email DAO unit tests', function() { setFlagsStub = sinon.stub(dao, 'setFlags'); }); - it('should get new message', function() { + it('should get new message', function(done) { fetchMessagesStub.withArgs({ folder: inboxFolder, firstUid: 1, lastUid: 3 - }).yields(); + }).returns(resolves()); dao._onSyncUpdate({ type: 'new', @@ -1936,16 +1936,19 @@ describe('Email DAO unit tests', function() { list: [1, 3] }); - expect(dialogStub.error.calledOnce).to.be.true; - expect(fetchMessagesStub.calledOnce).to.be.true; + setTimeout(function() { + expect(dialogStub.error.calledOnce).to.be.true; + expect(fetchMessagesStub.calledOnce).to.be.true; + done(); + }, 0); }); - it('should delete message', function() { + it('should delete message', function(done) { deleteMessagesStub.withArgs({ folder: inboxFolder, message: msgs[0], localOnly: true - }).yields(); + }).returns(resolves()); dao._onSyncUpdate({ type: 'deleted', @@ -1953,16 +1956,19 @@ describe('Email DAO unit tests', function() { list: [5] }); - expect(dialogStub.error.calledOnce).to.be.true; - expect(deleteMessagesStub.calledOnce).to.be.true; + setTimeout(function() { + expect(dialogStub.error.calledOnce).to.be.true; + expect(deleteMessagesStub.calledOnce).to.be.true; + done(); + }, 0); }); - it('should fetch flags', function() { + it('should fetch flags', function(done) { setFlagsStub.withArgs({ folder: inboxFolder, message: msgs[0], localOnly: true - }).yields(); + }).returns(resolves()); dao._onSyncUpdate({ type: 'messages', @@ -1970,8 +1976,11 @@ describe('Email DAO unit tests', function() { list: msgs }); - expect(dialogStub.error.calledOnce).to.be.true; - expect(setFlagsStub.calledOnce).to.be.true; + setTimeout(function() { + expect(dialogStub.error.calledOnce).to.be.true; + expect(setFlagsStub.calledOnce).to.be.true; + done(); + }, 0); }); }); }); @@ -2102,14 +2111,14 @@ describe('Email DAO unit tests', function() { it('should initialize from imap if online', function(done) { account.folders = []; - imapClientStub.listWellKnownFolders.yieldsAsync(null, { + imapClientStub.listWellKnownFolders.returns(resolves({ Inbox: [inboxFolder], Sent: [sentFolder], Drafts: [draftsFolder], Trash: [trashFolder], Flagged: [flaggedFolder], Other: [otherFolder] - }); + })); devicestorageStub.storeList.withArgs(sinon.match(function(arg) { expect(arg[0][0].name).to.deep.equal(inboxFolder.name); expect(arg[0][0].path).to.deep.equal(inboxFolder.path); @@ -2151,14 +2160,14 @@ describe('Email DAO unit tests', function() { path: 'bar', }]; - imapClientStub.listWellKnownFolders.yieldsAsync(null, { + imapClientStub.listWellKnownFolders.returns(resolves({ Inbox: [inboxFolder], Sent: [sentFolder], Drafts: [draftsFolder], Trash: [trashFolder], Flagged: [flaggedFolder], Other: [otherFolder] - }); + })); devicestorageStub.storeList.withArgs(sinon.match(function(arg) { expect(arg[0]).to.deep.equal([{ name: inboxFolder.name, @@ -2218,7 +2227,7 @@ describe('Email DAO unit tests', function() { uid: 1, unread: false, answered: false - }).yieldsAsync(); + }).returns(resolves()); dao._imapMark({ folder: inboxFolder, @@ -2238,7 +2247,7 @@ describe('Email DAO unit tests', function() { path: inboxFolder.path, destination: sentFolder.path, uid: 123 - }).yieldsAsync(); + }).returns(resolves()); dao._imapMoveMessage({ folder: inboxFolder, @@ -2265,7 +2274,7 @@ describe('Email DAO unit tests', function() { path: inboxFolder.path, uid: uid, destination: trashFolder.path - }).yieldsAsync(); + }).returns(resolves()); dao._imapDeleteMessage({ folder: inboxFolder, @@ -2277,7 +2286,7 @@ describe('Email DAO unit tests', function() { imapClientStub.deleteMessage.withArgs({ path: trashFolder.path, uid: uid - }).yieldsAsync(); + }).returns(resolves()); dao._imapDeleteMessage({ folder: trashFolder, @@ -2296,7 +2305,7 @@ describe('Email DAO unit tests', function() { path: inboxFolder.path, firstUid: firstUid, lastUid: lastUid - }).yieldsAsync(null, []); + }).returns(resolves([])); dao._imapListMessages({ folder: inboxFolder, @@ -2312,7 +2321,7 @@ describe('Email DAO unit tests', function() { }); it('should fail when listMessages fails', function(done) { - imapClientStub.listMessages.yieldsAsync({}); + imapClientStub.listMessages.returns(rejects({})); dao._imapListMessages({ folder: inboxFolder, @@ -2343,7 +2352,7 @@ describe('Email DAO unit tests', function() { imapClientStub.uploadMessage.withArgs({ path: draftsFolder.path, message: msg - }).yields(); + }).returns(resolves()); dao._imapUploadMessage({ folder: draftsFolder, @@ -2363,7 +2372,7 @@ describe('Email DAO unit tests', function() { path: inboxFolder.path, uid: 123, bodyParts: [] - }).yieldsAsync(null, {}); + }).returns(resolves({})); parseStub.yieldsAsync(null, []); dao._getBodyParts({ @@ -2381,7 +2390,7 @@ describe('Email DAO unit tests', function() { }); it('should fail when getBody fails', function(done) { - imapClientStub.getBodyParts.yieldsAsync({}); + imapClientStub.getBodyParts.returns(rejects({})); dao._getBodyParts({ folder: inboxFolder, @@ -2476,7 +2485,7 @@ describe('Email DAO unit tests', function() { imapClientStub.uploadMessage.withArgs({ path: sentFolder.path, message: msg - }).yields(); + }).returns(resolves()); dao._uploadToSent({ message: msg diff --git a/test/unit/util/connection-doctor-test.js b/test/unit/util/connection-doctor-test.js index 18b7bbb..844781c 100644 --- a/test/unit/util/connection-doctor-test.js +++ b/test/unit/util/connection-doctor-test.js @@ -167,11 +167,11 @@ describe('Connection Doctor', function() { describe('#_checkImap', function() { it('should perform IMAP login, list folders, logout', function(done) { - imapStub.login.yieldsAsync(); - imapStub.listWellKnownFolders.yieldsAsync(null, { + imapStub.login.returns(resolves()); + imapStub.listWellKnownFolders.returns(resolves({ Inbox: [{}] - }); - imapStub.logout.yieldsAsync(); + })); + imapStub.logout.returns(resolves()); doctor._checkImap().then(function() { expect(imapStub.login.calledOnce).to.be.true; @@ -183,10 +183,11 @@ describe('Connection Doctor', function() { }); it('should fail w/ generic error on logout', function(done) { - imapStub.login.yieldsAsync(); - imapStub.listWellKnownFolders.yieldsAsync(null, { + imapStub.login.returns(resolves()); + imapStub.listWellKnownFolders.returns(resolves({ Inbox: [{}] - }); + })); + imapStub.logout.returns(rejects(new Error())); doctor._checkImap().catch(function(error) { expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR); @@ -197,18 +198,13 @@ describe('Connection Doctor', function() { done(); }); - - setTimeout(function() { - // this error is thrown while we're waiting for the logout - imapStub.onError(new Error()); - }, 50); }); it('should fail w/ generic error on inbox missing', function(done) { - imapStub.login.yieldsAsync(); - imapStub.listWellKnownFolders.yieldsAsync(null, { + imapStub.login.returns(resolves()); + imapStub.listWellKnownFolders.returns(resolves({ Inbox: [] - }); + })); doctor._checkImap().catch(function(error) { expect(error.code).to.equal(ConnectionDoctor.NO_INBOX); @@ -221,8 +217,8 @@ describe('Connection Doctor', function() { }); it('should fail w/ generic error on listing folders fails', function(done) { - imapStub.login.yieldsAsync(); - imapStub.listWellKnownFolders.yieldsAsync(new Error()); + imapStub.login.returns(resolves()); + imapStub.listWellKnownFolders.returns(rejects(new Error())); doctor._checkImap().catch(function(error) { expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR); @@ -236,6 +232,12 @@ describe('Connection Doctor', function() { }); it('should fail w/ auth rejected', function(done) { + imapStub.login.returns(new Promise(function() { + setTimeout(function() { + imapStub.onError(new Error()); + }, 0); + })); + doctor._checkImap().catch(function(error) { expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED); expect(error.underlyingError).to.exist; @@ -245,11 +247,6 @@ describe('Connection Doctor', function() { done(); }); - - setTimeout(function() { - // this error is thrown while we're waiting for the login - imapStub.onError(new Error()); - }, 50); }); }); From 2e3e07aa1db14eed78459c9055bc0e8de5b28441 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Thu, 12 Feb 2015 16:18:42 +0100 Subject: [PATCH 2/5] [WO-855] Fix trying to set a flag when email is deleted --- src/js/email/email.js | 6 ++++++ test/unit/email/email-dao-test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/js/email/email.js b/src/js/email/email.js index b0a75fa..04da5b5 100644 --- a/src/js/email/email.js +++ b/src/js/email/email.js @@ -492,6 +492,12 @@ Email.prototype.setFlags = function(options) { }).then(function(storedMessages) { // set the flags var storedMessage = storedMessages[0]; + + if (!storedMessage) { + // the message has been deleted in the meantime + return; + } + storedMessage.unread = options.message.unread; storedMessage.flagged = options.message.flagged; storedMessage.answered = options.message.answered; diff --git a/test/unit/email/email-dao-test.js b/test/unit/email/email-dao-test.js index 4ef5b81..ec92765 100644 --- a/test/unit/email/email-dao-test.js +++ b/test/unit/email/email-dao-test.js @@ -733,6 +733,32 @@ describe('Email DAO unit tests', function() { }); }); + it('should not explode when message has been deleted during imap roundtrip', function(done) { + imapMark.withArgs({ + folder: inboxFolder, + uid: message.uid, + unread: message.unread, + answered: message.answered, + flagged: message.flagged + }).returns(resolves()); + + localListStub.withArgs({ + folder: inboxFolder, + uid: message.uid + }).returns(resolves([])); + + dao.setFlags({ + folder: inboxFolder, + message: message + }).then(function() { + expect(imapMark.calledOnce).to.be.true; + expect(localListStub.calledOnce).to.be.true; + expect(localStoreStub.called).to.be.false; + + done(); + }); + }); + it('should set flags for outbox for disk, memory', function(done) { localListStub.withArgs({ folder: outboxFolder, From 0faa5b37431ad8059e8b61d2da17ac95375a87bc Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Fri, 13 Feb 2015 16:18:10 +0100 Subject: [PATCH 3/5] Track emails by uid instead hashKey attribute --- src/tpl/mail-list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tpl/mail-list.html b/src/tpl/mail-list.html index e0c758e..7a230f0 100644 --- a/src/tpl/mail-list.html +++ b/src/tpl/mail-list.html @@ -28,7 +28,7 @@
  • + ng-repeat="email in displayMessages track by email.uid">
    • From 54d495d8d9bc49991d3756724a3f3afc63bafc5f Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Fri, 13 Feb 2015 17:10:44 +0100 Subject: [PATCH 4/5] [WO-804] Fix bug where message cannot be parsed when deleted from IMAP --- src/js/email/email.js | 23 +++++- test/unit/email/email-dao-test.js | 129 +++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/src/js/email/email.js b/src/js/email/email.js index 04da5b5..3c6d477 100644 --- a/src/js/email/email.js +++ b/src/js/email/email.js @@ -609,12 +609,15 @@ Email.prototype.getBody = function(options) { folder: folder, uid: message.uid }).then(function(localMessages) { - if (localMessages.length === 0) { - return; - } - localMessage = localMessages[0]; + if (!localMessage) { + // the message has been deleted in the meantime + var error = new Error('Can not get the contents of this message. It has already been deleted!'); + error.hide = true; + throw error; + } + // treat attachment and non-attachment body parts separately: // we need to fetch the content for non-attachment body parts (encrypted, signed, text, html, resources referenced from the html) // but we spare the effort and fetch attachment content later upon explicit user request. @@ -730,6 +733,10 @@ Email.prototype.getBody = function(options) { }).catch(function(err) { self.done(); message.loadingBody = false; + if (err.hide) { + // ignore errors with err.hide + return message; + } throw err; }); @@ -1558,6 +1565,14 @@ Email.prototype._getBodyParts = function(options) { options.path = options.folder.path; return self._imapClient.getBodyParts(options); }).then(function() { + if (options.bodyParts.filter(function(bodyPart) { + return !(bodyPart.raw || bodyPart.content); + }).length) { + var error = new Error('Can not get the contents of this message. It has already been deleted!'); + error.hide = true; + throw error; + } + return self._parse(options); }); }; diff --git a/test/unit/email/email-dao-test.js b/test/unit/email/email-dao-test.js index ec92765..b1c6298 100644 --- a/test/unit/email/email-dao-test.js +++ b/test/unit/email/email-dao-test.js @@ -1250,6 +1250,80 @@ describe('Email DAO unit tests', function() { expect(message.loadingBody).to.be.true; }); + it('should not error when message is deleted from imap', function(done) { + var error = new Error('Can not get the contents of this message. It has already been deleted!'); + error.hide = true; + + var message = { + uid: uid, + encrypted: true, + bodyParts: [{ + type: 'text' + }] + }; + + localListStub.withArgs({ + folder: inboxFolder, + uid: uid + }).returns(resolves([message])); + + localStoreStub.withArgs({ + folder: inboxFolder, + emails: [message] + }).returns(resolves()); + + imapGetStub.withArgs({ + folder: inboxFolder, + uid: message.uid, + bodyParts: message.bodyParts + }).returns(rejects(error)); + + + dao.getBody({ + message: message, + folder: inboxFolder + }).then(function(msg) { + expect(msg).to.equal(message); + expect(msg.body).to.not.exist; + expect(msg.loadingBody).to.be.false; + + expect(localListStub.calledOnce).to.be.true; + expect(imapGetStub.calledOnce).to.be.true; + expect(localStoreStub.called).to.be.false; + + done(); + }); + expect(message.loadingBody).to.be.true; + }); + + it('should not error when message has already been removed from memory', function(done) { + var message = { + uid: uid, + encrypted: true, + bodyParts: [{ + type: 'text' + }] + }; + + localListStub.returns(resolves([])); + + dao.getBody({ + message: message, + folder: inboxFolder + }).then(function(msg) { + expect(msg).to.equal(message); + expect(msg.body).to.not.exist; + expect(msg.loadingBody).to.be.false; + + expect(localListStub.calledOnce).to.be.true; + expect(imapGetStub.called).to.be.false; + expect(localStoreStub.called).to.be.false; + + done(); + }); + expect(message.loadingBody).to.be.true; + }); + it('fail to stream from imap due to error when persisting', function(done) { var message = { uid: uid, @@ -2393,18 +2467,23 @@ describe('Email DAO unit tests', function() { describe('#_getBodyParts', function() { it('should get bodyParts', function(done) { + var bp = [{ + type: 'text', + content: 'bender is great! bender is great!' + }]; + imapClientStub.getBodyParts.withArgs({ folder: inboxFolder, path: inboxFolder.path, uid: 123, - bodyParts: [] - }).returns(resolves({})); + bodyParts: bp + }).returns(resolves(bp)); parseStub.yieldsAsync(null, []); dao._getBodyParts({ folder: inboxFolder, uid: 123, - bodyParts: [] + bodyParts: bp }).then(function(parts) { expect(parts).to.exist; @@ -2415,6 +2494,50 @@ describe('Email DAO unit tests', function() { }); }); + it('should fail when deleted on IMAP', function(done) { + var bp = [{ + type: 'text' + }]; + + imapClientStub.getBodyParts.withArgs({ + folder: inboxFolder, + path: inboxFolder.path, + uid: 123, + bodyParts: bp + }).returns(resolves()); + parseStub.yieldsAsync(null, []); + + dao._getBodyParts({ + folder: inboxFolder, + uid: 123, + bodyParts: bp + }).catch(function(err) { + expect(err).to.exist; + + expect(imapClientStub.getBodyParts.calledOnce).to.be.true; + expect(parseStub.called).to.be.false; + + done(); + }); + }); + + it('should fail when getBody fails', function(done) { + imapClientStub.getBodyParts.returns(rejects({})); + + dao._getBodyParts({ + folder: inboxFolder, + uid: 123, + bodyParts: [] + }).catch(function(err) { + expect(err).to.exist; + + expect(imapClientStub.getBodyParts.calledOnce).to.be.true; + expect(parseStub.called).to.be.false; + + done(); + }); + }); + it('should fail when getBody fails', function(done) { imapClientStub.getBodyParts.returns(rejects({})); From 163ad5db79d842d87d9080bf56c55a5b00ac09b9 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 16 Feb 2015 13:35:15 +0100 Subject: [PATCH 5/5] Bump email.js libs to promisified versions --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2bdae6c..7285942 100644 --- a/package.json +++ b/package.json @@ -62,13 +62,13 @@ "grunt-string-replace": "~1.0.0", "grunt-svgmin": "~1.0.0", "grunt-svgstore": "~0.3.4", - "imap-client": "https://github.com/whiteout-io/imap-client/tarball/dev/WO-794", + "imap-client": "~0.11.0", "jquery": "~2.1.1", "mailreader": "~0.4.0", "mocha": "^1.21.4", "ng-infinite-scroll": "~1.1.2", - "pgpbuilder": "https://github.com/whiteout-io/pgpbuilder/tarball/dev/WO-795", - "pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/dev/WO-795", + "pgpbuilder": "~0.6.0", + "pgpmailer": "~0.9.0", "sinon": "~1.7.3", "tcp-socket": "~0.5.0", "time-grunt": "^1.0.0",