time-warp-0.1.1: test/Test/Control/TimeWarp/Rpc/MonadRpcSpec.hs
{-# LANGUAGE ViewPatterns #-}
-- | RSCoin.Test.MonadRpc specification
module Test.Control.TimeWarp.Rpc.MonadRpcSpec
( spec
) where
import Control.Monad.State (StateT, execStateT, modify)
import Network.MessagePack.Server (ServerT)
import Test.Hspec (Spec, describe, runIO)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (Property, Testable (property),
ioProperty)
import Test.QuickCheck.Monadic (PropertyM, assert, monadic, run)
import Control.TimeWarp.Rpc (Client (..), Host, MonadRpc (..),
MsgPackRpc, NetworkAddress,
Port, PureRpc, call, method,
runMsgPackRpc, runPureRpc)
import Control.TimeWarp.Timed (for, fork, fork_, killThread, ms,
wait, Microsecond)
import Test.Control.TimeWarp.Common ()
spec :: Spec
spec =
describe "MonadRpc" $ do
describe "MsgPackRpc" $
describe "msgpack-rpc based implementation of RPC layer" $ do
runIO $ runMsgPackRpc (fork_ server)
prop "client should be able to execute server method" $
runMsgPackRpcProp serverMethodShouldExecuteSimpleSpec
describe "PureRpc" $
describe "pure implementation of RPC layer" $
prop "client should be able to execute server method" $
runPureRpcProp serverMethodShouldExecuteSimplePureSpec
type MsgPackRpcProp = PropertyM MsgPackRpc
type PureRpcProp = PureRpc (StateT Bool IO)
assertPure :: Bool -> PureRpcProp ()
assertPure b = modify (b &&)
runMsgPackRpcProp :: MsgPackRpcProp () -> Property
runMsgPackRpcProp = monadic $ ioProperty . runMsgPackRpc
runPureRpcProp :: PureRpcProp () -> Property
runPureRpcProp test =
property $
\gen ->
ioProperty $ execStateT (runPureRpc gen delays test) True
where
delays :: (Microsecond, Microsecond)
delays = (0, 1000)
-- TODO: it would be useful to create an instance of Function for Client and Method;
-- see here https://hackage.haskell.org/package/QuickCheck-2.8.2/docs/Test-QuickCheck-Function.html#t:Function
port :: Port
port = 5000
host :: Host
host = "127.0.0.1"
addr :: NetworkAddress
addr = (host, port)
serverMethodShouldExecuteSimpleSpec :: PropertyM MsgPackRpc ()
serverMethodShouldExecuteSimpleSpec = client
serverMethodShouldExecuteSimplePureSpec
:: PureRpcProp ()
serverMethodShouldExecuteSimplePureSpec = do
serverThread <- fork server
clientPure
killThread serverThread
server :: MonadRpc m => m ()
server = do
let respAdd = add
respEcho = echo
restrict $ respAdd 0 0
restrict $ respEcho ""
serve port [method "add" respAdd, method "echo" respEcho]
where
add
:: Monad m
=> Int -> Int -> ServerT m Int
add x y = return $ x + y
echo
:: Monad m
=> String -> ServerT m String
echo s = return $ "***" ++ s ++ "***"
restrict :: Monad m => ServerT m a -> m ()
restrict _ = return ()
client :: PropertyM MsgPackRpc ()
client = do
run $ wait $ for 50 ms
r1 <- run $ execClient addr $ addC 123 456
assert $ r1 == 123 + 456
r2 <- run $ execClient addr $ echoC "hello"
assert $ r2 == "***hello***"
clientPure :: PureRpcProp ()
clientPure = do
wait $ for 50 ms
r1 <- execClient addr $ addC 123 456
assertPure $ r1 == 123 + 456
r2 <- execClient addr $ echoC "hello"
assertPure $ r2 == "***hello***"
addC :: Int -> Int -> Client Int
addC = call "add"
echoC :: String -> Client String
echoC = call "echo"
-- TODO: this method triggers some msgpack exceptions
-- | Method should execute if called correctly
-- serverMethodShouldExecuteSpec
-- :: (MonadTimed m, MonadRpc m)
-- => NonEmptyList (NonEmptyList Char)
-- -> PropertyM m ()
-- serverMethodShouldExecuteSpec (getNonEmpty -> methodNames') = do
-- mtds <- createMethods methodNames
-- let methodMap = createMethodMap mtds
-- run . fork $ serve port mtds
-- run . wait $ for 500 ms
-- name <- pick $ elements methodNames
-- res <- run . execClient addr $ call name
-- shouldBe <- run $ fromJust $ M.lookup name methodMap <*> pure []
-- assert $ shouldBe == res
-- where methodNames = nub $ map getNonEmpty methodNames'
-- -- TODO: we wouldn't need to do this if Function was defined
-- createMethods :: Monad m => [String] -> PropertyM m [Method m]
-- createMethods = mapM $ \name -> do
-- res <- toObject <$> pick (arbitrary :: Gen Int)
-- return . Method name . const $ return res
-- createMethodMap :: Monad m => [Method m] -> M.Map String ([Object] -> m Object)
-- createMethodMap = M.fromList . map (\m -> (methodName m, methodBody m))