eventful-test-helpers 0.1.2 → 0.1.3
raw patch · 2 files changed
+200/−66 lines, 2 filesdep +textPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: text
API changes (from Hackage documentation)
+ Eventful.TestHelpers: EventStoreRunner :: (forall a. (EventStore CounterEvent m -> m a) -> IO a) -> EventStoreRunner m
+ Eventful.TestHelpers: GloballyOrderedEventStoreRunner :: (forall a. (EventStore CounterEvent m -> GloballyOrderedEventStore CounterEvent m -> m a) -> IO a) -> GloballyOrderedEventStoreRunner m
+ Eventful.TestHelpers: GloballyOrderedProjectionCacheRunner :: (forall a. (EventStore CounterEvent m -> GloballyOrderedEventStore CounterEvent m -> GloballyOrderedProjectionCache Text Counter m -> m a) -> IO a) -> GloballyOrderedProjectionCacheRunner m
+ Eventful.TestHelpers: StreamProjectionCacheRunner :: (forall a. (EventStore CounterEvent m -> StreamProjectionCache Counter m -> m a) -> IO a) -> StreamProjectionCacheRunner m
+ Eventful.TestHelpers: data Text :: *
+ Eventful.TestHelpers: globallyOrderedProjectionCacheSpec :: (Monad m) => GloballyOrderedProjectionCacheRunner m -> Spec
+ Eventful.TestHelpers: newtype EventStoreRunner m
+ Eventful.TestHelpers: newtype GloballyOrderedEventStoreRunner m
+ Eventful.TestHelpers: newtype GloballyOrderedProjectionCacheRunner m
+ Eventful.TestHelpers: newtype StreamProjectionCacheRunner m
+ Eventful.TestHelpers: streamProjectionCacheSpec :: (Monad m) => StreamProjectionCacheRunner m -> Spec
- Eventful.TestHelpers: eventStoreSpec :: (Monad m) => IO (EventStore CounterEvent m, runargs) -> (forall a. runargs -> m a -> IO a) -> Spec
+ Eventful.TestHelpers: eventStoreSpec :: (Monad m) => EventStoreRunner m -> Spec
- Eventful.TestHelpers: sequencedEventStoreSpec :: (Monad m) => IO (EventStore CounterEvent m, GloballyOrderedEventStore CounterEvent m, runargs) -> (forall a. runargs -> m a -> IO a) -> Spec
+ Eventful.TestHelpers: sequencedEventStoreSpec :: (Monad m) => GloballyOrderedEventStoreRunner m -> Spec
Files
- eventful-test-helpers.cabal +2/−1
- src/Eventful/TestHelpers.hs +198/−65
eventful-test-helpers.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: eventful-test-helpers-version: 0.1.2+version: 0.1.3 synopsis: Common module used for eventful tests description: Common module used for eventful tests category: Database,Eventsourcing@@ -32,6 +32,7 @@ , extra , hspec , monad-logger+ , text exposed-modules: Eventful.TestHelpers default-language: Haskell2010
src/Eventful/TestHelpers.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} @@ -12,8 +13,15 @@ , counterAggregate , CounterEvent (..) , CounterCommand (..)+ , EventStoreRunner (..)+ , GloballyOrderedEventStoreRunner (..) , eventStoreSpec , sequencedEventStoreSpec+ , StreamProjectionCacheRunner (..)+ , streamProjectionCacheSpec+ , GloballyOrderedProjectionCacheRunner (..)+ , globallyOrderedProjectionCacheSpec+ , Text , module X ) where @@ -24,6 +32,7 @@ import Data.Aeson import Data.Aeson.Casing import Data.Aeson.TH+import Data.Text (Text) import Test.Hspec import Eventful@@ -47,6 +56,12 @@ (Counter 0) (\(Counter k) (Added x) -> Counter (k + x)) +counterGlobalProjection :: Projection Counter (GloballyOrderedEvent CounterEvent)+counterGlobalProjection =+ Projection+ (Counter 0)+ (\(Counter k) (GloballyOrderedEvent _ _ _ (Added x)) -> Counter (k + x))+ data CounterCommand = Increment { _counterCommandAmount :: Int@@ -76,58 +91,78 @@ -- Test harness for stores +newtype EventStoreRunner m =+ EventStoreRunner (forall a. (EventStore CounterEvent m -> m a) -> IO a)+newtype GloballyOrderedEventStoreRunner m =+ GloballyOrderedEventStoreRunner (forall a. (EventStore CounterEvent m -> GloballyOrderedEventStore CounterEvent m -> m a) -> IO a)+ eventStoreSpec :: (Monad m)- => IO (EventStore CounterEvent m, runargs)- -> (forall a. runargs -> m a -> IO a)+ => EventStoreRunner m -> Spec-eventStoreSpec makeStore runAsIO = do+eventStoreSpec (EventStoreRunner withStore) = do+ let+ withStoreExampleEvents action = withStore $ \store -> do+ _ <- insertExampleEvents store+ action store+ context "when the event store is empty" $ do it "should return versions of -1 for a UUID" $ do- -- TODO: Abstract out creating all of these stores and having to manually- -- thread around makeStore, runAsIO, etc.- (store, runargs) <- liftIO makeStore- runAsIO runargs (getLatestVersion store nil) `shouldReturn` (-1)+ withStore (\store -> getLatestVersion store nil) `shouldReturn` (-1) context "when a few events are inserted" $ do let- events = [Added 1, Added 4, Added (-3)]+ sampleEvents = [Added 1, Added 4, Added (-3), Added 5]+ withStore' action = withStore $ \store -> do+ _ <- storeEvents store NoStream nil sampleEvents+ action store it "should return events" $ do- (store, runargs) <- makeStore- events' <- runAsIO runargs $ do- _ <- storeEvents store NoStream nil events- getEvents store nil Nothing- (storedEventEvent <$> events') `shouldBe` events- --(storedEventSequenceNumber <$> events') `shouldBe` [1, 2, 3]+ events' <- withStore' $ \store -> getEvents store nil allEvents+ (storedEventEvent <$> events') `shouldBe` sampleEvents it "should return correct event versions" $ do- (store, runargs) <- makeStore- (latestVersion, allEvents, someEvents) <- runAsIO runargs $ do- _ <- storeEvents store NoStream nil events- (,,) <$>+ (latestVersion, events) <- withStore' $ \store ->+ (,) <$> getLatestVersion store nil <*>- getEvents store nil (Just (-1)) <*>- getEvents store nil (Just 1)- latestVersion `shouldBe` 2- (storedEventEvent <$> allEvents) `shouldBe` events- (storedEventEvent <$> someEvents) `shouldBe` drop 1 events+ getEvents store nil allEvents+ latestVersion `shouldBe` 3+ (storedEventVersion <$> events) `shouldBe` [0, 1, 2, 3] + it "should return correct events with queries" $ do+ (firstEvents, middleEvents, laterEvents, maxEvents) <- withStore' $ \store ->+ (,,,) <$>+ getEvents store nil (eventsUntil 1) <*>+ getEvents store nil (eventsStartingAtUntil 1 2) <*>+ getEvents store nil (eventsStartingAt 2) <*>+ getEvents store nil (eventsStartingAtTakeLimit 0 2)+ (storedEventEvent <$> firstEvents) `shouldBe` take 2 sampleEvents+ (storedEventEvent <$> middleEvents) `shouldBe` take 2 (drop 1 sampleEvents)+ (storedEventEvent <$> laterEvents) `shouldBe` drop 2 sampleEvents+ (storedEventEvent <$> maxEvents) `shouldBe` take 2 sampleEvents+ it "should return the latest projection" $ do- (store, runargs) <- makeStore- projection <- runAsIO runargs $ do- _ <- storeEvents store NoStream nil events- getLatestProjection store counterProjection nil- projection `shouldBe` (Counter 2, 2)+ projection <- withStore' $ \store ->+ getLatestProjection store (streamProjection counterProjection nil)+ streamProjectionState projection `shouldBe` Counter 7+ streamProjectionVersion projection `shouldBe` 3+ streamProjectionUuid projection `shouldBe` nil + it "should return the latest projection with some starting StreamProjection" $ do+ projection <- withStore' $ \store -> do+ initialEvents <- getEvents store nil (eventsUntil 1)+ let initialProjection = latestProjection counterProjection (storedEventEvent <$> initialEvents)+ getLatestProjection store (StreamProjection counterProjection nil 1 initialProjection)+ streamProjectionState projection `shouldBe` Counter 7+ streamProjectionVersion projection `shouldBe` 3+ streamProjectionUuid projection `shouldBe` nil+ context "when events from multiple UUIDs are inserted" $ do it "should have the correct events for each aggregate" $ do- (store, runargs) <- makeStore- (events1, events2) <- runAsIO runargs $ do- _ <- insertExampleEvents store- (,) <$> getEvents store uuid1 Nothing <*> getEvents store uuid2 Nothing+ (events1, events2) <- withStoreExampleEvents $ \store ->+ (,) <$> getEvents store uuid1 allEvents <*> getEvents store uuid2 allEvents (storedEventEvent <$> events1) `shouldBe` Added <$> [1, 4] (storedEventEvent <$> events2) `shouldBe` Added <$> [2, 3, 5] (storedEventProjectionId <$> events1) `shouldBe` [uuid1, uuid1]@@ -136,34 +171,41 @@ (storedEventVersion <$> events2) `shouldBe` [0, 1, 2] it "should return correct event versions" $ do- (store, runargs) <- makeStore- (latestVersion1, latestVersion2, events1, events2) <- runAsIO runargs $ do- _ <- insertExampleEvents store+ (latestVersion1, latestVersion2, events1, events2) <- withStoreExampleEvents $ \store -> (,,,) <$> getLatestVersion store uuid1 <*> getLatestVersion store uuid2 <*>- getEvents store uuid1 (Just 0) <*>- getEvents store uuid2 (Just 1)+ getEvents store uuid1 allEvents <*>+ getEvents store uuid2 allEvents latestVersion1 `shouldBe` 1 latestVersion2 `shouldBe` 2 storedEventEvent <$> events1 `shouldBe` [Added 1, Added 4]- storedEventEvent <$> events2 `shouldBe` [Added 3, Added 5]+ storedEventEvent <$> events2 `shouldBe` [Added 2, Added 3, Added 5] + it "should return correct events with queries" $ do+ (firstEvents, middleEvents, laterEvents, maxEvents) <- withStoreExampleEvents $ \store ->+ (,,,) <$>+ getEvents store uuid1 (eventsUntil 1) <*>+ getEvents store uuid2 (eventsStartingAtUntil 1 2) <*>+ getEvents store uuid2 (eventsStartingAt 2) <*>+ getEvents store uuid1 (eventsStartingAtTakeLimit 1 1)+ (storedEventEvent <$> firstEvents) `shouldBe` [Added 1, Added 4]+ (storedEventEvent <$> middleEvents) `shouldBe` [Added 3, Added 5]+ (storedEventEvent <$> laterEvents) `shouldBe` [Added 5]+ (storedEventEvent <$> maxEvents) `shouldBe` [Added 4]+ it "should produce the correct projections" $ do- (store, runargs) <- makeStore- (proj1, proj2) <- runAsIO runargs $ do- _ <- insertExampleEvents store+ (proj1, proj2) <- withStoreExampleEvents $ \store -> (,) <$>- getLatestProjection store counterProjection uuid1 <*>- getLatestProjection store counterProjection uuid2- proj1 `shouldBe` (Counter 5, 1)- proj2 `shouldBe` (Counter 10, 2)+ getLatestProjection store (streamProjection counterProjection uuid1) <*>+ getLatestProjection store (streamProjection counterProjection uuid2)+ (streamProjectionState proj1, streamProjectionVersion proj1) `shouldBe` (Counter 5, 1)+ (streamProjectionState proj2, streamProjectionVersion proj2) `shouldBe` (Counter 10, 2) describe "can handle event storage errors" $ do it "rejects some writes when event store isn't created" $ do- (store, runargs) <- makeStore- (err1, err2) <- runAsIO runargs $+ (err1, err2) <- withStore $ \store -> do (,) <$> storeEvents store StreamExists nil [Added 1] <*> storeEvents store (ExactVersion 0) nil [Added 1]@@ -171,12 +213,10 @@ err2 `shouldBe` Just (EventStreamNotAtExpectedVersion (-1)) it "should be able to store events starting with an empty stream" $ do- (store, runargs) <- makeStore- runAsIO runargs (storeEvents store NoStream nil [Added 1]) `shouldReturn` Nothing+ withStore (\store -> storeEvents store NoStream nil [Added 1]) `shouldReturn` Nothing it "should reject storing events sometimes with a stream" $ do- (store, runargs) <- makeStore- (err1, err2, err3) <- runAsIO runargs $+ (err1, err2, err3) <- withStore $ \store -> (,,) <$> storeEvents store NoStream nil [Added 1] <*> storeEvents store NoStream nil [Added 1] <*>@@ -186,8 +226,7 @@ err3 `shouldBe` Just (EventStreamNotAtExpectedVersion 0) it "should accepts storing events sometimes with a stream" $ do- (store, runargs) <- makeStore- errors <- runAsIO runargs $+ errors <- withStore $ \store -> sequence [ storeEvents store NoStream nil [Added 1] , storeEvents store AnyVersion nil [Added 1]@@ -198,28 +237,40 @@ sequencedEventStoreSpec :: (Monad m)- => IO (EventStore CounterEvent m, GloballyOrderedEventStore CounterEvent m, runargs)- -> (forall a. runargs -> m a -> IO a)+ => GloballyOrderedEventStoreRunner m -> Spec-sequencedEventStoreSpec makeStore runAsIO = do+sequencedEventStoreSpec (GloballyOrderedEventStoreRunner withStore) = do context "when the event store is empty" $ do it "shouldn't have any events" $ do- (_, globalStore, runargs) <- makeStore- length <$> runAsIO runargs (getSequencedEvents globalStore 0) `shouldReturn` 0+ events <- withStore (\_ globalStore -> getSequencedEvents globalStore allEvents)+ length events `shouldBe` 0 context "when events from multiple UUIDs are inserted" $ do it "should have the correct events in global order" $ do- (store, globalStore, runargs) <- makeStore- events' <- runAsIO runargs $ do+ events <- withStore $ \store globalStore -> do insertExampleEvents store- getSequencedEvents globalStore 0- (globallyOrderedEventEvent <$> events') `shouldBe` Added <$> [1..5]- (globallyOrderedEventProjectionId <$> events') `shouldBe` [uuid1, uuid2, uuid2, uuid1, uuid2]- (globallyOrderedEventVersion <$> events') `shouldBe` [0, 0, 1, 1, 2]- (globallyOrderedEventSequenceNumber <$> events') `shouldBe` [1..5]+ getSequencedEvents globalStore allEvents+ (globallyOrderedEventEvent <$> events) `shouldBe` Added <$> [1..5]+ (globallyOrderedEventProjectionId <$> events) `shouldBe` [uuid1, uuid2, uuid2, uuid1, uuid2]+ (globallyOrderedEventVersion <$> events) `shouldBe` [0, 0, 1, 1, 2]+ (globallyOrderedEventSequenceNumber <$> events) `shouldBe` [1..5] + it "should handle queries" $ do+ (firstEvents, middleEvents, laterEvents, maxEvents) <- withStore $ \store globalStore -> do+ insertExampleEvents store+ (,,,) <$>+ getSequencedEvents globalStore (eventsUntil 2) <*>+ getSequencedEvents globalStore (eventsStartingAtUntil 2 3) <*>+ getSequencedEvents globalStore (eventsStartingAt 3) <*>+ getSequencedEvents globalStore (eventsStartingAtTakeLimit 2 3)++ (globallyOrderedEventEvent <$> firstEvents) `shouldBe` Added <$> [1..2]+ (globallyOrderedEventEvent <$> middleEvents) `shouldBe` Added <$> [2..3]+ (globallyOrderedEventEvent <$> laterEvents) `shouldBe` Added <$> [3..5]+ (globallyOrderedEventEvent <$> maxEvents) `shouldBe` Added <$> [2..4]+ insertExampleEvents :: (Monad m) => EventStore CounterEvent m@@ -235,3 +286,85 @@ uuid2 :: UUID uuid2 = uuidFromInteger 2++newtype StreamProjectionCacheRunner m =+ StreamProjectionCacheRunner (forall a. (EventStore CounterEvent m -> StreamProjectionCache Counter m -> m a) -> IO a)++streamProjectionCacheSpec+ :: (Monad m)+ => StreamProjectionCacheRunner m+ -> Spec+streamProjectionCacheSpec (StreamProjectionCacheRunner withStoreAndCache) = do+ context "when the store is empty" $ do++ it "should be able to store and load simple projections" $ do+ snapshot <- withStoreAndCache $ \_ cache -> do+ storeProjectionSnapshot cache nil 4 (Counter 100)+ loadProjectionSnapshot cache nil+ snapshot `shouldBe` Just (4, Counter 100)++ context "when the store has some events in one stream" $ do++ it "should load from a stream of events" $ do+ snapshot <- withStoreAndCache $ \store cache -> do+ _ <- storeEvents store AnyVersion nil [Added 1, Added 2]+ getLatestProjectionWithCache store cache (streamProjection counterProjection nil)+ streamProjectionVersion snapshot `shouldBe` 1+ streamProjectionState snapshot `shouldBe` Counter 3++ it "should work with updateProjectionCache" $ do+ snapshot <- withStoreAndCache $ \store cache -> do+ _ <- storeEvents store AnyVersion nil [Added 1, Added 2, Added 3]+ updateProjectionCache store cache (streamProjection counterProjection nil)+ getLatestProjectionWithCache store cache (streamProjection counterProjection nil)+ streamProjectionUuid snapshot `shouldBe` nil+ streamProjectionVersion snapshot `shouldBe` 2+ streamProjectionState snapshot `shouldBe` Counter 6++newtype GloballyOrderedProjectionCacheRunner m =+ GloballyOrderedProjectionCacheRunner+ (forall a.+ ( EventStore CounterEvent m+ -> GloballyOrderedEventStore CounterEvent m+ -> GloballyOrderedProjectionCache Text Counter m -> m a+ ) -> IO a)++globallyOrderedProjectionCacheSpec+ :: (Monad m)+ => GloballyOrderedProjectionCacheRunner m+ -> Spec+globallyOrderedProjectionCacheSpec (GloballyOrderedProjectionCacheRunner withStoreAndCache) = do+ context "when the store is empty" $ do++ it "should be able to store and load simple projections" $ do+ snapshot <- withStoreAndCache $ \_ _ cache -> do+ storeProjectionSnapshot cache "key" 4 (Counter 100)+ loadProjectionSnapshot cache "key"+ snapshot `shouldBe` Just (4, Counter 100)++ context "when the store has some events in one stream" $ do++ it "should load from a global stream of events" $ do+ snapshot <- withStoreAndCache $ \store globalStore cache -> do+ _ <- storeEvents store AnyVersion nil [Added 1, Added 2]+ getLatestGlobalProjectionWithCache globalStore cache (globallyOrderedProjection counterGlobalProjection) "key"+ globallyOrderedProjectionSequenceNumber snapshot `shouldBe` 2+ globallyOrderedProjectionState snapshot `shouldBe` Counter 3++ it "should work with updateGlobalProjectionCache" $ do+ snapshot <- withStoreAndCache $ \store globalStore cache -> do+ _ <- storeEvents store AnyVersion nil [Added 1, Added 2, Added 3]+ updateGlobalProjectionCache globalStore cache (globallyOrderedProjection counterGlobalProjection) "key"+ getLatestGlobalProjectionWithCache globalStore cache (globallyOrderedProjection counterGlobalProjection) "key"+ globallyOrderedProjectionSequenceNumber snapshot `shouldBe` 3+ globallyOrderedProjectionState snapshot `shouldBe` Counter 6++ context "when events from multiple UUIDs are inserted" $ do++ it "should have the correct cached projection value" $ do+ snapshot <- withStoreAndCache $ \store globalStore cache -> do+ insertExampleEvents store+ updateGlobalProjectionCache globalStore cache (globallyOrderedProjection counterGlobalProjection) "key"+ getLatestGlobalProjectionWithCache globalStore cache (globallyOrderedProjection counterGlobalProjection) "key"+ globallyOrderedProjectionSequenceNumber snapshot `shouldBe` 5+ globallyOrderedProjectionState snapshot `shouldBe` Counter 15