packages feed

haskoin-store-data-1.0.0: test/Haskoin/Store/DataSpec.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Haskoin.Store.DataSpec (spec) where

import Control.Arrow (second)
import Control.Monad (forM_)
import Data.Aeson (FromJSON (..))
import Data.ByteString qualified as B
import Data.String.Conversions (cs)
import Haskoin
import Haskoin.Store.Data
import Haskoin.Util
import Haskoin.Util.Arbitrary
import Test.Hspec
import Test.QuickCheck

serialVals :: Ctx -> [SerialBox]
serialVals ctx =
  [ SerialBox (arbitrary :: Gen DeriveType),
    SerialBox (arbitraryXPubSpec ctx :: Gen XPubSpec),
    SerialBox (arbitrary :: Gen BlockRef),
    SerialBox (arbitrary :: Gen TxRef),
    SerialBox (arbitrary :: Gen Balance),
    SerialBox (arbitrary :: Gen Unspent),
    SerialBox (arbitrary :: Gen BlockData),
    SerialBox (arbitrary :: Gen StoreInput),
    SerialBox (arbitrary :: Gen Spender),
    SerialBox (arbitrary :: Gen StoreOutput),
    SerialBox (arbitrary :: Gen Prev),
    SerialBox (arbitraryTxData ctx :: Gen TxData),
    SerialBox (arbitrary :: Gen Transaction),
    SerialBox (arbitrary :: Gen XPubBal),
    SerialBox (arbitrary :: Gen XPubUnspent),
    SerialBox (arbitrary :: Gen XPubSummary),
    SerialBox (arbitrary :: Gen HealthCheck),
    SerialBox (arbitrary :: Gen Event),
    SerialBox (arbitrary :: Gen TxId),
    SerialBox (arbitrary :: Gen PeerInfo),
    SerialBox (arbitrary :: Gen (GenericResult BlockData)),
    SerialBox (arbitrary :: Gen (RawResult BlockData)),
    SerialBox (arbitrary :: Gen (RawResultList BlockData))
  ]

jsonVals :: [JsonBox]
jsonVals =
  [ JsonBox (arbitrary :: Gen TxRef),
    JsonBox (arbitrary :: Gen BlockRef),
    JsonBox (arbitrary :: Gen Spender),
    JsonBox (arbitrary :: Gen XPubSummary),
    JsonBox (arbitrary :: Gen HealthCheck),
    JsonBox (arbitrary :: Gen Event),
    JsonBox (arbitrary :: Gen TxId),
    JsonBox (arbitrary :: Gen PeerInfo),
    JsonBox (arbitrary :: Gen (GenericResult XPubSummary)),
    JsonBox (arbitrary :: Gen (RawResult BlockData)),
    JsonBox (arbitrary :: Gen (RawResultList BlockData)),
    JsonBox (arbitrary :: Gen Except),
    JsonBox (arbitrary :: Gen BinfoWallet),
    JsonBox (arbitrary :: Gen BinfoSymbol),
    JsonBox (arbitrary :: Gen BinfoBlockInfo),
    JsonBox (arbitrary :: Gen BinfoInfo),
    JsonBox (arbitrary :: Gen BinfoSpender),
    JsonBox (arbitrary :: Gen BinfoRate),
    JsonBox (arbitrary :: Gen BinfoTicker),
    JsonBox (arbitrary :: Gen BinfoTxId),
    JsonBox (arbitrary :: Gen BinfoShortBal),
    JsonBox (arbitrary :: Gen BinfoHistory),
    JsonBox (arbitrary :: Gen BinfoHeader),
    JsonBox (arbitrary :: Gen BinfoBlockInfos)
  ]

netVals :: Ctx -> [NetBox]
netVals ctx =
  [ NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryNetData :: Gen (Network, Balance)
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryNetData :: Gen (Network, StoreOutput)
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryNetData :: Gen (Network, Unspent)
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryNetData :: Gen (Network, XPubBal)
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryNetData :: Gen (Network, XPubUnspent)
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryStoreInputNet
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryBlockDataNet
      ),
    NetBox
      ( marshalValue,
        marshalEncoding,
        unmarshalValue,
        arbitraryNetData :: Gen (Network, Transaction)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoMultiAddr ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoBalance ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoBlock ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoTx ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoTxInput ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoTxOutput ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoXPubPath ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoUnspent ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (listOf (arbitraryBinfoBlock ctx))
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoRawAddr ctx)
      ),
    NetBox
      ( marshalValue . (,ctx),
        marshalEncoding . (,ctx),
        unmarshalValue . (,ctx),
        genNetData (arbitraryBinfoMempool ctx)
      )
  ]

spec :: Spec
spec = prepareContext $ \ctx -> do
  describe "Binary Encoding" $
    forM_ (serialVals ctx) $
      \(SerialBox g) -> testSerial g
  describe "JSON Encoding" $
    forM_ jsonVals $
      \(JsonBox g) -> testJson g
  describe "JSON Encoding with Network" $
    forM_ (netVals ctx) $
      \(NetBox (j, e, p, g)) -> testNetJson j e p g

instance Arbitrary BlockRef where
  arbitrary =
    oneof [BlockRef <$> arbitrary <*> arbitrary, MemRef <$> arbitrary]

instance Arbitrary Prev where
  arbitrary = Prev <$> arbitraryBS1 <*> arbitrary

arbitraryTxData :: Ctx -> Gen TxData
arbitraryTxData ctx =
  TxData
    <$> arbitrary
    <*> arbitraryTx btc ctx
    <*> arbitrary
    <*> arbitrary
    <*> arbitrary
    <*> arbitrary
    <*> arbitrary

instance Arbitrary StoreInput where
  arbitrary =
    oneof
      [ StoreCoinbase
          <$> arbitraryOutPoint
          <*> arbitrary
          <*> arbitraryBS1
          <*> listOf arbitraryBS1,
        StoreInput
          <$> arbitraryOutPoint
          <*> arbitrary
          <*> arbitraryBS1
          <*> arbitraryBS1
          <*> arbitrary
          <*> listOf arbitraryBS1
          <*> arbitraryMaybe arbitraryAddress
      ]

arbitraryStoreInputNet :: Gen (Network, StoreInput)
arbitraryStoreInputNet = do
  net <- arbitraryNetwork
  store <- arbitrary
  let res
        | net.segWit = store
        | otherwise = witless store
  return (net, res)
  where
    witless StoreInput {..} = StoreInput {witness = [], ..}
    witless StoreCoinbase {..} = StoreCoinbase {witness = [], ..}

instance Arbitrary Spender where
  arbitrary = Spender <$> arbitraryTxHash <*> arbitrary

instance Arbitrary StoreOutput where
  arbitrary =
    StoreOutput
      <$> arbitrary
      <*> arbitraryBS1
      <*> arbitrary
      <*> arbitraryMaybe arbitraryAddress

instance Arbitrary Transaction where
  arbitrary =
    Transaction
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitraryTxHash
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary PeerInfo where
  arbitrary =
    PeerInfo
      <$> (cs <$> listOf arbitraryUnicodeChar)
      <*> listOf arbitraryPrintableChar
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary BlockHealth where
  arbitrary =
    BlockHealth
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary TimeHealth where
  arbitrary =
    TimeHealth
      <$> arbitrary
      <*> arbitrary

instance Arbitrary CountHealth where
  arbitrary =
    CountHealth
      <$> arbitrary
      <*> arbitrary

instance Arbitrary MaxHealth where
  arbitrary =
    MaxHealth
      <$> arbitrary
      <*> arbitrary

instance Arbitrary HealthCheck where
  arbitrary =
    HealthCheck
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary RejectCode where
  arbitrary =
    elements
      [ RejectMalformed,
        RejectInvalid,
        RejectObsolete,
        RejectDuplicate,
        RejectNonStandard,
        RejectDust,
        RejectInsufficientFee,
        RejectCheckpoint
      ]

arbitraryXPubSpec :: Ctx -> Gen XPubSpec
arbitraryXPubSpec ctx = XPubSpec <$> (snd <$> arbitraryXPubKey ctx) <*> arbitrary

instance Arbitrary DeriveType where
  arbitrary = elements [DeriveNormal, DeriveP2SH, DeriveP2WPKH]

instance Arbitrary TxId where
  arbitrary = TxId <$> arbitraryTxHash

instance Arbitrary TxRef where
  arbitrary = TxRef <$> arbitrary <*> arbitraryTxHash

instance Arbitrary Balance where
  arbitrary =
    Balance
      <$> arbitraryAddress
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary Unspent where
  arbitrary =
    Unspent
      <$> arbitrary
      <*> arbitraryOutPoint
      <*> arbitrary
      <*> arbitraryBS1
      <*> arbitraryMaybe arbitraryAddress

instance Arbitrary BlockData where
  arbitrary =
    BlockData
      <$> arbitrary
      <*> arbitrary
      <*> (fromInteger <$> suchThat arbitrary (0 <=))
      <*> arbitraryBlockHeader
      <*> arbitrary
      <*> arbitrary
      <*> listOf1 arbitraryTxHash
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

arbitraryBlockDataNet :: Gen (Network, BlockData)
arbitraryBlockDataNet = do
  net <- arbitraryNetwork
  dat@BlockData {..} <- arbitrary
  let res
        | net.segWit = dat
        | otherwise = BlockData {weight = 0, ..}
  return (net, res)

instance (Arbitrary a) => Arbitrary (GenericResult a) where
  arbitrary = GenericResult <$> arbitrary

instance (Arbitrary a) => Arbitrary (RawResult a) where
  arbitrary = RawResult <$> arbitrary

instance (Arbitrary a) => Arbitrary (RawResultList a) where
  arbitrary = RawResultList <$> arbitrary

instance Arbitrary XPubBal where
  arbitrary = XPubBal <$> arbitrary <*> arbitrary

instance Arbitrary XPubUnspent where
  arbitrary = XPubUnspent <$> arbitrary <*> arbitrary

instance Arbitrary XPubSummary where
  arbitrary =
    XPubSummary
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary Event where
  arbitrary =
    oneof
      [ EventBlock <$> arbitraryBlockHash,
        EventTx <$> arbitraryTxHash
      ]

instance Arbitrary Except where
  arbitrary =
    oneof
      [ return ThingNotFound,
        return ServerError,
        return BadRequest,
        UserError <$> arbitrary,
        StringError <$> arbitrary,
        TxIndexConflict <$> listOf1 arbitraryTxHash,
        return ServerTimeout
      ]

---------------------------------------
-- Blockchain.info API Compatibility --
---------------------------------------

instance Arbitrary BinfoTxId where
  arbitrary =
    oneof
      [ BinfoTxIdHash <$> arbitraryTxHash,
        BinfoTxIdIndex <$> arbitrary
      ]

arbitraryBinfoMultiAddr :: Ctx -> Gen BinfoMultiAddr
arbitraryBinfoMultiAddr ctx = do
  addresses <- listOf1 $ arbitraryBinfoBalance ctx
  wallet <- arbitrary
  txs <- listOf $ arbitraryBinfoTx ctx
  info <- arbitrary
  recommendFee <- arbitrary
  cashAddr <- arbitrary
  return BinfoMultiAddr {..}

arbitraryBinfoRawAddr :: Ctx -> Gen BinfoRawAddr
arbitraryBinfoRawAddr ctx = do
  address <-
    oneof
      [ BinfoAddr <$> arbitraryAddress,
        BinfoXpub . snd <$> arbitraryXPubKey ctx
      ]
  balance <- arbitrary
  ntx <- arbitrary
  utxo <- arbitrary
  received <- arbitrary
  sent <- arbitrary
  txs <- listOf $ arbitraryBinfoTx ctx
  return $ BinfoRawAddr {..}

instance Arbitrary BinfoShortBal where
  arbitrary = BinfoShortBal <$> arbitrary <*> arbitrary <*> arbitrary

arbitraryBinfoBalance :: Ctx -> Gen BinfoBalance
arbitraryBinfoBalance ctx = do
  address <- arbitraryAddress
  txs <- arbitrary
  received <- arbitrary
  sent <- arbitrary
  balance <- arbitrary
  xpub <- snd <$> arbitraryXPubKey ctx
  external <- arbitrary
  change <- arbitrary
  elements [BinfoAddrBalance {..}, BinfoXPubBalance {..}]

instance Arbitrary BinfoWallet where
  arbitrary = do
    balance <- arbitrary
    txs <- arbitrary
    filtered <- arbitrary
    received <- arbitrary
    sent <- arbitrary
    return BinfoWallet {..}

arbitraryBinfoBlock :: Ctx -> Gen BinfoBlock
arbitraryBinfoBlock ctx = do
  hash <- arbitraryBlockHash
  version <- arbitrary
  prev <- arbitraryBlockHash
  merkle <- (.get) <$> arbitraryTxHash
  timestamp <- arbitrary
  bits <- arbitrary
  next <- listOf arbitraryBlockHash
  ntx <- arbitrary
  fee <- arbitrary
  nonce <- arbitrary
  size <- arbitrary
  index <- arbitrary
  main <- arbitrary
  height <- arbitrary
  weight <- arbitrary
  txs <- resize 5 $ listOf $ arbitraryBinfoTx ctx
  return BinfoBlock {..}

arbitraryBinfoTx :: Ctx -> Gen BinfoTx
arbitraryBinfoTx ctx = do
  txid <- arbitraryTxHash
  version <- arbitrary
  inputs <- resize 5 $ listOf1 $ arbitraryBinfoTxInput ctx
  outputs <- resize 5 $ listOf1 $ arbitraryBinfoTxOutput ctx
  let inputCount = fromIntegral $ length inputs
      outputCount = fromIntegral $ length outputs
  size <- arbitrary
  weight <- arbitrary
  fee <- arbitrary
  relayed <- cs <$> listOf arbitraryUnicodeChar
  locktime <- arbitrary
  index <- arbitrary
  doubleSpend <- arbitrary
  rbf <- arbitrary
  timestamp <- arbitrary
  blockIndex <- arbitrary
  blockHeight <- arbitrary
  balance <- arbitrary
  return BinfoTx {..}

arbitraryBinfoTxInput :: Ctx -> Gen BinfoTxInput
arbitraryBinfoTxInput ctx = do
  sequence <- arbitrary
  witness <- B.pack <$> listOf arbitrary
  script <- B.pack <$> listOf arbitrary
  index <- arbitrary
  output <- arbitraryBinfoTxOutput ctx
  return BinfoTxInput {..}

arbitraryBinfoTxOutput :: Ctx -> Gen BinfoTxOutput
arbitraryBinfoTxOutput ctx = do
  typ <- arbitrary
  spent <- arbitrary
  value <- arbitrary
  index <- arbitrary
  txidx <- arbitrary
  script <- B.pack <$> listOf arbitrary
  spenders <- arbitrary
  address <- arbitraryMaybe arbitraryAddress
  xpub <- arbitraryMaybe $ arbitraryBinfoXPubPath ctx
  return BinfoTxOutput {..}

instance Arbitrary BinfoSpender where
  arbitrary = do
    txidx <- arbitrary
    input <- arbitrary
    return BinfoSpender {..}

arbitraryBinfoXPubPath :: Ctx -> Gen BinfoXPubPath
arbitraryBinfoXPubPath ctx = do
  key <- snd <$> arbitraryXPubKey ctx
  deriv <- arbitrarySoftPath
  return BinfoXPubPath {..}

instance Arbitrary BinfoInfo where
  arbitrary = do
    connected <- arbitrary
    conversion <- arbitrary
    fiat <- arbitrary
    crypto <- arbitrary
    head <- arbitrary
    return BinfoInfo {..}

instance Arbitrary BinfoBlockInfo where
  arbitrary = do
    hash <- arbitraryBlockHash
    height <- arbitrary
    timestamp <- arbitrary
    index <- arbitrary
    return BinfoBlockInfo {..}

instance Arbitrary BinfoSymbol where
  arbitrary = do
    code <- cs <$> listOf1 arbitraryUnicodeChar
    symbol <- cs <$> listOf1 arbitraryUnicodeChar
    name <- cs <$> listOf1 arbitraryUnicodeChar
    conversion <- arbitrary
    after <- arbitrary
    local <- arbitrary
    return BinfoSymbol {..}

instance Arbitrary BinfoRate where
  arbitrary = BinfoRate <$> arbitrary <*> arbitrary <*> arbitrary

instance Arbitrary BinfoTicker where
  arbitrary = do
    fifteen <- arbitrary
    sell <- arbitrary
    buy <- arbitrary
    last <- arbitrary
    symbol <- cs <$> listOf1 arbitraryUnicodeChar
    return BinfoTicker {..}

instance Arbitrary BinfoHistory where
  arbitrary = do
    date <- cs <$> listOf1 arbitraryUnicodeChar
    time <- cs <$> listOf1 arbitraryUnicodeChar
    typ <- cs <$> listOf1 arbitraryUnicodeChar
    amount <- arbitrary
    valueThen <- arbitrary
    valueNow <- arbitrary
    rateThen <- arbitrary
    txid <- arbitraryTxHash
    fee <- arbitrary
    return BinfoHistory {..}

arbitraryBinfoUnspent :: Ctx -> Gen BinfoUnspent
arbitraryBinfoUnspent ctx = do
  txid <- arbitraryTxHash
  index <- arbitrary
  script <- B.pack <$> listOf arbitrary
  value <- arbitrary
  confirmations <- arbitrary
  txidx <- arbitrary
  xpub <- arbitraryMaybe $ arbitraryBinfoXPubPath ctx
  return BinfoUnspent {..}

arbitraryBinfoUnspents :: Ctx -> Gen BinfoUnspents
arbitraryBinfoUnspents ctx =
  fmap BinfoUnspents $ listOf $ arbitraryBinfoUnspent ctx

instance Arbitrary BinfoHeader where
  arbitrary =
    BinfoHeader
      <$> arbitraryBlockHash
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

arbitraryBinfoMempool :: Ctx -> Gen BinfoMempool
arbitraryBinfoMempool ctx =
  fmap BinfoMempool $ listOf $ arbitraryBinfoTx ctx

instance Arbitrary BinfoBlockInfos where
  arbitrary = BinfoBlockInfos <$> arbitrary