diff --git a/lib/emailservice.js b/lib/emailservice.js index b05bb07..8181f4e 100644 --- a/lib/emailservice.js +++ b/lib/emailservice.js @@ -326,6 +326,15 @@ EmailService.prototype._readAndApplyTemplates = function(notification, emailType }); }; +EmailService.prototype._checkShouldSendEmail = function(notification, cb) { + var self = this; + + if (notification.type != 'NewTxProposal') return cb(null, true); + self.storage.fetchWallet(notification.walletId, function(err, wallet) { + return cb(err, wallet.m > 1); + }); +}; + EmailService.prototype.sendEmail = function(notification, cb) { var self = this; @@ -334,63 +343,69 @@ EmailService.prototype.sendEmail = function(notification, cb) { var emailType = EMAIL_TYPES[notification.type]; if (!emailType) return cb(); - self._getRecipientsList(notification, emailType, function(err, recipientsList) { - if (_.isEmpty(recipientsList)) return cb(); + self._checkShouldSendEmail(notification, function(err, should) { + if (err) return cb(err); + if (!should) return cb(); - // TODO: Optimize so one process does not have to wait until all others are done - // Instead set a flag somewhere in the db to indicate that this process is free - // to serve another request. - self.lock.runLocked('email-' + notification.id, cb, function(cb) { - self.storage.fetchEmailByNotification(notification.id, function(err, email) { - if (err) return cb(err); - if (email) return cb(); + self._getRecipientsList(notification, emailType, function(err, recipientsList) { + if (_.isEmpty(recipientsList)) return cb(); - async.waterfall([ + // TODO: Optimize so one process does not have to wait until all others are done + // Instead set a flag somewhere in the db to indicate that this process is free + // to serve another request. + self.lock.runLocked('email-' + notification.id, cb, function(cb) { + self.storage.fetchEmailByNotification(notification.id, function(err, email) { + if (err) return cb(err); + if (email) return cb(); - function(next) { - self._readAndApplyTemplates(notification, emailType, recipientsList, next); - }, - function(contents, next) { - async.map(recipientsList, function(recipient, next) { - var content = contents[recipient.language]; - var email = Model.Email.create({ - walletId: notification.walletId, - copayerId: recipient.copayerId, - from: self.from, - to: recipient.emailAddress, - subject: content.plain.subject, - bodyPlain: content.plain.body, - bodyHtml: content.html ? content.html.body : null, - notificationId: notification.id, + async.waterfall([ + + function(next) { + self._readAndApplyTemplates(notification, emailType, recipientsList, next); + }, + function(contents, next) { + async.map(recipientsList, function(recipient, next) { + var content = contents[recipient.language]; + var email = Model.Email.create({ + walletId: notification.walletId, + copayerId: recipient.copayerId, + from: self.from, + to: recipient.emailAddress, + subject: content.plain.subject, + bodyPlain: content.plain.body, + bodyHtml: content.html ? content.html.body : null, + notificationId: notification.id, + }); + self.storage.storeEmail(email, function(err) { + return next(err, email); + }); + }, next); + }, + function(emails, next) { + async.each(emails, function(email, next) { + self._send(email, function(err) { + if (err) { + email.setFail(); + } else { + email.setSent(); + } + self.storage.storeEmail(email, next); + }); + }, function(err) { + return next(); }); - self.storage.storeEmail(email, function(err) { - return next(err, email); - }); - }, next); - }, - function(emails, next) { - async.each(emails, function(email, next) { - self._send(email, function(err) { - if (err) { - email.setFail(); - } else { - email.setSent(); - } - self.storage.storeEmail(email, next); - }); - }, function(err) { - return next(); - }); - }, - ], function(err) { - if (err) { - log.error('An error ocurred generating email notification', err); - } - return cb(err); + }, + ], function(err) { + if (err) { + log.error('An error ocurred generating email notification', err); + } + return cb(err); + }); }); }); }); }); + }; module.exports = EmailService; diff --git a/test/integration/server.js b/test/integration/server.js index c84d4c8..41fc629 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -380,277 +380,64 @@ describe('Wallet service', function() { describe('Email notifications', function() { var server, wallet, mailerStub, emailService; - beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w) { - server = s; - wallet = w; + describe('Shared wallet', function() { + beforeEach(function(done) { + helpers.createAndJoinWallet(2, 3, function(s, w) { + server = s; + wallet = w; - var i = 0; - async.eachSeries(w.copayers, function(copayer, next) { - helpers.getAuthServer(copayer.id, function(server) { - server.savePreferences({ - email: 'copayer' + (++i) + '@domain.com', - unit: 'bit', - }, next); - }); - }, function(err) { - should.not.exist(err); - - mailerStub = sinon.stub(); - mailerStub.sendMail = sinon.stub(); - mailerStub.sendMail.yields(); - - emailService = new EmailService(); - emailService.start({ - lockOpts: {}, - messageBroker: server.messageBroker, - storage: storage, - mailer: mailerStub, - emailOpts: { - from: 'bws@dummy.net', - subjectPrefix: '[test wallet]', - publicTxUrlTemplate: { - livenet: 'https://insight.bitpay.com/tx/{{txid}}', - testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', - }, - }, + var i = 0; + async.eachSeries(w.copayers, function(copayer, next) { + helpers.getAuthServer(copayer.id, function(server) { + server.savePreferences({ + email: 'copayer' + (++i) + '@domain.com', + unit: 'bit', + }, next); + }); }, function(err) { should.not.exist(err); - done(); + + mailerStub = sinon.stub(); + mailerStub.sendMail = sinon.stub(); + mailerStub.sendMail.yields(); + + emailService = new EmailService(); + emailService.start({ + lockOpts: {}, + messageBroker: server.messageBroker, + storage: storage, + mailer: mailerStub, + emailOpts: { + from: 'bws@dummy.net', + subjectPrefix: '[test wallet]', + publicTxUrlTemplate: { + livenet: 'https://insight.bitpay.com/tx/{{txid}}', + testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', + }, + }, + }, function(err) { + should.not.exist(err); + done(); + }); }); }); }); - }); - it('should notify copayers a new tx proposal has been created', function(done) { - var _readTemplateFile_old = emailService._readTemplateFile; - emailService._readTemplateFile = function(language, filename, cb) { - if (_.endsWith(filename, '.html')) { - return cb(null, '{{walletName}}'); - } else { - _readTemplateFile_old.call(emailService, language, filename, cb); - } - }; - helpers.stubUtxos(server, wallet, [1, 1], function() { - var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { - message: 'some message' - }); - server.createTx(txOpts, function(err, tx) { - should.not.exist(err); - setTimeout(function() { - var calls = mailerStub.sendMail.getCalls(); - calls.length.should.equal(2); - var emails = _.map(calls, function(c) { - return c.args[0]; - }); - _.difference(['copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; - var one = emails[0]; - one.from.should.equal('bws@dummy.net'); - one.subject.should.contain('New payment proposal'); - one.text.should.contain(wallet.name); - one.text.should.contain(wallet.copayers[0].name); - should.exist(one.html); - one.html.indexOf('').should.equal(0); - one.html.should.contain(wallet.name); - server.storage.fetchUnsentEmails(function(err, unsent) { - should.not.exist(err); - unsent.should.be.empty; - emailService._readTemplateFile = _readTemplateFile_old; - done(); - }); - }, 100); - }); - }); - }); - - it('should not send email if unable to apply template to notification', function(done) { - var _applyTemplate_old = emailService._applyTemplate; - emailService._applyTemplate = function(template, data, cb) { - _applyTemplate_old.call(emailService, template, undefined, cb); - }; - helpers.stubUtxos(server, wallet, [1, 1], function() { - var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { - message: 'some message' - }); - server.createTx(txOpts, function(err, tx) { - should.not.exist(err); - setTimeout(function() { - var calls = mailerStub.sendMail.getCalls(); - calls.length.should.equal(0); - server.storage.fetchUnsentEmails(function(err, unsent) { - should.not.exist(err); - unsent.should.be.empty; - emailService._applyTemplate = _applyTemplate_old; - done(); - }); - }, 100); - }); - }); - }); - - it('should notify copayers a new outgoing tx has been created', function(done) { - var _readTemplateFile_old = emailService._readTemplateFile; - emailService._readTemplateFile = function(language, filename, cb) { - if (_.endsWith(filename, '.html')) { - return cb(null, '{{&urlForTx}}'); - } else { - _readTemplateFile_old.call(emailService, language, filename, cb); - } - }; - helpers.stubUtxos(server, wallet, [1, 1], function() { - var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { - message: 'some message' - }); - - var txp; - async.waterfall([ - - function(next) { - server.createTx(txOpts, next); - }, - function(t, next) { - txp = t; - async.eachSeries(_.range(2), function(i, next) { - var copayer = TestData.copayers[i]; - helpers.getAuthServer(copayer.id44, function(server) { - var signatures = helpers.clientSign(txp, copayer.xPrivKey); - server.signTx({ - txProposalId: txp.id, - signatures: signatures, - }, function(err, t) { - txp = t; - next(); - }); - }); - }, next); - }, - function(next) { - helpers.stubBroadcast(); - server.broadcastTx({ - txProposalId: txp.id, - }, next); - }, - ], function(err) { - should.not.exist(err); - - setTimeout(function() { - var calls = mailerStub.sendMail.getCalls(); - var emails = _.map(_.takeRight(calls, 3), function(c) { - return c.args[0]; - }); - _.difference(['copayer1@domain.com', 'copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; - var one = emails[0]; - one.from.should.equal('bws@dummy.net'); - one.subject.should.contain('Payment sent'); - one.text.should.contain(wallet.name); - one.text.should.contain('800,000'); - should.exist(one.html); - one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid); - server.storage.fetchUnsentEmails(function(err, unsent) { - should.not.exist(err); - unsent.should.be.empty; - emailService._readTemplateFile = _readTemplateFile_old; - done(); - }); - }, 100); - }); - }); - }); - - it('should notify copayers a tx has been finally rejected', function(done) { - helpers.stubUtxos(server, wallet, 1, function() { - var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { - message: 'some message' - }); - - var txpId; - async.waterfall([ - - function(next) { - server.createTx(txOpts, next); - }, - function(txp, next) { - txpId = txp.id; - async.eachSeries(_.range(1, 3), function(i, next) { - var copayer = TestData.copayers[i]; - helpers.getAuthServer(copayer.id44, function(server) { - server.rejectTx({ - txProposalId: txp.id, - }, next); - }); - }, next); - }, - ], function(err) { - should.not.exist(err); - - setTimeout(function() { - var calls = mailerStub.sendMail.getCalls(); - var emails = _.map(_.takeRight(calls, 2), function(c) { - return c.args[0]; - }); - _.difference(['copayer1@domain.com', 'copayer2@domain.com'], _.pluck(emails, 'to')).should.be.empty; - var one = emails[0]; - one.from.should.equal('bws@dummy.net'); - one.subject.should.contain('Payment proposal rejected'); - one.text.should.contain(wallet.name); - one.text.should.contain('copayer 2, copayer 3'); - one.text.should.not.contain('copayer 1'); - server.storage.fetchUnsentEmails(function(err, unsent) { - should.not.exist(err); - unsent.should.be.empty; - done(); - }); - }, 100); - }); - }); - }); - - it('should notify copayers of incoming txs', function(done) { - server.createAddress({}, function(err, address) { - should.not.exist(err); - - // Simulate incoming tx notification - server._notify('NewIncomingTx', { - txid: '999', - address: address, - amount: 12300000, - }, function(err) { - setTimeout(function() { - var calls = mailerStub.sendMail.getCalls(); - calls.length.should.equal(3); - var emails = _.map(calls, function(c) { - return c.args[0]; - }); - _.difference(['copayer1@domain.com', 'copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; - var one = emails[0]; - one.from.should.equal('bws@dummy.net'); - one.subject.should.contain('New payment received'); - one.text.should.contain(wallet.name); - one.text.should.contain('123,000'); - server.storage.fetchUnsentEmails(function(err, unsent) { - should.not.exist(err); - unsent.should.be.empty; - done(); - }); - }, 100); - }); - }); - }); - - it('should notify each email address only once', function(done) { - // Set same email address for copayer1 and copayer2 - server.savePreferences({ - email: 'copayer2@domain.com', - }, function(err) { - server.createAddress({}, function(err, address) { - should.not.exist(err); - - // Simulate incoming tx notification - server._notify('NewIncomingTx', { - txid: '999', - address: address, - amount: 12300000, - }, function(err) { + it('should notify copayers a new tx proposal has been created', function(done) { + var _readTemplateFile_old = emailService._readTemplateFile; + emailService._readTemplateFile = function(language, filename, cb) { + if (_.endsWith(filename, '.html')) { + return cb(null, '{{walletName}}'); + } else { + _readTemplateFile_old.call(emailService, language, filename, cb); + } + }; + helpers.stubUtxos(server, wallet, [1, 1], function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { + message: 'some message' + }); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); setTimeout(function() { var calls = mailerStub.sendMail.getCalls(); calls.length.should.equal(2); @@ -660,9 +447,155 @@ describe('Wallet service', function() { _.difference(['copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; var one = emails[0]; one.from.should.equal('bws@dummy.net'); - one.subject.should.contain('New payment received'); + one.subject.should.contain('New payment proposal'); one.text.should.contain(wallet.name); - one.text.should.contain('123,000'); + one.text.should.contain(wallet.copayers[0].name); + should.exist(one.html); + one.html.indexOf('').should.equal(0); + one.html.should.contain(wallet.name); + server.storage.fetchUnsentEmails(function(err, unsent) { + should.not.exist(err); + unsent.should.be.empty; + emailService._readTemplateFile = _readTemplateFile_old; + done(); + }); + }, 100); + }); + }); + }); + + it('should not send email if unable to apply template to notification', function(done) { + var _applyTemplate_old = emailService._applyTemplate; + emailService._applyTemplate = function(template, data, cb) { + _applyTemplate_old.call(emailService, template, undefined, cb); + }; + helpers.stubUtxos(server, wallet, [1, 1], function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { + message: 'some message' + }); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + calls.length.should.equal(0); + server.storage.fetchUnsentEmails(function(err, unsent) { + should.not.exist(err); + unsent.should.be.empty; + emailService._applyTemplate = _applyTemplate_old; + done(); + }); + }, 100); + }); + }); + }); + + it('should notify copayers a new outgoing tx has been created', function(done) { + var _readTemplateFile_old = emailService._readTemplateFile; + emailService._readTemplateFile = function(language, filename, cb) { + if (_.endsWith(filename, '.html')) { + return cb(null, '{{&urlForTx}}'); + } else { + _readTemplateFile_old.call(emailService, language, filename, cb); + } + }; + helpers.stubUtxos(server, wallet, [1, 1], function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { + message: 'some message' + }); + + var txp; + async.waterfall([ + + function(next) { + server.createTx(txOpts, next); + }, + function(t, next) { + txp = t; + async.eachSeries(_.range(2), function(i, next) { + var copayer = TestData.copayers[i]; + helpers.getAuthServer(copayer.id44, function(server) { + var signatures = helpers.clientSign(txp, copayer.xPrivKey); + server.signTx({ + txProposalId: txp.id, + signatures: signatures, + }, function(err, t) { + txp = t; + next(); + }); + }); + }, next); + }, + function(next) { + helpers.stubBroadcast(); + server.broadcastTx({ + txProposalId: txp.id, + }, next); + }, + ], function(err) { + should.not.exist(err); + + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + var emails = _.map(_.takeRight(calls, 3), function(c) { + return c.args[0]; + }); + _.difference(['copayer1@domain.com', 'copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; + var one = emails[0]; + one.from.should.equal('bws@dummy.net'); + one.subject.should.contain('Payment sent'); + one.text.should.contain(wallet.name); + one.text.should.contain('800,000'); + should.exist(one.html); + one.html.should.contain('https://insight.bitpay.com/tx/' + txp.txid); + server.storage.fetchUnsentEmails(function(err, unsent) { + should.not.exist(err); + unsent.should.be.empty; + emailService._readTemplateFile = _readTemplateFile_old; + done(); + }); + }, 100); + }); + }); + }); + + it('should notify copayers a tx has been finally rejected', function(done) { + helpers.stubUtxos(server, wallet, 1, function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { + message: 'some message' + }); + + var txpId; + async.waterfall([ + + function(next) { + server.createTx(txOpts, next); + }, + function(txp, next) { + txpId = txp.id; + async.eachSeries(_.range(1, 3), function(i, next) { + var copayer = TestData.copayers[i]; + helpers.getAuthServer(copayer.id44, function(server) { + server.rejectTx({ + txProposalId: txp.id, + }, next); + }); + }, next); + }, + ], function(err) { + should.not.exist(err); + + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + var emails = _.map(_.takeRight(calls, 2), function(c) { + return c.args[0]; + }); + _.difference(['copayer1@domain.com', 'copayer2@domain.com'], _.pluck(emails, 'to')).should.be.empty; + var one = emails[0]; + one.from.should.equal('bws@dummy.net'); + one.subject.should.contain('Payment proposal rejected'); + one.text.should.contain(wallet.name); + one.text.should.contain('copayer 2, copayer 3'); + one.text.should.not.contain('copayer 1'); server.storage.fetchUnsentEmails(function(err, unsent) { should.not.exist(err); unsent.should.be.empty; @@ -672,15 +605,8 @@ describe('Wallet service', function() { }); }); }); - }); - it('should build each email using preferences of the copayers', function(done) { - // Set same email address for copayer1 and copayer2 - server.savePreferences({ - email: 'copayer1@domain.com', - language: 'es', - unit: 'btc', - }, function(err) { + it('should notify copayers of incoming txs', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); @@ -696,48 +622,12 @@ describe('Wallet service', function() { var emails = _.map(calls, function(c) { return c.args[0]; }); - var spanish = _.find(emails, { - to: 'copayer1@domain.com' - }); - spanish.from.should.equal('bws@dummy.net'); - spanish.subject.should.contain('Nuevo pago recibido'); - spanish.text.should.contain(wallet.name); - spanish.text.should.contain('0.123 BTC'); - var english = _.find(emails, { - to: 'copayer2@domain.com' - }); - english.from.should.equal('bws@dummy.net'); - english.subject.should.contain('New payment received'); - english.text.should.contain(wallet.name); - english.text.should.contain('123,000 bits'); - done(); - }, 100); - }); - }); - }); - }); - - it('should support multiple emailservice instances running concurrently', function(done) { - var emailService2 = new EmailService(); - emailService2.start({ - lock: emailService.lock, // Use same locker service - messageBroker: server.messageBroker, - storage: storage, - mailer: mailerStub, - emailOpts: { - from: 'bws2@dummy.net', - subjectPrefix: '[test wallet 2]', - }, - }, function(err) { - helpers.stubUtxos(server, wallet, 1, function() { - var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { - message: 'some message' - }); - server.createTx(txOpts, function(err, tx) { - should.not.exist(err); - setTimeout(function() { - var calls = mailerStub.sendMail.getCalls(); - calls.length.should.equal(2); + _.difference(['copayer1@domain.com', 'copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; + var one = emails[0]; + one.from.should.equal('bws@dummy.net'); + one.subject.should.contain('New payment received'); + one.text.should.contain(wallet.name); + one.text.should.contain('123,000'); server.storage.fetchUnsentEmails(function(err, unsent) { should.not.exist(err); unsent.should.be.empty; @@ -747,7 +637,180 @@ describe('Wallet service', function() { }); }); }); + + it('should notify each email address only once', function(done) { + // Set same email address for copayer1 and copayer2 + server.savePreferences({ + email: 'copayer2@domain.com', + }, function(err) { + server.createAddress({}, function(err, address) { + should.not.exist(err); + + // Simulate incoming tx notification + server._notify('NewIncomingTx', { + txid: '999', + address: address, + amount: 12300000, + }, function(err) { + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + calls.length.should.equal(2); + var emails = _.map(calls, function(c) { + return c.args[0]; + }); + _.difference(['copayer2@domain.com', 'copayer3@domain.com'], _.pluck(emails, 'to')).should.be.empty; + var one = emails[0]; + one.from.should.equal('bws@dummy.net'); + one.subject.should.contain('New payment received'); + one.text.should.contain(wallet.name); + one.text.should.contain('123,000'); + server.storage.fetchUnsentEmails(function(err, unsent) { + should.not.exist(err); + unsent.should.be.empty; + done(); + }); + }, 100); + }); + }); + }); + }); + + it('should build each email using preferences of the copayers', function(done) { + // Set same email address for copayer1 and copayer2 + server.savePreferences({ + email: 'copayer1@domain.com', + language: 'es', + unit: 'btc', + }, function(err) { + server.createAddress({}, function(err, address) { + should.not.exist(err); + + // Simulate incoming tx notification + server._notify('NewIncomingTx', { + txid: '999', + address: address, + amount: 12300000, + }, function(err) { + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + calls.length.should.equal(3); + var emails = _.map(calls, function(c) { + return c.args[0]; + }); + var spanish = _.find(emails, { + to: 'copayer1@domain.com' + }); + spanish.from.should.equal('bws@dummy.net'); + spanish.subject.should.contain('Nuevo pago recibido'); + spanish.text.should.contain(wallet.name); + spanish.text.should.contain('0.123 BTC'); + var english = _.find(emails, { + to: 'copayer2@domain.com' + }); + english.from.should.equal('bws@dummy.net'); + english.subject.should.contain('New payment received'); + english.text.should.contain(wallet.name); + english.text.should.contain('123,000 bits'); + done(); + }, 100); + }); + }); + }); + }); + + it('should support multiple emailservice instances running concurrently', function(done) { + var emailService2 = new EmailService(); + emailService2.start({ + lock: emailService.lock, // Use same locker service + messageBroker: server.messageBroker, + storage: storage, + mailer: mailerStub, + emailOpts: { + from: 'bws2@dummy.net', + subjectPrefix: '[test wallet 2]', + }, + }, function(err) { + helpers.stubUtxos(server, wallet, 1, function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { + message: 'some message' + }); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + calls.length.should.equal(2); + server.storage.fetchUnsentEmails(function(err, unsent) { + should.not.exist(err); + unsent.should.be.empty; + done(); + }); + }, 100); + }); + }); + }); + }); }); + + describe('1-of-N wallet', function() { + beforeEach(function(done) { + helpers.createAndJoinWallet(1, 2, function(s, w) { + server = s; + wallet = w; + + var i = 0; + async.eachSeries(w.copayers, function(copayer, next) { + helpers.getAuthServer(copayer.id, function(server) { + server.savePreferences({ + email: 'copayer' + (++i) + '@domain.com', + unit: 'bit', + }, next); + }); + }, function(err) { + should.not.exist(err); + + mailerStub = sinon.stub(); + mailerStub.sendMail = sinon.stub(); + mailerStub.sendMail.yields(); + + emailService = new EmailService(); + emailService.start({ + lockOpts: {}, + messageBroker: server.messageBroker, + storage: storage, + mailer: mailerStub, + emailOpts: { + from: 'bws@dummy.net', + subjectPrefix: '[test wallet]', + publicTxUrlTemplate: { + livenet: 'https://insight.bitpay.com/tx/{{txid}}', + testnet: 'https://test-insight.bitpay.com/tx/{{txid}}', + }, + }, + }, function(err) { + should.not.exist(err); + done(); + }); + }); + }); + }); + + it('should NOT notify copayers a new tx proposal has been created', function(done) { + helpers.stubUtxos(server, wallet, [1, 1], function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, TestData.copayers[0].privKey_1H_0, { + message: 'some message' + }); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + setTimeout(function() { + var calls = mailerStub.sendMail.getCalls(); + calls.length.should.equal(0); + done(); + }, 100); + }); + }); + }); + }); + }); describe('#getInstance', function() {