{-# LANGUAGE FlexibleContexts, OverloadedStrings #-} module Data.CQRS.Test.Internal.EventStoreTest ( mkEventStoreSpec ) where import Control.Exception.Lifted (try) import Control.Monad.IO.Class (liftIO) import Data.ByteString (ByteString) import Data.CQRS.Test.Internal.TestKitSettings import Data.CQRS.Test.Internal.Scope (ScopeM, verify, ask, randomUUID) import qualified Data.CQRS.Test.Internal.Scope as S import Data.CQRS.Types.EventStore (EventStore, StoreError(VersionConflict)) import qualified Data.CQRS.Types.EventStore as ES import Data.CQRS.Types.PersistedEvent import Data.UUID.Types (UUID) import Test.Hspec (Spec, describe, shouldBe) import qualified Test.Hspec as Hspec import qualified System.IO.Streams.List as SL -- Ambient data for test scope for each spec. data Scope e = Scope { scopeEventStore :: EventStore e } -- Store given events in exactly the order given. storeEvents :: UUID -> [PersistedEvent e] -> ScopeM (Scope e) () storeEvents aggregateId events = do eventStore <- fmap scopeEventStore ask liftIO $ (ES.esStoreEvents eventStore) aggregateId events -- Read all events for a given aggregate. readEvents :: UUID -> ScopeM (Scope e) [PersistedEvent e] readEvents aggregateId = do eventStore <- fmap scopeEventStore ask liftIO $ ES.esRetrieveEvents eventStore aggregateId (-1) $ SL.toList -- Test suite for event store which stores 'ByteString' events. mkEventStoreSpec :: TestKitSettings a (EventStore ByteString) -> Spec mkEventStoreSpec testKitSettings = do describe "EventStore implementation" $ do it "should be able to retrieve stored events" $ do aggregateId <- randomUUID eventId0 <- randomUUID eventId1 <- randomUUID -- Write two events. let expectedEvents = [ PersistedEvent "test event 0" 0 eventId0 , PersistedEvent "test event 1" 1 eventId1 ] storeEvents aggregateId expectedEvents -- Retrieve the stored events. actualEvents <- readEvents aggregateId -- Assert that we've retrieved the expected events in order. verify $ actualEvents `shouldBe` expectedEvents it "should throw a VersionConflict exception when storing conflicting events in a single operation" $ do aggregateId <- randomUUID eventId0 <- randomUUID eventId1 <- randomUUID -- Write two conflicting events. let conflictingEvents = [ PersistedEvent "test event 0" 0 eventId0 , PersistedEvent "test event 1" 0 eventId1 ] storeEvents aggregateId conflictingEvents `shouldThrow` VersionConflict aggregateId -- Make sure we didn't actually store any events storedEvents <- readEvents aggregateId verify $ length storedEvents `shouldBe` 0 it "should throw a VersionConflict exception when storing conflicting events in multiple operations" $ do aggregateId <- randomUUID eventId0 <- randomUUID eventId1 <- randomUUID -- Write a single event let initialEvent = PersistedEvent "test event 0" 0 eventId0 storeEvents aggregateId [initialEvent] -- Write the event that should conflict let conflictingEvents = [ PersistedEvent "test event 1" 0 eventId1 ] storeEvents aggregateId conflictingEvents `shouldThrow` VersionConflict aggregateId -- Make sure we didn't write the second event storedEvents <- readEvents aggregateId verify $ length storedEvents `shouldBe` 1 verify $ (storedEvents !! 0) `shouldBe` initialEvent where runScope = S.mkRunScope testKitSettings $ \a -> do eventStore <- tksMakeContext testKitSettings a return $ Scope eventStore -- Shorthands it msg scope = Hspec.it msg $ runScope scope shouldThrow action exc = do resultOrExc <- try $ action liftIO $ resultOrExc `shouldBe` (Left exc)