freckle-app-1.3.0.0: tests/Freckle/App/MemcachedSpec.hs
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
module Freckle.App.MemcachedSpec
( spec
) where
import Freckle.App.Test
import Control.Lens ((^?!))
import Control.Monad.Reader
import Data.Aeson
import Data.Aeson.Lens
import qualified Data.List.NonEmpty as NE
import qualified Freckle.App.Env as Env
import Freckle.App.Memcached
import Freckle.App.Memcached.Client (MemcachedClient, newMemcachedClient)
import qualified Freckle.App.Memcached.Client as Memcached
import Freckle.App.Memcached.Servers
import Freckle.App.Test.Logging
data ExampleValue
= A
| B
| C
deriving stock (Eq, Show)
instance Cachable ExampleValue where
toCachable = \case
A -> "A"
B -> "Broken"
C -> "C"
fromCachable = \case
"A" -> Right A
"B" -> Right B
"C" -> Right C
x -> Left $ "invalid: " <> show x
-- |
--
-- NB. we could use 'withApp' and not need to call this runner ourselves within
-- an @'it'-'example'@ -- except that we want to use 'runCapturedLoggingT' and
-- assert on the logged messages. We should add "Blammo.Logging.Test" to support
-- this use-case.
--
runTestAppT
:: MonadUnliftIO m => AppExample MemcachedClient a -> m (a, [Maybe Value])
runTestAppT f = liftIO $ do
mc <- loadClient
fmap (second $ map logLineToJSON) $ runCapturedLoggingT $ runReaderT
(unAppExample f)
mc
loadClient :: IO MemcachedClient
loadClient = do
servers <- Env.parse id $ Env.var
(Env.eitherReader readMemcachedServers)
"MEMCACHED_SERVERS"
(Env.def defaultMemcachedServers)
newMemcachedClient servers
spec :: Spec
spec = do
describe "caching" $ do
it "caches the given action by key using Cachable" $ example $ do
void $ runTestAppT $ do
k <- cacheKeyThrow "A"
val <- caching k (cacheTTL 5) $ pure A
mbs <- Memcached.get k
liftIO $ val `shouldBe` A
liftIO $ mbs `shouldBe` Just "A"
it "logs, but doesn't fail, on deserialization errors" $ example $ do
(_, msgs) <- runTestAppT $ do
k <- cacheKeyThrow "B"
val0 <- caching k (cacheTTL 5) $ pure B -- set
val1 <- caching k (cacheTTL 5) $ pure B -- get will fail
mbs <- Memcached.get k
liftIO $ val0 `shouldBe` B
liftIO $ val1 `shouldBe` B
liftIO $ mbs `shouldBe` Just "Broken"
let Just val = NE.last =<< NE.nonEmpty msgs
val ^?! key "text" . _String `shouldBe` "Error deserializing"
val ^?! key "meta" . key "action" . _String `shouldBe` "deserializing"
val
^?! key "meta"
. key "message"
. _String
`shouldBe` "invalid: \"Broken\""