eventsourcing-0.9.0: test/Database/CQRS/TransformerTest.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
module Database.CQRS.TransformerTest
( tests
) where
import Control.DeepSeq (NFData)
import Control.Monad (forM_, unless, void, when)
import Control.Monad.Trans (liftIO)
import Data.Maybe (maybeToList)
import GHC.Generics (Generic)
import Hedgehog hiding (collect)
import Test.Tasty
import Test.Tasty.Hedgehog
import qualified Control.Monad.Except as Exc
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Helpers (collect, interleaveSeqs)
import qualified Database.CQRS as CQRS
import qualified Database.CQRS.InMemory as CQRS.InMem
tests :: TestTree
tests = testGroup "Transformer"
[ testProperty "Events are dropped and merged accordingly" $
property eventsDroppedAndMerged
]
data TestEvent
= CreatedEvent String
| RenamedEvent String
| SuffixAddedEvent String -- We want to get rid of this one.
| DeletedEvent
deriving (Eq, Show, Generic)
deriving anyclass (NFData)
testEventGen :: Gen TestEvent
testEventGen = do
name <- Gen.string (Range.linear 0 10) Gen.alphaNum
Gen.element
[ RenamedEvent name
, SuffixAddedEvent name
]
testEventSequenceGen :: Gen [TestEvent]
testEventSequenceGen = do
events <- Gen.list (Range.linear 1 1000) testEventGen
name <- Gen.string (Range.linear 0 10) Gen.alphaNum
deletedEvent <- Gen.element [Nothing, Just DeletedEvent]
pure $ CreatedEvent name : events ++ maybeToList deletedEvent
isCreatedEvent :: CQRS.EventWithContext Integer () TestEvent -> Bool
isCreatedEvent = \case
CQRS.EventWithContext _ _ (CreatedEvent _) -> True
_ -> False
isRenamedEvent :: CQRS.EventWithContext Integer () TestEvent -> Bool
isRenamedEvent = \case
CQRS.EventWithContext _ _ (RenamedEvent _) -> True
_ -> False
isDeletedEvent :: CQRS.EventWithContext Integer () TestEvent -> Bool
isDeletedEvent = \case
CQRS.EventWithContext _ _ DeletedEvent -> True
_ -> False
transformer
:: CQRS.Transformer
(Either (Integer, String) (CQRS.EventWithContext Integer () TestEvent))
Integer (CQRS.EventWithContext Integer () TestEvent)
transformer = \case
Left (i, err) -> CQRS.failTransformer i err
Right ewc@(CQRS.EventWithContext _ () event) ->
case event of
CreatedEvent _ -> CQRS.pushEvent ewc
RenamedEvent name -> do
changed <- CQRS.mergeEvents $ \events ->
case break isCreatedEvent events of
(levs, CQRS.EventWithContext eventId _ (CreatedEvent _) : revs) ->
let ewc' = CQRS.EventWithContext eventId () (CreatedEvent name)
in (True, levs ++ ewc' : revs)
_ -> (False, events)
unless changed $ CQRS.pushEvent ewc
SuffixAddedEvent suffix -> do
changed <- CQRS.mergeEvents $ \events ->
case break (\e -> isCreatedEvent e || isRenamedEvent e) events of
(levs, CQRS.EventWithContext eventId _ (CreatedEvent n) : revs) ->
let ewc' =
CQRS.EventWithContext eventId ()
(CreatedEvent (n ++ suffix))
in (True, levs ++ ewc' : revs)
(levs, CQRS.EventWithContext eventId _ (RenamedEvent n) : revs) ->
let ewc' =
CQRS.EventWithContext eventId ()
(RenamedEvent (n ++ suffix))
in (True, levs ++ ewc' : revs)
_ -> (False, events)
unless changed $ CQRS.pushEvent ewc
DeletedEvent ->
CQRS.mergeEvents $ \events ->
case break isCreatedEvent events of
(_, CQRS.EventWithContext _ _ (CreatedEvent _) : _) ->
((), [])
_ -> ((), [ewc])
eventsDroppedAndMerged :: PropertyT IO ()
eventsDroppedAndMerged = do
inputEvents <- forAll $ do
seq1 <- map (1,) <$> testEventSequenceGen
seq2 <- map (2,) <$> testEventSequenceGen
interleaveSeqs seq1 seq2
inputStreamFamily <- liftIO CQRS.InMem.emptyStreamFamily
let transformedStreamFamily =
CQRS.transformStreamFamily @IO @Int
transformer pure pure pure pure inputStreamFamily
liftIO . forM_ inputEvents $ \(streamId, event) -> do
stream <- CQRS.getStream inputStreamFamily streamId
void . Exc.runExceptT $ CQRS.writeEvent stream event
stream1 <- liftIO $ CQRS.getStream transformedStreamFamily 1
stream2 <- liftIO $ CQRS.getStream transformedStreamFamily 2
outputEvents1 <- liftIO . collect $ CQRS.streamEvents stream1 mempty
outputEvents2 <- liftIO . collect $ CQRS.streamEvents stream2 mempty
forM_ [outputEvents1, outputEvents2] $ \outputEvents -> do
forM_ outputEvents $ \batch -> do
when (any (either (const False) isCreatedEvent) batch) $
length batch === 1
when (any (either (const False) isDeletedEvent) batch) $
length batch === 1