registry-0.6.0.0: test/Test/Data/Registry/Make/RioSpec.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
module Test.Data.Registry.Make.RioSpec where
import Control.Monad.Trans.Resource
import Data.IORef
import Data.Map as M hiding (singleton)
import Data.Registry as Rio
import Protolude hiding (C1, D1)
import Test.Tasty.Extensions
test_fmap = test "a cached value can still be mapped over" $ do
-- count the number of creation for the counter
creations <- liftIO (newIORef (0 :: Int))
-- create a singleton counter starting at 1
let counter = Rio.singleton $ liftIO $ do
c <- newIORef (1 :: Int)
modifyIORef creations (+ 1)
pure c
-- make the counter once, then make the counter start at 2
c1 <- liftIO (newIORef (2 :: Int))
ref <- runRio (counter >> (counter $> c1))
-- the counter starts at 2
incremented <- liftIO . readIORef $ ref
incremented === 2
-- the counter has been created only once
created <- liftIO . readIORef $ creations
created === 1
test_singletons_with_specialization = test "some effectful components can be cached to become singletons in some contexts" $ do
let registry =
singletons $
specialize @(Rio Storage3) (valTo @Rio (DatabaseConfig "localhost" 9876)) $
funTo @Rio newApp
<: funTo @Rio newBusinessLogic
<: funTo @Rio Storage3
<: funTo @Rio Storage2
<: funTo @Rio Storage1
<: funTo @Rio newConnectionPool
<: fun newCounter
<: valTo @Rio (DatabaseConfig "localhost" 5432)
withRegistryM @App registry $ \App {..} -> do
let db1 = getDatabaseConfig $ getStorage1ConnectionPool storage1
let db2 = getDatabaseConfig $ getStorage2ConnectionPool storage2
let db3 = getDatabaseConfig $ getStorage3ConnectionPool storage3
db1 === db2
db2 /== db3
db3 === DatabaseConfig "localhost" 9876
n <- readCounter counter
n
=== [ ("create the App", 1),
("create the BusinessLogic", 1),
("http://localhost:5432", 1),
("http://localhost:9876", 1)
]
-- * HELPERS
data App = App
{ storage1 :: Storage1,
storage2 :: Storage2,
storage3 :: Storage3,
businessLogic :: BusinessLogic,
counter :: Counter
}
newApp :: Counter -> Storage1 -> Storage2 -> Storage3 -> BusinessLogic -> Rio App
newApp counter s1 s2 s3 bl = do
incrementCounter counter "create the App"
pure (App s1 s2 s3 bl counter)
newtype ConnectionPool = ConnectionPool {getDatabaseConfig :: DatabaseConfig}
newConnectionPool :: Counter -> DatabaseConfig -> Rio ConnectionPool
newConnectionPool counter config = do
let key = databaseConfigUrl config
let create = do
-- count the number of times that a pool is created with some specific parameters
incrementCounter counter (databaseConfigUrl config)
pure (ConnectionPool config)
let destroy = const $ pure ()
cacheAt key (snd <$> allocate create destroy)
data DatabaseConfig = DatabaseConfig {host :: Text, port :: Int} deriving (Eq, Show)
databaseConfigUrl :: DatabaseConfig -> Text
databaseConfigUrl (DatabaseConfig h p) = "http://" <> h <> ":" <> show p
newtype Storage1 = Storage1 {getStorage1ConnectionPool :: ConnectionPool}
newtype Storage2 = Storage2 {getStorage2ConnectionPool :: ConnectionPool}
newtype Storage3 = Storage3 {getStorage3ConnectionPool :: ConnectionPool}
newtype Counter = Counter (IORef (Map Text Int))
newtype BusinessLogic = BusinessLogic Storage1
newBusinessLogic :: Counter -> Storage1 -> Storage3 -> Rio BusinessLogic
newBusinessLogic counter storage1 _storage3 = do
incrementCounter counter "create the BusinessLogic"
pure $ BusinessLogic storage1
newCounter :: Rio Counter
newCounter = Counter <$> liftIO (newIORef mempty)
incrementCounter :: MonadIO m => Counter -> Text -> m ()
incrementCounter (Counter ref) key = liftIO $ do
m <- readIORef ref
let current = fromMaybe 0 (M.lookup key m)
modifyIORef ref $ pure (M.insert key (current + 1) m)
readCounter :: MonadIO m => Counter -> m (Map Text Int)
readCounter (Counter ref) = liftIO $ readIORef ref