[WO-515] Fix folder handling

Use the first folder provided in well known folders

Add migration script to fix clients with multiple folders for same
category

Handle folder deletion smarter

Extract some magic number/strings to constants in emaildao
This commit is contained in:
Felix Hammerl 2014-07-31 09:57:57 +02:00
parent bb9a641b4e
commit 7adc095277
8 changed files with 394 additions and 171 deletions

View File

@ -11,7 +11,7 @@
},
"dependencies": {
"crypto-lib": "~0.2.1",
"imap-client": "~0.3.7",
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/dev/WO-515",
"mailreader": "~0.3.5",
"pgpmailer": "~0.3.11",
"pgpbuilder": "~0.3.7",

View File

@ -92,9 +92,10 @@ define(function(require) {
iconPath: '/img/icon.png',
verificationUrl: '/verify/',
verificationUuidLength: 36,
dbVersion: 4,
dbVersion: 5,
appVersion: appVersion,
outboxMailboxPath: 'OUTBOX',
outboxMailboxName: 'Outbox',
outboxMailboxType: 'Outbox'
};

View File

@ -533,92 +533,25 @@ define(function(require) {
this.cc = [{
address: 'john.doe@gmail.com'
}]; // list of receivers
if (attachments) {
// body structure with three attachments
this.bodystructure = {
"1": {
"part": "1",
"type": "text/plain",
"parameters": {
"charset": "us-ascii"
},
"encoding": "7bit",
"size": 9,
"lines": 2
},
"2": {
"part": "2",
"type": "application/octet-stream",
"parameters": {
"name": "a.md"
},
"encoding": "7bit",
"size": 123,
"disposition": [{
"type": "attachment",
"filename": "a.md"
}]
},
"3": {
"part": "3",
"type": "application/octet-stream",
"parameters": {
"name": "b.md"
},
"encoding": "7bit",
"size": 456,
"disposition": [{
"type": "attachment",
"filename": "b.md"
}]
},
"4": {
"part": "4",
"type": "application/octet-stream",
"parameters": {
"name": "c.md"
},
"encoding": "7bit",
"size": 789,
"disposition": [{
"type": "attachment",
"filename": "c.md"
}]
},
"type": "multipart/mixed"
};
this.attachments = [{
"filename": "a.md",
"filesize": 123,
"mimeType": "text/x-markdown",
"part": "2",
"content": null
}, {
"filename": "b.md",
"filesize": 456,
"mimeType": "text/x-markdown",
"part": "3",
"content": null
}, {
"filename": "c.md",
"filesize": 789,
"mimeType": "text/x-markdown",
"part": "4",
"content": null
}];
} else {
this.bodystructure = {
"part": "1",
"type": "text/plain",
"parameters": {
"charset": "us-ascii"
},
"encoding": "7bit",
"size": 9,
"lines": 2
};
this.attachments = [];
}
this.attachments = attachments ? [{
"filename": "a.md",
"filesize": 123,
"mimeType": "text/x-markdown",
"part": "2",
"content": null
}, {
"filename": "b.md",
"filesize": 456,
"mimeType": "text/x-markdown",
"part": "3",
"content": null
}, {
"filename": "c.md",
"filesize": 789,
"mimeType": "text/x-markdown",
"part": "4",
"content": null
}] : [];
this.unread = unread;
this.answered = answered;
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
@ -639,13 +572,14 @@ define(function(require) {
this.decrypted = true;
};
var dummys = [new Email(true, true), new Email(true, false, true), new Email(false, true, true), new Email(false, true)];
for (var i = 0; i < 100; i++) {
dummys.push(new Email(false));
var dummies = [],
i = 100;
while (i--) {
// every second/third/fourth dummy mail with unread/attachments/answered
dummies.push(new Email((i % 2 === 0), (i % 3 === 0), (i % 5 === 0)));
}
return dummys;
return dummies;
}
return MailListCtrl;

View File

@ -6,6 +6,38 @@ define(function(require) {
config = require('js/app-config').config,
str = require('js/app-config').string;
//
//
// Constants
//
//
var FOLDER_DB_TYPE = 'folders';
var SYNC_TYPE_NEW = 'new';
var SYNC_TYPE_DELETED = 'deleted';
var SYNC_TYPE_MSGS = 'messages';
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
var MSG_ATTR_UID = 'uid';
var MSG_PART_ATTR_CONTENT = 'content';
var MSG_PART_TYPE_ATTACHMENT = 'attachment';
var MSG_PART_TYPE_ENCRYPTED = 'encrypted';
var MSG_PART_TYPE_SIGNED = 'signed';
var MSG_PART_TYPE_TEXT = 'text';
var MSG_PART_TYPE_HTML = 'html';
//
//
// Email Dao
//
//
/**
* High-level data access object that orchestrates everything around the handling of encrypted mails:
* PGP de-/encryption, receiving via IMAP, sending via SMTP, MIME parsing, local db persistence
@ -253,8 +285,8 @@ define(function(require) {
return;
}
var storedUids = _.pluck(storedMessages, 'uid'),
memoryUids = _.pluck(folder.messages, 'uid'),
var storedUids = _.pluck(storedMessages, MSG_ATTR_UID),
memoryUids = _.pluck(folder.messages, MSG_ATTR_UID),
newUids = _.difference(storedUids, memoryUids), // uids of messages that are not yet in memory
removedUids = _.difference(memoryUids, storedUids); // uids of messages that are no longer stored on the disk
@ -370,7 +402,7 @@ define(function(require) {
// this enables us to already show the attachment clip in the message list ui
messages.forEach(function(message) {
message.attachments = message.bodyParts.filter(function(bodyPart) {
return bodyPart.type === 'attachment';
return bodyPart.type === MSG_PART_TYPE_ATTACHMENT;
});
});
@ -401,7 +433,7 @@ define(function(require) {
return;
}
var body = _.pluck(filterBodyParts(parsedBodyParts, 'text'), 'content').join('\n'),
var body = _.pluck(filterBodyParts(parsedBodyParts, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n'),
verificationUrlPrefix = config.cloudUrl + config.verificationUrl,
uuid = body.split(verificationUrlPrefix).pop().substr(0, config.verificationUuidLength),
uuidRegex = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
@ -645,10 +677,10 @@ define(function(require) {
// 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.
var contentParts = localMessage.bodyParts.filter(function(bodyPart) {
return bodyPart.type !== "attachment" || (bodyPart.type === "attachment" && bodyPart.id);
return bodyPart.type !== MSG_PART_TYPE_ATTACHMENT || (bodyPart.type === MSG_PART_TYPE_ATTACHMENT && bodyPart.id);
});
var attachmentParts = localMessage.bodyParts.filter(function(bodyPart) {
return bodyPart.type === "attachment" && !bodyPart.id;
return bodyPart.type === MSG_PART_TYPE_ATTACHMENT && !bodyPart.id;
});
// do we need to fetch content from the imap server?
@ -700,7 +732,7 @@ define(function(require) {
function extractContent() {
if (message.encrypted) {
// show the encrypted message
message.body = filterBodyParts(message.bodyParts, 'encrypted')[0].content;
message.body = filterBodyParts(message.bodyParts, MSG_PART_TYPE_ENCRYPTED)[0].content;
return done();
}
@ -708,13 +740,13 @@ define(function(require) {
if (message.signed) {
// PGP/MIME signed
var signedRoot = filterBodyParts(message.bodyParts, 'signed')[0]; // in case of a signed message, you only want to show the signed content and ignore the rest
var signedRoot = filterBodyParts(message.bodyParts, MSG_PART_TYPE_SIGNED)[0]; // in case of a signed message, you only want to show the signed content and ignore the rest
message.signedMessage = signedRoot.signedMessage;
message.signature = signedRoot.signature;
root = signedRoot.content;
}
var body = _.pluck(filterBodyParts(root, 'text'), 'content').join('\n');
var body = _.pluck(filterBodyParts(root, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n');
/*
* if the message is plain text and contains pgp/inline, we are only interested in the encrypted
@ -730,7 +762,7 @@ define(function(require) {
// replace the bodyParts info with an artificial bodyPart of type "encrypted"
message.bodyParts = [{
type: 'encrypted',
type: MSG_PART_TYPE_ENCRYPTED,
content: pgpInlineMatch[0],
_isPgpInline: true // used internally to avoid trying to parse non-MIME text with the mailreader
}];
@ -770,8 +802,8 @@ define(function(require) {
function setBody() {
message.body = body;
if (!message.clearSignedMessage) {
message.attachments = filterBodyParts(root, 'attachment');
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
message.attachments = filterBodyParts(root, MSG_PART_TYPE_ATTACHMENT);
message.html = _.pluck(filterBodyParts(root, MSG_PART_TYPE_HTML), MSG_PART_ATTR_CONTENT).join('\n');
inlineExternalImages(message);
}
@ -863,7 +895,7 @@ define(function(require) {
}
// get the receiver's public key to check the message signature
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
var encryptedNode = filterBodyParts(message.bodyParts, MSG_PART_TYPE_ENCRYPTED)[0];
var senderKey = senderPublicKey ? senderPublicKey.publicKey : undefined;
self._pgp.decrypt(encryptedNode.content, senderKey, function(err, decrypted, signaturesValid) {
if (err || !decrypted) {
@ -897,7 +929,7 @@ define(function(require) {
if (!message.signed) {
// message had no signature in the ciphertext, so there's a little extra effort to be done here
// is there a signed MIME node?
var signedRoot = filterBodyParts(root, 'signed')[0];
var signedRoot = filterBodyParts(root, MSG_PART_TYPE_SIGNED)[0];
if (!signedRoot) {
// no signed MIME node, obviously an unsigned PGP/MIME message
return setBody();
@ -927,9 +959,9 @@ define(function(require) {
function setBody() {
// we have successfully interpreted the descrypted message,
// so let's update the views on the message parts
message.body = _.pluck(filterBodyParts(root, 'text'), 'content').join('\n');
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
message.attachments = _.reject(filterBodyParts(root, 'attachment'), function(attmt) {
message.body = _.pluck(filterBodyParts(root, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n');
message.html = _.pluck(filterBodyParts(root, MSG_PART_TYPE_HTML), MSG_PART_ATTR_CONTENT).join('\n');
message.attachments = _.reject(filterBodyParts(root, MSG_PART_TYPE_ATTACHMENT), function(attmt) {
// remove the pgp-signature from the attachments
return attmt.mimeType === "application/pgp-signature";
});
@ -989,7 +1021,7 @@ define(function(require) {
// upload the sent message to the sent folder if necessary
var sentFolder = _.findWhere(self._account.folders, {
type: 'Sent'
type: FOLDER_TYPE_SENT
});
if (self.ignoreUploadOnSent || !sentFolder || !rfcText) {
@ -1039,7 +1071,7 @@ define(function(require) {
// upload the sent message to the sent folder if necessary
var sentFolder = _.findWhere(self._account.folders, {
type: 'Sent'
type: FOLDER_TYPE_SENT
});
if (self.ignoreUploadOnSent || !sentFolder || !rfcText) {
@ -1130,7 +1162,7 @@ define(function(require) {
var uids, highestModseq, lastUid;
uids = _.pluck(folder.messages, 'uid').sort(function(a, b) {
uids = _.pluck(folder.messages, MSG_ATTR_UID).sort(function(a, b) {
return a - b;
});
lastUid = uids[uids.length - 1];
@ -1153,7 +1185,7 @@ define(function(require) {
// set up the imap client to listen for changes in the inbox
var inbox = _.findWhere(self._account.folders, {
type: 'Inbox'
type: FOLDER_TYPE_INBOX
});
if (!inbox) {
@ -1199,14 +1231,14 @@ define(function(require) {
return;
}
if (options.type === 'new') {
if (options.type === SYNC_TYPE_NEW) {
// new messages available on imap, fetch from imap and store to disk and memory
self.fetchMessages({
folder: folder,
firstUid: Math.min.apply(null, options.list),
lastUid: Math.max.apply(null, options.list)
}, self.onError.bind(self));
} else if (options.type === 'deleted') {
} else if (options.type === SYNC_TYPE_DELETED) {
// messages have been deleted, remove from local storage and memory
options.list.forEach(function(uid) {
var message = _.findWhere(folder.messages, {
@ -1223,7 +1255,7 @@ define(function(require) {
localOnly: true
}, self.onError.bind(self));
});
} else if (options.type === 'messages') {
} else if (options.type === SYNC_TYPE_MSGS) {
// NB! several possible reasons why this could be called.
// if a message in the array has uid value and flag array, it had a possible flag update
options.list.forEach(function(changedMsg) {
@ -1268,13 +1300,12 @@ define(function(require) {
* @param {Function} callback Invoked when the folders are up to date
*/
EmailDAO.prototype._initFoldersFromDisk = function(callback) {
var self = this,
folderDbType = 'folders';
var self = this;
self.busy(); // start the spinner
// fetch list from local cache
self._devicestorage.listItems(folderDbType, 0, null, function(err, stored) {
self._devicestorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
if (err) {
return done(err);
}
@ -1297,8 +1328,7 @@ define(function(require) {
* @param {Function} callback Invoked when the folders are up to date
*/
EmailDAO.prototype._initFoldersFromImap = function(callback) {
var self = this,
folderDbType = 'folders';
var self = this;
self.busy(); // start the spinner
@ -1308,57 +1338,73 @@ define(function(require) {
return done(err);
}
// this array is dropped directly into the ui to create the folder list
var folders = [];
if (wellKnownFolders.inbox) {
folders.push(wellKnownFolders.inbox);
}
if (wellKnownFolders.sent) {
folders.push(wellKnownFolders.sent);
}
folders.push({
type: 'Outbox',
// initialize the folders to something meaningful if that hasn't already happened
self._account.folders = self._account.folders || [];
// smuggle the outbox into the well known folders, which is obv not present on imap...
wellKnownFolders[config.outboxMailboxType] = [{
name: config.outboxMailboxName,
type: config.outboxMailboxType,
path: config.outboxMailboxPath
});
if (wellKnownFolders.drafts) {
folders.push(wellKnownFolders.drafts);
}
if (wellKnownFolders.trash) {
folders.push(wellKnownFolders.trash);
}
}];
var foldersChanged = false; // indicates if are there any new/removed folders?
// indicates if we need to persist anything to disk
var foldersChanged = false;
// check for added folders
folders.forEach(function(folder) {
if (!_.findWhere(self._account.folders, {
path: folder.path
})) {
// add the missing folder
self._account.folders.push(folder);
// the folders listed in the navigation pane
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, config.outboxMailboxType, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
var localFolderWithType, imapFolderWithPath;
// check if there is a folder of this type locally available
localFolderWithType = _.findWhere(self._account.folders, {
type: mbxType
});
if (localFolderWithType) {
// we have a local folder available, so let's check if this folder still exists on imap
imapFolderWithPath = _.findWhere(wellKnownFolders[mbxType], {
path: localFolderWithType.path
});
if (imapFolderWithPath) {
// folder present on imap, no need to update.
return;
}
// folder not present on imap, so remove the folder and see if there are any updates for this folder type
self._account.folders.splice(self._account.folders.indexOf(localFolderWithType), 1);
foldersChanged = true;
}
});
// check for deleted folders
self._account.folders.forEach(function(folder) {
if (!_.findWhere(folders, {
path: folder.path
})) {
// remove the obsolete folder
self._account.folders.splice(self._account.folders.indexOf(folder), 1);
foldersChanged = true;
if (!wellKnownFolders[mbxType] || !wellKnownFolders[mbxType].length) {
// no imap folders of the respective mailbox type, so nothing to do here
return;
}
/**
* we have no local folder of the type, so do something intelligent,
* i.e. take the first folder of the respective type
*/
self._account.folders.push(wellKnownFolders[mbxType][0]);
foldersChanged = true;
});
// if folder have changed, we need to persist them to disk.
// if folders have not changed, can fill them with messages directly
if (!foldersChanged) {
return self._initMessagesFromDisk(done);
}
// persist encrypted list in device storage
// NB! persis the array we received from IMAP! do *not* persist self._account.folders with all the messages...
self._devicestorage.storeList([folders], folderDbType, function(err) {
// note: the folders in the ui also include the messages array, so let's create a clean array here
var folders = self._account.folders.map(function(folder) {
return {
name: folder.name,
path: folder.path,
type: folder.type
};
});
self._devicestorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
if (err) {
return done(err);
}
@ -1463,7 +1509,7 @@ define(function(require) {
}
var trash = _.findWhere(this._account.folders, {
type: 'Trash'
type: FOLDER_TYPE_TRASH
});
// there's no known trash folder to move the mail to or we're in the trash folder, so we can purge the message

View File

@ -5,7 +5,8 @@ define(function(require) {
updateV1 = require('js/util/update/update-v1'),
updateV2 = require('js/util/update/update-v2'),
updateV3 = require('js/util/update/update-v3'),
updateV4 = require('js/util/update/update-v4');
updateV4 = require('js/util/update/update-v4'),
updateV5 = require('js/util/update/update-v5');
/**
* Handles database migration
@ -13,7 +14,7 @@ define(function(require) {
var UpdateHandler = function(appConfigStorage, userStorage, auth) {
this._appConfigStorage = appConfigStorage;
this._userStorage = userStorage;
this._updateScripts = [updateV1, updateV2, updateV3, updateV4];
this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5];
this._auth = auth;
};

View File

@ -0,0 +1,56 @@
define(function() {
'use strict';
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
var FOLDER_DB_TYPE = 'folders';
var VERSION_DB_TYPE = 'dbVersion';
var POST_UPDATE_DB_VERSION = 5;
/**
* Update handler for transition database version 4 -> 5
*
* Due to an overlooked issue, there may be multiple folders, e.g. for sent mails.
* This removes the "duplicate" folders.
*/
function update(options, callback) {
// remove the emails
options.userStorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
if (err) {
return callback(err);
}
var folders = stored[0] || [];
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
var foldersForType = folders.filter(function(mbx) {
return mbx.type === mbxType;
});
if (foldersForType.length <= 1) {
return; // nothing to do here
}
// remove duplicate folders
for (var i = 1; i < foldersForType.length; i++) {
folders.splice(folders.indexOf(foldersForType[i]), 1);
}
});
options.userStorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
if (err) {
return callback(err);
}
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
});
}
return update;
});

View File

@ -40,30 +40,35 @@ define(function(require) {
asymKeySize = 2048;
inboxFolder = {
name: 'Inbox',
type: 'Inbox',
path: 'INBOX',
messages: []
};
sentFolder = {
name: 'Sent',
type: 'Sent',
path: 'SENT',
messages: []
};
draftsFolder = {
name: 'Drafts',
type: 'Drafts',
path: 'DRAFTS',
messages: []
};
outboxFolder = {
name: 'Outbox',
type: 'Outbox',
path: 'OUTBOX',
messages: []
};
trashFolder = {
name: 'Trash',
type: 'Trash',
path: 'TRASH',
messages: []
@ -1969,18 +1974,76 @@ define(function(require) {
it('should initialize from imap if online', function(done) {
account.folders = [];
imapClientStub.listWellKnownFolders.yieldsAsync(null, {
inbox: inboxFolder,
sent: sentFolder,
drafts: draftsFolder,
trash: trashFolder
Inbox: [inboxFolder],
Sent: [sentFolder],
Drafts: [draftsFolder],
Trash: [trashFolder]
});
devicestorageStub.storeList.withArgs(sinon.match(function(arg) {
expect(arg[0][0]).to.deep.equal(inboxFolder);
expect(arg[0][1]).to.deep.equal(sentFolder);
expect(arg[0][0].name).to.deep.equal(inboxFolder.name);
expect(arg[0][0].path).to.deep.equal(inboxFolder.path);
expect(arg[0][0].type).to.deep.equal(inboxFolder.type);
expect(arg[0][1].name).to.deep.equal(sentFolder.name);
expect(arg[0][1].path).to.deep.equal(sentFolder.path);
expect(arg[0][1].type).to.deep.equal(sentFolder.type);
expect(arg[0][2].name).to.deep.equal(outboxFolder.name);
expect(arg[0][2].path).to.deep.equal(outboxFolder.path);
expect(arg[0][2].type).to.deep.equal(outboxFolder.type);
expect(arg[0][3]).to.deep.equal(draftsFolder);
expect(arg[0][4]).to.deep.equal(trashFolder);
expect(arg[0][3].name).to.deep.equal(draftsFolder.name);
expect(arg[0][3].path).to.deep.equal(draftsFolder.path);
expect(arg[0][3].type).to.deep.equal(draftsFolder.type);
expect(arg[0][4].name).to.deep.equal(trashFolder.name);
expect(arg[0][4].path).to.deep.equal(trashFolder.path);
expect(arg[0][4].type).to.deep.equal(trashFolder.type);
return true;
}), 'folders').yieldsAsync();
dao.refreshFolder.yieldsAsync();
dao._initFoldersFromImap(function(err) {
expect(err).to.not.exist;
expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should update folders from imap', function(done) {
account.folders = [inboxFolder, outboxFolder, trashFolder, {
name: 'foo',
type: 'Sent',
path: 'bar',
}];
imapClientStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: [inboxFolder],
Sent: [sentFolder],
Drafts: [draftsFolder],
Trash: [trashFolder]
});
devicestorageStub.storeList.withArgs(sinon.match(function(arg) {
expect(arg[0]).to.deep.equal([{
name: inboxFolder.name,
path: inboxFolder.path,
type: inboxFolder.type
}, {
name: outboxFolder.name,
path: outboxFolder.path,
type: outboxFolder.type
}, {
name: trashFolder.name,
path: trashFolder.path,
type: trashFolder.type
}, {
name: sentFolder.name,
path: sentFolder.path,
type: sentFolder.type
}, {
name: draftsFolder.name,
path: draftsFolder.path,
type: draftsFolder.type
}]);
return true;
}), 'folders').yieldsAsync();

View File

@ -277,7 +277,7 @@ define(function(require) {
var SMTP_DB_KEY = 'smtp';
var REALNAME_DB_KEY = 'realname';
var emailaddress = 'bla@blubb.io';
var imap = config.gmail.imap,
smtp = config.gmail.smtp;
@ -347,6 +347,128 @@ define(function(require) {
});
});
});
describe('v4 -> v5', function() {
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
var FOLDER_DB_TYPE = 'folders';
var VERSION_DB_TYPE = 'dbVersion';
var POST_UPDATE_DB_VERSION = 5;
beforeEach(function() {
cfg.dbVersion = 5; // app requires database version 4
appConfigStorageStub.listItems.withArgs(VERSION_DB_TYPE).yieldsAsync(null, [4]); // database version is 4
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).yieldsAsync(null, [
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
}, {
name: 'inbox2',
type: FOLDER_TYPE_INBOX
}, {
name: 'sent1',
type: FOLDER_TYPE_SENT
}, {
name: 'sent2',
type: FOLDER_TYPE_SENT
}, {
name: 'drafts1',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'drafts2',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'trash1',
type: FOLDER_TYPE_TRASH
}, {
name: 'trash2',
type: FOLDER_TYPE_TRASH
}]
]);
userStorageStub.storeList.withArgs([
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
}, {
name: 'sent1',
type: FOLDER_TYPE_SENT
}, {
name: 'drafts1',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'trash1',
type: FOLDER_TYPE_TRASH
}]
], FOLDER_DB_TYPE).yieldsAsync();
appConfigStorageStub.storeList.withArgs([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
it('should fail when listing folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.called).to.be.false;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});
});
});
});