diff --git a/copay.js b/copay.js index bdb5c759b..79a70f8b0 100644 --- a/copay.js +++ b/copay.js @@ -20,6 +20,3 @@ module.exports.Compatibility = require('./js/models/Compatibility'); module.exports.PluginManager = require('./js/models/PluginManager'); module.exports.version = require('./version').version; module.exports.commitHash = require('./version').commitHash; - -// test hack :s, will fix -module.exports.FakePayProServer = require('./test/mocks/FakePayProServer'); diff --git a/js/models/Async.js b/js/models/Async.js index 522cf2ffe..b7b9bca33 100644 --- a/js/models/Async.js +++ b/js/models/Async.js @@ -260,7 +260,7 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) { if (fromTs) { self.ignoreMessageFromTs = fromTs; } - log.info('Async: syncing from: ', fromTs); + log.info('Async: synchronizing from: ', fromTs); self.socket.emit('sync', fromTs); self.started = true; }); diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 09c41b749..99cdb825f 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -36,8 +36,7 @@ var TX_MAX_INS = 70; /** * @desc - * Wallet manages a private key for Copay, network, storage of the wallet for - * persistance, and blockchain information. + * Wallet manages a private key for Copay, network and blockchain information. * * @TODO: Split this leviathan. * @@ -133,13 +132,13 @@ Wallet.prototype.emitAndKeepAlive = function(args) { }; /** - * @TODO: Document this. Its usage is kind of weird + * @desc Fixed & Forced TransactionBuilder options, for genereration transactions. * * @static - * @property lockTime - * @property signhash - * @property fee - * @property feeSat + * @property lockTime null + * @property signhash SIGHASH + * @property fee null (automatic) + * @property feeSat null */ Wallet.builderOpts = { lockTime: null, @@ -468,6 +467,15 @@ Wallet.prototype._checkIfTxProposalIsSent = function(ntxid, cb) { }; +/** + * _processTxProposalPayPro + * + * @desc Process and incoming PayPro TX Proposal. Fetchs the payment request + * from the merchant. + * + * @param mergeInfo Proposals merge information, as returned by TxProposals.merge + * @return {fetchPaymentRequestCallback} + */ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) { var self = this; var txp = mergeInfo.txp; @@ -494,6 +502,14 @@ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) { }); }; +/** + * _processIncomingTxProposal + * + * @desc Process an incoming transaction proposal. Runs safety and sanity checks on it. + * + * @param mergeInfo Proposals merge information, as returned by TxProposals.merge + * @return {errCallback} + */ Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) { if (!mergeInfo) return cb(); var self = this; @@ -617,7 +633,8 @@ Wallet.prototype._onAddressBook = function(senderId, data) { if (!data.addressBook || !_.isObject(data.addressBook)) return; - var self = this, hasChange; + var self = this, + hasChange; _.each(data.addressBook, function(value, key) { if (!self.addressBook[key] && Address.validate(key)) { @@ -629,7 +646,6 @@ Wallet.prototype._onAddressBook = function(senderId, data) { hasChange = true; } }); -console.log('[Wallet.js.635:hasChange:]',hasChange); //TODO if (hasChange) { this.emitAndKeepAlive('addressBookUpdated'); @@ -675,7 +691,6 @@ Wallet.prototype._onNoMessages = function() { * @emits corrupt */ Wallet.prototype._onData = function(senderId, data, ts) { -console.log('[Wallet.js.533]0', this.txProposals.txps); //TODO preconditions.checkArgument(senderId); preconditions.checkArgument(data); preconditions.checkArgument(data.type); @@ -1159,13 +1174,13 @@ Wallet.fromObj = function(o, readOpts) { /** - * @desc sendToPeers a message to other peers - * @param {string[]} recipients - the pubkey of the recipients of the message - * @param {Object} obj - the data to be sent to them + * @desc sends a message to peers + * @param {string[]} recipients - the pubkey of the recipients of the message. Null for sending to all peers. + * @param {Object} obj - the data to be sent to them. + * @param {String} obj.type - Type of the message to be send */ Wallet.prototype._sendToPeers = function(recipients, obj) { if (!this.isShared()) return; - log.info('Wallet:' + this.getName() + ' ### Sending ' + obj.type); log.debug('Sending obj', obj); @@ -1173,7 +1188,7 @@ Wallet.prototype._sendToPeers = function(recipients, obj) { }; /** - * @desc Send the set of TxProposals to some peers + * @desc Send the set of TxProposals to peers * @param {string[]} recipients - the pubkeys of the recipients */ Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) { @@ -1243,8 +1258,6 @@ Wallet.prototype.sendWalletReady = function(recipients, sinceTs) { * @param {string[]} [recipients] - the pubkeys of the recipients */ Wallet.prototype.sendWalletId = function(recipients) { - log.debug('Wallet:' + this.id + ' ### SENDING walletId TO:', recipients || 'All', this.id); - this._sendToPeers(recipients, { type: 'walletId', walletId: this.id, @@ -1454,12 +1467,25 @@ Wallet.prototype.reject = function(ntxid) { }; /** - * @desc Sign a proposal + * @callback signCallback + * @param {Error} error if any + * @param {number} Transaction ID or Transaction Proposal ID + * @param {status} Wallet.TX_* Status: + * + * TX_BROADCASTED + * TX_SIGNED + * TX_PROPOSAL_SENT + */ + + +/** + * @desc Signs a proposal * @param {string} ntxid the id of the transaction proposal to sign * @emits txProposalsUpdated * @throws {Error} Could not sign proposal * @throws {Error} Bad payment request * @return {boolean} true if signing actually incremented the number of signatures + * @emits txProposalsUpdated */ Wallet.prototype.sign = function(ntxid) { preconditions.checkState(!_.isUndefined(this.getMyCopayerId())); @@ -1476,6 +1502,17 @@ Wallet.prototype.sign = function(ntxid) { }; +/** + * + * @desc signs and send or broadcast a transaction. + * In m-n wallets, + * if m==1 it will broadcast it to the Bitcoin Network + * if n>1 it will send the proposal to the peers + * + * @param ntxid Transaction Proposal Id + * @param {signCallback} cb + * @throws {Error} Could not sign proposal + */ Wallet.prototype.signAndSend = function(ntxid, cb) { if (this.sign(ntxid)) { var txp = this.txProposals.get(ntxid); @@ -1483,7 +1520,7 @@ Wallet.prototype.signAndSend = function(ntxid, cb) { return this.broadcastTx(ntxid, cb); } else { this.sendTxProposal(ntxid); - return cb(null, ntxid, Wallet.TX_SIGNED ); + return cb(null, ntxid, Wallet.TX_SIGNED); } } else { return cb(new Error('Could not sign the proposal')); @@ -1493,9 +1530,8 @@ Wallet.prototype.signAndSend = function(ntxid, cb) { /** * @desc Broadcasts a transaction to the blockchain * @param {string} ntxid - the transaction proposal id - * @param {broadcastCallback} cb - * @callback broadcastCallback * @param {string} txid - the transaction id on the blockchain + * @param {signCallback} cb */ Wallet.prototype.broadcastTx = function(ntxid, cb) { var self = this; @@ -1505,32 +1541,36 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) { if (!tx.isComplete()) throw new Error('Tx is not complete. Can not broadcast'); - - var serializedTx = tx.serialize(); - log.info('Wallet:' + this.id + ' Broadcasting Transaction ntxid:' + ntxid); - var txHex = serializedTx.toString('hex'); + var txHex = tx.serialize().toString('hex'); log.debug('\tRaw transaction: ', txHex); this.blockchain.broadcast(txHex, function(err, txid) { - if (err) - log.error('Error sending TX:', err); + if (err) { + log.error('Error sending TX:' + err); + return cb(err);; + } if (txid) { log.debug('Wallet:' + self.getName() + ' broadcasted a TX. BITCOIND txid:', txid); txp.setSent(txid); - self.sendTxProposal(ntxid); - self.emitAndKeepAlive('txProposalsUpdated'); // PAYPRO: Payment message is optional, only if payment_url is set // This is async. and will notify and update txp async. if (txp.merchant && txp.merchant.pr.pd.payment_url) { - self.sendPaymentTx(ntxid, serializedTx); + var data = this.createPayProPayment(txp); + self.sendPayProPayment(txp, data, function(err, data) { + if (err) return cb(err); + self.onPayProPaymentAck(ntxid, data); + }); } + self.sendTxProposal(ntxid); + self.emitAndKeepAlive('txProposalsUpdated'); return cb(null, txid, Wallet.TX_BROADCASTED); + } else { log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already'); self._checkIfTxProposalIsSent(ntxid, cb); @@ -1538,11 +1578,18 @@ Wallet.prototype.broadcastTx = function(ntxid, cb) { }); }; + /** - * @desc Create a Payment Protocol transaction + * @callback {fetchPaymentRequestCallback} + * @param {string=} err - an error, if any + * @param {Object} merchantData - object representing the payment request. Add described on BIP70 merchant_data + */ + +/** + * @desc Creates a Payment Protocol transaction * @param {Object|string} options - if it's a string, parse it as the url * @param {string} options.url the url for the transaction - * @param {Function} cb + * @return {fetchPaymentRequestCallback} cb */ Wallet.prototype.fetchPaymentRequest = function(options, cb) { preconditions.checkArgument(_.isObject(options)); @@ -1578,24 +1625,18 @@ Wallet.prototype.fetchPaymentRequest = function(options, cb) { }); }; -/* - * addOutputsToMerchantData - * - * NOTE: We use to: set the TX scripts with the payment request scripts: - * but this is a hack around transaction builder, so we dont do it anymore. - * See Readme.md. For now we only support p2scripthash or p2pubkeyhash - merchantData.pr.pd.outputs.forEach(function(output, i) { - var script = { - offset: output.script.offset, - limit: output.script.limit, - buffer: new Buffer(output.script.buffer, 'hex') - }; - var s = script.buffer.slice(script.offset, script.limit); - b.tx.outs[i].s = s; - }); - * - */ +/** + * _addOutputsToMerchantData + * + * @desc parses merchant_data internal output representation and stores + * the result in merchant_data.outs = [{address: xx, amountSatStr: xx}], + * to be compatible with TransactionBuilder. + *` + * @param merchantData BIP70 merchant_data (from the payment request) + * @throws {Error} PayPro: Unsupported inputs + * @return {undefined} + */ Wallet.prototype._addOutputsToMerchantData = function(merchantData) { var total = bignum(0); @@ -1737,7 +1778,7 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) { /** * _getPayProRefundOutputs - * Create refund address for PayPro. + * Create refund outputs for a PayPro Payment Message * Uses current transaction's change address. * * @param txp @@ -1750,13 +1791,23 @@ Wallet.prototype._getPayProRefundOutputs = function(txp) { var output = new PayPro.Output(); var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey); - output.set('script',new Buffer(script, 'hex')); + output.set('script', new Buffer(script, 'hex')); output.set('amount', amount); return [output]; }; -Wallet.prototype._createPaymentTx = function(txp, txHex) { +/** + * + * @desc Creates a Payment Protocol Payment message for the given TX Proposal + * @param txp Transaction Proposal + * @param txHex + * @return {undefined} + */ +Wallet.prototype.createPayProPayment = function(txp) { + + var tx = txp.builder.build(); + var txBuf = tx.serialize(); var refund_outputs = this._getPayProRefundOutputs(txp); @@ -1770,7 +1821,7 @@ Wallet.prototype._createPaymentTx = function(txp, txHex) { pay.set('merchant_data', merchant_data); } - pay.set('transactions', [txHex]); + pay.set('transactions', [txBuf]); pay.set('refund_to', refund_outputs); // Unused for now @@ -1787,19 +1838,40 @@ Wallet.prototype._createPaymentTx = function(txp, txHex) { return view; }; + /** - * @desc Send a payment transaction to a server, complying with BIP70 + * onPayProPaymentAck * - * @param {string} ntxid - the transaction proposal id - * @param {Function} txHex + * @desc parse and process a Payment Protocol Payment Ack. Updates + * given TX Proposal with merchant's memo and send it to copayers * - * emits paymentACK(server's memo) + * @param ntxid ID of the Transaction Proposal + * @param rawData of the Payment Ack + * @emits paymentACK - (merchants's memo) */ -Wallet.prototype.sendPaymentTx = function(ntxid, txHex) { +Wallet.prototype.onPayProPaymentAck = function(ntxid, rawData) { + var data = PayPro.PaymentACK.decode(rawData); + var paypro = new PayPro(); + var ack = paypro.makePaymentACK(data); + var memo = ack.get('memo'); + log.debug('Payment Acknowledged!: %s', memo); + txp.paymentAckMemo = memo; + self.sendTxProposal(ntxid); + self.emitAndKeepAlive('paymentACK', memo); +}; + + +/** + * @desc Send a payment transaction to a merchant, complying with BIP70 + * on Acknoledge, updates the TX Proposal with server's memo and send it + * to peers + * + * @param {string} ntxid - the transaction proposal ID for with the + * + */ +Wallet.prototype.sendPayProPayment = function(txp, data, cb) { var self = this; - var txp = this.txProposals.get(ntxid); - var data = this._createPaymentTx(txp, txHex); - + log.debug('Sending Payment Message to merchant server'); var postInfo = { method: 'POST', @@ -1818,19 +1890,13 @@ Wallet.prototype.sendPaymentTx = function(ntxid, txHex) { responseType: 'arraybuffer' }; - return this.httpUtil.request(postInfo) + this.httpUtil.request(postInfo) .success(function(rawData) { - var data = PayPro.PaymentACK.decode(rawData); - var paypro = new PayPro(); - var ack = paypro.makePaymentACK(data); - var memo = ack.get('memo'); - log.debug('Payment Acknowledged!: %s', memo); - txp.paymentAckMemo = memo; - self.sendTxProposal(ntxid); - self.emitAndKeepAlive('paymentACK', memo); + return cb(null, rawData); }) .error(function(data, status) { log.error('Sending payment notification: XHR status: ' + status); + return cb(new Error(status)); }); }; @@ -1899,7 +1965,7 @@ Wallet.prototype.addressIsOwn = function(addrStr) { /** * Estimate a tx fee in satoshis given its input count - * only for spending all wallet funds + * (only used when spending all wallet funds) */ Wallet.estimatedFee = function(unspentCount) { preconditions.checkArgument(_.isNumber(unspentCount)); @@ -2071,6 +2137,7 @@ Wallet.prototype.spend = function(opts, cb) { var comment = opts.comment; var url = opts.url; + // PayPro? Fetch payment data and recurse if (url && !opts.merchantData) { return self.fetchPaymentRequest({ url: url, @@ -2083,7 +2150,8 @@ Wallet.prototype.spend = function(opts, cb) { opts.amountSat = parseInt(merchantData.outs[0].amountSatStr); return self.spend(opts, cb); }); - }; + } + preconditions.checkArgument(amountSat, 'no amount'); preconditions.checkArgument(toAddress, 'no address'); @@ -2213,7 +2281,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx var tx = b.build(); var myId = this.getMyCopayerId(); var keys = priv.getForPaths(inputChainPaths); - return new TxProposal({ + return new TxProposal({ inputChainPaths: inputChainPaths, comment: comment, builder: b, diff --git a/test/Wallet.js b/test/Wallet.js index d3738e0c5..a1ce3642d 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -767,7 +767,10 @@ describe('Wallet model', function() { 'confirmations': 10, 'confirmationsFromCache': false }]; + sinon.stub(w, 'sendIndexes'); var addr = w.generateAddress().toString(); + w.sendIndexes.restore(); + utxo[0].address = addr; utxo[0].scriptPubKey = (new bitcore.Address(addr)).getScriptPubKey().serialize().toString('hex'); return utxo; @@ -775,124 +778,154 @@ describe('Wallet model', function() { var toAddress = 'mjfAe7YrzFujFf8ub5aUrCaN5GfSABdqjh'; var amountSatStr = '10000'; - it('should create transaction', function(done) { - var w = cachedCreateW2(); - var utxo = createUTXO(w); - w.blockchain.fixUnspent(utxo); - w.spend({ - toAddress: toAddress, - amountSat: amountSatStr, - }, function(err, ntxid) { - ntxid.length.should.equal(64); - done(); - }); - }); - - it('should create & sign transaction from received funds', function(done) { - var k2 = new PrivateKey({ - networkName: walletConfig.networkName - }); - - var w = createW2([k2]); - var utxo = createUTXO(w); - w.blockchain.fixUnspent(utxo); - w.spend({ - toAddress: toAddress, - amountSat: amountSatStr, - }, function(err, ntxid) { - w.on('txProposalsUpdated', function() { - w.getTxProposals()[0].signedByUs.should.equal(true); - w.getTxProposals()[0].rejectedByUs.should.equal(false); + describe('#spend', function() { + it('should create transaction', function(done) { + var w = cachedCreateW2(); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, ntxid) { + ntxid.length.should.equal(64); done(); }); - w.privateKey = k2; - w.sign(ntxid).should.equal.true; }); - }); - it('should fail to reject a signed transaction', function() { - var w = cachedCreateW2(); - var utxo = createUTXO(w); - w.blockchain.fixUnspent(utxo); - w.spend({ - toAddress: toAddress, - amountSat: amountSatStr, - }, function(err, ntxid) { - (function() { + + it('should create & sign transaction from received funds', function(done) { + var k2 = new PrivateKey({ + networkName: walletConfig.networkName + }); + + var w = createW2([k2]); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, ntxid) { + w.on('txProposalsUpdated', function() { + w.getTxProposals()[0].signedByUs.should.equal(true); + w.getTxProposals()[0].rejectedByUs.should.equal(false); + done(); + }); + w.privateKey = k2; + w.sign(ntxid).should.equal.true; + }); + }); + it('should fail to reject a signed transaction', function() { + var w = cachedCreateW2(); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, ntxid) { + (function() { + w.reject(ntxid); + }).should.throw('reject a signed'); + }); + }); + + it('should create & reject transaction', function(done) { + var w = cachedCreateW2(); + var oldK = w.privateKey; + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, ntxid) { + var s = sinon.stub(w, 'getMyCopayerId').returns('213'); + Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0); w.reject(ntxid); - }).should.throw('reject a signed'); + Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1); + w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1); + s.restore(); + done(); + }); }); - }); + it('should fail to send incomplete transaction', function(done) { + var w = createW2(null, 1); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); - it('should create & reject transaction', function(done) { - var w = cachedCreateW2(); - var oldK = w.privateKey; - var utxo = createUTXO(w); - w.blockchain.fixUnspent(utxo); - w.spend({ - toAddress: toAddress, - amountSat: amountSatStr, - }, function(err, ntxid) { - var s = sinon.stub(w, 'getMyCopayerId').returns('213'); - Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0); - w.reject(ntxid); - Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1); - w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1); - s.restore(); - done(); + // TODO in this test, txp should be created with createTxProposal + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, ntxid) { + var txp = w.txProposals.get(ntxid); + // Assign fake builder + txp.builder = new Builder(); + sinon.stub(txp.builder, 'build').returns({ + isComplete: function() { + return false; + } + }); + (function() { + w.broadcastTx(ntxid); + }).should.throw('Tx is not complete. Can not broadcast'); + done(); + }); }); - }); - it('should create & sign & send a transaction', function(done) { - var w = createW2(null, 1); - var utxo = createUTXO(w); - w.blockchain.fixUnspent(utxo); - w.spend({ - toAddress: toAddress, - amountSat: amountSatStr, - }, function(err, ntxid) { - w.broadcastTx(ntxid, function(err, txid, status) { + it('should send a TX proposal to peers if incomplete', function(done) { + var w = createW2(null, 1); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + + sinon.spy(w, 'sendIndexes'); + sinon.spy(w, 'sendTxProposal'); + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, id, status) { should.not.exist(err); - txid.length.should.equal(64); + should.exist(id); + status.should.equal(Wallet.TX_PROPOSAL_SENT); + w.sendTxProposal.calledOnce.should.equal(true); + w.sendIndexes.calledOnce.should.equal(true); + done(); + }); + }); + it('should broadcast a TX if complete', function(done) { + var w = createW2(null, 1); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + sinon.spy(w, 'sendIndexes'); + sinon.spy(w, 'sendTxProposal'); + sinon.spy(w, 'broadcastTx'); + sinon.stub(w, 'requiresMultipleSignatures').returns(false); + w.spend({ + toAddress: toAddress, + amountSat: amountSatStr, + }, function(err, id, status) { + should.not.exist(err); + should.exist(id); status.should.equal(Wallet.TX_BROADCASTED); + w.sendTxProposal.calledOnce.should.equal(true); + w.sendIndexes.calledOnce.should.equal(true); + w.broadcastTx.calledOnce.should.equal(true); done(); }); }); }); - it('should fail to send incomplete transaction', function(done) { + + it('should return error if failing to send', function(done) { var w = createW2(null, 1); var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo); + sinon.stub(w, 'requiresMultipleSignatures').returns(false); + sinon.spy(w, 'sendIndexes'); + sinon.spy(w, 'sendTxProposal'); + sinon.stub(w.blockchain, 'broadcast').yields('error'); w.spend({ toAddress: toAddress, amountSat: amountSatStr, - }, function(err, ntxid) { - var txp = w.txProposals.get(ntxid); - // Assign fake builder - txp.builder = new Builder(); - sinon.stub(txp.builder, 'build').returns({ - isComplete: function() { - return false; - } - }); - (function() { - w.broadcastTx(ntxid); - }).should.throw('Tx is not complete. Can not broadcast'); - done(); - }); - }); - it('should check if transaction already sent when failing to send', function(done) { - var w = createW2(null, 1); - var utxo = createUTXO(w); - w.blockchain.fixUnspent(utxo); - w.spend({ - toAddress: toAddress, - amountSat: amountSatStr, - }, function(err, ntxid) { - sinon.stub(w.blockchain, 'broadcast').yields({ - statusCode: 303 - }); - var spyCheckSentTx = sinon.spy(w, '_checkSentTx'); - w.broadcastTx(ntxid, function() {}); - chai.expect(spyCheckSentTx.calledOnce).to.be.true; + }, function(err, id, status) { + err.should.equal('error'); + w.sendTxProposal.calledOnce.should.equal(false); + w.sendIndexes.calledOnce.should.equal(true); done(); }); }); @@ -940,19 +973,19 @@ describe('Wallet model', function() { url: 'http://xxx', }; - var rawData ='wqer'; + var rawData = 'wqer'; var e = sinon.stub(); e.error = sinon.stub(); var s = sinon.stub(); s.success = sinon.stub().yields(rawData).returns(e); - sinon.stub(w.httpUtil,'request').returns(s); + sinon.stub(w.httpUtil, 'request').returns(s); - w.fetchPaymentRequest(opts, function(err, merchantData){ + w.fetchPaymentRequest(opts, function(err, merchantData) { should.not.exist(err); should.exist(merchantData); - w.parsePaymentRequest.firstCall.args.should.deep.equal([opts,rawData]); + w.parsePaymentRequest.firstCall.args.should.deep.equal([opts, rawData]); done(); }); }); @@ -964,14 +997,14 @@ describe('Wallet model', function() { url: 'http://xxx', }; - var rawData ='wqer'; + var rawData = 'wqer'; var e = sinon.stub(); e.error = sinon.stub().yields(null, 'status'); var s = sinon.stub(); s.success = sinon.stub().returns(e); - sinon.stub(w.httpUtil,'request').returns(s); - w.fetchPaymentRequest(opts, function(err, merchantData){ + sinon.stub(w.httpUtil, 'request').returns(s); + w.fetchPaymentRequest(opts, function(err, merchantData) { err.toString().should.contain('status'); done(); }); @@ -983,13 +1016,13 @@ describe('Wallet model', function() { // FakePayProServer.getRequest should be parametrizable describe('#parsePaymentRequest', function() { it('should parse a Payment Request', function() { - var now = Date.now()/1000; + var now = Date.now() / 1000; var w = cachedCreateW2(); var opts = { url: 'http://xxx', }; var data = FakePayProServer.getRequest(); - var md = w.parsePaymentRequest(opts,data); + var md = w.parsePaymentRequest(opts, data); md.outs.should.deep.equal(FakePayProServer.outs); md.request_url.should.equal(opts.url); md.pr.untrusted.should.equal(true); @@ -1158,7 +1191,9 @@ describe('Wallet model', function() { var w = cachedCreateW2(); var save = w.network.send; w.network.send = sinon.spy(); - w._sendToPeers(null, {type:'hola'}); + w._sendToPeers(null, { + type: 'hola' + }); w.network.send.calledOnce.should.equal(true); w.network.send = save; }); @@ -2389,6 +2424,12 @@ describe('Wallet model', function() { }); }); + describe.skip('#onPayProPaymentAck', function() { + it('should emit', function() { + var w = cachedCreateW2(); + w.onPayProPaymentAck('id', 'data'); + }); + }); describe.skip('#read', function() { var network, blockchain; diff --git a/util/build.js b/util/build.js index 908729562..ace41ebfe 100644 --- a/util/build.js +++ b/util/build.js @@ -131,9 +131,6 @@ var createBundle = function(opts) { b.require('./test/mocks/FakePayProServer', { expose: './mocks/FakePayProServer' }); - b.require('./test/mocks/FakePayProServer', { - expose: '../../mocks/FakePayProServer' - }); b.require('./test/mocks/FakeBuilder', { expose: './mocks/FakeBuilder' });