1
0
mirror of https://github.com/moparisthebest/mail synced 2024-12-23 07:48:48 -05:00

Merge pull request #16 from whiteout-io/dev/attachments-ui

Dev/attachments ui
This commit is contained in:
Tankred Hase 2014-02-06 20:40:12 +01:00
commit 0c2fd7f052
17 changed files with 693 additions and 222 deletions

View File

@ -137,13 +137,11 @@ module.exports = function(grunt) {
'imap-client/node_modules/mimelib/node_modules/addressparser/src/addressparser.js',
'imap-client/node_modules/mimelib/node_modules/encoding/src/encoding.js',
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/iconv-lite/src/*.js',
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/mime/src/*.js',
'imap-client/node_modules/mailparser/src/*.js',
'imap-client/node_modules/mailparser/node_modules/mime/src/mime.js',
'smtp-client/src/*.js',
'smtp-client/node_modules/mailcomposer/src/*',
'smtp-client/node_modules/nodemailer/src/*',
'smtp-client/node_modules/nodemailer/node_modules/simplesmtp/src/*',
'imap-client/node_modules/mime/src/mime.js',
'pgpmailer/src/*.js',
'pgpmailer/node_modules/simplesmtp/src/*',
'pgpmailer/node_modules/mailbuilder/src/*.js'
],
dest: 'src/lib/'
},

View File

@ -12,7 +12,7 @@
"dependencies": {
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/master",
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/master",
"smtp-client": "https://github.com/whiteout-io/smtp-client/tarball/master",
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/master",
"requirejs": "2.1.10"
},
"devDependencies": {

View File

@ -54,7 +54,7 @@ define(function(require) {
fallbackSubject: '(no subject)',
invitationSubject: 'Invitation to a private conversation',
invitationMessage: 'I would like to invite you to a private conversation!\n\nIn order to read my encrypted message please install the Whiteout Mail application. This application is used to read and write messages securely with strong encryption applied.\n\nGo to the Whiteout Networks homepage to learn more and to download the application: https://whiteout.io',
message: 'this is a private conversation. To read my encrypted message below, simply open it in Whiteout Mail.\n\nOpen Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamholjigjoghcfcekhkonijg',
message: 'Hi,\n\nthis is a private conversation. To read my encrypted message below, simply open it in Whiteout Mail.\n\nOpen Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamholjigjoghcfcekhkonijg\n\n',
cryptPrefix: '-----BEGIN PGP MESSAGE-----',
cryptSuffix: '-----END PGP MESSAGE-----',
signature: 'Sent securely from Whiteout Mail',

View File

@ -6,7 +6,7 @@ define(function(require) {
var $ = require('jquery'),
ImapClient = require('imap-client'),
SmtpClient = require('smtp-client'),
PgpMailer = require('pgpmailer'),
EmailDAO = require('js/dao/email-dao'),
RestDAO = require('js/dao/rest-dao'),
PublicKeyDAO = require('js/dao/publickey-dao'),
@ -89,7 +89,7 @@ define(function(require) {
});
function initClients(oauth, certificate) {
var auth, imapOptions, imapClient, smtpOptions, smtpClient;
var auth, imapOptions, imapClient, smtpOptions, pgpMailer;
auth = {
XOAuth2: {
@ -106,15 +106,18 @@ define(function(require) {
ca: [certificate]
};
smtpOptions = {
secure: config.gmail.smtp.secure,
secureConnection: config.gmail.smtp.secure,
port: config.gmail.smtp.port,
host: config.gmail.smtp.host,
auth: auth,
tls: {
ca: [certificate]
},
onError: console.error
};
imapClient = new ImapClient(imapOptions);
smtpClient = new SmtpClient(smtpOptions);
pgpMailer = new PgpMailer(smtpOptions);
imapClient.onError = function(err) {
console.log('IMAP error.', err);
@ -138,7 +141,7 @@ define(function(require) {
// connect to clients
self._emailDao.onConnect({
imapClient: imapClient,
smtpClient: smtpClient
pgpMailer: pgpMailer
}, callback);
}
};

View File

@ -96,7 +96,7 @@ define(function(require) {
self._outboxBusy = true;
// get last item from outbox
self._emailDao.list(function(err, pending) {
self._emailDao.listForOutbox(function(err, pending) {
if (err) {
self._outboxBusy = false;
callback(err);

View File

@ -277,7 +277,14 @@ define(function(require) {
this.to = [{
address: 'max.musterman@gmail.com'
}]; // list of receivers
this.attachments = (attachments) ? [true] : undefined;
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.unread = unread;
this.answered = answered;
this.html = html;
@ -286,7 +293,7 @@ define(function(require) {
this.body = 'Here are a few pointers to help you get started with Whiteout Mail.\n\n# Write encrypted message\n- You can compose a message by clicking on the compose button on the upper right (keyboard shortcut is "n" for a new message or "r" to reply).\n- When typing the recipient\'s email address, secure recipients are marked with a blue label and insecure recipients are red.\n- When sending an email to insecure recipients, the default behavior for Whiteout Mail is to invite them to the service and only send the message content in an encrypted form, once they have joined.\n\n# Advanced features\n- To verify a recipient\'s PGP key, you can hover over the blue label containing their email address and their key fingerprint will be displayed.\n- To view your own key fingerprint, open the account view in the navigation bar on the left. You can compare these with your correspondants over a second channel such as a phonecall.\n\nWe hope this helped you to get started with Whiteout Mail.\n\nYour Whiteout Networks team'; // plaintext body
};
var dummys = [new Email(true, true), new Email(true, false, true, true), new Email(false, true, true), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false)];
var dummys = [new Email(true, true), new Email(true, false, true, true), new Email(false, true, true), new Email(false, true), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false), new Email(false)];
return dummys;
}

View File

@ -2,14 +2,17 @@ define(function(require) {
'use strict';
var appController = require('js/app-controller'),
download = require('js/util/download'),
angular = require('angular'),
crypto, keychain;
emailDao, crypto, keychain;
//
// Controller
//
var ReadCtrl = function($scope) {
emailDao = appController._emailDao;
crypto = appController._crypto;
keychain = appController._keychain;
@ -74,6 +77,38 @@ define(function(require) {
$scope.$apply();
});
}
$scope.download = function(attachment) {
// download file to disk if content is available
if (attachment.content) {
saveToDisk(attachment);
return;
}
var folder = $scope.state.nav.currentFolder;
var email = $scope.state.mailList.selected;
emailDao.getAttachment({
path: folder.path,
uid: email.uid,
attachment: attachment
}, function(err) {
if (err) {
$scope.onError(err);
return;
}
saveToDisk(attachment);
});
function saveToDisk(attachment) {
download.createDownload({
content: attachment.content,
filename: attachment.filename,
contentType: attachment.mimeType
}, $scope.onError);
}
};
};
//

View File

@ -56,6 +56,7 @@ define(function(require) {
$scope.subject = '';
$scope.body = '';
$scope.ciphertextPreview = '';
$scope.attachments = [];
}
function fillFields(re) {
@ -79,7 +80,7 @@ define(function(require) {
// only display non html mails in reply part
if (!re.html) {
body += re.body.split('\n').join('\n> ');
body += re.body.trim().split('\n').join('\n> ');
$scope.body = body;
}
}
@ -182,6 +183,14 @@ define(function(require) {
}
};
//
// Editing attachments
//
$scope.remove = function(attachment) {
$scope.attachments.splice($scope.attachments.indexOf(attachment), 1);
};
//
// Editing email body
//
@ -239,8 +248,13 @@ define(function(require) {
});
}
// add attachment to email object
if ($scope.attachments.length > 0) {
email.attachments = $scope.attachments;
}
// persist the email locally for later smtp transmission
emailDao.store(email, function(err) {
emailDao.storeForOutbox(email, function(err) {
if (err) {
$scope.onError(err);
return;
@ -440,5 +454,36 @@ define(function(require) {
};
});
ngModule.directive('attachmentInput', function() {
return function(scope, elm) {
elm.on('change', function(e) {
for (var i = 0; i < e.target.files.length; i++) {
addAttachment(e.target.files.item(i));
}
});
function addAttachment(file) {
var reader = new FileReader();
reader.onload = function(e) {
scope.attachments.push({
filename: file.name,
mimeType: file.type,
content: new Uint8Array(e.target.result)
});
scope.$apply();
};
reader.readAsArrayBuffer(file);
}
};
});
ngModule.directive('attachmentBtn', function() {
return function(scope, elm) {
elm.on('click', function() {
document.querySelector('#attachment-input').click();
});
};
});
return WriteCtrl;
});

View File

@ -73,7 +73,11 @@ define(function(require) {
var self = this;
self._imapClient = options.imapClient;
self._smtpClient = options.smtpClient;
self._pgpMailer = options.pgpMailer;
// set private key
if (self._crypto && self._crypto._privateKey) {
self._pgpMailer._privateKey = self._crypto._privateKey;
}
// delegation-esque pattern to mitigate between node-style events and plain js
self._imapClient.onIncomingMessage = function(message) {
@ -82,6 +86,9 @@ define(function(require) {
}
};
// connect the pgpmailer
self._pgpmailerLogin();
// connect to newly created imap client
self._imapLogin(function(err) {
if (err) {
@ -116,7 +123,7 @@ define(function(require) {
// set status to online
this._account.online = false;
this._imapClient = undefined;
this._smtpClient = undefined;
self._pgpMailer = undefined;
callback();
};
@ -131,6 +138,8 @@ define(function(require) {
privateKeyArmored: options.keypair.privateKey.encryptedKey,
publicKeyArmored: options.keypair.publicKey.publicKey
}, callback);
// set decrypted privateKey to pgpMailer
self._pgpMailer._privateKey = self._crypto._privateKey;
return;
}
@ -805,7 +814,8 @@ define(function(require) {
if (!senderPubkey) {
// this should only happen if a mail from another channel is in the inbox
setBodyAndContinue('Public key for sender not found!');
email.body = 'Public key for sender not found!';
localCallback(null, email);
return;
}
@ -817,7 +827,23 @@ define(function(require) {
// set encrypted flag
email.encrypted = true;
setBodyAndContinue(decrypted);
// does our message block even need to be parsed?
// this is a very primitive detection if we have a mime node or plain text
// taking this out breaks compatibility to clients < 0.5
if (decrypted.indexOf('Content-Transfer-Encoding:') === -1 &&
decrypted.indexOf('Content-Type:') === -1) {
// decrypted message is plain text and not a well-formed email
email.body = decrypted;
localCallback(null, email);
return;
}
// parse decrypted message
self._imapParseMessageBlock({
message: email,
block: decrypted
}, localCallback);
});
});
@ -828,11 +854,6 @@ define(function(require) {
// parse email body for encrypted message block
email.body = email.body.substring(start, end);
}
function setBodyAndContinue(text) {
email.body = text;
localCallback(null, email);
}
}
};
@ -869,6 +890,18 @@ define(function(require) {
}, callback);
};
EmailDAO.prototype.getAttachment = function(options, callback) {
if (!this._account.online) {
callback({
errMsg: 'Client is currently offline!',
code: 42
});
return;
}
this._imapClient.getAttachment(options, callback);
};
EmailDAO.prototype.sendEncrypted = function(options, callback) {
var self = this,
email = options.email;
@ -889,17 +922,26 @@ define(function(require) {
return;
}
// public key found... encrypt and send
self._encrypt({
email: email,
keys: email.receiverKeys // this Array is set in writer controller
}, function(err, email) {
// get own public key so send message can be read
self._crypto.exportKeys(function(err, ownKeys) {
if (err) {
callback(err);
return;
}
self._smtpClient.send(email, callback);
// add own public key to receiver list
email.receiverKeys.push(ownKeys.publicKeyArmored);
// add whiteout tag to subject
email.subject = str.subjectPrefix + email.subject;
// mime encode, sign, encrypt and send email via smtp
self._pgpMailer.send({
encrypt: true,
cleartextMessage: str.message,
mail: email,
publicKeysArmored: email.receiverKeys
}, callback);
});
};
@ -912,56 +954,19 @@ define(function(require) {
return;
}
this._smtpClient.send(options.email, callback);
// add whiteout tag to subject
options.email.subject = str.subjectPrefix + options.email.subject;
// mime encode, sign and send email via smtp
this._pgpMailer.send({
mail: options.email
}, callback);
};
//
// Internal API
//
// Encryption API
EmailDAO.prototype._encrypt = function(options, callback) {
var self = this,
pt = options.email.body;
options.keys = options.keys || [];
// get own public key so send message can be read
self._crypto.exportKeys(function(err, ownKeys) {
if (err) {
callback(err);
return;
}
// add own public key to receiver list
options.keys.push(ownKeys.publicKeyArmored);
// encrypt the email
self._crypto.encrypt(pt, options.keys, function(err, ct) {
if (err) {
callback(err);
return;
}
// bundle encrypted email together for sending
frameEncryptedMessage(options.email, ct);
callback(null, options.email);
});
});
function frameEncryptedMessage(email, ct) {
var greeting,
message = str.message + '\n\n\n',
signature = '\n\n' + str.signature + '\n\n';
greeting = 'Hi,\n\n';
// build encrypted text body
email.body = greeting + message + ct + signature;
email.subject = str.subjectPrefix + email.subject;
}
};
// Local Storage API
EmailDAO.prototype._localListMessages = function(options, callback) {
@ -989,6 +994,16 @@ define(function(require) {
};
// PGP Mailer API
/**
* Login the smtp client
*/
EmailDAO.prototype._pgpmailerLogin = function() {
this._pgpMailer.login();
};
// IMAP API
/**
@ -1068,6 +1083,10 @@ define(function(require) {
}, callback);
};
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
this._imapClient.parseDecryptedMessageBlock(options, callback);
};
/**
* Get an email messsage including the email body from imap
* @param {String} options.messageId The
@ -1161,29 +1180,47 @@ define(function(require) {
}
};
// to be removed and solved with IMAP!
EmailDAO.prototype.store = function(email, callback) {
/**
* Persists an email object for the outbox, encrypted with the user's public key
* @param {Object} email The email object
* @param {Function} callback(error) Invoked when the email was encrypted and persisted, contains information in case of an error
*/
EmailDAO.prototype.storeForOutbox = function(email, callback) {
var self = this,
dbType = 'email_OUTBOX';
dbType = 'email_OUTBOX',
plaintext = email.body;
// give the email a random identifier (used for storage)
email.id = util.UUID();
// encrypt
self._encrypt({
email: email
}, function(err, email) {
// get own public key so send message can be read
self._crypto.exportKeys(function(err, ownKeys) {
if (err) {
callback(err);
return;
}
// encrypt the email with the user's public key
self._crypto.encrypt(plaintext, [ownKeys.publicKeyArmored], function(err, ciphertext) {
if (err) {
callback(err);
return;
}
// replace plaintext body with pgp message
email.body = ciphertext;
// store to local storage
self._devicestorage.storeList([email], dbType, callback);
});
});
};
// to be removed and solved with IMAP!
EmailDAO.prototype.list = function(callback) {
/**
* Reads and decrypts persisted email objects for the outbox
* @param {Function} callback(error, emails) Invoked when the email was encrypted and persisted, contains information in case of an error
*/
EmailDAO.prototype.listForOutbox = function(callback) {
var self = this,
dbType = 'email_OUTBOX';
@ -1209,15 +1246,12 @@ define(function(require) {
});
mails.forEach(function(mail) {
mail.body = str.cryptPrefix + mail.body.split(str.cryptPrefix)[1].split(str.cryptSuffix)[0] + str.cryptSuffix;
mail.subject = mail.subject.split(str.subjectPrefix)[1];
self._crypto.decrypt(mail.body, ownKeys.publicKeyArmored, function(err, decrypted) {
mail.body = err ? err.errMsg : decrypted;
after();
});
mail.encrypted = true;
});
});
});
};

View File

@ -5,6 +5,8 @@
color: $color-grey-dark;
.headers {
margin-bottom: 1em;
p {
margin: 0px;
padding: 0px;
@ -36,15 +38,47 @@
}
}
.attachments {
position: relative;
width: inherit;
border: 1px;
border-style: solid;
border-color: $color-grey-lighter;
min-height: em(44);
.attachment {
height: 32px;
border-radius: 16px;
vertical-align: middle;
margin: 5px 0 5px 5px;
padding: 5px 10px 5px 10px;
border: 1px;
border-style: solid;
border-color: $color-grey-lighter;
display: inline-block;
span {
font-size: 14px;
color: $color-grey-input;
vertical-align: middle;
}
&:hover,
&:focus {
background-color: darken($color-white, 3%);
cursor: pointer;
}
}
}
.seperator-line {
height: 1px;
color: $color-grey-lighter;
background-color: $color-grey-lighter;
margin-top: 1em;
margin-bottom: 1.75em;
}
.body {
margin-top: 1.75em;
cursor: text;
padding-bottom: 200px;
line-height: 1.5em;

View File

@ -38,7 +38,8 @@
}
.subject-box {
margin: 20px 0;
position: relative;
margin: 20px 0 7px 0;
width: inherit;
border: 1px;
border-style: solid;
@ -57,16 +58,76 @@
width: 100%;
}
input[type=file] {
visibility: hidden;
width: 0;
height: 0;
}
.btn-attachment {
float: right;
position: absolute;
top: 0;
right: 0;
padding: em(7.5) em(7.5) em(4) em(7.5);
margin: em(5);
outline: none;
//color: $color-grey-lightest;
//background-color: $color-blue;
color: $btn-disabled-color;
background-color: $btn-disabled-back-color;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJUlEQVQIW2NkQABJIPM5lCvJCGMgC4LYIAkUlTAFMB0gjRQaBQCw8go5lVnl5wAAAABJRU5ErkJggg==);
color: $color-grey-lightest;
background-color: $color-blue;
&:hover,
&:focus {
background-color: lighten($color-blue, 10%);
}
&:active,
&.active {
top: 1px;
right: -1px;
}
}
}
.attachments-box {
position: relative;
margin: 0 0 5px 0;
width: inherit;
border: 1px;
border-style: solid;
border-color: $color-grey-lighter;
min-height: em(44);
.attachment {
height: 32px;
border-radius: 16px;
vertical-align: middle;
margin: 5px 0 5px 5px;
padding: 5px 5px 5px 10px;
border: 1px;
border-style: solid;
border-color: $color-grey-lighter;
cursor: default;
display: inline-block;
span {
font-size: 14px;
color: $color-grey-input;
vertical-align: middle;
}
.close {
margin-left: 5px;
&:hover,
&:focus {
color: darken($color-grey, 10%);
cursor: pointer;
}
}
&:hover,
&:focus {
background-color: darken($color-white, 2%);
}
}
}

View File

@ -21,7 +21,20 @@
</div>
</div><!--/.headers-->
<div ng-switch="state.mailList.selected.attachments !== undefined && state.mailList.selected.attachments.length > 0">
<div ng-switch-when="true">
<div class="attachments">
<span class="attachment" ng-repeat="attachment in state.mailList.selected.attachments" ng-click="download(attachment)">
<span data-icon="&#xe003;"></span>
{{attachment.filename}}
</span><!--/.attachment-->
</div><!--/.attachments-->
</div>
<div ng-switch-default>
<div class="seperator-line"></div>
</div>
</div><!--/.ng-switch-->
<div class="body">
<!-- Render lines of a text-email in divs for easier styling -->

View File

@ -24,12 +24,25 @@
<div class="subject-box">
<div class="subject-line">
<input ng-model="subject" class="subject" spellcheck="true" tabindex="2" placeholder="Subject" ng-change="updatePreview()">
</div>
<button class="btn-attachment">
</div><!--/.subject-line-->
<input id="attachment-input" type="file" multiple attachment-input>
<button class="btn-attachment" attachment-btn>
<div data-icon="&#xe003;"></div>
</button>
</button><!--/.btn-attachment-->
</div><!--/.subject-box-->
<div ng-switch="attachments.length > 0">
<div ng-switch-when="true">
<div class="attachments-box">
<span ng-repeat="attachment in attachments" class="attachment">
<span data-icon="&#xe003;"></span>
{{attachment.filename}}
<span class="close" data-icon="&#xe000;" ng-click="remove(attachment)"></span>
</span><!--/.attachment-->
</div><!--/.attachments-box-->
</div><!--/ng-switch-when-->
</div><!--/ng-switch-->
<div class="body" focus-child>
<p ng-model="body" contentEditable="true" spellcheck="false" ng-change="updatePreview()" tabindex="3" focus-me="state.writer.open && writerTitle === 'Reply'"></p>

View File

@ -50,13 +50,20 @@ define(function(require) {
mocks.module('addaccounttest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login');
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
location.path.restore();
scope.$apply.restore();
done();
});
scope = $rootScope.$new();
scope.state = {};
sinon.stub(scope, '$apply', function() {});
ctrl = $controller(AddAccountCtrl, {
$location: location,
$scope: scope

View File

@ -4,17 +4,18 @@ define(function(require) {
var EmailDAO = require('js/dao/email-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
ImapClient = require('imap-client'),
SmtpClient = require('smtp-client'),
PgpMailer = require('pgpmailer'),
PGP = require('js/crypto/pgp'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
str = require('js/app-config').string,
expect = chai.expect;
describe('Email DAO unit tests', function() {
var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub;
var dao, keychainStub, imapClientStub, pgpMailerStub, pgpStub, devicestorageStub;
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
dummyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
dummyDecryptedMail, dummyLegacyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
corruptedVerificationMail, corruptedVerificationUuid,
nonWhitelistedMail;
@ -65,6 +66,20 @@ define(function(require) {
answered: false
};
dummyDecryptedMail = {
uid: 1234,
from: [{
address: 'asd@asd.de'
}],
to: [{
address: 'qwe@qwe.de'
}],
subject: 'qweasd',
body: 'Content-Type: multipart/signed;\r\n boundary="Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2";\r\n protocol="application/pgp-signature";\r\n micalg=pgp-sha512\r\n\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Type: multipart/mixed;\r\n boundary="Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA"\r\n\r\n\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/plain;\r\n charset=us-ascii\r\n\r\nasdasd \r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Disposition: attachment;\r\n filename=dummy.txt\r\nContent-Type: text/plain;\r\n name="dummy.txt"\r\nContent-Transfer-Encoding: 7bit\r\n\r\noaudbcoaurbvosuabvlasdjbfalwubjvawvb\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA--\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=signature.asc\r\nContent-Type: application/pgp-signature;\r\n name=signature.asc\r\nContent-Description: Message signed with OpenPGP using GPGMail\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nComment: GPGTools - https://gpgtools.org\r\n\r\niQEcBAEBCgAGBQJS2kO1AAoJEDzmUwH7XO/cP+YH/2PSBxX1ZZd83Uf9qBGDY807\r\niHOdgPFXm64YjSnohO7XsPcnmihqP1ipS2aaCXFC3/Vgb9nc4isQFS+i1VdPwfuR\r\n1Pd2l3dC4/nD4xO9h/W6JW7Yd24NS5TJD5cA7LYwQ8LF+rOzByMatiTMmecAUCe8\r\nEEalEjuogojk4IacA8dg/bfLqQu9E+0GYUJBcI97dx/0jZ0qMOxbWOQLsJ3DnUnV\r\nOad7pAIbHEO6T0EBsH7TyTj4RRHkP6SKE0mm6ZYUC7KCk2Z3MtkASTxUrnqW5qZ5\r\noaXUO9GEc8KZcmbCdhZY2Y5h+dmucaO0jpbeSKkvtYyD4KZrSvt7NTb/0dSLh4Y=\r\n=G8km\r\n-----END PGP SIGNATURE-----\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2--\r\n',
unread: false,
answered: false,
receiverKeys: ['-----BEGIN PGP PUBLIC KEY-----\nasd\n-----END PGP PUBLIC KEY-----']
};
dummyLegacyDecryptedMail = {
uid: 1234,
from: [{
address: 'asd@asd.de'
@ -110,7 +125,7 @@ define(function(require) {
keychainStub = sinon.createStubInstance(KeychainDAO);
imapClientStub = sinon.createStubInstance(ImapClient);
smtpClientStub = sinon.createStubInstance(SmtpClient);
pgpMailerStub = sinon.createStubInstance(PgpMailer);
pgpStub = sinon.createStubInstance(PGP);
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
@ -130,7 +145,7 @@ define(function(require) {
dao.onConnect({
imapClient: imapClientStub,
smtpClient: smtpClientStub
pgpMailer: pgpMailerStub
}, function(err) {
expect(err).to.not.exist;
expect(dao._account.online).to.be.true;
@ -275,11 +290,12 @@ define(function(require) {
});
describe('onConnect', function() {
var imapLoginStub, imapListFoldersStub;
var imapLoginStub, imapListFoldersStub, pgpmailerLoginStub;
beforeEach(function(done) {
// imap login
imapLoginStub = sinon.stub(dao, '_imapLogin');
pgpmailerLoginStub = sinon.stub(dao, '_pgpmailerLogin');
imapListFoldersStub = sinon.stub(dao, '_imapListFolders');
dao.onDisconnect(null, function(err) {
@ -293,17 +309,20 @@ define(function(require) {
afterEach(function() {
imapLoginStub.restore();
pgpmailerLoginStub.restore();
imapListFoldersStub.restore();
});
it('should fail due to error in imap login', function(done) {
imapLoginStub.yields({});
pgpmailerLoginStub.returns();
dao.onConnect({
imapClient: imapClientStub,
smtpClient: smtpClientStub
pgpMailer: pgpMailerStub
}, function(err) {
expect(err).to.exist;
expect(pgpmailerLoginStub.calledOnce).to.be.true;
expect(imapLoginStub.calledOnce).to.be.true;
expect(dao._account.online).to.be.false;
done();
@ -313,10 +332,11 @@ define(function(require) {
it('should work when folder already initiated', function(done) {
dao._account.folders = [];
imapLoginStub.yields();
pgpmailerLoginStub.returns();
dao.onConnect({
imapClient: imapClientStub,
smtpClient: smtpClientStub
pgpMailer: pgpMailerStub
}, function(err) {
expect(err).to.not.exist;
expect(dao._account.online).to.be.true;
@ -329,11 +349,12 @@ define(function(require) {
it('should work when folder not yet initiated', function(done) {
var folders = [];
imapLoginStub.yields();
pgpmailerLoginStub.returns();
imapListFoldersStub.yields(null, folders);
dao.onConnect({
imapClient: imapClientStub,
smtpClient: smtpClientStub
pgpMailer: pgpMailerStub
}, function(err) {
expect(err).to.not.exist;
expect(dao._account.online).to.be.true;
@ -471,6 +492,33 @@ define(function(require) {
});
});
describe('_imapParseMessageBlock', function() {
it('should parse a message', function(done) {
imapClientStub.parseDecryptedMessageBlock.yields(null, {});
dao._imapParseMessageBlock(function(err, msg) {
expect(err).to.not.exist;
expect(msg).to.exist;
done();
});
});
});
describe('_pgpmailerLogin', function() {
it('should work', function() {
// called once in onConnect
expect(pgpMailerStub.login.calledOnce).to.be.true;
pgpMailerStub.login.returns();
dao._pgpmailerLogin();
expect(pgpMailerStub.login.calledTwice).to.be.true;
});
});
describe('_imapLogin', function() {
it('should fail when disconnected', function(done) {
dao.onDisconnect(null, function(err) {
@ -823,7 +871,7 @@ define(function(require) {
describe('sync', function() {
it('should work initially', function(done) {
var folder, localListStub, invocations, imapSearchStub;
var folder, localListStub, invocations, imapSearchStub, imapParseStub;
invocations = 0;
folder = 'FOLDAAAA';
@ -852,6 +900,13 @@ define(function(require) {
answered: true
}).yields(null, []);
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
imapParseStub.withArgs({
message: dummyEncryptedMail,
block: dummyDecryptedMail.body
}).yields(null, dummyDecryptedMail);
dao.sync({
folder: folder
}, function(err) {
@ -870,6 +925,7 @@ define(function(require) {
expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(imapSearchStub.calledThrice).to.be.true;
expect(dao._account.folders[0].count).to.equal(1);
expect(imapParseStub.calledOnce).to.be.true;
done();
});
@ -903,7 +959,7 @@ define(function(require) {
});
it('should initially sync downstream when storage is empty', function(done) {
var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub;
var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub, imapParseStub;
invocations = 0;
folder = 'FOLDAAAA';
@ -912,8 +968,8 @@ define(function(require) {
path: folder
}];
dummyEncryptedMail.unread = true;
dummyEncryptedMail.answered = true;
dummyDecryptedMail.unread = true;
dummyDecryptedMail.answered = true;
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
@ -938,6 +994,9 @@ define(function(require) {
answered: true
}).yields(null, [dummyEncryptedMail.uid]);
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
imapParseStub.yields(null, dummyDecryptedMail);
localStoreStub = sinon.stub(dao, '_localStoreMessages').yields();
dao.sync({
@ -960,6 +1019,7 @@ define(function(require) {
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(dao._account.folders[0].count).to.equal(1);
expect(imapParseStub.calledOnce).to.be.true;
done();
});
@ -1162,7 +1222,7 @@ define(function(require) {
});
});
it('should error whilte removing messages from local', function(done) {
it('should error while removing messages from local', function(done) {
var invocations, folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub;
invocations = 0;
@ -1316,7 +1376,7 @@ define(function(require) {
});
});
it('should fetch messages downstream from the remote', function(done) {
it('should fetch legacy messages downstream from the remote', function(done) {
var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub;
invocations = 0;
@ -1348,10 +1408,75 @@ define(function(require) {
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, dummyLegacyDecryptedMail.body);
dao.sync({
folder: folder
}, function(err) {
expect(err).to.not.exist;
if (invocations === 0) {
expect(dao._account.busy).to.be.true;
invocations++;
return;
}
expect(dao._account.busy).to.be.false;
expect(dao._account.folders[0].messages).to.not.be.empty;
expect(localListStub.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;
expect(pgpStub.decrypt.calledOnce).to.be.true;
done();
});
});
it('should fetch valid pgp messages downstream from the remote', function(done) {
var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub, imapParseStub;
invocations = 0;
folder = 'FOLDAAAA';
dao._account.folders = [{
type: 'Folder',
path: folder,
messages: []
}];
localListStub = sinon.stub(dao, '_localListMessages').withArgs({
folder: folder
}).yields(null, []);
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, []);
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);
imapParseStub = sinon.stub(dao, '_imapParseMessageBlock');
imapParseStub.withArgs({
message: dummyEncryptedMail,
block: dummyDecryptedMail.body
}).yields(null, dummyDecryptedMail);
dao.sync({
folder: folder
@ -1372,6 +1497,7 @@ define(function(require) {
expect(localStoreStub.calledOnce).to.be.true;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(imapParseStub.calledOnce).to.be.true;
done();
});
});
@ -2354,13 +2480,27 @@ define(function(require) {
describe('sendPlaintext', function() {
it('should work', function(done) {
smtpClientStub.send.withArgs(dummyEncryptedMail).yields();
pgpMailerStub.send.withArgs({
mail: dummyEncryptedMail
}).yields();
dao.sendPlaintext({
email: dummyEncryptedMail
}, function(err) {
expect(err).to.not.exist;
expect(smtpClientStub.send.calledOnce).to.be.true;
expect(pgpMailerStub.send.calledOnce).to.be.true;
done();
});
});
it('should not work when pgpmailer fails', function(done) {
pgpMailerStub.send.yields({});
dao.sendPlaintext({
email: dummyEncryptedMail
}, function(err) {
expect(err).to.exist;
expect(pgpMailerStub.send.calledOnce).to.be.true;
done();
});
});
@ -2368,101 +2508,76 @@ define(function(require) {
describe('sendEncrypted', function() {
it('should work', function(done) {
var encryptStub = sinon.stub(dao, '_encrypt').yields(null, {});
smtpClientStub.send.yields();
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.not.exist;
expect(encryptStub.calledOnce).to.be.true;
expect(smtpClientStub.send.calledOnce).to.be.true;
done();
});
});
it('should not work when encryption fails', function(done) {
var encryptStub = sinon.stub(dao, '_encrypt').yields({});
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.exist;
expect(encryptStub.calledOnce).to.be.true;
expect(smtpClientStub.send.called).to.be.false;
done();
});
});
it('should not work without recipients', function(done) {
var encryptStub = sinon.stub(dao, '_encrypt');
delete dummyDecryptedMail.to;
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.exist;
expect(encryptStub.called).to.be.false;
expect(smtpClientStub.send.called).to.be.false;
done();
});
});
it('should not work with without sender', function(done) {
var encryptStub = sinon.stub(dao, '_encrypt');
delete dummyDecryptedMail.from;
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.exist;
expect(encryptStub.called).to.be.false;
expect(smtpClientStub.send.called).to.be.false;
done();
});
});
});
describe('_encrypt', function() {
it('should work without attachments', function(done) {
var ct = 'OMGSOENCRYPTED';
pgpStub.exportKeys.yields(null, {
privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
publicKeyArmored: mockKeyPair.publicKey.publicKey
});
pgpStub.encrypt.yields(null, ct);
dao._encrypt({
pgpMailerStub.send.withArgs({
encrypt: true,
cleartextMessage: str.message,
mail: dummyDecryptedMail,
publicKeysArmored: dummyDecryptedMail.receiverKeys
}).yields();
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.not.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.encrypt.calledOnce).to.be.true;
expect(dummyDecryptedMail.body).to.contain(ct);
expect(pgpMailerStub.send.calledOnce).to.be.true;
done();
});
});
it('should not work when pgpmailer fails', function(done) {
pgpStub.exportKeys.yields(null, {
privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
publicKeyArmored: mockKeyPair.publicKey.publicKey
});
pgpMailerStub.send.yields({});
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpMailerStub.send.calledOnce).to.be.true;
done();
});
});
it('should not work when sender key export fails', function(done) {
pgpStub.exportKeys.yields({});
dao.sendEncrypted({
email: dummyDecryptedMail
}, function(err) {
expect(err).to.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpMailerStub.send.calledOnce).to.be.false;
done();
});
});
});
describe('store', function() {
describe('storeForOutbox', function() {
it('should work', function(done) {
pgpStub.exportKeys.yields(null, {
publicKeyArmored: 'omgsocrypto'
});
pgpStub.encrypt.yields(null, 'asdfasfd');
devicestorageStub.storeList.yields();
var key = 'omgsocrypto';
dao.store(dummyDecryptedMail, function(err) {
pgpStub.exportKeys.yields(null, {
publicKeyArmored: key
});
pgpStub.encrypt.withArgs(dummyDecryptedMail.body, [key]).yields(null, 'asdfasfd');
devicestorageStub.storeList.withArgs([dummyDecryptedMail], 'email_OUTBOX').yields();
dao.storeForOutbox(dummyDecryptedMail, function(err) {
expect(err).to.not.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.encrypt.calledOnce).to.be.true;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
@ -2470,17 +2585,72 @@ define(function(require) {
done();
});
});
});
describe('list', function() {
it('should work', function(done) {
devicestorageStub.listItems.yields(null, [dummyEncryptedMail]);
it('should work when store fails', function(done) {
var key = 'omgsocrypto';
pgpStub.exportKeys.yields(null, {
publicKeyArmored: 'omgsocrypto'
publicKeyArmored: key
});
pgpStub.decrypt.yields(null, dummyDecryptedMail.body);
pgpStub.encrypt.yields(null, 'asdfasfd');
devicestorageStub.storeList.yields({});
dao.list(function(err, mails) {
dao.storeForOutbox(dummyDecryptedMail, function(err) {
expect(err).to.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.encrypt.calledOnce).to.be.true;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should work when encryption fails', function(done) {
var key = 'omgsocrypto';
pgpStub.exportKeys.yields(null, {
publicKeyArmored: key
});
pgpStub.encrypt.yields({});
dao.storeForOutbox(dummyDecryptedMail, function(err) {
expect(err).to.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.encrypt.calledOnce).to.be.true;
expect(devicestorageStub.storeList.called).to.be.false;
done();
});
});
it('should work when key export fails', function(done) {
pgpStub.exportKeys.yields({});
dao.storeForOutbox(dummyDecryptedMail, function(err) {
expect(err).to.exist;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.called).to.be.false;
done();
});
});
});
describe('listForOutbox', function() {
it('should work', function(done) {
var key = 'omgsocrypto';
devicestorageStub.listItems.withArgs('email_OUTBOX', 0, null).yields(null, [dummyEncryptedMail]);
pgpStub.exportKeys.yields(null, {
publicKeyArmored: key
});
pgpStub.decrypt.withArgs(dummyEncryptedMail.body, key).yields(null, dummyDecryptedMail.body);
dao.listForOutbox(function(err, mails) {
expect(err).to.not.exist;
expect(devicestorageStub.listItems.calledOnce).to.be.true;
@ -2488,12 +2658,63 @@ define(function(require) {
expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(mails.length).to.equal(1);
expect(mails[0].body).to.equal(dummyDecryptedMail.body);
expect(mails[0].subject).to.equal(dummyDecryptedMail.subject);
done();
});
});
it('should not work when decryption fails', function(done) {
var key = 'omgsocrypto',
errMsg = 'THIS IS AN ERROR!';
devicestorageStub.listItems.yields(null, [dummyEncryptedMail]);
pgpStub.exportKeys.yields(null, {
publicKeyArmored: key
});
pgpStub.decrypt.yields({errMsg: errMsg});
dao.listForOutbox(function(err, mails) {
expect(err).to.not.exist;
expect(mails[0].body).to.equal(errMsg);
expect(devicestorageStub.listItems.calledOnce).to.be.true;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
done();
});
});
it('should not work when key export fails', function(done) {
devicestorageStub.listItems.yields(null, [dummyEncryptedMail]);
pgpStub.exportKeys.yields({});
dao.listForOutbox(function(err, mails) {
expect(err).to.exist;
expect(mails).to.not.exist;
expect(devicestorageStub.listItems.calledOnce).to.be.true;
expect(pgpStub.exportKeys.calledOnce).to.be.true;
expect(pgpStub.decrypt.called).to.be.false;
done();
});
});
it('should not work when list from storage fails', function(done) {
devicestorageStub.listItems.yields({});
dao.listForOutbox(function(err, mails) {
expect(err).to.exist;
expect(mails).to.not.exist;
expect(devicestorageStub.listItems.calledOnce).to.be.true;
expect(pgpStub.exportKeys.called).to.be.false;
expect(pgpStub.decrypt.called).to.be.false;
done();
});
});
});
});
});

View File

@ -85,7 +85,7 @@ define(function(require) {
};
dummyMails = [member, invited, notinvited];
emailDaoStub.list.yieldsAsync(null, dummyMails);
emailDaoStub.listForOutbox.yieldsAsync(null, dummyMails);
emailDaoStub.sendEncrypted.withArgs(sinon.match(function(opts) {
return typeof opts.email !== 'undefined' && opts.email.to.address === member.to.address;
})).yieldsAsync();
@ -111,7 +111,7 @@ define(function(require) {
expect(outbox._outboxBusy).to.be.false;
expect(unsentCount).to.equal(2);
expect(emailDaoStub.list.callCount).to.equal(1);
expect(emailDaoStub.listForOutbox.callCount).to.equal(1);
expect(emailDaoStub.sendEncrypted.callCount).to.equal(1);
expect(emailDaoStub.sendPlaintext.callCount).to.equal(1);
expect(devicestorageStub.removeList.callCount).to.equal(1);
@ -136,7 +136,7 @@ define(function(require) {
it('should not process outbox in offline mode', function(done) {
emailDaoStub._account.online = false;
emailDaoStub.list.yieldsAsync(null, [{
emailDaoStub.listForOutbox.yieldsAsync(null, [{
id: '123',
to: [{
name: 'member',
@ -147,7 +147,7 @@ define(function(require) {
outbox._processOutbox(function(err, count) {
expect(err).to.not.exist;
expect(count).to.equal(1);
expect(emailDaoStub.list.callCount).to.equal(1);
expect(emailDaoStub.listForOutbox.callCount).to.equal(1);
expect(outbox._outboxBusy).to.be.false;
done();
});

View File

@ -277,13 +277,13 @@ define(function(require) {
scope.onError = function(err) {
expect(err).to.not.exist;
expect(scope.state.writer.open).to.be.false;
expect(emailDaoMock.store.calledOnce).to.be.true;
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
expect(emailDaoMock.sync.calledOnce).to.be.true;
done();
};
emailDaoMock.store.yields();
emailDaoMock.storeForOutbox.yields();
emailDaoMock.sync.yields({
code: 42
});
@ -310,13 +310,13 @@ define(function(require) {
scope.onError = function(err) {
expect(err).to.not.exist;
expect(scope.state.writer.open).to.be.false;
expect(emailDaoMock.store.calledOnce).to.be.true;
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
expect(emailDaoMock.sync.calledOnce).to.be.true;
done();
};
emailDaoMock.store.yields();
emailDaoMock.storeForOutbox.yields();
emailDaoMock.sync.yields();
scope.state.writer.write(re);
@ -341,13 +341,13 @@ define(function(require) {
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.state.writer.open).to.be.false;
expect(emailDaoMock.store.calledOnce).to.be.true;
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
expect(emailDaoMock.sync.calledOnce).to.be.true;
done();
};
emailDaoMock.store.yields();
emailDaoMock.storeForOutbox.yields();
emailDaoMock.sync.yields({});
scope.state.writer.write(re);
@ -367,7 +367,7 @@ define(function(require) {
scope.subject = 'yaddablabla';
scope.toKey = 'Public Key';
emailDaoMock.store.withArgs(sinon.match(function(mail) {
emailDaoMock.storeForOutbox.withArgs(sinon.match(function(mail) {
return mail.from[0].address === emailAddress && mail.to.length === 1 && mail.receiverKeys.length === 1;
})).yields({
errMsg: 'snafu'
@ -376,7 +376,7 @@ define(function(require) {
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.state.writer.open).to.be.true;
expect(emailDaoMock.store.calledOnce).to.be.true;
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
done();
};
scope.sendToOutbox();