OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / payment-provider.test.js

78 lines · javascript ref:
/**
 * Contract tests for the formalized PaymentProvider interface and the
 * selectProvider helper. The existing LightningProvider and MockProvider
 * are duck-typed; this test pins the new base class's shape so that any
 * future provider (Tether over Lightning, Tether Wallet SDK, etc.)
 * inherits a consistent contract.
 */

import test from 'brittle'
import { PaymentProvider, selectProvider } from 'p2p-hiverelay/incentive/payment/provider.js'

test('PaymentProvider: base class throws on abstract methods', async (t) => {
  const p = new PaymentProvider()
  try { await p.connect(); t.fail('connect should throw') } catch (err) { t.ok(err.message.includes('connect')) }
  try { await p.pay('x', 100); t.fail('pay should throw') } catch (err) { t.ok(err.message.includes('pay')) }
  try { await p.createInvoice(100); t.fail('createInvoice should throw') } catch (err) { t.ok(err.message.includes('createInvoice')) }
  try { await p.getInvoiceStatus('r'); t.fail('getInvoiceStatus should throw') } catch (err) { t.ok(err.message.includes('getInvoiceStatus')) }
})

test('PaymentProvider: default capabilities shape', (t) => {
  const p = new PaymentProvider()
  const caps = p.capabilities()
  t.is(caps.name, 'PaymentProvider')
  t.alike(caps.assets, ['BTC'], 'BTC default asset for back-compat')
  t.alike(caps.rails, ['lightning'])
  t.is(caps.micropayments, true)
  t.is(caps.topUpModel, false)
  t.is(caps.connected, false)
})

// Minimal fakes to exercise selectProvider without touching real backends.
function makeFake (opts) {
  const p = new PaymentProvider()
  p.connected = opts.connected !== false
  p.capabilities = () => ({
    name: opts.name || 'fake',
    assets: opts.assets || ['BTC'],
    rails: opts.rails || ['lightning'],
    micropayments: opts.micropayments !== false,
    topUpModel: !!opts.topUpModel,
    connected: p.connected
  })
  return p
}

test('selectProvider: returns null when no provider supports the asset', (t) => {
  const btcOnly = makeFake({ name: 'btc', assets: ['BTC'] })
  const picked = selectProvider([btcOnly], { asset: 'USDT' })
  t.is(picked, null)
})

test('selectProvider: skips disconnected providers', (t) => {
  const disconnected = makeFake({ name: 'down', connected: false })
  const up = makeFake({ name: 'up' })
  const picked = selectProvider([disconnected, up], { asset: 'BTC' })
  t.is(picked.capabilities().name, 'up')
})

test('selectProvider: prefers micropayment-friendly provider for small amounts', (t) => {
  const ln = makeFake({ name: 'lightning', assets: ['BTC', 'USDT'], rails: ['lightning'], micropayments: true })
  const walletSdk = makeFake({ name: 'wallet', assets: ['USDT'], rails: ['wallet-sdk'], micropayments: false, topUpModel: true })

  // 5-cent call → micropayment path
  const smallPick = selectProvider([walletSdk, ln], { asset: 'USDT', amountUsd: 0.05 })
  t.is(smallPick.capabilities().name, 'lightning', 'micropayment-friendly provider wins')

  // $10 call → either is fine; we pick the first supporting one (wallet first in list)
  const largePick = selectProvider([walletSdk, ln], { asset: 'USDT', amountUsd: 10 })
  t.is(largePick.capabilities().name, 'wallet', 'top-up model OK for larger amount')
})

test('selectProvider: honors explicit rail requirement', (t) => {
  const ln = makeFake({ name: 'lightning', assets: ['USDT'], rails: ['lightning'] })
  const wallet = makeFake({ name: 'wallet', assets: ['USDT'], rails: ['wallet-sdk'], micropayments: false, topUpModel: true })
  const picked = selectProvider([ln, wallet], { asset: 'USDT', rail: 'wallet-sdk' })
  t.is(picked.capabilities().name, 'wallet', 'rail filter overrides micropayment preference')
})