packages feed

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 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