Artifact Content
Not logged in

Artifact 28f6045635fe58d2ade371abc1777413dc1d16c7:


'use strict'

const crypto = require('crypto')
const base64url = require('base64url')
const btpPacket = require('btp-packet')

const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert

const getObjBackend = require('../src/util/backend')
const MockSocket = require('./helpers/mockSocket')
const PluginPaymentChannel = require('..')
const { ilpAndCustomToProtocolData } =
  require('../src/util/protocolDataConverter')

const conditionPair = () => {
  const preimage = crypto.randomBytes(32)
  const hash = crypto.createHash('sha256').update(preimage).digest()

  return {
    fulfillment: base64url(preimage),
    condition: base64url(hash)
  }
}

const info = {
  prefix: 'example.red.',
  currencyCode: 'USD',
  currencyScale: 2,
  connectors: [ { id: 'other', name: 'other', connector: 'peer.usd.other' } ]
}

const peerAddress = 'example.red.server'
const options = {
  server: 'btp+wss://user:shhh secret@example.com/rpc'
}

describe('Asymmetric plugin virtual', () => {
  beforeEach(async function () {
    this.mockSocketIndex = 0
    this.mockSocket = new MockSocket()
    this.mockSocket
      .reply(btpPacket.TYPE_MESSAGE, ({ requestId }) => btpPacket.serializeResponse(requestId, []))
      .reply(btpPacket.TYPE_MESSAGE, ({ requestId }) => btpPacket.serializeResponse(requestId, [{
        protocolName: 'info',
        contentType: btpPacket.MIME_APPLICATION_JSON,
        data: Buffer.from(JSON.stringify(info))
      }]))

    this.plugin = new PluginPaymentChannel(Object.assign({},
      options))

    await this.plugin.addSocket(this.mockSocket, { username: 'user', token: 'shhh secret' })
    await this.plugin.connect()
  })

  afterEach(async function () {
    assert(await this.mockSocket.isDone(), 'response handlers must be called')
  })

  describe('setup', () => {
    it('should parse the BTP URI correctly', function () {
      assert.equal(this.plugin._client._wsUri, 'wss://example.com/rpc')
      assert.equal(this.plugin._client._authUsername, 'user')
      assert.equal(this.plugin._client._authToken, 'shhh secret')
    })

    it('should get info from rpc endpoint', function () {
      assert.deepEqual(this.plugin.getInfo(), info)
    })

    it('should get balance from peer', async function () {
      this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => {
        return btpPacket.serializeResponse(requestId, [{
          protocolName: 'balance',
          contentType: btpPacket.MIME_APPLICATION_OCTET_STREAM,
          data: Buffer.from([ 255, 255, 255, 255, 255, 255, 255, 251 ])
        }])
      })

      assert.equal(await this.plugin.getBalance(), '5')
    })
  })

  describe('sending', () => {
    beforeEach(function () {
      const { condition, fulfillment } = conditionPair()

      this.condition = condition
      this.fulfillment = fulfillment
      this.transferJson = {
        id: '5709e97e-ffb5-5454-5c53-cfaa5a0cd4c1',
        to: peerAddress,
        amount: '10',
        executionCondition: condition,
        expiresAt: new Date(Date.now() + 1000).toISOString()
      }
      const requestId = 12345

      this.transfer = btpPacket.serializePrepare(
        Object.assign({},
          this.transferJson,
          {transferId: this.transferJson.id}),
        requestId,
        ilpAndCustomToProtocolData(this.transferJson)
      )
      this.btpFulfillment = btpPacket.serializeFulfill({
        transferId: this.transferJson.id,
        fulfillment: this.fulfillment
      }, requestId + 1, [])
    })

    it('should send a request', async function () {
      const response = {
        to: this.plugin.getAccount(),
        from: this.plugin.getPeerAccount(),
        ledger: this.plugin._prefix,
        ilp: base64url('some_base64_encoded_data_goes_here')
      }

      this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => {
        return btpPacket.serializeResponse(requestId,
          ilpAndCustomToProtocolData(response))
      })

      const result = await this.plugin.sendRequest({
        to: peerAddress,
        ilp: 'some_data'
      })

      assert.deepEqual(result, response)
    })

    it('should prepare and execute a transfer', async function () {
      this.mockSocket.reply(btpPacket.TYPE_PREPARE, ({requestId, data}) => {
        const expectedPacket = btpPacket.deserialize(this.transfer)
        assert.deepEqual(data, expectedPacket.data)
        return btpPacket.serializeResponse(requestId, [])
      })

      const prepared = new Promise((resolve) =>
        this.plugin.once('outgoing_prepare', () => resolve()))

      await this.plugin.sendTransfer(this.transferJson)
      await prepared

      const fulfilled = new Promise((resolve) =>
        this.plugin.once('outgoing_fulfill', () => resolve()))

      await this.plugin._rpc.handleMessage(this.mockSocketIndex, this.btpFulfillment)
      await fulfilled
    })

    it('should receive and fulfill a transfer', async function () {
      this.transferJson.to = this.plugin.getAccount()

      this.mockSocket
        .reply(btpPacket.TYPE_RESPONSE, ({requestId, data}) => {
          return btpPacket.serializeResponse(requestId, [])
        })
        .reply(btpPacket.TYPE_FULFILL, ({requestId, data}) => {
          assert.equal(data.transferId, this.transferJson.id)
          assert.equal(data.fulfillment, this.fulfillment)
          return btpPacket.serializeResponse(requestId, [])
        })

      const prepared = new Promise((resolve) =>
        this.plugin.once('incoming_prepare', () => resolve()))

      await this.plugin._rpc.handleMessage(this.mockSocketIndex, this.transfer)
      await prepared

      const fulfilled = new Promise((resolve) =>
        this.plugin.once('incoming_fulfill', () => resolve()))

      await this.plugin.fulfillCondition(this.transferJson.id, this.fulfillment)
      await fulfilled
    })

    it('should not send a transfer if peer gives error', async function () {
      this.mockSocket
        .reply(btpPacket.TYPE_PREPARE, ({requestId, data}) => {
          const expectedPacket = btpPacket.deserialize(this.transfer)
          assert.deepEqual(data, expectedPacket.data)

          const error = {
            code: 'F00',
            name: 'Bad Request',
            triggeredAt: new Date(),
            data: JSON.stringify({ message: 'Peer isn\'t feeling like it.' })
          }

          return btpPacket.serializeError(error, requestId, [])
        })

      const prepared = new Promise((resolve, reject) => {
        this.plugin.once('outgoing_prepare', () => {
          reject(new Error('should not be accepted'))
        })
        setTimeout(resolve, 10)
      })

      await assert.isRejected(this.plugin.sendTransfer(this.transferJson))
      await prepared
    })
  })

  describe('server', function () {
    // TODO: Is this test case still relevant?  (cc: sharafian)
    // Would you ever add several several sockets to a single plugin?
    it.skip('should call several plugins over RPC', async function () {
      const _options = Object.assign({}, options)

      delete _options.rpcUri
      _options.info = info
      _options._backend = getObjBackend(null)
      _options.tolerateFailure = true
      _options.rpcUris = [
        'https://example.com/1/rpc',
        'https://example.com/2/rpc',
        'https://example.com/3/rpc'
      ]

      this.mockSocket
        .reply(btpPacket.TYPE_PREPARE, ({requestId, data}) => {
          const expectedPacket = btpPacket.deserialize(this.transfer)
          assert.deepEqual(data, expectedPacket.data)
          return btpPacket.serializeResponse(requestId, [])
        })
        // .reply(....)

      // nock('https://example.com')
      //   .post('/1/rpc?method=send_transfer&prefix=example.red.')
      //   .reply(200, true)
      //   .post('/2/rpc?method=send_transfer&prefix=example.red.')
      //   .reply(200, true)
      //   .post('/3/rpc?method=send_transfer&prefix=example.red.')
      //   .reply(500) // should tolerate an error from one

      this.plugin = new PluginPaymentChannel(_options)
      await this.plugin.connect()

      await this.plugin.sendTransfer({
        id: '0aad44fd-a64e-537a-14b0-aec8a4e80b9c',
        to: this.plugin.getPeerAccount(),
        amount: '10',
        executionCondition: '8EhfVB4NBL3Bpa7PPqA0-LbJPg_xGyNnnRkBJ1oYLSU',
        expiresAt: new Date(Date.now() + 1000).toISOString()
      })
    })
  })
})