mirror of
https://github.com/moparisthebest/mail
synced 2025-02-07 02:20:14 -05:00
added good case test for delta sync
This commit is contained in:
parent
4e4fa0f16f
commit
150cf23948
@ -24,7 +24,7 @@ define(function(require) {
|
||||
};
|
||||
|
||||
//
|
||||
// Housekeeping APIs
|
||||
// External API
|
||||
//
|
||||
|
||||
EmailDAO.prototype.init = function(options, callback) {
|
||||
@ -139,11 +139,10 @@ define(function(require) {
|
||||
/*
|
||||
* Here's how delta sync works:
|
||||
* 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
|
||||
* delta4: imap > memory => we have new messages available, fetch to memory and storage
|
||||
*/
|
||||
// TODO: error handling
|
||||
|
||||
var self = this,
|
||||
folder,
|
||||
@ -160,11 +159,12 @@ define(function(require) {
|
||||
}
|
||||
|
||||
folder = _.findWhere(self._account.folders, {
|
||||
path: options.path
|
||||
path: options.folder
|
||||
});
|
||||
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) {
|
||||
folder.messages = [];
|
||||
self._localListMessages({
|
||||
@ -183,6 +183,7 @@ define(function(require) {
|
||||
|
||||
var after = _.after(messages.length, function() {
|
||||
callback();
|
||||
doImapDelta();
|
||||
});
|
||||
|
||||
messages.forEach(function(message) {
|
||||
@ -203,7 +204,6 @@ define(function(require) {
|
||||
folder: folder.path
|
||||
}, function(err, messages) {
|
||||
/*
|
||||
* Reminder:
|
||||
* delta1: storage > memory => we deleted messages, remove from remote
|
||||
* delta2: memory > storage => we added messages, push to remote
|
||||
*/
|
||||
@ -217,7 +217,11 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
doDelta1();
|
||||
|
||||
function doDelta1() {
|
||||
var after = _.after(delta1.length, function() {
|
||||
// doDelta2(); when it is implemented
|
||||
doImapDelta();
|
||||
callback();
|
||||
});
|
||||
@ -234,6 +238,7 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -246,8 +251,12 @@ define(function(require) {
|
||||
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
|
||||
* delta4: imap > memory => we have new messages available, fetch to memory and storage
|
||||
*/
|
||||
@ -262,13 +271,15 @@ define(function(require) {
|
||||
|
||||
doDelta3();
|
||||
|
||||
// we deleted messages directly from the remote, remove from memory and storage
|
||||
function doDelta3() {
|
||||
if (_.isEmpty(delta3)) {
|
||||
doDelta4();
|
||||
return;
|
||||
}
|
||||
|
||||
var after = _.after(delta1.length, function() {
|
||||
var after = _.after(delta3.length, function() {
|
||||
// we're done with delta 3, so let's continue
|
||||
doDelta4();
|
||||
});
|
||||
|
||||
@ -292,16 +303,20 @@ 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)) {
|
||||
callback();
|
||||
}
|
||||
|
||||
var after = _.after(delta1.length, function() {
|
||||
var after = _.after(delta4.length, function() {
|
||||
callback();
|
||||
});
|
||||
|
||||
delta4.forEach(function(header) {
|
||||
// get the whole message
|
||||
self._imapGetMessage({
|
||||
folder: folder.path,
|
||||
uid: header.uid
|
||||
@ -311,6 +326,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add the encrypted message to the local storage
|
||||
self._localStoreMessages({
|
||||
folder: folder.path,
|
||||
emails: [message]
|
||||
@ -320,6 +336,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
// encrypt and add to folder in memory
|
||||
handleMessage(message, function(err, cleartextMessage) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@ -336,7 +353,6 @@ define(function(require) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Checks which messages are included in a, but not in b
|
||||
*/
|
||||
@ -347,7 +363,7 @@ define(function(require) {
|
||||
// find the delta
|
||||
for (i = a.length - 1; i >= 0; i--) {
|
||||
msg = a[i];
|
||||
exists = !! _.findWhere(b, {
|
||||
exists = _.findWhere(b, {
|
||||
uid: msg.uid,
|
||||
subject: msg.subject
|
||||
});
|
||||
@ -394,7 +410,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
decrypted = err.errMsg;
|
||||
}
|
||||
@ -419,44 +435,42 @@ define(function(require) {
|
||||
};
|
||||
|
||||
//
|
||||
// Local Storage Apis
|
||||
// Internal API
|
||||
//
|
||||
|
||||
// Local Storage API
|
||||
|
||||
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) {
|
||||
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) {
|
||||
self._devicestorage.removeList('email_' + options.folder + '_' + options.uid, callback);
|
||||
var dbType = 'email_' + options.folder + '_' + options.uid;
|
||||
this._devicestorage.removeList(dbType, callback);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// IMAP Apis
|
||||
//
|
||||
// IMAP API
|
||||
|
||||
/**
|
||||
* Login the imap client
|
||||
*/
|
||||
EmailDAO.prototype._imapLogin = function(callback) {
|
||||
var self = this;
|
||||
|
||||
// login IMAP client if existent
|
||||
self._imapClient.login(callback);
|
||||
this._imapClient.login(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleanup by logging the user off.
|
||||
*/
|
||||
EmailDAO.prototype._imapLogout = function(callback) {
|
||||
var self = this;
|
||||
|
||||
self._imapClient.logout(callback);
|
||||
this._imapClient.logout(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -464,9 +478,7 @@ define(function(require) {
|
||||
* @param {String} options.folderName The name of the imap folder.
|
||||
*/
|
||||
EmailDAO.prototype._imapListMessages = function(options, callback) {
|
||||
var self = this;
|
||||
|
||||
self._imapClient.listMessages({
|
||||
this._imapClient.listMessages({
|
||||
path: options.folder,
|
||||
offset: 0,
|
||||
length: 100
|
||||
@ -491,7 +503,6 @@ define(function(require) {
|
||||
}, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* List the folders in the user's IMAP mailbox.
|
||||
*/
|
||||
|
@ -7,16 +7,43 @@ define(function(require) {
|
||||
SmtpClient = require('smtp-client'),
|
||||
PGP = require('js/crypto/pgp'),
|
||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||
_ = require('underscore'),
|
||||
expect = chai.expect;
|
||||
|
||||
|
||||
describe('Email DAO 2 unit tests', function() {
|
||||
var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub;
|
||||
|
||||
var emailAddress = 'asdf@asdf.com',
|
||||
passphrase = 'asdf',
|
||||
asymKeySize = 2048,
|
||||
mockkeyId = 1234,
|
||||
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
|
||||
dummyDecryptedMail, mockKeyPair, account;
|
||||
|
||||
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 = {
|
||||
publicKey: {
|
||||
_id: mockkeyId,
|
||||
@ -28,12 +55,12 @@ define(function(require) {
|
||||
userId: emailAddress,
|
||||
encryptedKey: 'privateprivateprivateprivate'
|
||||
}
|
||||
}, account = {
|
||||
};
|
||||
account = {
|
||||
emailAddress: emailAddress,
|
||||
asymKeySize: asymKeySize,
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
keychainStub = sinon.createStubInstance(KeychainDAO);
|
||||
imapClientStub = sinon.createStubInstance(ImapClient);
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user