moonlight-delta-0.1.0.0: test/epoch/TransportSpec.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
module TransportSpec
( transportTests,
)
where
import Data.IntMap.Strict qualified as IntMap
import Data.IntSet qualified as IntSet
import EpochSupport.Expected (viewProjection)
import EpochSupport.Generators
import EpochSupport.Reference
import EpochSupport.Types
import LawManifest (lawManifestCase, lawProperty)
import Moonlight.Core (IsLawName (..), OrdMap (..), OrdSet (..), SetKey, constructorLawName)
import Moonlight.Delta.Epoch
import Test.QuickCheck (Gen, Property, counterexample, forAll, (===), (.&&.))
import Test.Tasty (TestTree, testGroup)
data TransportLaw
= TransportPartitionsQuery
| TransportTargetsBelongToEndpoint
| TransportAgreesWithSequentialReference
| EpochViewTransportSourceVersionGuard
| EpochViewTransportDropsRetiredKeys
| EpochViewTransportTargetsValid
deriving stock (Bounded, Enum, Eq, Ord, Show)
instance IsLawName TransportLaw where
lawNameText = constructorLawName . show
transportTests :: TestTree
transportTests =
testGroup
"transport"
[ lawManifestCase "epoch transport" ([minBound .. maxBound] :: [TransportLaw]),
lawProperty TransportPartitionsQuery $
epochDeltaIntCaseProperty (transportPartitionsQuery intSetGen)
.&&. epochDeltaGenericCaseProperty (transportPartitionsQuery genericSetGen),
lawProperty TransportTargetsBelongToEndpoint $
epochDeltaIntCaseProperty (transportTargetsBelongToEndpoint intSetGen)
.&&. epochDeltaGenericCaseProperty (transportTargetsBelongToEndpoint genericSetGen),
lawProperty TransportAgreesWithSequentialReference $
forAll epochDeltaIntCaseGen transportAgreesWithReference,
lawProperty EpochViewTransportSourceVersionGuard viewTransportSourceVersionGuard,
lawProperty EpochViewTransportDropsRetiredKeys viewTransportDropsRetiredKeys,
lawProperty EpochViewTransportTargetsValid $
epochDeltaIntCaseProperty (viewTransportTargetsValid intSubsetOf)
.&&. epochDeltaGenericCaseProperty (viewTransportTargetsValid genericSubsetOf)
]
transportPartitionsQuery ::
(EpochKeyed keyMap observed, Eq observed, Show observed, Show keyMap, Show (SetKey observed)) =>
Gen observed ->
EpochDeltaCase keyMap observed ->
Property
transportPartitionsQuery queryGen epochCase =
forAll queryGen $ \queryKeys ->
let result = transportKeys deltaValue queryKeys
transportedDomain = fromListSet (fmap fst (toAscListMap (transportedKeys result)))
retired = transportRetiredKeys result
unknown = transportUnknownKeys result
in counterexample ("transport result: " <> show result) $
( unionsSet [transportedDomain, retired, unknown],
intersectionSet transportedDomain retired,
intersectionSet transportedDomain unknown,
intersectionSet retired unknown
)
=== (queryKeys, emptySet, emptySet, emptySet)
where
deltaValue = edcDelta epochCase
transportTargetsBelongToEndpoint ::
(EpochKeyed keyMap observed, Eq observed, Show observed, Show keyMap, Show (SetKey observed)) =>
Gen observed ->
EpochDeltaCase keyMap observed ->
Property
transportTargetsBelongToEndpoint queryGen epochCase =
forAll queryGen $ \queryKeys ->
let result = transportKeys deltaValue queryKeys
targetImages = fromListSet (fmap snd (toAscListMap (transportedKeys result)))
in intersectionSet targetImages (targetKeys deltaValue) === targetImages
where
deltaValue = edcDelta epochCase
transportAgreesWithReference :: EpochDeltaCase (IntMap.IntMap Int) IntSet.IntSet -> Property
transportAgreesWithReference epochCase =
forAll intSetGen $ \queryKeys ->
transportKeys deltaValue queryKeys
=== referenceTransportKeys (referenceFromInput (edcInput epochCase)) queryKeys
where
deltaValue = edcDelta epochCase
viewTransportSourceVersionGuard :: Property
viewTransportSourceVersionGuard =
case epochDelta source target IntMap.empty IntSet.empty IntSet.empty of
Left err ->
counterexample ("valid view fixture rejected: " <> show err) False
Right deltaValue ->
transportView deltaValue (viewAt (versionFromKey 0) (IntSet.singleton 1) ())
=== Left (ViewSourceVersionMismatch (versionFromKey 1) (versionFromKey 0))
where
source = Endpoint (versionFromKey 1) (IntSet.singleton 1)
target = Endpoint (versionFromKey 2) (IntSet.singleton 1)
viewTransportDropsRetiredKeys :: Property
viewTransportDropsRetiredKeys =
case epochDelta source target IntMap.empty (IntSet.singleton 1) IntSet.empty of
Left err ->
counterexample ("valid retirement fixture rejected: " <> show err) False
Right deltaValue ->
fmap viewProjection (transportView deltaValue (viewAt (versionFromKey 1) (IntSet.singleton 1) ()))
=== Right (versionFromKey 2, IntSet.empty, ())
where
source = Endpoint (versionFromKey 1) (IntSet.singleton 1)
target = Endpoint (versionFromKey 2) IntSet.empty
viewTransportTargetsValid ::
(EpochKeyed keyMap observed, Eq observed, Show observed, Show keyMap, Show (SetKey observed)) =>
(observed -> Gen observed) ->
EpochDeltaCase keyMap observed ->
Property
viewTransportTargetsValid subsetGen epochCase =
forAll (subsetGen (sourceKeys deltaValue)) $ \observedKeys ->
case transportView deltaValue (viewAt (sourceVersion deltaValue) observedKeys ()) of
Left err ->
counterexample ("source-contained view transport failed: " <> show err) False
Right transportedView ->
intersectionSet (cvObservedKeys transportedView) (targetKeys deltaValue)
=== cvObservedKeys transportedView
where
deltaValue = edcDelta epochCase