module Test.MockStorage
( newMockStorage
, MockStorage (..)
, hoistMockStorage
, offTheRecordIO
, takeTranscript
) where
import Internal.Prelude
import Control.Concurrent.STM.TVar
( TVar
, modifyTVar'
, newTVar
, readTVar
, readTVarIO
, writeTVar
)
import Control.Monad.STM (STM, atomically)
import Data.Map.Strict qualified as Map
import Data.Sequence ((|>))
import Data.Sequence qualified as Seq
import Session.Key
import Yesod.Session.SessionType
import Yesod.Session.Storage.Exceptions
import Yesod.Session.Storage.Operation
data MockStorage m = MockStorage
{ storage :: forall a. StorageOperation a -> STM a
, runSTM :: forall a. STM a -> m a
, transcript :: TVar (Seq StorageOperation')
, getSessionKeys :: STM (Set SessionKey)
, recordingVar :: TVar Bool
}
hoistMockStorage :: (forall a. m a -> m' a) -> MockStorage m -> MockStorage m'
hoistMockStorage f MockStorage {..} =
MockStorage {runSTM = f . runSTM, ..}
-- | Perform some action without modifying the transcript
offTheRecordIO :: MonadIO m => MockStorage m -> m a -> m a
offTheRecordIO mock action = do
wasRecording <- liftIO $ readTVarIO mock.recordingVar
liftIO $ atomically $ writeTVar mock.recordingVar False
x <- action
liftIO $ atomically $ writeTVar mock.recordingVar wasRecording
pure x
takeTranscript :: MockStorage m -> m (Seq StorageOperation')
takeTranscript MockStorage {transcript, runSTM} =
runSTM $ readTVar transcript <* writeTVar transcript Seq.empty
newMockStorage :: HasCallStack => STM (MockStorage STM)
newMockStorage = do
transcript <- newTVar Seq.empty
sessionsVar <- newTVar Map.empty
recordingVar <- newTVar True
pure
MockStorage
{ transcript
, recordingVar
, getSessionKeys = Map.keysSet <$> readTVar sessionsVar
, storage = \(op :: StorageOperation result) ->
do
readTVar recordingVar
>>= (`when` modifyTVar' transcript (|> StorageOperation' op))
handleOp sessionsVar op
, runSTM = id
}
handleOp
:: HasCallStack
=> TVar (Map SessionKey Session)
-> StorageOperation result
-> STM result
handleOp ref = \case
GetSession sessionKey -> readTVar ref <&> Map.lookup sessionKey
DeleteSession sessionKey -> modifyTVar' ref $ Map.delete sessionKey
InsertSession newSession -> do
modifyTVarSTM ref
$ Map.alterF
( maybe
(pure $ Just newSession)
(const $ throwWithCallStack SessionAlreadyExists)
)
newSession.key
ReplaceSession newSession ->
modifyTVarSTM ref
$ Map.alterF
( maybe
(throwWithCallStack SessionDoesNotExist)
(const $ pure $ Just newSession)
)
newSession.key
modifyTVarSTM :: TVar a -> (a -> STM a) -> STM ()
modifyTVarSTM ref f = readTVar ref >>= f >>= (writeTVar ref $!)