1
0
mirror of https://github.com/moparisthebest/mail synced 2025-02-11 20:50:10 -05:00

added good case test for delta sync

This commit is contained in:
Felix Hammerl 2013-12-02 09:07:16 +01:00
parent 4e4fa0f16f
commit 150cf23948
2 changed files with 345 additions and 48 deletions

View File

@ -24,7 +24,7 @@ define(function(require) {
}; };
// //
// Housekeeping APIs // External API
// //
EmailDAO.prototype.init = function(options, callback) { EmailDAO.prototype.init = function(options, callback) {
@ -139,11 +139,10 @@ define(function(require) {
/* /*
* Here's how delta sync works: * Here's how delta sync works:
* delta1: storage > memory => we deleted messages, remove from remote * delta1: storage > memory => we deleted messages, remove from remote
* delta2: memory > storage => we added messages, push to remote * delta2: memory > storage => we added messages, push to remote <<< not supported yet
* delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage * 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 * delta4: imap > memory => we have new messages available, fetch to memory and storage
*/ */
// TODO: error handling
var self = this, var self = this,
folder, folder,
@ -160,11 +159,12 @@ define(function(require) {
} }
folder = _.findWhere(self._account.folders, { folder = _.findWhere(self._account.folders, {
path: options.path path: options.folder
}); });
isFolderInitialized = !! folder.messages; isFolderInitialized = !! folder.messages;
// initial filling from local storage is an exception from the normal sync // initial filling from local storage is an exception from the normal sync.
// after reading from local storage, do imap sync
if (!isFolderInitialized) { if (!isFolderInitialized) {
folder.messages = []; folder.messages = [];
self._localListMessages({ self._localListMessages({
@ -183,6 +183,7 @@ define(function(require) {
var after = _.after(messages.length, function() { var after = _.after(messages.length, function() {
callback(); callback();
doImapDelta();
}); });
messages.forEach(function(message) { messages.forEach(function(message) {
@ -203,7 +204,6 @@ define(function(require) {
folder: folder.path folder: folder.path
}, function(err, messages) { }, function(err, messages) {
/* /*
* Reminder:
* delta1: storage > memory => we deleted messages, remove from remote * delta1: storage > memory => we deleted messages, remove from remote
* delta2: memory > storage => we added messages, push to remote * delta2: memory > storage => we added messages, push to remote
*/ */
@ -217,7 +217,11 @@ define(function(require) {
return; return;
} }
doDelta1();
function doDelta1() {
var after = _.after(delta1.length, function() { var after = _.after(delta1.length, function() {
// doDelta2(); when it is implemented
doImapDelta(); doImapDelta();
callback(); callback();
}); });
@ -234,6 +238,7 @@ define(function(require) {
}); });
}); });
}); });
}
}); });
} }
@ -246,8 +251,12 @@ define(function(require) {
return; return;
} }
// ignore non-whiteout mails
headers = _.without(headers, _.filter(headers, function(header) {
return header.subject.indexOf(str.subjectPrefix) === -1;
}));
/* /*
* Reminder:
* delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage * 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 * delta4: imap > memory => we have new messages available, fetch to memory and storage
*/ */
@ -262,13 +271,15 @@ define(function(require) {
doDelta3(); doDelta3();
// we deleted messages directly from the remote, remove from memory and storage
function doDelta3() { function doDelta3() {
if (_.isEmpty(delta3)) { if (_.isEmpty(delta3)) {
doDelta4(); doDelta4();
return; return;
} }
var after = _.after(delta1.length, function() { var after = _.after(delta3.length, function() {
// we're done with delta 3, so let's continue
doDelta4(); doDelta4();
}); });
@ -292,16 +303,20 @@ define(function(require) {
}); });
} }
// we have new messages available, fetch to memory and storage
// (downstream sync)
function doDelta4() { function doDelta4() {
// no delta, we're done here
if (_.isEmpty(delta4)) { if (_.isEmpty(delta4)) {
callback(); callback();
} }
var after = _.after(delta1.length, function() { var after = _.after(delta4.length, function() {
callback(); callback();
}); });
delta4.forEach(function(header) { delta4.forEach(function(header) {
// get the whole message
self._imapGetMessage({ self._imapGetMessage({
folder: folder.path, folder: folder.path,
uid: header.uid uid: header.uid
@ -311,6 +326,7 @@ define(function(require) {
return; return;
} }
// add the encrypted message to the local storage
self._localStoreMessages({ self._localStoreMessages({
folder: folder.path, folder: folder.path,
emails: [message] emails: [message]
@ -320,6 +336,7 @@ define(function(require) {
return; return;
} }
// encrypt and add to folder in memory
handleMessage(message, function(err, cleartextMessage) { handleMessage(message, function(err, cleartextMessage) {
if (err) { if (err) {
callback(err); callback(err);
@ -336,7 +353,6 @@ define(function(require) {
}); });
} }
/* /*
* Checks which messages are included in a, but not in b * Checks which messages are included in a, but not in b
*/ */
@ -347,7 +363,7 @@ define(function(require) {
// find the delta // find the delta
for (i = a.length - 1; i >= 0; i--) { for (i = a.length - 1; i >= 0; i--) {
msg = a[i]; msg = a[i];
exists = !! _.findWhere(b, { exists = _.findWhere(b, {
uid: msg.uid, uid: msg.uid,
subject: msg.subject subject: msg.subject
}); });
@ -394,7 +410,7 @@ define(function(require) {
} }
// decrypt and verfiy signatures // decrypt and verfiy signatures
self._pgp.decrypt(email.body, senderPubkey.publicKey, function(err, decrypted) { self._crypto.decrypt(email.body, senderPubkey.publicKey, function(err, decrypted) {
if (err) { if (err) {
decrypted = err.errMsg; decrypted = err.errMsg;
} }
@ -419,44 +435,42 @@ define(function(require) {
}; };
// //
// Local Storage Apis // Internal API
// //
// Local Storage API
EmailDAO.prototype._localListMessages = function(options, callback) { EmailDAO.prototype._localListMessages = function(options, callback) {
this._devicestorage.listItems('email_' + options.folder, 0, null, callback); var dbType = 'email_' + options.folder;
this._devicestorage.listItems(dbType, 0, null, callback);
}; };
EmailDAO.prototype._localStoreMessages = function(options, callback) { EmailDAO.prototype._localStoreMessages = function(options, callback) {
var dbType = 'email_' + options.folder; var dbType = 'email_' + options.folder;
self._devicestorage.storeList(options.emails, dbType, callback); this._devicestorage.storeList(options.emails, dbType, callback);
}; };
EmailDAO.prototype._localDeleteMessage = function(options, callback) { EmailDAO.prototype._localDeleteMessage = function(options, callback) {
self._devicestorage.removeList('email_' + options.folder + '_' + options.uid, callback); var dbType = 'email_' + options.folder + '_' + options.uid;
this._devicestorage.removeList(dbType, callback);
}; };
// // IMAP API
// IMAP Apis
//
/** /**
* Login the imap client * Login the imap client
*/ */
EmailDAO.prototype._imapLogin = function(callback) { EmailDAO.prototype._imapLogin = function(callback) {
var self = this;
// login IMAP client if existent // login IMAP client if existent
self._imapClient.login(callback); this._imapClient.login(callback);
}; };
/** /**
* Cleanup by logging the user off. * Cleanup by logging the user off.
*/ */
EmailDAO.prototype._imapLogout = function(callback) { EmailDAO.prototype._imapLogout = function(callback) {
var self = this; this._imapClient.logout(callback);
self._imapClient.logout(callback);
}; };
/** /**
@ -464,9 +478,7 @@ define(function(require) {
* @param {String} options.folderName The name of the imap folder. * @param {String} options.folderName The name of the imap folder.
*/ */
EmailDAO.prototype._imapListMessages = function(options, callback) { EmailDAO.prototype._imapListMessages = function(options, callback) {
var self = this; this._imapClient.listMessages({
self._imapClient.listMessages({
path: options.folder, path: options.folder,
offset: 0, offset: 0,
length: 100 length: 100
@ -491,7 +503,6 @@ define(function(require) {
}, callback); }, callback);
}; };
/** /**
* List the folders in the user's IMAP mailbox. * List the folders in the user's IMAP mailbox.
*/ */

View File

@ -7,16 +7,43 @@ define(function(require) {
SmtpClient = require('smtp-client'), SmtpClient = require('smtp-client'),
PGP = require('js/crypto/pgp'), PGP = require('js/crypto/pgp'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'), DeviceStorageDAO = require('js/dao/devicestorage-dao'),
_ = require('underscore'),
expect = chai.expect; expect = chai.expect;
describe('Email DAO 2 unit tests', function() { describe('Email DAO 2 unit tests', function() {
var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub; var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub;
var emailAddress = 'asdf@asdf.com', var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
passphrase = 'asdf', dummyDecryptedMail, mockKeyPair, account;
asymKeySize = 2048,
mockkeyId = 1234, beforeEach(function() {
emailAddress = 'asdf@asdf.com';
passphrase = 'asdf';
asymKeySize = 2048;
mockkeyId = 1234;
dummyEncryptedMail = {
uid: 1234,
from: [{
address: 'asd@asd.de'
}],
to: [{
address: 'qwe@qwe.de'
}],
subject: '[whiteout] qweasd',
body: '-----BEGIN PGP MESSAGE-----\nasd\n-----END PGP MESSAGE-----'
};
dummyDecryptedMail = {
uid: 1234,
from: [{
address: 'asd@asd.de'
}],
to: [{
address: 'qwe@qwe.de'
}],
subject: '[whiteout] qweasd',
body: 'asd'
};
mockKeyPair = { mockKeyPair = {
publicKey: { publicKey: {
_id: mockkeyId, _id: mockkeyId,
@ -28,12 +55,12 @@ define(function(require) {
userId: emailAddress, userId: emailAddress,
encryptedKey: 'privateprivateprivateprivate' encryptedKey: 'privateprivateprivateprivate'
} }
}, account = { };
account = {
emailAddress: emailAddress, emailAddress: emailAddress,
asymKeySize: asymKeySize, asymKeySize: asymKeySize,
}; };
beforeEach(function() {
keychainStub = sinon.createStubInstance(KeychainDAO); keychainStub = sinon.createStubInstance(KeychainDAO);
imapClientStub = sinon.createStubInstance(ImapClient); imapClientStub = sinon.createStubInstance(ImapClient);
smtpClientStub = sinon.createStubInstance(SmtpClient); smtpClientStub = sinon.createStubInstance(SmtpClient);
@ -410,5 +437,264 @@ define(function(require) {
}); });
}); });
describe('_imapListMessages', function() {
it('should work', function(done) {
var path = 'FOLDAAAA';
imapClientStub.listMessages.withArgs({
path: path,
offset: 0,
length: 100
}).yields();
dao._imapListMessages({
folder: path
}, done);
});
});
describe('_imapDeleteMessage', function() {
it('should work', function(done) {
var path = 'FOLDAAAA',
uid = 1337;
imapClientStub.deleteMessage.withArgs({
path: path,
uid: uid
}).yields();
dao._imapDeleteMessage({
folder: path,
uid: uid
}, done);
});
});
describe('_imapGetMessage', function() {
it('should work', function(done) {
var path = 'FOLDAAAA',
uid = 1337;
imapClientStub.getMessagePreview.withArgs({
path: path,
uid: uid
}).yields();
dao._imapGetMessage({
folder: path,
uid: uid
}, done);
});
});
describe('_localListMessages', function() {
it('should work', function(done) {
var folder = 'FOLDAAAA';
devicestorageStub.listItems.withArgs('email_' + folder, 0, null).yields();
dao._localListMessages({
folder: folder
}, done);
});
});
describe('_localStoreMessages', function() {
it('should work', function(done) {
var folder = 'FOLDAAAA',
emails = [{}];
devicestorageStub.storeList.withArgs(emails, 'email_' + folder).yields();
dao._localStoreMessages({
folder: folder,
emails: emails
}, done);
});
});
describe('_localDeleteMessage', function() {
it('should work', function(done) {
var folder = 'FOLDAAAA',
uid = 1337;
devicestorageStub.removeList.withArgs('email_' + folder + '_' + uid).yields();
dao._localDeleteMessage({
folder: folder,
uid: uid
}, done);
});
});
describe('sync', function() {
it('should work initially', function(done) {
var folder, localListStub;
folder = 'FOLDAAAA';
dao._account.folders = [{
type: 'Folder',
path: folder
}];
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
}).yields(null, [dummyEncryptedMail]);
keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair);
pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body);
dao.sync({
folder: folder
}, function() {
expect(dao._account.folders[0].messages).to.not.be.empty;
expect(localListStub.calledOnce).to.be.true;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
done();
});
});
it('should be up to date', function(done) {
var after, folder, localListStub, imapListStub;
folder = 'FOLDAAAA';
dao._account.folders = [{
type: 'Folder',
path: folder,
messages: [dummyDecryptedMail]
}];
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
}).yields(null, [dummyEncryptedMail]);
imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({
folder: folder
}).yields(null, [dummyEncryptedMail]);
after = _.after(2, function() {
expect(dao._account.folders[0]).to.not.be.empty;
expect(localListStub.calledOnce).to.be.true;
expect(imapListStub.calledOnce).to.be.true;
done();
});
dao.sync({
folder: folder
}, after);
});
it('should remove messages from the remote', function(done) {
var after, folder, localListStub, imapListStub, localDeleteStub, imapDeleteStub;
folder = 'FOLDAAAA';
dao._account.folders = [{
type: 'Folder',
path: folder,
messages: []
}];
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
}).yields(null, [dummyEncryptedMail]);
imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({
folder: folder
}).yields(null, []);
imapDeleteStub = sinon.stub(dao, '_imapDeleteMessage').withArgs({
folder: folder,
uid: dummyEncryptedMail.uid
}).yields();
localDeleteStub = sinon.stub(dao, '_localDeleteMessage').withArgs({
folder: folder,
uid: dummyEncryptedMail.uid
}).yields();
after = _.after(2, function() {
expect(dao._account.folders[0].messages).to.be.empty;
expect(localListStub.calledOnce).to.be.true;
expect(imapListStub.calledOnce).to.be.true;
expect(localDeleteStub.calledOnce).to.be.true;
expect(imapDeleteStub.calledOnce).to.be.true;
done();
});
dao.sync({
folder: folder
}, after);
});
it('should delete messages locally if not present on remote', function(done) {
var after, folder, localListStub, imapListStub, localDeleteStub;
folder = 'FOLDAAAA';
dao._account.folders = [{
type: 'Folder',
path: folder,
messages: [dummyDecryptedMail]
}];
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
}).yields(null, [dummyEncryptedMail]);
imapListStub = sinon.stub(dao, '_imapListMessages').withArgs({
folder: folder
}).yields(null, []);
localDeleteStub = sinon.stub(dao, '_localDeleteMessage').withArgs({
folder: folder,
uid: dummyEncryptedMail.uid
}).yields();
after = _.after(2, function() {
expect(dao._account.folders[0].messages).to.be.empty;
expect(localListStub.calledOnce).to.be.true;
expect(imapListStub.calledOnce).to.be.true;
expect(localDeleteStub.calledOnce).to.be.true;
done();
});
dao.sync({
folder: folder
}, after);
});
it('should fetch messages downstream from the remote', function(done) {
var after, folder, localListStub, imapListStub, imapGetStub, localStoreStub;
folder = 'FOLDAAAA';
dao._account.folders = [{
type: 'Folder',
path: folder,
messages: []
}];
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
}).yields(null, []);
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);
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);
after = _.after(2, function() {
expect(dao._account.folders[0].messages).to.not.be.empty;
expect(localListStub.calledOnce).to.be.true;
expect(imapListStub.calledOnce).to.be.true;
expect(imapGetStub.calledOnce).to.be.true;
expect(localStoreStub.calledOnce).to.be.true;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
done();
});
dao.sync({
folder: folder
}, after);
});
});
}); });
}); });