packages feed

cleveland-0.1.0: morley-test/Test/Serialization/Util.hs

-- SPDX-FileCopyrightText: 2021 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

{-# LANGUAGE OverloadedLists #-}


module Test.Serialization.Util
  ( Example(..)
  , TestMethod(..)
  , deserializeNegSpecImpl
  , examples
  , fromBinaryTestMethodImpl
  , fromJsonTestMethodImpl
  , serializeSpecManualImpl
  , toBinaryTestMethodImpl
  , toJsonTestMethodImpl
  , (~:)
  ) where

import qualified Data.Aeson as J
import Data.Aeson.Encode.Pretty (Indent(Spaces), confCompare, confIndent, defConfig, encodePretty')
import Fmt (pretty)
import Test.Hspec (Expectation, Spec, describe, it, shouldBe, shouldSatisfy)
import Test.Hspec.Expectations (expectationFailure)
import Text.Hex (encodeHex)

import Morley.Micheline.Class (FromExpression(..), ToExpression(..))
import Morley.Micheline.Expression (Expression)
import Morley.Michelson.Interpret.Unpack
import Test.Cleveland.Util

-- | Helper for defining tests cases for 'serializeSpec'.
(~:) :: a -> b -> (a, b)
(~:) = (,)
infixr 0 ~:

-- | Dummy wrapper for the data needed to test an example scenario + its name.
data Example a = Example
  { _eTestName :: String
  , _eTestData :: a
  }

-- | Creates a list of example scenarios and labels them using their index.
-- examples :: [a] -> [Example a]
examples :: [a] -> [Example a]
examples xs =
  xs `zip` [1..] <&>
    \(testData, index) -> Example ("Example #" <> show @String @Int index) testData

-- | Dummy wrapper for what do we test - serialize/deserialize from/to binary/json.
data TestMethod t = TestMethod
  { _tmName :: String
  , _tmApply :: t -> Text -> J.Value -> Expectation
  }

toBinaryTestMethodImpl :: HasCallStack => (t -> ByteString) -> [TestMethod t]
toBinaryTestMethodImpl doPack = one $
  TestMethod "To binary" $
    \val encodedHex _ ->
      encodeHex (doPack val) `shouldBe` stripOptional0x encodedHex

fromBinaryTestMethodImpl :: (HasCallStack, Show t, Eq t) => (ByteString -> Either UnpackError t) -> [TestMethod t]
fromBinaryTestMethodImpl doUnpack = one $
  TestMethod "From binary" $
    \val encodedHex _ ->
      doUnpack (unsafeFromHex encodedHex) `shouldBe` Right val

toJsonTestMethodImpl :: (ToExpression t) => HasCallStack => [TestMethod t]
toJsonTestMethodImpl = one $
  TestMethod "To json" $
    \val _ expectedJson -> do
      let actualJson = J.toJSON (toExpression val)
      ShowWith prettify actualJson `shouldBe` ShowWith prettify expectedJson
  where
    prettify :: J.Value -> String
    prettify =
      decodeUtf8 . encodePretty'
        -- we use `confCompare` to reverse the order of the json keys, so that `prim` appears before `args`.
        (defConfig { confCompare = \x y -> compare y x, confIndent = Spaces 2 })

fromJsonTestMethodImpl :: forall t. (FromExpression t, Show t, Eq t) => HasCallStack => [TestMethod t]
fromJsonTestMethodImpl = one $
  TestMethod "From json" $
    \expectedVal _ json -> do
      case J.eitherDecode' @Expression (J.encode json) of
        Left err -> expectationFailure $ pretty err
        Right expr -> do
          case fromExpression @t expr of
            Left err -> expectationFailure $ pretty err
            Right val ->
              val `shouldBe` expectedVal

serializeSpecManualImpl
  :: forall y x. (HasCallStack)
  => (y -> x) -> String -> [TestMethod x] -> [Example (y, (Text, J.Value))] -> Spec
serializeSpecManualImpl toVal' name testMethods suites =
  forM_ @[_] testMethods $ \(TestMethod mname method) ->
    describe mname $
      describe name $ forM_ suites $ \(Example exampleName (x, (hex, json))) ->
        it exampleName $ method (toVal' x) hex json

deserializeNegSpecImpl
  :: forall t. (FromExpression t, Show t)
  => (ByteString -> Either UnpackError t)
  -> String
  -> Text
  -> J.Value
  -> Spec
deserializeNegSpecImpl doUnpack name encodedHex json =
  describe name $ do
    it "From binary" $
      let encoded = unsafeFromHex (stripOptional0x encodedHex)
      in doUnpack encoded `shouldSatisfy` isLeft
    it "From json" $
      case J.eitherDecode' @Expression (J.encode json) of
        Left _ -> pass
        Right expr -> do
          fromExpression @t expr `shouldSatisfy` isLeft