eventium-core-0.2.1: tests/Eventium/CommandDispatcherSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Eventium.CommandDispatcherSpec (spec) where
import Data.IORef
import Eventium.CommandDispatcher
import Eventium.CommandHandler
import Eventium.ProcessManager (CommandDispatchResult (..), CommandDispatcher (..), RejectionReason (..))
import Eventium.Projection
import Eventium.Store.Class
import Eventium.UUID
import Test.Hspec
-- Minimal test domain
data CounterEvent = Incremented | Decremented deriving (Show, Eq)
data CounterCommand = Increment | Decrement | Unknown deriving (Show, Eq)
data CounterError = AlreadyZero deriving (Show, Eq)
type Counter = Int
counterProjection :: Projection Counter CounterEvent
counterProjection =
Projection 0 $ \s e -> case e of
Incremented -> s + 1
Decremented -> s - 1
counterHandler :: CommandHandler Counter CounterEvent CounterCommand CounterError
counterHandler = CommandHandler decide' counterProjection
where
decide' _ Increment = Right [Incremented]
decide' s Decrement
| s <= 0 = Left AlreadyZero
| otherwise = Right [Decremented]
decide' _ Unknown = Right []
-- | Simple IORef-based event store for testing.
mkTestStore :: IO (VersionedEventStoreWriter IO CounterEvent, VersionedEventStoreReader IO CounterEvent)
mkTestStore = do
eventsRef <- newIORef ([] :: [VersionedStreamEvent CounterEvent])
let writer = EventStoreWriter $ \uuid _expected events -> do
existing <- readIORef eventsRef
let startVersion = fromIntegral (length existing)
versioned = zipWith (\i e -> StreamEvent uuid i (emptyMetadata "") e) [startVersion ..] events
modifyIORef eventsRef (++ versioned)
pure (Right (startVersion + fromIntegral (length events) - 1))
reader = EventStoreReader $ \query -> do
allEvts <- readIORef eventsRef
pure $ filterByQuery query allEvts
pure (writer, reader)
where
filterByQuery (QueryRange uuid _ _) =
filter (\(StreamEvent k _ _ _) -> k == uuid)
spec :: Spec
spec = describe "CommandDispatcher" $ do
describe "commandHandlerDispatcher" $ do
it "routes command to matching handler and reports success" $ do
(writer, reader) <- mkTestStore
let handlers = [mkAggregateHandler counterHandler]
dispatcher = commandHandlerDispatcher writer reader handlers
result <- dispatcher.dispatchCommand (uuidFromInteger 1) Increment
result `shouldBe` CommandSucceeded
it "reports failure when command is rejected" $ do
(writer, reader) <- mkTestStore
let handlers = [mkAggregateHandler counterHandler]
dispatcher = commandHandlerDispatcher writer reader handlers
-- Counter starts at 0, Decrement should fail
result <- dispatcher.dispatchCommand (uuidFromInteger 1) Decrement
result `shouldBe` CommandFailed (RejectionReason "AlreadyZero")
it "returns CommandSucceeded when no handler matches" $ do
(writer, reader) <- mkTestStore
let handlers = [mkAggregateHandler counterHandler]
dispatcher = commandHandlerDispatcher writer reader handlers
-- Unknown returns Right [], so no handler "matches" (produces events)
result <- dispatcher.dispatchCommand (uuidFromInteger 1) Unknown
result `shouldBe` CommandSucceeded