packages feed

cleveland-0.1.0: test/TestSuite/Cleveland/Lorentz/Entrypoints.hs

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

-- To avoid explicitly depending on @containers@
{-# LANGUAGE OverloadedLists #-}

module TestSuite.Cleveland.Lorentz.Entrypoints
  ( test_ContractCoversEntrypoints
  , test_ContractMatchEntrypoints
  , test_ContractMatchEntrypointsT
  , test_ContractCoverEntrypointsT
  , expectFailure
  ) where

import qualified Lorentz as L

import Data.List (isInfixOf)
import Test.Tasty (testGroup)
import Test.Tasty.HUnit
import Test.Tasty.Providers (IsTest, run)
import Test.Tasty.Runners

import Lorentz.Annotation
import Lorentz.Constraints
import Lorentz.Entrypoints
import Lorentz.Run
import Lorentz.Value
import qualified Morley.Michelson.Untyped as U

import Test.Cleveland.Lorentz.Entrypoints

data MyEntrypoints1
  = Do1 Integer
  | Do2 (Integer, Integer)
  | Do3 MyEntrypoints2
  | Do4 MyParams
  deriving stock Generic
  deriving anyclass (IsoValue, HasAnnotation)

data MyEntrypoints2
  = Do10
  | Do11 Natural
  deriving stock Generic
  deriving anyclass (IsoValue, HasAnnotation)

data MyParams = MyParams
  { param1 :: ()
  , param2 :: ByteString
  }
  deriving stock Generic
  deriving anyclass (IsoValue, HasAnnotation)

instance ParameterHasEntrypoints MyEntrypoints1 where
  type ParameterEntrypointsDerivation MyEntrypoints1 = EpdRecursive

instance ParameterHasEntrypoints MyEntrypoints2 where
  type ParameterEntrypointsDerivation MyEntrypoints2 = EpdPlain

dummyContract :: NiceParameterFull param => Contract param () ()
dummyContract = L.defaultContract $
  L.drop L.# L.unit L.# L.nil L.# L.pair

epSpecPartial :: Map EpName U.Ty
epSpecPartial =
  [ ( U.UnsafeEpName "do1", U.Ty (U.TInt) def )
  , ( U.UnsafeEpName "do2"
    , U.Ty (U.TPair def def def def (U.Ty U.TInt def) (U.Ty U.TInt def)) def )
  , ( U.UnsafeEpName "do10", U.Ty (U.TUnit) def )
  , ( U.UnsafeEpName "do11", U.Ty (U.TNat) def )
  ]

epSpecFull :: Map EpName U.Ty
epSpecFull = epSpecPartial <>
  [ ( U.UnsafeEpName "do4"
    , U.Ty (
        U.TPair (U.unsafeMkAnnotation "param1")
                (U.unsafeMkAnnotation "param2")
                def
                def
                (U.Ty U.TUnit def)
                (U.Ty U.TBytes def)
        ) def )
  ]

extractTests :: TestTree -> (forall t. IsTest t => String -> t -> TestTree) -> TestTree
extractTests tree f = case tree of
  SingleTest name t -> f name t
  TestGroup name ts -> testGroup name $ map (`extractTests` f) ts
  _ -> error "unsupported"

expectFailure :: String -> TestTree -> TestTree
expectFailure str tree = AskOptions $ \opts ->
  extractTests tree $ \name test -> testCase name do
    result <- run opts test (const $ pure ())
    case result of
      Result{..} -> do
        assertBool "Result is failure" $ case resultOutcome of
          Failure TestFailed -> True
          _ -> False
        if str `isInfixOf` resultDescription
        then pass
        else assertFailure $ "Expected '" <> resultDescription <> "' to contain '" <> str <> "'"

test_ContractCoversEntrypoints :: [TestTree]
test_ContractCoversEntrypoints =
  [ testContractCoversEntrypoints
      "testContractCoversEntrypoints works as expected"
        (dummyContract @MyEntrypoints1) epSpecPartial
  , testContractCoversEntrypoints
      "testContractCoversEntrypoints fails on wrong type"
        (dummyContract @MyEntrypoints1) [ ( U.UnsafeEpName "do1", U.Ty (U.TNat) def ) ]
      & expectFailure "Expected: nat"
  , testContractCoversEntrypoints
      "testContractCoversEntrypoints fails on missing entrypoints"
        (dummyContract @MyEntrypoints1) [ ( U.UnsafeEpName "do123", U.Ty (U.TInt) def ) ]
      & expectFailure "Missing entrypoints in the contract: do123"
  ]

test_ContractMatchEntrypoints :: [TestTree]
test_ContractMatchEntrypoints =
  [ testContractMatchesEntrypoints
      "testContractMatchesEntrypoints works as expected" (dummyContract @MyEntrypoints1) epSpecFull
  , testContractMatchesEntrypoints
      "testContractMatchesEntrypoints fails on wrong type"
        (dummyContract @MyEntrypoints1) [ ( U.UnsafeEpName "do1", U.Ty (U.TNat) def ) ]
      & expectFailure "Expected: nat"
  , testContractMatchesEntrypoints
      "testContractMatchesEntrypoints fails on missing entrypoints"
        (dummyContract @MyEntrypoints1) [ ( U.UnsafeEpName "do123", U.Ty (U.TInt) def ) ]
      & expectFailure "Missing entrypoints in the contract: do123"
  , testContractMatchesEntrypoints
      "testContractMatchesEntrypoints fails on extraneous entrypoints"
        (dummyContract @MyEntrypoints1) epSpecPartial
      & expectFailure "Extraneous entrypoints in the contract: do4:\
                      \ pair (unit %param1) (bytes %param2)"
  ]

test_ContractMatchEntrypointsT :: [TestTree]
test_ContractMatchEntrypointsT =
  [ testContractMatchesEntrypointsT @MyEntrypoints1
      "testContractMatchesEntrypointsT works as expected" (dummyContract @MyEntrypoints1)
  , testContractMatchesEntrypointsT @MyEntrypoints2
      "testContractMatchesEntrypointsT fails on extra entrypoints" (dummyContract @MyEntrypoints1)
      & expectFailure "Extraneous entrypoints in the contract"
  ]

test_ContractCoverEntrypointsT :: [TestTree]
test_ContractCoverEntrypointsT =
  [ testContractCoversEntrypointsT @MyEntrypoints2
      "testContractCoversEntrypointsT works on partial spec" (dummyContract @MyEntrypoints1)
  , testContractCoversEntrypointsT @MyEntrypoints1
      "testContractCoversEntrypointsT works on full spec" (dummyContract @MyEntrypoints1)
  ]