-- | This modules provides primitives to run tests over mockchain executions and
-- to provide requirements on the the number and results of these runs.
module Cooked.MockChain.Testing where

import Control.Exception qualified as E
import Control.Monad
import Cooked.InitialDistribution
import Cooked.MockChain.BlockChain
import Cooked.MockChain.Direct
import Cooked.MockChain.Staged
import Cooked.MockChain.UtxoState
import Cooked.Pretty
import Cooked.Wallet
import Data.Default
import Data.List (isInfixOf)
import Data.Set qualified as Set
import Data.Text qualified as T
import Ledger qualified
import PlutusLedgerApi.V1.Value qualified as Api
import Test.QuickCheck qualified as QC
import Test.Tasty qualified as HU
import Test.Tasty.HUnit qualified as HU
import Test.Tasty.QuickCheck qualified as QC

-- * Common interface between HUnit and QuickCheck

-- | 'IsProp' is a common interface for HUnit and QuickCheck tests. It abstracts
-- uses of 'HU.Assertion' and 'QC.Property' for @(IsProp prop) => prop@, then
-- provide instances for both @HU.Asserton@ and @QC.Property@.
class IsProp prop where
  -- | Displays the string to the user in case of failure
  testCounterexample :: String -> prop -> prop

  -- | Conjunction of a number of results
  testConjoin :: [prop] -> prop

  -- | Disjunction of a number of results
  testDisjoin :: [prop] -> prop

  -- | Flags a failure
  testFailure :: prop
  testFailure = [prop] -> prop
forall prop. IsProp prop => [prop] -> prop
testDisjoin []

  -- | Flags a success
  testSuccess :: prop
  testSuccess = [prop] -> prop
forall prop. IsProp prop => [prop] -> prop
testConjoin []

  -- | Flags a failure with a message
  testFailureMsg :: String -> prop
  testFailureMsg String
msg = String -> prop -> prop
forall prop. IsProp prop => String -> prop -> prop
testCounterexample String
msg prop
forall prop. IsProp prop => prop
testFailure

-- | Turns a boolean into a @prop@
testBool :: (IsProp prop) => Bool -> prop
testBool :: forall prop. IsProp prop => Bool -> prop
testBool Bool
True = prop
forall prop. IsProp prop => prop
testSuccess
testBool Bool
False = prop
forall prop. IsProp prop => prop
testFailure

-- | Ensures all elements of a list satisfy a given @prop@
testAll :: (IsProp prop) => (a -> prop) -> [a] -> prop
testAll :: forall prop a. IsProp prop => (a -> prop) -> [a] -> prop
testAll a -> prop
f = [prop] -> prop
forall prop. IsProp prop => [prop] -> prop
testConjoin ([prop] -> prop) -> ([a] -> [prop]) -> [a] -> prop
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> prop) -> [a] -> [prop]
forall a b. (a -> b) -> [a] -> [b]
map a -> prop
f

-- | Ensures at least one element of a list satisfy a given @prop@
testAny :: (IsProp prop) => (a -> prop) -> [a] -> prop
testAny :: forall prop a. IsProp prop => (a -> prop) -> [a] -> prop
testAny a -> prop
f = [prop] -> prop
forall prop. IsProp prop => [prop] -> prop
testDisjoin ([prop] -> prop) -> ([a] -> [prop]) -> [a] -> prop
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> prop) -> [a] -> [prop]
forall a b. (a -> b) -> [a] -> [b]
map a -> prop
f

infix 4 .==.

-- | Lifts an equality test to a @prop@
(.==.) :: (IsProp prop, Eq a) => a -> a -> prop
a
a .==. :: forall prop a. (IsProp prop, Eq a) => a -> a -> prop
.==. a
b = Bool -> prop
forall prop. IsProp prop => Bool -> prop
testBool (Bool -> prop) -> Bool -> prop
forall a b. (a -> b) -> a -> b
$ a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
b

infixr 3 .&&.

-- | Conjunction of two @prop@s
(.&&.) :: (IsProp prop) => prop -> prop -> prop
prop
a .&&. :: forall prop. IsProp prop => prop -> prop -> prop
.&&. prop
b = [prop] -> prop
forall prop. IsProp prop => [prop] -> prop
testConjoin [prop
a, prop
b]

infixr 2 .||.

-- | Disjunction of two @prop@s
(.||.) :: (IsProp prop) => prop -> prop -> prop
prop
a .||. :: forall prop. IsProp prop => prop -> prop -> prop
.||. prop
b = [prop] -> prop
forall prop. IsProp prop => [prop] -> prop
testDisjoin [prop
a, prop
b]

-- | Catches a HUnit test failure, if the test fails.
assertionToMaybe :: HU.Assertion -> IO (Maybe HU.HUnitFailure)
assertionToMaybe :: Assertion -> IO (Maybe HUnitFailure)
assertionToMaybe = (IO (Maybe HUnitFailure)
 -> [Handler (Maybe HUnitFailure)] -> IO (Maybe HUnitFailure))
-> [Handler (Maybe HUnitFailure)]
-> IO (Maybe HUnitFailure)
-> IO (Maybe HUnitFailure)
forall a b c. (a -> b -> c) -> b -> a -> c
flip IO (Maybe HUnitFailure)
-> [Handler (Maybe HUnitFailure)] -> IO (Maybe HUnitFailure)
forall a. IO a -> [Handler a] -> IO a
E.catches [(HUnitFailure -> IO (Maybe HUnitFailure))
-> Handler (Maybe HUnitFailure)
forall a e. Exception e => (e -> IO a) -> Handler a
E.Handler ((HUnitFailure -> IO (Maybe HUnitFailure))
 -> Handler (Maybe HUnitFailure))
-> (HUnitFailure -> IO (Maybe HUnitFailure))
-> Handler (Maybe HUnitFailure)
forall a b. (a -> b) -> a -> b
$ Maybe HUnitFailure -> IO (Maybe HUnitFailure)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe HUnitFailure -> IO (Maybe HUnitFailure))
-> (HUnitFailure -> Maybe HUnitFailure)
-> HUnitFailure
-> IO (Maybe HUnitFailure)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HUnitFailure -> Maybe HUnitFailure
forall a. a -> Maybe a
Just] (IO (Maybe HUnitFailure) -> IO (Maybe HUnitFailure))
-> (Assertion -> IO (Maybe HUnitFailure))
-> Assertion
-> IO (Maybe HUnitFailure)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Assertion -> IO (Maybe HUnitFailure) -> IO (Maybe HUnitFailure)
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Maybe HUnitFailure -> IO (Maybe HUnitFailure)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe HUnitFailure
forall a. Maybe a
Nothing)

-- | HUnit instance of 'IsProp'
instance IsProp HU.Assertion where
  testCounterexample :: String -> Assertion -> Assertion
testCounterexample String
msg = Assertion
-> (HUnitFailure -> Assertion) -> Maybe HUnitFailure -> Assertion
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Assertion
forall prop. IsProp prop => prop
testSuccess (HUnitFailure -> Assertion
forall a e. Exception e => e -> a
E.throw (HUnitFailure -> Assertion)
-> (HUnitFailure -> HUnitFailure) -> HUnitFailure -> Assertion
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HUnitFailure -> HUnitFailure
adjustMsg) (Maybe HUnitFailure -> Assertion)
-> (Assertion -> IO (Maybe HUnitFailure)) -> Assertion -> Assertion
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< Assertion -> IO (Maybe HUnitFailure)
assertionToMaybe
    where
      joinMsg :: String -> String
      joinMsg :: String -> String
joinMsg String
rest = String
msg String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
";\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
rest

      adjustMsg :: HU.HUnitFailure -> HU.HUnitFailure
      adjustMsg :: HUnitFailure -> HUnitFailure
adjustMsg (HU.HUnitFailure Maybe SrcLoc
loc String
txt) =
        Maybe SrcLoc -> String -> HUnitFailure
HU.HUnitFailure Maybe SrcLoc
loc (String -> String
joinMsg String
txt)

  testFailure :: Assertion
testFailure = String -> Assertion
forall a. HasCallStack => String -> IO a
HU.assertFailure String
""
  testFailureMsg :: String -> Assertion
testFailureMsg = String -> Assertion
forall a. HasCallStack => String -> IO a
HU.assertFailure

  testConjoin :: [Assertion] -> Assertion
testConjoin = [Assertion] -> Assertion
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_

  testDisjoin :: [Assertion] -> Assertion
testDisjoin [] = Assertion
forall prop. IsProp prop => prop
testFailure
  testDisjoin (Assertion
x : [Assertion]
xs) = Assertion -> IO (Maybe HUnitFailure)
assertionToMaybe Assertion
x IO (Maybe HUnitFailure)
-> (Maybe HUnitFailure -> Assertion) -> Assertion
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Assertion
-> (HUnitFailure -> Assertion) -> Maybe HUnitFailure -> Assertion
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ([Assertion] -> Assertion
forall prop. IsProp prop => [prop] -> prop
testDisjoin [Assertion]
xs) HUnitFailure -> Assertion
forall a e. Exception e => e -> a
E.throw

  testSuccess :: Assertion
testSuccess = () -> Assertion
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- | QuickCheck instance of 'IsProp'
instance IsProp QC.Property where
  testCounterexample :: String -> Property -> Property
testCounterexample = String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
QC.counterexample
  testFailure :: Property
testFailure = Bool -> Property
forall prop. Testable prop => prop -> Property
QC.property Bool
False
  testSuccess :: Property
testSuccess = Bool -> Property
forall prop. Testable prop => prop -> Property
QC.property Bool
True
  testConjoin :: [Property] -> Property
testConjoin = [Property] -> Property
forall prop. Testable prop => [prop] -> Property
QC.conjoin
  testDisjoin :: [Property] -> Property
testDisjoin = [Property] -> Property
forall prop. Testable prop => [prop] -> Property
QC.disjoin

-- | Here we provide our own universsal quantifier instead of 'QC.forAll', so we
--  can monomorphize it to returning a 'QC.Property'
forAll :: (Show a) => QC.Gen a -> (a -> QC.Property) -> QC.Property
forAll :: forall a. Show a => Gen a -> (a -> Property) -> Property
forAll = Gen a -> (a -> Property) -> Property
forall a prop.
(Show a, Testable prop) =>
Gen a -> (a -> prop) -> Property
QC.forAll

-- * Extra HUnit assertions

-- | Asserts whether a set is a subset of another one, both given as lists.
assertSubset :: (Show a, Eq a) => [a] -> [a] -> HU.Assertion
assertSubset :: forall a. (Show a, Eq a) => [a] -> [a] -> Assertion
assertSubset [a]
l [a]
r =
  [Assertion] -> Assertion
forall prop. IsProp prop => [prop] -> prop
testConjoin
    ( (a -> Assertion) -> [a] -> [Assertion]
forall a b. (a -> b) -> [a] -> [b]
map
        ( \a
x ->
            HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
HU.assertBool
              ( String
"not a subset:\n\n"
                  String -> String -> String
forall a. [a] -> [a] -> [a]
++ a -> String
forall a. Show a => a -> String
show a
x
                  String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n\nis not an element of\n\n"
                  String -> String -> String
forall a. [a] -> [a] -> [a]
++ [a] -> String
forall a. Show a => a -> String
show [a]
r
              )
              (Bool -> Assertion) -> Bool -> Assertion
forall a b. (a -> b) -> a -> b
$ a
x a -> [a] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [a]
r
        )
        [a]
l
    )

-- | Asserts whether 2 sets are equal, both given as lists.
assertSameSets :: (Show a, Eq a) => [a] -> [a] -> HU.Assertion
assertSameSets :: forall a. (Show a, Eq a) => [a] -> [a] -> Assertion
assertSameSets [a]
l [a]
r =
  HasCallStack => String -> Bool -> Assertion
String -> Bool -> Assertion
HU.assertBool
    (String
"expected lists of the same length, got " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show ([a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
l) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" and " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show ([a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
r))
    ([a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
l Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== [a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
r)
    Assertion -> Assertion -> Assertion
forall prop. IsProp prop => prop -> prop -> prop
.&&. [a] -> [a] -> Assertion
forall a. (Show a, Eq a) => [a] -> [a] -> Assertion
assertSubset [a]
l [a]
r
    Assertion -> Assertion -> Assertion
forall prop. IsProp prop => prop -> prop -> prop
.&&. [a] -> [a] -> Assertion
forall a. (Show a, Eq a) => [a] -> [a] -> Assertion
assertSubset [a]
r [a]
l

-- * Data structure to test mockchain traces

{--
  Note on properties over the journal (or list of 'MockChainLogEntry'): our
  'Test' structure does not directly embed a predicate over the journal. Instead
  it is embedded in both the failure and success prediates. The reason is
  simple: the journal is generated and accessible in both cases and thus it is
  theoretically possible to define predicates that combine requirements over the
  journal and the error in case of failure, and the journal and the returning
  state and value in the case of success. If the journal predicate was a field
  in itself, this link would be broken and it would not be possible to epxress
  complex requirements that involve both the journal and other components of the
  returned elements in the mockchain run. Granted, this use cas is extremely
  rare, but it does not mean our API should not reflect this capability.
  However, we also provide 'JournalProp' as in most cases on predicating over
  the journal itself will be sufficient.
--}

-- | Type of properties over failures
type FailureProp prop = PrettyCookedOpts -> [MockChainLogEntry] -> MockChainError -> UtxoState -> prop

-- | Type of properties over successes
type SuccessProp a prop = PrettyCookedOpts -> [MockChainLogEntry] -> a -> UtxoState -> prop

-- | Type of properties over the number of run outcomes. This does not
-- necessitate a 'PrettyCookedOpts' as parameter as an 'Integer' does not
-- contain anything significant that can be pretty printed.
type SizeProp prop = Integer -> prop

-- | Type of properties over the mockchain journal
type JournalProp prop = PrettyCookedOpts -> [MockChainLogEntry] -> prop

-- | Type of properties over the 'UtxoState'
type StateProp prop = PrettyCookedOpts -> UtxoState -> prop

-- | Data structure to test a mockchain trace. @a@ is the return typed of the
-- tested trace, @prop@ is the domain in which the properties live. This is not
-- enforced here, but it will often be assumed that @prop@ satisfies 'IsProp'.
data Test a prop = Test
  { -- | The mockchain trace to test, which returns a result of type a
    forall a prop. Test a prop -> StagedMockChain a
testTrace :: StagedMockChain a,
    -- | The initial distribution from which the trace should be run
    forall a prop. Test a prop -> InitialDistribution
testInitDist :: InitialDistribution,
    -- | The requirement on the number of results, as 'StagedMockChain' is a
    -- 'Control.Monad.MonadPlus'
    forall a prop. Test a prop -> SizeProp prop
testSizeProp :: SizeProp prop,
    -- | The property that should hold in case of failure over the resulting
    -- error and the logs emitted during the run
    forall a prop. Test a prop -> FailureProp prop
testFailureProp :: FailureProp prop,
    -- | The property that should hold in case of success over the returned
    -- result and the final state of the trace, as well as the logs
    forall a prop. Test a prop -> SuccessProp a prop
testSuccessProp :: SuccessProp a prop,
    -- | The printing option that should be use to render test results
    forall a prop. Test a prop -> PrettyCookedOpts
testPrettyOpts :: PrettyCookedOpts
  }

-- | This takes a 'Test' and transforms it into an actual test case in
-- prop. This is the main function justifying the existence of 'Test'. This runs
-- the traces, ensures there is the right number of outcomes and, depending on
-- the nature of these outcomes, either calls 'testFailureProp' or
-- 'testSuccessProp'. It also uses the aliases emitted during the mockchain run
-- to pretty print messages when applicable.
testToProp :: (IsProp prop, Show a) => Test a prop -> prop
testToProp :: forall prop a. (IsProp prop, Show a) => Test a prop -> prop
testToProp Test {StagedMockChain a
PrettyCookedOpts
InitialDistribution
SizeProp prop
SuccessProp a prop
FailureProp prop
testTrace :: forall a prop. Test a prop -> StagedMockChain a
testInitDist :: forall a prop. Test a prop -> InitialDistribution
testSizeProp :: forall a prop. Test a prop -> SizeProp prop
testFailureProp :: forall a prop. Test a prop -> FailureProp prop
testSuccessProp :: forall a prop. Test a prop -> SuccessProp a prop
testPrettyOpts :: forall a prop. Test a prop -> PrettyCookedOpts
testTrace :: StagedMockChain a
testInitDist :: InitialDistribution
testSizeProp :: SizeProp prop
testFailureProp :: FailureProp prop
testSuccessProp :: SuccessProp a prop
testPrettyOpts :: PrettyCookedOpts
..} =
  let results :: [MockChainReturn a]
results = (forall (m :: * -> *).
 Monad m =>
 MockChainT m a -> m (MockChainReturn a))
-> StagedMockChain a -> [MockChainReturn a]
forall a res.
(forall (m :: * -> *). Monad m => MockChainT m a -> m res)
-> StagedMockChain a -> [res]
interpretAndRunWith (InitialDistribution -> MockChainT m a -> m (MockChainReturn a)
forall (m :: * -> *) a.
Monad m =>
InitialDistribution -> MockChainT m a -> m (MockChainReturn a)
runMockChainTFrom InitialDistribution
testInitDist) StagedMockChain a
testTrace
   in SizeProp prop
testSizeProp (Int -> Integer
forall a. Integral a => a -> Integer
toInteger ([MockChainReturn a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [MockChainReturn a]
results))
        prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. (MockChainReturn a -> prop) -> [MockChainReturn a] -> prop
forall prop a. IsProp prop => (a -> prop) -> [a] -> prop
testAll
          ( \ret :: MockChainReturn a
ret@(MockChainReturn Either MockChainError a
outcome Map TxOutRef (TxSkelOut, Bool)
_ UtxoState
state [MockChainLogEntry]
mcLog Map BuiltinByteString String
names) ->
              let pcOpts :: PrettyCookedOpts
pcOpts = Map BuiltinByteString String
-> PrettyCookedOpts -> PrettyCookedOpts
addHashNames Map BuiltinByteString String
names PrettyCookedOpts
testPrettyOpts
               in String -> prop -> prop
forall prop. IsProp prop => String -> prop -> prop
testCounterexample
                    ((MockChainReturn a -> DocCooked) -> MockChainReturn a -> String
forall a. (a -> DocCooked) -> a -> String
renderString (PrettyCookedOpts -> MockChainReturn a -> DocCooked
forall a. PrettyCooked a => PrettyCookedOpts -> a -> DocCooked
prettyCookedOpt PrettyCookedOpts
pcOpts) MockChainReturn a
ret)
                    (prop -> prop) -> prop -> prop
forall a b. (a -> b) -> a -> b
$ case Either MockChainError a
outcome of
                      Left MockChainError
err -> FailureProp prop
testFailureProp PrettyCookedOpts
pcOpts [MockChainLogEntry]
mcLog MockChainError
err UtxoState
state
                      Right a
result -> SuccessProp a prop
testSuccessProp PrettyCookedOpts
pcOpts [MockChainLogEntry]
mcLog a
result UtxoState
state
          )
          [MockChainReturn a]
results

-- | A convenience helper when using 'HU.Assertion' which allows to replace
-- 'HU.testCase' with 'testCooked' and thus avoid the use of 'testToProp'.
-- Sadly we cannot generalise it with type classes on @prop@ to work for
-- QuichCheck at GHC will never be able to instantiate @prop@.
testCooked :: (Show a) => String -> Test a HU.Assertion -> HU.TestTree
testCooked :: forall a. Show a => String -> Test a Assertion -> TestTree
testCooked String
name = String -> Assertion -> TestTree
HU.testCase String
name (Assertion -> TestTree)
-> (Test a Assertion -> Assertion) -> Test a Assertion -> TestTree
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Test a Assertion -> Assertion
forall prop a. (IsProp prop, Show a) => Test a prop -> prop
testToProp

-- | Same as 'testCooked', but for 'QC.Property'
testCookedQC :: (Show a) => String -> Test a QC.Property -> HU.TestTree
testCookedQC :: forall a. Show a => String -> Test a Property -> TestTree
testCookedQC String
name = String -> Property -> TestTree
forall a. Testable a => String -> a -> TestTree
QC.testProperty String
name (Property -> TestTree)
-> (Test a Property -> Property) -> Test a Property -> TestTree
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Test a Property -> Property
forall prop a. (IsProp prop, Show a) => Test a prop -> prop
testToProp

-- * Simple test templates

-- | A test template which expects a success from a trace
mustSucceedTest :: (IsProp prop) => StagedMockChain a -> Test a prop
mustSucceedTest :: forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustSucceedTest StagedMockChain a
trace =
  Test
    { testTrace :: StagedMockChain a
testTrace = StagedMockChain a
trace,
      testInitDist :: InitialDistribution
testInitDist = InitialDistribution
forall a. Default a => a
def,
      testSizeProp :: SizeProp prop
testSizeProp = prop -> SizeProp prop
forall a b. a -> b -> a
const prop
forall prop. IsProp prop => prop
testSuccess,
      testFailureProp :: FailureProp prop
testFailureProp = \PrettyCookedOpts
_ [MockChainLogEntry]
_ MockChainError
_ UtxoState
_ -> String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg String
"💀 Unexpected failure!",
      testSuccessProp :: SuccessProp a prop
testSuccessProp = \PrettyCookedOpts
_ [MockChainLogEntry]
_ a
_ UtxoState
_ -> prop
forall prop. IsProp prop => prop
testSuccess,
      testPrettyOpts :: PrettyCookedOpts
testPrettyOpts = PrettyCookedOpts
forall a. Default a => a
def
    }

-- | A test template which expects a failure from a trace
mustFailTest :: (IsProp prop) => StagedMockChain a -> Test a prop
mustFailTest :: forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailTest StagedMockChain a
trace =
  Test
    { testTrace :: StagedMockChain a
testTrace = StagedMockChain a
trace,
      testInitDist :: InitialDistribution
testInitDist = InitialDistribution
forall a. Default a => a
def,
      testSizeProp :: SizeProp prop
testSizeProp = prop -> SizeProp prop
forall a b. a -> b -> a
const prop
forall prop. IsProp prop => prop
testSuccess,
      testFailureProp :: FailureProp prop
testFailureProp = \PrettyCookedOpts
_ [MockChainLogEntry]
_ MockChainError
_ UtxoState
_ -> prop
forall prop. IsProp prop => prop
testSuccess,
      testSuccessProp :: SuccessProp a prop
testSuccessProp = \PrettyCookedOpts
_ [MockChainLogEntry]
_ a
_ UtxoState
_ -> String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg String
"💀 Unexpected success!",
      testPrettyOpts :: PrettyCookedOpts
testPrettyOpts = PrettyCookedOpts
forall a. Default a => a
def
    }

-- * Appending elements (in particular requirements) to existing tests

-- | Gives an initial distribution from which the trace will be run
withInitDist :: Test a prop -> InitialDistribution -> Test a prop
withInitDist :: forall a prop. Test a prop -> InitialDistribution -> Test a prop
withInitDist Test a prop
test InitialDistribution
initDist = Test a prop
test {testInitDist = initDist}

-- | Gives some pretty options to render test messages
withPrettyOpts :: Test a prop -> PrettyCookedOpts -> Test a prop
withPrettyOpts :: forall a prop. Test a prop -> PrettyCookedOpts -> Test a prop
withPrettyOpts Test a prop
test PrettyCookedOpts
opts = Test a prop
test {testPrettyOpts = opts}

-- | Appends a requirements over the emitted log, which will need to be satisfied
-- both in case of success or failure of the run.
withJournalProp :: (IsProp prop) => Test a prop -> JournalProp prop -> Test a prop
withJournalProp :: forall prop a.
IsProp prop =>
Test a prop -> JournalProp prop -> Test a prop
withJournalProp Test a prop
test JournalProp prop
journalProp =
  Test a prop
test
    { testFailureProp = \PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state -> Test a prop
-> PrettyCookedOpts
-> [MockChainLogEntry]
-> MockChainError
-> UtxoState
-> prop
forall a prop. Test a prop -> FailureProp prop
testFailureProp Test a prop
test PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. JournalProp prop
journalProp PrettyCookedOpts
opts [MockChainLogEntry]
journal,
      testSuccessProp = \PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state -> Test a prop
-> PrettyCookedOpts
-> [MockChainLogEntry]
-> a
-> UtxoState
-> prop
forall a prop. Test a prop -> SuccessProp a prop
testSuccessProp Test a prop
test PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. JournalProp prop
journalProp PrettyCookedOpts
opts [MockChainLogEntry]
journal
    }

-- | Appends a requirements over the resulting 'UtxoState', which will need to
-- be satisfied both in case of success or failure of the run.
withStateProp :: (IsProp prop) => Test a prop -> StateProp prop -> Test a prop
withStateProp :: forall prop a.
IsProp prop =>
Test a prop -> StateProp prop -> Test a prop
withStateProp Test a prop
test StateProp prop
stateProp =
  Test a prop
test
    { testFailureProp = \PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state -> Test a prop
-> PrettyCookedOpts
-> [MockChainLogEntry]
-> MockChainError
-> UtxoState
-> prop
forall a prop. Test a prop -> FailureProp prop
testFailureProp Test a prop
test PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. StateProp prop
stateProp PrettyCookedOpts
opts UtxoState
state,
      testSuccessProp = \PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state -> Test a prop
-> PrettyCookedOpts
-> [MockChainLogEntry]
-> a
-> UtxoState
-> prop
forall a prop. Test a prop -> SuccessProp a prop
testSuccessProp Test a prop
test PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. StateProp prop
stateProp PrettyCookedOpts
opts UtxoState
state
    }

-- | Appends a requirement over the resulting value and state of the mockchain
-- run which will need to be satisfied if the run is successful
withSuccessProp :: (IsProp prop) => Test a prop -> SuccessProp a prop -> Test a prop
withSuccessProp :: forall prop a.
IsProp prop =>
Test a prop -> SuccessProp a prop -> Test a prop
withSuccessProp Test a prop
test SuccessProp a prop
successProp =
  Test a prop
test
    { testSuccessProp = \PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state -> Test a prop -> SuccessProp a prop
forall a prop. Test a prop -> SuccessProp a prop
testSuccessProp Test a prop
test PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. SuccessProp a prop
successProp PrettyCookedOpts
opts [MockChainLogEntry]
journal a
val UtxoState
state
    }

-- | Same as 'withSuccessProp' but only considers the returning value of the run
withResultProp :: (IsProp prop) => Test a prop -> (a -> prop) -> Test a prop
withResultProp :: forall prop a.
IsProp prop =>
Test a prop -> (a -> prop) -> Test a prop
withResultProp Test a prop
test a -> prop
p = Test a prop -> SuccessProp a prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> SuccessProp a prop -> Test a prop
withSuccessProp Test a prop
test (\PrettyCookedOpts
_ [MockChainLogEntry]
_ a
res UtxoState
_ -> a -> prop
p a
res)

-- | Appends a requirement over the resulting number of outcomes of the run
withSizeProp :: (IsProp prop) => Test a prop -> SizeProp prop -> Test a prop
withSizeProp :: forall prop a.
IsProp prop =>
Test a prop -> SizeProp prop -> Test a prop
withSizeProp Test a prop
test SizeProp prop
reqSize =
  Test a prop
test
    { testSizeProp = \Integer
size -> Test a prop -> SizeProp prop
forall a prop. Test a prop -> SizeProp prop
testSizeProp Test a prop
test Integer
size prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. SizeProp prop
reqSize Integer
size
    }

-- | Appends a requirement over the resulting value and state of the mockchain
-- run which will need to be satisfied if the run is successful
withFailureProp :: (IsProp prop) => Test a prop -> FailureProp prop -> Test a prop
withFailureProp :: forall prop a.
IsProp prop =>
Test a prop -> FailureProp prop -> Test a prop
withFailureProp Test a prop
test FailureProp prop
failureProp = Test a prop
test {testFailureProp = \PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state -> Test a prop -> FailureProp prop
forall a prop. Test a prop -> FailureProp prop
testFailureProp Test a prop
test PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state prop -> prop -> prop
forall prop. IsProp prop => prop -> prop -> prop
.&&. FailureProp prop
failureProp PrettyCookedOpts
opts [MockChainLogEntry]
journal MockChainError
err UtxoState
state}

-- | Same as 'withFailureProp' but only considers the returning error of the run
withErrorProp :: (IsProp prop) => Test a prop -> (MockChainError -> prop) -> Test a prop
withErrorProp :: forall prop a.
IsProp prop =>
Test a prop -> (MockChainError -> prop) -> Test a prop
withErrorProp Test a prop
test MockChainError -> prop
errorProp = Test a prop -> FailureProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> FailureProp prop -> Test a prop
withFailureProp Test a prop
test (\PrettyCookedOpts
_ [MockChainLogEntry]
_ MockChainError
err UtxoState
_ -> MockChainError -> prop
errorProp MockChainError
err)

-- * Specific properties around failures

-- | A property to ensure a phase 1 failure
isPhase1Failure :: (IsProp prop) => FailureProp prop
isPhase1Failure :: forall prop. IsProp prop => FailureProp prop
isPhase1Failure PrettyCookedOpts
_ [MockChainLogEntry]
_ (MCEValidationError ValidationPhase
Ledger.Phase1 ValidationError
_) UtxoState
_ = prop
forall prop. IsProp prop => prop
testSuccess
isPhase1Failure PrettyCookedOpts
pcOpts [MockChainLogEntry]
_ MockChainError
e UtxoState
_ = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Expected phase 1 evaluation failure, got: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ (MockChainError -> DocCooked) -> MockChainError -> String
forall a. (a -> DocCooked) -> a -> String
renderString (PrettyCookedOpts -> MockChainError -> DocCooked
forall a. PrettyCooked a => PrettyCookedOpts -> a -> DocCooked
prettyCookedOpt PrettyCookedOpts
pcOpts) MockChainError
e

-- | A property to ensure a phase 2 failure
isPhase2Failure :: (IsProp prop) => FailureProp prop
isPhase2Failure :: forall prop. IsProp prop => FailureProp prop
isPhase2Failure PrettyCookedOpts
_ [MockChainLogEntry]
_ (MCEValidationError ValidationPhase
Ledger.Phase2 ValidationError
_) UtxoState
_ = prop
forall prop. IsProp prop => prop
testSuccess
isPhase2Failure PrettyCookedOpts
pcOpts [MockChainLogEntry]
_ MockChainError
e UtxoState
_ = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Expected phase 2 evaluation failure, got: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ (MockChainError -> DocCooked) -> MockChainError -> String
forall a. (a -> DocCooked) -> a -> String
renderString (PrettyCookedOpts -> MockChainError -> DocCooked
forall a. PrettyCooked a => PrettyCookedOpts -> a -> DocCooked
prettyCookedOpt PrettyCookedOpts
pcOpts) MockChainError
e

-- | Same as 'isPhase1Failure' with an added predicate on the text error
isPhase1FailureWithMsg :: (IsProp prop) => String -> FailureProp prop
isPhase1FailureWithMsg :: forall prop. IsProp prop => String -> FailureProp prop
isPhase1FailureWithMsg String
s PrettyCookedOpts
_ [MockChainLogEntry]
_ (MCEValidationError ValidationPhase
Ledger.Phase1 (Ledger.CardanoLedgerValidationError Text
text)) UtxoState
_ | String
s String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` Text -> String
T.unpack Text
text = prop
forall prop. IsProp prop => prop
testSuccess
isPhase1FailureWithMsg String
_ PrettyCookedOpts
pcOpts [MockChainLogEntry]
_ MockChainError
e UtxoState
_ = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Expected phase 1 evaluation failure with constrained messages, got: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ (MockChainError -> DocCooked) -> MockChainError -> String
forall a. (a -> DocCooked) -> a -> String
renderString (PrettyCookedOpts -> MockChainError -> DocCooked
forall a. PrettyCooked a => PrettyCookedOpts -> a -> DocCooked
prettyCookedOpt PrettyCookedOpts
pcOpts) MockChainError
e

-- | Same as 'isPhase2Failure' with an added predicate over the text error
isPhase2FailureWithMsg :: (IsProp prop) => String -> FailureProp prop
isPhase2FailureWithMsg :: forall prop. IsProp prop => String -> FailureProp prop
isPhase2FailureWithMsg String
s PrettyCookedOpts
_ [MockChainLogEntry]
_ (MCEValidationError ValidationPhase
Ledger.Phase2 (Ledger.ScriptFailure (Ledger.EvaluationError [Text]
texts String
_))) UtxoState
_ | (Text -> Bool) -> [Text] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isInfixOf String
s (String -> Bool) -> (Text -> String) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack) [Text]
texts = prop
forall prop. IsProp prop => prop
testSuccess
isPhase2FailureWithMsg String
_ PrettyCookedOpts
pcOpts [MockChainLogEntry]
_ MockChainError
e UtxoState
_ = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Expected phase 2 evaluation failure with constrained messages, got: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ (MockChainError -> DocCooked) -> MockChainError -> String
forall a. (a -> DocCooked) -> a -> String
renderString (PrettyCookedOpts -> MockChainError -> DocCooked
forall a. PrettyCooked a => PrettyCookedOpts -> a -> DocCooked
prettyCookedOpt PrettyCookedOpts
pcOpts) MockChainError
e

-- * Specific properties around number of outcomes

-- | Ensures the run has an exact given number of outcomes
isOfSize :: (IsProp prop) => Integer -> SizeProp prop
isOfSize :: forall prop. IsProp prop => Integer -> SizeProp prop
isOfSize Integer
n1 Integer
n2 | Integer
n1 Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
n2 = prop
forall prop. IsProp prop => prop
testSuccess
isOfSize Integer
n1 Integer
n2 = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Incorrect number of results (expected: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
n1 String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
n2 String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
")"

-- | Ensures the run has a minimal number of outcomes
isAtLeastOfSize :: (IsProp prop) => Integer -> SizeProp prop
isAtLeastOfSize :: forall prop. IsProp prop => Integer -> SizeProp prop
isAtLeastOfSize Integer
n1 Integer
n2 | Integer
n1 Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
n2 = prop
forall prop. IsProp prop => prop
testSuccess
isAtLeastOfSize Integer
n1 Integer
n2 = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Incorrect number of results (expected at least: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
n1 String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
n2 String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
")"

-- | Ensures the run has a minimal number of outcomes
isAtMostOfSize :: (IsProp prop) => Integer -> SizeProp prop
isAtMostOfSize :: forall prop. IsProp prop => Integer -> SizeProp prop
isAtMostOfSize Integer
n1 Integer
n2 | Integer
n1 Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
n2 = prop
forall prop. IsProp prop => prop
testSuccess
isAtMostOfSize Integer
n1 Integer
n2 = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Incorrect number of results (expected at most: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
n1 String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" but got: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
n2 String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
")"

-- * Specific properties over the journal

-- | Ensures a certain event has been emitted. This uses the constructor's name
-- of the 'MockChainLogEntry' by relying on 'show' being lazy.
happened :: (IsProp prop) => String -> JournalProp prop
happened :: forall prop. IsProp prop => String -> JournalProp prop
happened String
eventName PrettyCookedOpts
_ [MockChainLogEntry]
journal
  | Set String
allEventNames <- [String] -> Set String
forall a. Ord a => [a] -> Set a
Set.fromList ([String] -> String
forall a. HasCallStack => [a] -> a
head ([String] -> String)
-> (MockChainLogEntry -> [String]) -> MockChainLogEntry -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String]
words (String -> [String])
-> (MockChainLogEntry -> String) -> MockChainLogEntry -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MockChainLogEntry -> String
forall a. Show a => a -> String
show (MockChainLogEntry -> String) -> [MockChainLogEntry] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [MockChainLogEntry]
journal) =
      if String
eventName String -> Set String -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set String
allEventNames
        then prop
forall prop. IsProp prop => prop
testSuccess
        else String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"The event " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
forall a. Show a => a -> String
show String
eventName String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" did not occur (but those did: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Set String -> String
forall a. Show a => a -> String
show Set String
allEventNames String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
")"

-- | Ensures a certain event has not been emitted. This uses the constructor's
-- name of the 'MockChainLogEntry' by relying on 'show' being lazy.
didNotHappen :: (IsProp prop) => String -> JournalProp prop
didNotHappen :: forall prop. IsProp prop => String -> JournalProp prop
didNotHappen String
eventName PrettyCookedOpts
_ [MockChainLogEntry]
journal | Bool -> Bool
not (String
eventName String -> Set String -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` [String] -> Set String
forall a. Ord a => [a] -> Set a
Set.fromList ([String] -> String
forall a. HasCallStack => [a] -> a
head ([String] -> String)
-> (MockChainLogEntry -> [String]) -> MockChainLogEntry -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String]
words (String -> [String])
-> (MockChainLogEntry -> String) -> MockChainLogEntry -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MockChainLogEntry -> String
forall a. Show a => a -> String
show (MockChainLogEntry -> String) -> [MockChainLogEntry] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [MockChainLogEntry]
journal)) = prop
forall prop. IsProp prop => prop
testSuccess
didNotHappen String
eventName PrettyCookedOpts
_ [MockChainLogEntry]
_ = String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"The event " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String -> String
forall a. Show a => a -> String
show String
eventName String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" was forbidden but occurred nonetheless"

-- * Specific properties over successes

-- | Ensures that the given wallets satisfy certain amount requirements over a
-- list of given asset classes in the end of the run
isInWallets :: (IsProp prop) => [(Wallet, [(Api.AssetClass, Integer -> Bool)])] -> SuccessProp a prop
isInWallets :: forall prop a.
IsProp prop =>
[(Wallet, [(AssetClass, Integer -> Bool)])] -> SuccessProp a prop
isInWallets [(Wallet, [(AssetClass, Integer -> Bool)])]
walletsReqs PrettyCookedOpts
_ [MockChainLogEntry]
_ a
_ UtxoState
utxoState =
  ((Wallet, [(AssetClass, Integer -> Bool)]) -> prop)
-> [(Wallet, [(AssetClass, Integer -> Bool)])] -> prop
forall prop a. IsProp prop => (a -> prop) -> [a] -> prop
testAll
    ( \(Wallet
w, [(AssetClass, Integer -> Bool)]
assetsReqs) ->
        let ownedValue :: Value
ownedValue = Wallet -> UtxoState -> Value
forall a. ToAddress a => a -> UtxoState -> Value
holdsInState Wallet
w UtxoState
utxoState
         in ((AssetClass, Integer -> Bool) -> prop)
-> [(AssetClass, Integer -> Bool)] -> prop
forall prop a. IsProp prop => (a -> prop) -> [a] -> prop
testAll
              ( \(AssetClass
ac, Integer -> Bool
nbReq) ->
                  let amount :: Integer
amount = Value -> AssetClass -> Integer
Api.assetClassValueOf Value
ownedValue AssetClass
ac
                   in if Integer -> Bool
nbReq Integer
amount
                        then prop
forall prop. IsProp prop => prop
testSuccess
                        else String -> prop
forall prop. IsProp prop => String -> prop
testFailureMsg (String -> prop) -> String -> prop
forall a b. (a -> b) -> a -> b
$ String
"Unsatisfied quantity requirement for " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Wallet -> String
forall a. Show a => a -> String
show Wallet
w String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" over asset class " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> AssetClass -> String
forall a. Show a => a -> String
show AssetClass
ac
              )
              [(AssetClass, Integer -> Bool)]
assetsReqs
    )
    [(Wallet, [(AssetClass, Integer -> Bool)])]
walletsReqs

-- | Ensures that a given wallet possesses exactly a certain amount of a given
-- asset class in the end of the run
isInWallet :: (IsProp prop) => (Wallet, Api.AssetClass, Integer) -> SuccessProp a prop
isInWallet :: forall prop a.
IsProp prop =>
(Wallet, AssetClass, Integer) -> SuccessProp a prop
isInWallet (Wallet
w, AssetClass
ac, Integer
n) = [(Wallet, [(AssetClass, Integer -> Bool)])] -> SuccessProp a prop
forall prop a.
IsProp prop =>
[(Wallet, [(AssetClass, Integer -> Bool)])] -> SuccessProp a prop
isInWallets [(Wallet
w, [(AssetClass
ac, (Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
n))])]

-- * Advanced test templates

{--
  Note on advanced templates:

  The idea here is definely not to have a huge number of combinations of
  predicates and bundle them into templates. This has been attempted in the past
  and is never a good idea. However, there are a few more advanced template that
  make sense because they will occur a lot when testing smart contracts. Only
  those should appear in the following list. For all the other templates, the
  pattern @testCaseCookedXX $ template testName trace `withXXX` myPropicate@
  should be advocated.

--}

-- | A test template which expects a Phase 2 failure
mustFailInPhase2Test :: (IsProp prop) => StagedMockChain a -> Test a prop
mustFailInPhase2Test :: forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailInPhase2Test StagedMockChain a
run = StagedMockChain a -> Test a prop
forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailTest StagedMockChain a
run Test a prop -> FailureProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> FailureProp prop -> Test a prop
`withFailureProp` FailureProp prop
forall prop. IsProp prop => FailureProp prop
isPhase2Failure

-- | A test template which expects a specific phase 2 error message
mustFailInPhase2WithMsgTest :: (IsProp prop) => String -> StagedMockChain a -> Test a prop
mustFailInPhase2WithMsgTest :: forall prop a.
IsProp prop =>
String -> StagedMockChain a -> Test a prop
mustFailInPhase2WithMsgTest String
msg StagedMockChain a
run = StagedMockChain a -> Test a prop
forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailTest StagedMockChain a
run Test a prop -> FailureProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> FailureProp prop -> Test a prop
`withFailureProp` String -> FailureProp prop
forall prop. IsProp prop => String -> FailureProp prop
isPhase2FailureWithMsg String
msg

-- | A test template which expects a Phase 1 failure
mustFailInPhase1Test :: (IsProp prop) => StagedMockChain a -> Test a prop
mustFailInPhase1Test :: forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailInPhase1Test StagedMockChain a
run = StagedMockChain a -> Test a prop
forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailTest StagedMockChain a
run Test a prop -> FailureProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> FailureProp prop -> Test a prop
`withFailureProp` FailureProp prop
forall prop. IsProp prop => FailureProp prop
isPhase1Failure

-- | A test template which expects a specific phase 1 error message
mustFailInPhase1WithMsgTest :: (IsProp prop) => String -> StagedMockChain a -> Test a prop
mustFailInPhase1WithMsgTest :: forall prop a.
IsProp prop =>
String -> StagedMockChain a -> Test a prop
mustFailInPhase1WithMsgTest String
msg StagedMockChain a
run = StagedMockChain a -> Test a prop
forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailTest StagedMockChain a
run Test a prop -> FailureProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> FailureProp prop -> Test a prop
`withFailureProp` String -> FailureProp prop
forall prop. IsProp prop => String -> FailureProp prop
isPhase1FailureWithMsg String
msg

-- | A test template which expects a certain number of successful outcomes
mustSucceedWithSizeTest :: (IsProp prop) => Integer -> StagedMockChain a -> Test a prop
mustSucceedWithSizeTest :: forall prop a.
IsProp prop =>
Integer -> StagedMockChain a -> Test a prop
mustSucceedWithSizeTest Integer
size StagedMockChain a
run = StagedMockChain a -> Test a prop
forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustSucceedTest StagedMockChain a
run Test a prop -> SizeProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> SizeProp prop -> Test a prop
`withSizeProp` (Bool -> prop
forall prop. IsProp prop => Bool -> prop
testBool (Bool -> prop) -> (Integer -> Bool) -> SizeProp prop
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
size))

-- | A test template which expects a certain number of unsuccessful outcomes
mustFailWithSizeTest :: (IsProp prop) => Integer -> StagedMockChain a -> Test a prop
mustFailWithSizeTest :: forall prop a.
IsProp prop =>
Integer -> StagedMockChain a -> Test a prop
mustFailWithSizeTest Integer
size StagedMockChain a
run = StagedMockChain a -> Test a prop
forall prop a. IsProp prop => StagedMockChain a -> Test a prop
mustFailTest StagedMockChain a
run Test a prop -> SizeProp prop -> Test a prop
forall prop a.
IsProp prop =>
Test a prop -> SizeProp prop -> Test a prop
`withSizeProp` Integer -> SizeProp prop
forall prop. IsProp prop => Integer -> SizeProp prop
isOfSize Integer
size