eventium-memory-0.2.1: tests/Eventium/EventSubscriptionSpec.hs
module Eventium.EventSubscriptionSpec (spec) where
import Control.Concurrent (forkIO, killThread, threadDelay)
import Control.Concurrent.STM
import Data.IORef
import Eventium.CheckpointStore.Memory
import Eventium.EventHandler
import Eventium.EventSubscription
import Eventium.Store.Class
import Eventium.Store.Memory
import Eventium.UUID
import Test.Hspec
-- Helper to create IO-compatible stores from a TVar
makeIOStores :: TVar (EventMap event) -> (VersionedEventStoreWriter IO event, GlobalEventStoreReader IO event)
makeIOStores tvar =
( runEventStoreWriterUsing atomically (tvarEventStoreWriter tvar),
runEventStoreReaderUsing atomically (tvarGlobalEventStoreReader tvar)
)
spec :: Spec
spec = do
describe "pollingSubscription" $ do
it "should deliver events from the global store to a handler" $ do
tvar <- eventMapTVar
let (writer, globalReader) = makeIOStores tvar
-- Write some events
_ <- writer.storeEvents (uuidFromInteger 1) NoStream [10 :: Int, 20]
_ <- writer.storeEvents (uuidFromInteger 2) NoStream [30]
-- Set up checkpoint tracking
checkpointRef <- newIORef (0 :: SequenceNumber)
let cpStore = ioRefCheckpointStore checkpointRef
-- Set up the subscription with very short poll interval
let sub = pollingSubscription globalReader cpStore 50
-- Collect delivered events
deliveredRef <- newIORef ([] :: [Int])
let handler = EventHandler $ \globalEvent ->
modifyIORef deliveredRef (++ [globalEvent.payload.payload])
-- Run subscription in background, give it time to poll once
tid <- forkIO $ sub.runSubscription handler
threadDelay 200000 -- 200ms
killThread tid
delivered <- readIORef deliveredRef
delivered `shouldBe` [10, 20, 30]
-- Checkpoint should be updated
checkpoint <- readIORef checkpointRef
checkpoint `shouldBe` 3
it "should not deliver already-consumed events" $ do
tvar <- eventMapTVar
let (writer, globalReader) = makeIOStores tvar
-- Write some initial events
_ <- writer.storeEvents (uuidFromInteger 1) NoStream [10 :: Int, 20]
-- Start with checkpoint at 2 (already consumed)
checkpointRef <- newIORef (2 :: SequenceNumber)
let cpStore = ioRefCheckpointStore checkpointRef
let sub = pollingSubscription globalReader cpStore 50
deliveredRef <- newIORef ([] :: [Int])
let handler = EventHandler $ \globalEvent ->
modifyIORef deliveredRef (++ [globalEvent.payload.payload])
-- Write more events
_ <- writer.storeEvents (uuidFromInteger 2) NoStream [30]
tid <- forkIO $ sub.runSubscription handler
threadDelay 200000
killThread tid
-- Should only see the new event
delivered <- readIORef deliveredRef
delivered `shouldBe` [30]