shibuya-core-0.8.0.0: src/Shibuya/Adapter/Mock.hs
-- | Mock adapter for testing.
-- Provides adapters that produce messages from in-memory sources.
module Shibuya.Adapter.Mock
( -- * Mock Adapters
listAdapter,
-- * Test Helpers
TrackingAck (..),
newTrackingAck,
trackingAckHandle,
getTrackedDecisions,
mkTrackedIngested,
trackedListAdapter,
)
where
import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
import Effectful (Eff, IOE, liftIO, (:>))
import Shibuya.Adapter (Adapter (..))
import Shibuya.Core.Ack (AckDecision)
import Shibuya.Core.AckHandle (AckHandle (..))
import Shibuya.Core.Ingested (Ingested, mkIngested)
import Shibuya.Core.Types (Envelope (..), MessageId (..))
import Streamly.Data.Stream qualified as Stream
-- | Create an adapter from a list of ingested messages.
-- Useful for testing handlers with predetermined input.
listAdapter :: (IOE :> es) => [Ingested es msg] -> Adapter es msg
listAdapter msgs =
Adapter
{ adapterName = "mock:list",
source = Stream.fromList msgs,
shutdown = pure ()
}
-- | Tracking state for ack decisions.
data TrackingAck = TrackingAck
{ trackedDecisions :: IORef [(MessageId, AckDecision)]
}
-- | Create an AckHandle that tracks all decisions made.
-- Useful for testing that handlers make correct ack decisions.
trackingAckHandle ::
(IOE :> es) =>
TrackingAck ->
MessageId ->
AckHandle es
trackingAckHandle tracking msgId =
AckHandle $ \decision ->
liftIO $ atomicModifyIORef' tracking.trackedDecisions (\xs -> ((msgId, decision) : xs, ()))
-- | Create a new TrackingAck.
newTrackingAck :: (IOE :> es) => Eff es TrackingAck
newTrackingAck = liftIO $ TrackingAck <$> newIORef []
-- | Get all tracked decisions.
getTrackedDecisions :: (IOE :> es) => TrackingAck -> Eff es [(MessageId, AckDecision)]
getTrackedDecisions tracking = liftIO $ readIORef tracking.trackedDecisions
-- | Wrap an envelope into an ingested message whose acknowledgement is recorded by the
-- given tracker, keyed by the envelope's own message id. The lease is
-- @Nothing@. Every call to the resulting handle's @finalize@ appends one
-- @(messageId, decision)@ pair to the tracking list, so duplicate finalizes are
-- observable.
mkTrackedIngested :: (IOE :> es) => TrackingAck -> Envelope msg -> Ingested es msg
mkTrackedIngested tracking env =
mkIngested env (trackingAckHandle tracking env.messageId)
-- | Build an adapter from a list of envelopes where every message's acknowledgement
-- is recorded into one shared tracker. Combine with 'getTrackedDecisions' to
-- assert one successful finalization per message across a normal run.
trackedListAdapter :: (IOE :> es) => TrackingAck -> [Envelope msg] -> Adapter es msg
trackedListAdapter tracking envs =
listAdapter (map (mkTrackedIngested tracking) envs)