diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index 3195583..b1633dd 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -386,31 +386,29 @@ define(function(require) { } function doImapDelta() { - self._imapListMessages({ + self._imapSearch({ folder: folder.path - }, function(err, headers) { + }, function(err, uids) { if (err) { self._account.busy = false; callback(err); return; } - // ignore non-whitelisted mails - var nonWhitelisted = _.filter(headers, function(header) { - return header.subject.indexOf(str.subjectPrefix) === -1; - }); - nonWhitelisted.forEach(function(i) { - headers.splice(headers.indexOf(i), 1); + // uidWrappers is just to wrap the bare uids in an object { uid: 123 } so + // the checkDelta function can treat it like something that resembles a stripped down email object... + var uidWrappers = _.map(uids, function(uid) { + return { + uid: uid + }; }); /* * delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage * delta4: imap > memory => we have new messages available, fetch to memory and storage - * deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory */ - delta3 = checkDelta(folder.messages, headers); - delta4 = checkDelta(headers, folder.messages); - deltaF4 = checkFlags(headers, folder.messages); + delta3 = checkDelta(folder.messages, uidWrappers); + delta4 = checkDelta(uidWrappers, folder.messages); doDelta3(); @@ -451,22 +449,36 @@ define(function(require) { // we have new messages available, fetch to memory and storage // (downstream sync) function doDelta4() { - // no delta, we're done here - if (_.isEmpty(delta4)) { - doDeltaF4(); - return; + // eliminate uids smaller than the biggest local uid, i.e. just fetch everything + // that came in AFTER the most recent email we have in memory. Keep in mind that + // uids are strictly ascending, so there can't be a NEW mail in the mailbox with a + // uid smaller than anything we've encountered before. + if (!_.isEmpty(folder.messages)) { + var localUids = _.pluck(folder.messages, 'uid'), + maxLocalUid = Math.max.apply(null, localUids); + + // eliminate everything prior to maxLocalUid + delta4 = _.filter(delta4, function(uidWrapper) { + return uidWrapper.uid > maxLocalUid; + }); } - var after = _.after(delta4.length, function() { - doDeltaF4(); - }); + syncNextItem(); + + function syncNextItem() { + // no delta, we're done here + if (_.isEmpty(delta4)) { + doDeltaF4(); + return; + } + + // delta4 contains the headers that are newly available on the remote + var nextUidWrapper = delta4.shift(); - // delta4 contains the headers that are newly available on the remote - delta4.forEach(function(imapHeader) { // get the whole message self._imapGetMessage({ folder: folder.path, - uid: imapHeader.uid + uid: nextUidWrapper.uid }, function(err, message) { if (err) { self._account.busy = false; @@ -474,22 +486,22 @@ define(function(require) { return; } - // create a bastard child of smtp and imap. - // before thinking this is stupid, talk to the guys who wrote this. - imapHeader.id = message.id; - imapHeader.body = message.body; - imapHeader.html = message.html; - imapHeader.attachments = message.attachments; + // imap filtering may not be sufficient, since google filters out + // non-alphabetical characters + if (message.subject.indexOf(str.subjectPrefix) === -1) { + syncNextItem(); + return; + } - if (isVerificationMail(imapHeader)) { - verify(imapHeader, function(err) { + if (isVerificationMail(message)) { + verify(message, function(err) { if (err) { self._account.busy = false; callback(err); return; } - after(); + syncNextItem(); }); return; } @@ -497,7 +509,7 @@ define(function(require) { // add the encrypted message to the local storage self._localStoreMessages({ folder: folder.path, - emails: [imapHeader] + emails: [message] }, function(err) { if (err) { self._account.busy = false; @@ -506,7 +518,7 @@ define(function(require) { } // decrypt and add to folder in memory - handleMessage(imapHeader, function(err, cleartextMessage) { + handleMessage(message, function(err, cleartextMessage) { if (err) { self._account.busy = false; callback(err); @@ -514,40 +526,109 @@ define(function(require) { } folder.messages.push(cleartextMessage); - after(); + syncNextItem(); }); }); }); + } + } + }); + + function doDeltaF4() { + var answeredUids, unreadUids; + + getUnreadUids(); + + // find all the relevant unread mails + function getUnreadUids() { + self._imapSearch({ + folder: folder.path, + unread: true + }, function(err, uids) { + if (err) { + self._account.busy = false; + callback(err); + return; + } + + // we're done here, let's get all the answered mails + unreadUids = uids; + getAnsweredUids(); }); } - // we have a mismatch concerning flags between imap and memory. - // pull changes from imap. - function doDeltaF4() { - function finishSync() { - self._account.busy = false; - folder.count = _.filter(folder.messages, function(msg) { - return msg.unread === true; - }).length; - callback(); - } + // find all the relevant answered mails + function getAnsweredUids() { + // find all the relevant answered mails + self._imapSearch({ + folder: folder.path, + answered: true + }, function(err, uids) { + if (err) { + self._account.busy = false; + callback(err); + return; + } + // we're done here, let's update what we have in memory and persist that! + answeredUids = uids; + updateFlags(); + }); + + } + + function updateFlags() { + // deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory + deltaF4 = []; + + folder.messages.forEach(function(msg) { + // if the message's uid is among the uids that should be unread, + // AND the message is not unread, we clearly have to change that + var shouldBeUnread = _.contains(unreadUids, msg.uid); + if (msg.unread === shouldBeUnread) { + // everything is in order, we're good here + return; + } + + msg.unread = shouldBeUnread; + deltaF4.push(msg); + }); + + folder.messages.forEach(function(msg) { + // if the message's uid is among the uids that should be answered, + // AND the message is not answered, we clearly have to change that + var shouldBeAnswered = _.contains(answeredUids, msg.uid); + if (msg.answered === shouldBeAnswered) { + // everything is in order, we're good here + return; + } + + msg.answered = shouldBeAnswered; + deltaF4.push(msg); + }); + + // maybe a mail had BOTH flags wrong, so let's create + // a duplicate-free version of deltaF4 + deltaF4 = _.uniq(deltaF4); + + // everything up to date? fine, we're done! if (_.isEmpty(deltaF4)) { finishSync(); return; } var after = _.after(deltaF4.length, function() { + // we're doing updating everything finishSync(); }); - // deltaF4 contains the imap headers that have changed flags - deltaF4.forEach(function(imapHeader) { + // alright, so let's sync the corrected messages + deltaF4.forEach(function(inMemoryMessage) { // do a short round trip to the database to avoid re-encrypting, // instead use the encrypted object in the storage self._localListMessages({ folder: folder.path, - uid: imapHeader.uid + uid: inMemoryMessage.uid }, function(err, storedMessages) { if (err) { self._account.busy = false; @@ -556,9 +637,10 @@ define(function(require) { } var storedMessage = storedMessages[0]; - storedMessage.unread = imapHeader.unread; - storedMessage.answered = imapHeader.answered; + storedMessage.unread = inMemoryMessage.unread; + storedMessage.answered = inMemoryMessage.answered; + // persist the modified object self._localStoreMessages({ folder: folder.path, emails: [storedMessage] @@ -569,19 +651,24 @@ define(function(require) { return; } - // after the metadata of the encrypted object has changed, proceed with the live object - var inMemoryMessage = _.findWhere(folder.messages, { - uid: imapHeader.uid - }); - inMemoryMessage.unread = imapHeader.unread; - inMemoryMessage.answered = imapHeader.answered; - + // and we're done. after(); }); }); + }); } - }); + + function finishSync() { + // after all the tags are up to date, let's adjust the unread mail count + folder.count = _.filter(folder.messages, function(msg) { + return msg.unread === true; + }).length; + // allow the next sync to take place + self._account.busy = false; + callback(); + } + } } /* @@ -959,10 +1046,13 @@ define(function(require) { }; /** - * List messages from an imap folder. This will not yet fetch the email body. - * @param {String} options.folderName The name of the imap folder. + * Returns the relevant messages corresponding to the search terms in the options + * @param {String} options.folder The folder's path + * @param {Boolean} options.answered (optional) Mails with or without the \Answered flag set. + * @param {Boolean} options.unread (optional) Mails with or without the \Seen flag set. + * @param {Function} callback(error, uids) invoked with the uids of messages matching the search terms, or an error object if an error occurred */ - EmailDAO.prototype._imapListMessages = function(options, callback) { + EmailDAO.prototype._imapSearch = function(options, callback) { if (!this._imapClient) { callback({ errMsg: 'Client is currently offline!', @@ -971,11 +1061,19 @@ define(function(require) { return; } - this._imapClient.listMessages({ + var o = { path: options.folder, - offset: 0, - length: 100 - }, callback); + subject: str.subjectPrefix + }; + + if (typeof options.answered !== 'undefined') { + o.answered = options.answered; + } + if (typeof options.unread !== 'undefined') { + o.unread = options.unread; + } + + this._imapClient.search(o, callback); }; EmailDAO.prototype._imapDeleteMessage = function(options, callback) { @@ -998,6 +1096,8 @@ define(function(require) { * @param {String} options.messageId The */ EmailDAO.prototype._imapGetMessage = function(options, callback) { + var self = this; + if (!this._imapClient) { callback({ errMsg: 'Client is currently offline!', @@ -1006,10 +1106,37 @@ define(function(require) { return; } - this._imapClient.getMessagePreview({ + self._imapClient.listMessagesByUid({ path: options.folder, - uid: options.uid - }, callback); + firstUid: options.uid, + lastUid: options.uid + }, function(err, imapHeaders) { + if (err) { + callback(err); + return; + } + + var imapHeader = imapHeaders[0]; + self._imapClient.getMessagePreview({ + path: options.folder, + uid: options.uid + }, function(err, message) { + if (err) { + callback(err); + return; + } + + // create a bastard child of smtp and imap. before thinking this is stupid, talk to the guys who wrote this. + // p.s. it's a parsing issue. + + imapHeader.id = message.id; + imapHeader.body = message.body; + imapHeader.html = message.html; + imapHeader.attachments = message.attachments; + + callback(null, imapHeader); + }); + }); }; /** diff --git a/test/new-unit/email-dao-test.js b/test/new-unit/email-dao-test.js index 334930c..0b32534 100644 --- a/test/new-unit/email-dao-test.js +++ b/test/new-unit/email-dao-test.js @@ -609,12 +609,12 @@ define(function(require) { }); }); - describe('_imapListMessages', function() { + describe('_imapSearch', function() { it('should fail when disconnected', function(done) { dao.onDisconnect(null, function(err) { expect(err).to.not.exist; - dao._imapListMessages({}, function(err) { + dao._imapSearch({}, function(err) { expect(err.code).to.equal(42); done(); }); @@ -624,16 +624,43 @@ define(function(require) { it('should work', function(done) { var path = 'FOLDAAAA'; - imapClientStub.listMessages.withArgs({ + imapClientStub.search.withArgs({ path: path, - offset: 0, - length: 100 + subject: '[whiteout] ' }).yields(); - dao._imapListMessages({ + dao._imapSearch({ folder: path }, done); }); + it('should work', function(done) { + var path = 'FOLDAAAA'; + + imapClientStub.search.withArgs({ + path: path, + subject: '[whiteout] ', + answered: true + }).yields(); + + dao._imapSearch({ + folder: path, + answered: true + }, done); + }); + it('should work', function(done) { + var path = 'FOLDAAAA'; + + imapClientStub.search.withArgs({ + path: path, + subject: '[whiteout] ', + unread: true + }).yields(); + + dao._imapSearch({ + folder: path, + unread: true + }, done); + }); }); describe('_imapDeleteMessage', function() { @@ -680,15 +707,81 @@ define(function(require) { var path = 'FOLDAAAA', uid = 1337; + imapClientStub.listMessagesByUid.withArgs({ + path: path, + firstUid: uid, + lastUid: uid + }).yields(null, [{ + uid: uid, + subject: 'asdasd', + unread: true, + answered: false + }]); + imapClientStub.getMessagePreview.withArgs({ path: path, uid: uid - }).yields(); + }).yields(null, { + id: 'idididididid', + body: 'yes.' + }); dao._imapGetMessage({ folder: path, uid: uid - }, done); + }, function(err, msg) { + expect(err).to.not.exist; + expect(msg).to.exist; + + expect(imapClientStub.listMessagesByUid.calledOnce).to.be.true; + expect(imapClientStub.getMessagePreview.calledOnce).to.be.true; + + done(); + }); + }); + it('should not work when listMessages fails', function(done) { + var path = 'FOLDAAAA', + uid = 1337; + + imapClientStub.listMessagesByUid.yields({}); + + dao._imapGetMessage({ + folder: path, + uid: uid + }, function(err, msg) { + expect(err).to.exist; + expect(msg).to.not.exist; + + expect(imapClientStub.listMessagesByUid.calledOnce).to.be.true; + expect(imapClientStub.getMessagePreview.called).to.be.false; + + done(); + }); + }); + it('should not work when getMessagePreview fails', function(done) { + var path = 'FOLDAAAA', + uid = 1337; + + imapClientStub.listMessagesByUid.yields(null, [{ + uid: uid, + subject: 'asdasd', + unread: true, + answered: false + }]); + imapClientStub.getMessagePreview.yields({}); + + dao._imapGetMessage({ + folder: path, + uid: uid + }, function(err, msg) { + expect(err).to.exist; + expect(msg).to.not.exist; + + expect(imapClientStub.listMessagesByUid.calledOnce).to.be.true; + expect(imapClientStub.getMessagePreview.calledOnce).to.be.true; + + done(); + }); }); }); @@ -753,7 +846,7 @@ define(function(require) { describe('sync', function() { it('should work initially', function(done) { - var folder, localListStub, invocations, imapListStub; + var folder, localListStub, invocations, imapSearchStub; invocations = 0; folder = 'FOLDAAAA'; @@ -769,14 +862,18 @@ define(function(require) { }).yields(null, [dummyEncryptedMail]); keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body); - imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({ + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ folder: folder - }).yields(null, [{ - uid: dummyEncryptedMail.uid, - subject: '[whiteout] ' + dummyEncryptedMail.subject, // the object has already been manipulated as a side-effect... - unread: dummyEncryptedMail.unread, - answered: dummyEncryptedMail.answered - }]); + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); dao.sync({ folder: folder @@ -794,6 +891,7 @@ define(function(require) { expect(localListStub.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(dao._account.folders[0].count).to.equal(1); done(); @@ -828,7 +926,7 @@ define(function(require) { }); it('should initially sync downstream when storage is empty', function(done) { - var folder, localListStub, localStoreStub, invocations, imapListStub, imapGetStub; + var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub; invocations = 0; folder = 'FOLDAAAA'; @@ -838,19 +936,31 @@ define(function(require) { }]; dummyEncryptedMail.unread = true; + dummyEncryptedMail.answered = true; localListStub = sinon.stub(dao, '_localListMessages').withArgs({ folder: folder }).yields(null, []); - keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); - pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body); - imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({ - folder: folder - }).yields(null, [dummyEncryptedMail]); imapGetStub = sinon.stub(dao, '_imapGetMessage').withArgs({ folder: folder, uid: dummyEncryptedMail.uid }).yields(null, dummyEncryptedMail); + keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); + pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body); + + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, [dummyEncryptedMail.uid]); + localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); dao.sync({ @@ -867,7 +977,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.not.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; @@ -920,7 +1030,7 @@ define(function(require) { }); it('should be up to date', function(done) { - var folder, localListStub, imapListStub, invocations; + var folder, localListStub, imapSearchStub, invocations; invocations = 0; folder = 'FOLDAAAA'; @@ -933,9 +1043,18 @@ define(function(require) { localListStub = sinon.stub(dao, '_localListMessages').withArgs({ folder: folder }).yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({ + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ folder: folder - }).yields(null, [dummyEncryptedMail]); + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); dao.sync({ @@ -952,13 +1071,13 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0]).to.not.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; done(); }); }); - it('should error while listing from imap', function(done) { - var folder, localListStub, imapListStub, invocations; + it('should error while searching on imap', function(done) { + var folder, localListStub, imapSearchStub, invocations; invocations = 0; folder = 'FOLDAAAA'; @@ -969,8 +1088,10 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields({}); - + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields({}); dao.sync({ folder: folder @@ -987,7 +1108,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0]).to.not.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; done(); }); }); @@ -1016,7 +1137,7 @@ define(function(require) { }); it('should remove messages from the remote', function(done) { - var invocations, folder, localListStub, imapListStub, localDeleteStub, imapDeleteStub; + var invocations, folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1027,7 +1148,19 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, []); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); + imapDeleteStub = sinon.stub(dao, '_imapDeleteMessage').yields(); localDeleteStub = sinon.stub(dao, '_localDeleteMessage').yields(); @@ -1045,7 +1178,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(localDeleteStub.calledOnce).to.be.true; expect(imapDeleteStub.calledOnce).to.be.true; done(); @@ -1053,7 +1186,7 @@ define(function(require) { }); it('should error whilte removing messages from local', function(done) { - var invocations, folder, localListStub, imapListStub, localDeleteStub, imapDeleteStub; + var invocations, folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1064,7 +1197,7 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, []); + imapSearchStub = sinon.stub(dao, '_imapSearch'); imapDeleteStub = sinon.stub(dao, '_imapDeleteMessage').yields(); localDeleteStub = sinon.stub(dao, '_localDeleteMessage').yields({}); @@ -1076,15 +1209,15 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.called).to.be.false; expect(localDeleteStub.calledOnce).to.be.true; expect(imapDeleteStub.calledOnce).to.be.true; + expect(imapSearchStub.called).to.be.false; done(); }); }); it('should error while removing messages from the remote', function(done) { - var folder, localListStub, imapListStub, localDeleteStub, imapDeleteStub; + var folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub; folder = 'FOLDAAAA'; dao._account.folders = [{ @@ -1094,7 +1227,7 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, []); + imapSearchStub = sinon.stub(dao, '_imapSearch'); imapDeleteStub = sinon.stub(dao, '_imapDeleteMessage').yields({}); localDeleteStub = sinon.stub(dao, '_localDeleteMessage'); @@ -1108,13 +1241,14 @@ define(function(require) { expect(localListStub.calledOnce).to.be.true; expect(imapDeleteStub.calledOnce).to.be.true; expect(localDeleteStub.called).to.be.false; + expect(imapSearchStub.called).to.be.false; done(); }); }); it('should delete messages locally if not present on remote', function(done) { - var invocations, folder, localListStub, imapListStub, localDeleteStub; + var invocations, folder, localListStub, imapSearchStub, localDeleteStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1128,9 +1262,18 @@ define(function(require) { localListStub = sinon.stub(dao, '_localListMessages').withArgs({ folder: folder }).yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({ + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ folder: folder }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); localDeleteStub = sinon.stub(dao, '_localDeleteMessage').withArgs({ folder: folder, uid: dummyEncryptedMail.uid @@ -1150,7 +1293,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(localDeleteStub.calledOnce).to.be.true; done(); }); @@ -1158,7 +1301,7 @@ define(function(require) { }); it('should error while deleting locally if not present on remote', function(done) { - var invocations, folder, localListStub, imapListStub, localDeleteStub; + var invocations, folder, localListStub, imapSearchStub, localDeleteStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1170,8 +1313,11 @@ define(function(require) { localListStub = sinon.stub(dao, '_localListMessages').yields(null, [dummyEncryptedMail]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, []); localDeleteStub = sinon.stub(dao, '_localDeleteMessage').yields({}); + imapSearchStub = sinon.stub(dao, '_imapSearch').withArgs({ + folder: folder + }).yields(null, []); + dao.sync({ folder: folder @@ -1187,15 +1333,14 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.not.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(localDeleteStub.calledOnce).to.be.true; done(); }); - }); it('should fetch messages downstream from the remote', function(done) { - var invocations, folder, localListStub, imapListStub, imapGetStub, localStoreStub; + var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1208,13 +1353,24 @@ define(function(require) { localListStub = sinon.stub(dao, '_localListMessages').withArgs({ folder: folder }).yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({ + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ folder: folder - }).yields(null, [dummyEncryptedMail]); + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); + imapGetStub = sinon.stub(dao, '_imapGetMessage').withArgs({ folder: folder, uid: dummyEncryptedMail.uid }).yields(null, dummyEncryptedMail); + localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body); @@ -1234,7 +1390,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.not.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; @@ -1244,7 +1400,7 @@ define(function(require) { }); it('should not fetch non-whitelisted mails', function(done) { - var invocations, folder, localListStub, imapListStub, imapGetStub, localStoreStub; + var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1255,9 +1411,20 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [nonWhitelistedMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [nonWhitelistedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, nonWhitelistedMail); - localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); + localStoreStub = sinon.stub(dao, '_localStoreMessages'); dao.sync({ folder: folder @@ -1273,8 +1440,8 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; - expect(imapGetStub.called).to.be.false; + expect(imapSearchStub.calledThrice).to.be.true; + expect(imapGetStub.calledOnce).to.be.true; expect(localStoreStub.called).to.be.false; expect(keychainStub.getReceiverPublicKey.called).to.be.false; expect(pgpStub.decrypt.called).to.be.false; @@ -1283,7 +1450,7 @@ define(function(require) { }); it('should error while decrypting fetch messages from the remote', function(done) { - var invocations, folder, localListStub, imapListStub, imapGetStub, localStoreStub; + var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1294,7 +1461,10 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [dummyEncryptedMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, dummyEncryptedMail); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); keychainStub.getReceiverPublicKey.yields({}); @@ -1314,7 +1484,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; @@ -1324,7 +1494,7 @@ define(function(require) { }); it('should error while storing messages from the remote locally', function(done) { - var invocations, folder, localListStub, imapListStub, imapGetStub, localStoreStub; + var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1335,7 +1505,10 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [dummyEncryptedMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, dummyEncryptedMail); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields({}); @@ -1354,7 +1527,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.called).to.be.false; @@ -1364,7 +1537,7 @@ define(function(require) { }); it('should error while fetching messages from the remote', function(done) { - var invocations, folder, localListStub, imapListStub, imapGetStub, localStoreStub; + var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1375,7 +1548,10 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [dummyEncryptedMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields({}); localStoreStub = sinon.stub(dao, '_localStoreMessages'); @@ -1394,7 +1570,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(localStoreStub.called).to.be.false; expect(keychainStub.getReceiverPublicKey.called).to.be.false; @@ -1404,8 +1580,7 @@ define(function(require) { }); it('should verify an authentication mail', function(done) { - var invocations, folder, localListStub, imapListStub, - imapGetStub, markReadStub, imapDeleteStub; + var invocations, folder, localListStub, imapSearchStub, imapGetStub, markReadStub, imapDeleteStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1416,7 +1591,19 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [verificationMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [verificationMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); + imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, verificationMail); keychainStub.verifyPublicKey.withArgs(verificationUuid).yields(); markReadStub = sinon.stub(dao, '_imapMark').withArgs({ @@ -1443,7 +1630,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(keychainStub.verifyPublicKey.calledOnce).to.be.true; expect(markReadStub.calledOnce).to.be.true; @@ -1454,7 +1641,7 @@ define(function(require) { }); it('should fail during deletion of an authentication mail', function(done) { - var invocations, folder, localListStub, imapListStub, + var invocations, folder, localListStub, imapSearchStub, imapGetStub, markReadStub, imapDeleteStub; invocations = 0; @@ -1466,7 +1653,11 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [verificationMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [verificationMail.uid]); + imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, verificationMail); keychainStub.verifyPublicKey.yields(); markReadStub = sinon.stub(dao, '_imapMark').yields(); @@ -1487,7 +1678,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(keychainStub.verifyPublicKey.calledOnce).to.be.true; expect(markReadStub.calledOnce).to.be.true; @@ -1496,8 +1687,9 @@ define(function(require) { done(); }); }); + it('should fail during marking an authentication mail read', function(done) { - var invocations, folder, localListStub, imapListStub, + var invocations, folder, localListStub, imapSearchStub, imapGetStub, markReadStub, imapDeleteStub; invocations = 0; @@ -1509,7 +1701,11 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [verificationMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [verificationMail.uid]); + imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, verificationMail); keychainStub.verifyPublicKey.yields(); markReadStub = sinon.stub(dao, '_imapMark').yields({}); @@ -1530,7 +1726,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(keychainStub.verifyPublicKey.calledOnce).to.be.true; expect(markReadStub.calledOnce).to.be.true; @@ -1539,8 +1735,9 @@ define(function(require) { done(); }); }); + it('should fail during verifying authentication', function(done) { - var invocations, folder, localListStub, imapListStub, + var invocations, folder, localListStub, imapSearchStub, imapGetStub, markReadStub, imapDeleteStub; invocations = 0; @@ -1552,7 +1749,10 @@ define(function(require) { }]; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [verificationMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [verificationMail.uid]); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, verificationMail); keychainStub.verifyPublicKey.yields({}); markReadStub = sinon.stub(dao, '_imapMark'); @@ -1573,7 +1773,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledOnce).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(keychainStub.verifyPublicKey.calledOnce).to.be.true; expect(markReadStub.called).to.be.false; @@ -1582,8 +1782,9 @@ define(function(require) { done(); }); }); + it('should not bother about read authentication mails', function(done) { - var invocations, folder, localListStub, imapListStub, + var invocations, folder, localListStub, imapSearchStub, imapGetStub, markReadStub, imapDeleteStub; invocations = 0; @@ -1597,7 +1798,18 @@ define(function(require) { verificationMail.unread = false; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [verificationMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [verificationMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, verificationMail); markReadStub = sinon.stub(dao, '_imapMark'); imapDeleteStub = sinon.stub(dao, '_imapDeleteMessage'); @@ -1616,7 +1828,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(keychainStub.verifyPublicKey.called).to.be.false; expect(markReadStub.called).to.be.false; @@ -1625,8 +1837,9 @@ define(function(require) { done(); }); }); - it('should not bother about read authentication mails', function(done) { - var invocations, folder, localListStub, imapListStub, + + it('should not bother about corrupted authentication mails', function(done) { + var invocations, folder, localListStub, imapSearchStub, imapGetStub, markReadStub, imapDeleteStub; invocations = 0; @@ -1640,7 +1853,18 @@ define(function(require) { verificationMail.body = 'url? there is no url.'; localListStub = sinon.stub(dao, '_localListMessages').yields(null, []); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [verificationMail]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [verificationMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); imapGetStub = sinon.stub(dao, '_imapGetMessage').yields(null, verificationMail); markReadStub = sinon.stub(dao, '_imapMark'); imapDeleteStub = sinon.stub(dao, '_imapDeleteMessage'); @@ -1659,7 +1883,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0].messages).to.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(imapGetStub.calledOnce).to.be.true; expect(keychainStub.verifyPublicKey.called).to.be.false; expect(markReadStub.called).to.be.false; @@ -1670,7 +1894,7 @@ define(function(require) { }); it('should sync tags from memory to imap and storage', function(done) { - var folder, localListStub, imapListStub, invocations, + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; invocations = 0; @@ -1686,7 +1910,18 @@ define(function(require) { dummyDecryptedMail.unread = inImap.unread = true; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [inImap]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [inImap.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, [inImap.uid]); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); markStub = sinon.stub(dao, '_imapMark').withArgs({ folder: folder, uid: dummyDecryptedMail.uid, @@ -1712,7 +1947,7 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0]).to.not.be.empty; expect(localListStub.calledOnce).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(markStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true; @@ -1723,9 +1958,8 @@ define(function(require) { }); }); - it('should error while syncing tags from memory to storage', function(done) { - var folder, localListStub, imapListStub, invocations, - markStub, localStoreStub; + it('should error while syncing unread tags from memory to storage', function(done) { + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; invocations = 0; folder = 'FOLDAAAA'; @@ -1740,7 +1974,7 @@ define(function(require) { dummyDecryptedMail.unread = inImap.unread = true; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [inImap]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); markStub = sinon.stub(dao, '_imapMark').yields(); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields({}); @@ -1754,13 +1988,48 @@ define(function(require) { expect(localListStub.calledOnce).to.be.true; expect(markStub.calledOnce).to.be.true; expect(localStoreStub.calledOnce).to.be.true; - expect(imapListStub.called).to.be.false; + expect(imapSearchStub.called).to.be.false; + done(); + }); + }); + + it('should error while syncing answered tags from memory to storage', function(done) { + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; + + invocations = 0; + folder = 'FOLDAAAA'; + dao._account.folders = [{ + type: 'Folder', + path: folder, + messages: [dummyDecryptedMail] + }]; + + var inStorage = JSON.parse(JSON.stringify(dummyEncryptedMail)); + var inImap = JSON.parse(JSON.stringify(dummyEncryptedMail)); + dummyDecryptedMail.unread = inImap.unread = true; + + localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + markStub = sinon.stub(dao, '_imapMark').yields(); + localStoreStub = sinon.stub(dao, '_localStoreMessages').yields({}); + + dao.sync({ + folder: folder + }, function(err) { + expect(err).to.exist; + + expect(dao._account.busy).to.be.false; + expect(dao._account.folders[0]).to.not.be.empty; + expect(localListStub.calledOnce).to.be.true; + expect(markStub.calledOnce).to.be.true; + expect(localStoreStub.calledOnce).to.be.true; + expect(imapSearchStub.called).to.be.false; done(); }); }); it('should error while syncing tags from memory to imap', function(done) { - var folder, localListStub, imapListStub, invocations, + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; invocations = 0; @@ -1776,7 +2045,7 @@ define(function(require) { dummyDecryptedMail.unread = inImap.unread = true; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [inImap]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); markStub = sinon.stub(dao, '_imapMark').yields({}); localStoreStub = sinon.stub(dao, '_localStoreMessages'); @@ -1790,13 +2059,13 @@ define(function(require) { expect(localListStub.calledOnce).to.be.true; expect(markStub.calledOnce).to.be.true; expect(localStoreStub.called).to.be.false; - expect(imapListStub.called).to.be.false; + expect(imapSearchStub.called).to.be.false; done(); }); }); it('should sync tags from imap to memory and storage', function(done) { - var folder, localListStub, imapListStub, invocations, + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; invocations = 0; @@ -1808,11 +2077,21 @@ define(function(require) { }]; var inStorage = JSON.parse(JSON.stringify(dummyEncryptedMail)); - var inImap = JSON.parse(JSON.stringify(dummyEncryptedMail)); dummyDecryptedMail.unread = inStorage.unread = true; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [inImap]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); markStub = sinon.stub(dao, '_imapMark'); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); @@ -1830,19 +2109,127 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0]).to.not.be.empty; expect(localListStub.calledTwice).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(markStub.called).to.be.false; expect(localStoreStub.calledOnce).to.be.true; - expect(dummyDecryptedMail.unread).to.equal(inImap.unread); - expect(inStorage.unread).to.equal(inImap.unread); + expect(dummyDecryptedMail.unread).to.equal(false); + expect(inStorage.unread).to.equal(false); + + done(); + }); + }); + + it('should error while searching for unread tags on imap', function(done) { + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; + + invocations = 0; + folder = 'FOLDAAAA'; + dao._account.folders = [{ + type: 'Folder', + path: folder, + messages: [dummyDecryptedMail] + }]; + + var inStorage = JSON.parse(JSON.stringify(dummyEncryptedMail)); + dummyDecryptedMail.unread = inStorage.unread = true; + + localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields({}); + markStub = sinon.stub(dao, '_imapMark'); + localStoreStub = sinon.stub(dao, '_localStoreMessages'); + + dao.sync({ + folder: folder + }, function(err) { + + if (invocations === 0) { + expect(err).to.not.exist; + expect(dao._account.busy).to.be.true; + invocations++; + return; + } + + expect(err).to.exist; + expect(dao._account.busy).to.be.false; + expect(dao._account.folders[0]).to.not.be.empty; + expect(localListStub.calledOnce).to.be.true; + expect(markStub.called).to.be.false; + expect(imapSearchStub.calledTwice).to.be.true; + expect(localStoreStub.called).to.be.false; + + expect(inStorage.unread).to.equal(true); + expect(dummyDecryptedMail.unread).to.equal(true); // the live object has not been touched! + + done(); + }); + }); + + it('should error while searching for answered tags on imap', function(done) { + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; + + invocations = 0; + folder = 'FOLDAAAA'; + dao._account.folders = [{ + type: 'Folder', + path: folder, + messages: [dummyDecryptedMail] + }]; + + var inStorage = JSON.parse(JSON.stringify(dummyEncryptedMail)); + dummyDecryptedMail.unread = inStorage.unread = true; + + localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields({}); + markStub = sinon.stub(dao, '_imapMark'); + localStoreStub = sinon.stub(dao, '_localStoreMessages'); + + dao.sync({ + folder: folder + }, function(err) { + + if (invocations === 0) { + expect(err).to.not.exist; + expect(dao._account.busy).to.be.true; + invocations++; + return; + } + + expect(err).to.exist; + expect(dao._account.busy).to.be.false; + expect(dao._account.folders[0]).to.not.be.empty; + expect(localListStub.calledOnce).to.be.true; + expect(markStub.called).to.be.false; + expect(imapSearchStub.calledThrice).to.be.true; + expect(localStoreStub.called).to.be.false; + + expect(inStorage.unread).to.equal(true); + expect(dummyDecryptedMail.unread).to.equal(true); // the live object has not been touched! done(); }); }); it('should error while syncing tags from imap to storage', function(done) { - var folder, localListStub, imapListStub, invocations, + var folder, localListStub, imapSearchStub, invocations, markStub, localStoreStub; invocations = 0; @@ -1854,11 +2241,21 @@ define(function(require) { }]; var inStorage = JSON.parse(JSON.stringify(dummyEncryptedMail)); - var inImap = JSON.parse(JSON.stringify(dummyEncryptedMail)); dummyDecryptedMail.unread = inStorage.unread = true; localListStub = sinon.stub(dao, '_localListMessages').yields(null, [inStorage]); - imapListStub = sinon.stub(dao, '_imapListMessages').yields(null, [inImap]); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); markStub = sinon.stub(dao, '_imapMark'); localStoreStub = sinon.stub(dao, '_localStoreMessages').yields({}); @@ -1877,13 +2274,10 @@ define(function(require) { expect(dao._account.busy).to.be.false; expect(dao._account.folders[0]).to.not.be.empty; expect(localListStub.calledTwice).to.be.true; - expect(imapListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; expect(markStub.called).to.be.false; expect(localStoreStub.calledOnce).to.be.true; - expect(inStorage.unread).to.equal(inImap.unread); - expect(dummyDecryptedMail.unread).to.equal(true); // the live object has not been touched! - done(); }); });