packages feed

haskoin-store-data-2.0.0: test/Haskoin/Store/WebCommonSpec.hs

{-# OPTIONS_GHC -Wno-orphans #-}

module Haskoin.Store.WebCommonSpec (spec) where

import Data.ByteString qualified as B
import Data.Maybe (fromJust)
import Data.Proxy (Proxy (..))
import Data.String.Conversions (cs)
import Data.Text (Text)
import Data.Word (Word64)
import Haskoin
import Data.Aeson qualified as A
import Data.Aeson.Types qualified as A
import Haskoin.Store.Data (DeriveType (..), SerialList (..), TxRef)
import Haskoin.Store.Gen (arbitraryDeriveType, arbitraryTransaction)
import Haskoin.Store.WebCommon
import Haskoin.Util.Arbitrary
import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck

spec :: Spec
spec = prepareContext $ \ctx -> do
  -- each case on a random network: they differ in their address prefixes
  describe "Parameters" $ do
    roundTrip ctx arbitraryAddress
    roundTrip ctx (listOf . arbitraryAddress)
    roundTrip ctx (const (arbitrary :: Gen StartParam))
    roundTrip ctx (const (arbitrarySizedNatural :: Gen OffsetParam))
    roundTrip ctx (const (arbitrarySizedNatural :: Gen LimitParam))
    roundTrip ctx (const (arbitrarySizedNatural :: Gen HeightParam))
    roundTrip ctx (const (arbitrary :: Gen HeightsParam))
    roundTrip ctx (const (arbitrarySizedNatural :: Gen TimeParam))
    roundTrip ctx (const (arbitraryXPubKey ctx))
    roundTrip ctx arbitraryDeriveType
    roundTrip ctx (const (NoCache <$> arbitrary))
    roundTrip ctx (const (NoTx <$> arbitrary))
    roundTrip ctx (const (listOf arbitraryBlockHash))
    roundTrip ctx (const (listOf arbitraryTxHash))

  -- the client reads what the server writes: lists in the JSON of the
  -- network, and the rest in plain JSON
  describe "Responses" $ do
    prop "a list of transactions" . forAll (elements allNets) $ \net ->
      forAll (listOf (arbitraryTransaction net)) $ \xs ->
        A.parseEither (parseResponse net ctx) (A.toJSON (map (marshalValue net) xs))
          === Right (SerialList xs)
    prop "a list of references" . forAll (listOf arbitrary) $ \xs ->
      A.parseEither (parseResponse btc ctx) (A.toJSON (xs :: [TxRef]))
        === Right (SerialList xs)

  describe "Start of a page" $ do
    it "is a transaction hash, a height up to 1230768000, or a later time" $ do
      parse ["1230768000"] `shouldBe` Just (StartParamHeight 1230768000)
      parse ["1230768001"] `shouldBe` Just (StartParamTime 1230768001)
      parse [txHashToHex z] `shouldBe` Just (StartParamHash z.get)
    it "refuses anything else" $
      parse ["yesterday"] `shouldBe` Nothing

  describe "Derivation" $
    it "is only normal on networks without SegWit" $ do
      encodeParam bch ctx DeriveP2WPKH `shouldBe` Nothing
      encodeParam btc ctx DeriveP2WPKH `shouldBe` Just ["segwit"]

  describe "Paths" $ do
    let addr = fromJust (textToAddr btc "1111111111111111111114oLvT2")
        path :: (ApiResource a b) => a -> String
        path = cs . queryPath btc ctx
    it "of a block" $ do
      path (GetBlockBest (NoTx False)) `shouldBe` "/block/best"
      path (GetBlockHeight (HeightParam 5) (NoTx False)) `shouldBe` "/block/height/5"
    it "of a transaction" $
      path (GetTx z) `shouldBe` "/transaction/" <> cs (txHashToHex z)
    it "of an address" $
      path (GetAddrBalance addr) `shouldBe` "/address/1111111111111111111114oLvT2/balance"
    prop "of an xpub" $
      forAll (arbitraryXPubKey ctx) $ \x ->
        path (GetXPubBalances x DeriveNormal (NoCache False))
          `shouldBe` "/xpub/" <> cs (xPubExport btc ctx x) <> "/balances"
  where
    z = TxHash (sha256 B.empty)
    parse = parseParam btc undefined :: [Text] -> Maybe StartParam

-- | A parameter comes back from its encoding in a URL, on a random network.
roundTrip :: forall a. (Eq a, Show a, Param a) => Ctx -> (Network -> Gen a) -> Spec
roundTrip ctx g =
  prop (cs (proxyLabel (Proxy :: Proxy a))) . forAll (elements allNets) $ \net ->
    counterexample net.name . forAll (g net) $ \p ->
      (encodeParam net ctx p >>= parseParam net ctx) `shouldBe` Just p

instance Arbitrary StartParam where
  arbitrary =
    oneof
      [ StartParamHash <$> arbitraryHash256,
        StartParamHeight . fromIntegral <$> (choose (0, 1230768000) :: Gen Word64),
        StartParamTime <$> choose (1230768001, maxBound)
      ]

instance Arbitrary HeightsParam where
  arbitrary = HeightsParam <$> listOf arbitrarySizedNatural