packages feed

bech32-1.1.4: test/AppSpec.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module AppSpec
    ( spec
    ) where

import Prelude

import Codec.Binary.Bech32
    ( dataPartFromBytes, humanReadablePartFromText )
import Data.ByteArray.Encoding
    ( Base (..), convertToBase )
import Data.ByteString
    ( ByteString )
import Data.ByteString.Base58
    ( bitcoinAlphabet, encodeBase58 )
import Data.Text
    ( Text )
import System.Process
    ( readProcess )
import Test.Hspec
    ( Spec, describe )
import Test.Hspec.QuickCheck
    ( prop )
import Test.QuickCheck
    ( Arbitrary (..), choose, vector, withMaxSuccess )
import Test.QuickCheck.Monadic
    ( assert, monadicIO, run )

import qualified Codec.Binary.Bech32 as Bech32
import qualified Data.Text as T
import qualified Data.Text.Encoding as T

spec :: Spec
spec =
    describe "bech32 command-line" $ do
        specDecode
        specEncode base16
        specEncode (bech32 "bech32")
        specEncode base58

-- | Check that, for a given encoder, any encoded string can be decoded and
-- re-encoded to bech32 using the given prefix.
specEncode
    :: (String -> String)
    -> Spec
specEncode encode = prop ("can re-encode encoded strings " <> encode "...") $
    \(MinString str) ->
        withMaxSuccess 1000 $ monadicIO $ do
            out <- run $ readProcess "bech32" ["prefix"] (encode str)
            assert (init out == bech32 "prefix" str)

-- | Check that any bech32-encoded string can be decoded successfully.
specDecode :: Spec
specDecode = prop "any bech32 string can be decoded to hex" $
    \(MinString str) ->
        withMaxSuccess 1000 $ monadicIO $ do
            out <- run $ readProcess "bech32" [] (bech32 "bech32" str)
            assert (init out == base16 str)

base16 :: String -> String
base16 = fromUtf8 . convertToBase Base16 . utf8

bech32 :: Text -> String -> String
bech32 txt = T.unpack . Bech32.encodeLenient hrp . dataPartFromBytes . utf8
  where
    hrp = either (error . ("Error while parsing Bech32: " <>) . show) id
        $ humanReadablePartFromText txt

base58 :: String -> String
base58 = fromUtf8 . encodeBase58 bitcoinAlphabet . utf8

utf8 :: String -> ByteString
utf8 = T.encodeUtf8 . T.pack

fromUtf8 :: ByteString -> String
fromUtf8 = T.unpack . T.decodeUtf8

-- Generate strings of a minimal length.
newtype MinString = MinString String deriving (Eq, Show)

instance Arbitrary MinString where
    arbitrary =
        MinString <$> (choose (8, 100) >>= vector)
    shrink (MinString str) =
        MinString <$> filter ((> 8) . length) (shrink str)