bitcore-wallet-client
Version: 
Client for bitcore-wallet-service
230 lines (210 loc) • 6.33 kB
JavaScript
;
const chai = require('chai');
const sinon = require('sinon');
const crypto = require('crypto');
const should = chai.should();
const { PayPro: payPro } = require('../ts_build/lib/paypro');
const TestData = require('./testdata');
function mockRequest(bodyBuf, headers) {
  bodyBuf = Array.isArray(bodyBuf) ? bodyBuf : [bodyBuf];
  payPro.r = {
    'get': (_url) => {
      return {
        set: (_k, _v) => { },
        query: (_opts) => { },
        end: (cb) => {
          return cb(null, {
            headers: headers || {},
            statusCode: 200,
            statusMessage: 'OK',
            text: bodyBuf.shift()
          })
        }
      }
    },
    'post': (_url) => {
      return {
        set: (_k, _v) => { },
        send: (_opts) => { },
        end: (cb) => {
          return cb(null, {
            headers: headers || {},
            statusCode: 200,
            statusMessage: 'OK',
            text: bodyBuf.shift()
          })
        }
      }
    }
  }
};
describe('paypro', function () {
  var clock, oldreq;
  before(function () {
    // Stub time before cert expiration at Mar 27 2016
    clock = sinon.useFakeTimers(1459105693843);
  });
  beforeEach(() => {
    oldreq = payPro.r;
  });
  after(function () {
    clock.restore();
  });
  afterEach(function () {
    payPro.r = oldreq;
  });
  it('Make and verify PP request', function (done) {
    mockRequest(Buffer.from(TestData.payProJson.bch.body, 'hex'), TestData.payProJson.bch.headers);
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      should.not.exist(err);
      res.should.be.deep.equal({
        "amount": 769200,
        "coin": "bch",
        "expires": "2019-03-07T18:20:44.301Z",
        "memo": "Payment request for BitPay invoice 3oZcpotopVGcZ2stRw2dop for merchant GusPay",
        "network": "testnet",
        "paymentId": "3oZcpotopVGcZ2stRw2dop",
        "requiredFeeRate": 1.398,
        "toAddress": "qz78y0832kskq84rr4f9t22fequ5c0l4gu6wsehezr",
        "verified": true,
      });
      done();
    });
  });
  it('Should handle a failed (404) request', function (done) {
    payPro.r = {
      'get': (_url) => {
        return {
          set: (_k, _v) => { },
          query: (_opts) => { },
          end: (cb) => {
            return cb(null, {
              statusCode: 404,
              statusMessage: 'Not Found',
            }, 'This invoice was not found or has been archived');
          }
        }
      },
      'post': () => { }
    }
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      should.exist(err);
      done();
    });
  });
  it('Should detect a tampered PP request (bad signature)', function (done) {
    let h = JSON.parse(JSON.stringify(TestData.payProJson.bch.headers));
    h.signature = crypto.randomBytes(64).toString('hex');
    mockRequest(Buffer.from(TestData.payProJson.bch.body, 'hex'), h);
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      err.toString().should.contain('signature invalid');
      done();
    });
  });
  it('Should detect a tampered PP request (short signature)', function (done) {
    let h = JSON.parse(JSON.stringify(TestData.payProJson.bch.headers));
    h.signature = h.signature.slice(0, -1);
    mockRequest(Buffer.from(TestData.payProJson.bch.body, 'hex'), h);
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      err.toString().should.contain('signature invalid');
      done();
    });
  });
  it('Should detect a tampered PP request (non-hex signature)', function (done) {
    let h = JSON.parse(JSON.stringify(TestData.payProJson.bch.headers));
    h.signature = crypto.randomBytes(64).toString('base64');
    mockRequest(Buffer.from(TestData.payProJson.bch.body, 'hex'), h);
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      err.toString().should.contain('signature invalid');
      done();
    });
  });
  it('Should detect a tampered PP request (bogus signature)', function (done) {
    let h = JSON.parse(JSON.stringify(TestData.payProJson.bch.headers));
    h.signature = 'xx';
    mockRequest(Buffer.from(TestData.payProJson.bch.body, 'hex'), h);
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      err.toString().should.contain('signature invalid');
      done();
    });
  });
  it('Should detect a tampered PP request (bad amount)', function (done) {
    let b = JSON.parse(TestData.payProJson.bch.body);
    b.outputs[0].amount = 100;
    b = JSON.stringify(b);
    mockRequest(Buffer.from(b), TestData.payProJson.bch.headers);
    payPro.get({
      url: 'https://test.bitpay.com/paypro',
      network: 'testnet',
      coin: 'bch',
    }, function (err, res) {
      err.toString().should.contain('not match digest');
      done();
    });
  });
  it('should send a PP payment', function (done) {
    var opts = {
      rawTx: 'rawTx1',
      rawTxUnsigned: 'rawTxUnsigned',
      url: 'http://an.url.com/paypro',
      coin: 'bch',
    };
    mockRequest([Buffer.from('{"memo":"Payment seems OK"}'), Buffer.from('{"memo":"memo1"}')], {
    });
    payPro.send(opts, function (err, data, memo) {
      should.not.exist(err);
      memo.should.equal('memo1');
      done();
    });
  });
  it('should not send PP payment if verify fails', function (done) {
    var opts = {
      rawTx: 'rawTx1',
      rawTxUnsigned: 'rawTxUnsigned',
      url: 'http://an.url.com/paypro',
      coin: 'bch',
    };
    payPro.r = {
      'post': (_url) => {
        return {
          set: (_k, _v) => { },
          send: (_opts) => { },
          end: (cb) => {
            return cb(null, {
              statusCode: 400,
              statusMessage: 'ss',
            }, 'This invoice was not found or has been archived');
          }
        }
      }
    }
    payPro.send(opts, function (err, data, memo) {
      should.exist(err);
      done();
    });
  });
});