json-rpc-0.6.2.0: test/Network/JsonRpc/Tests.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Rank2Types #-}
module Network.JsonRpc.Tests (tests) where
import Control.Applicative
import Control.Concurrent.Async.Lifted
import Control.Concurrent.STM
import qualified Data.ByteString.Lazy as L
import Data.Conduit
import qualified Data.Conduit.List as CL
import Data.Conduit.TMChan
import Control.Monad
import Control.Monad.Logger
import Control.Monad.Trans
import Data.Aeson
import Data.Aeson.Types
import Data.Maybe
import Network.JsonRpc
import Test.QuickCheck
import Test.QuickCheck.Monadic
import Test.Framework
import Test.Framework.Providers.QuickCheck2
tests :: [Test]
tests =
[ testGroup "JSON-RPC Requests"
[ testProperty "Check fields"
(reqFields :: Request -> Bool)
, testProperty "Encode/decode"
(testEncodeDecode :: Request -> Bool)
]
, testGroup "JSON-RPC Responses"
[ testProperty "Check fields"
(resFields :: Response -> Bool)
, testProperty "Encode/decode"
(testEncodeDecode :: Response -> Bool)
]
, testGroup "Network"
[ testProperty "Test server" serverTest
, testProperty "Test client" clientTest
, testProperty "Test notifications" notifTest
]
]
checkVerId :: Ver -> Maybe Id -> Object -> Parser Bool
checkVerId ver i o = do
j <- o .:? "jsonrpc"
guard $ if ver == V2 then j == Just (String "2.0") else isNothing j
o .:? "id" >>= guard . (==i)
return True
checkFieldsReqNotif
:: Ver -> Method -> Value -> Maybe Id -> Object -> Parser Bool
checkFieldsReqNotif ver m v i o = do
checkVerId ver i o >>= guard
o .: "method" >>= guard . (==m)
o .: "params" >>= guard . (==v)
return True
checkFieldsReq :: Request -> Object -> Parser Bool
checkFieldsReq (Request ver m v i) = checkFieldsReqNotif ver m v (Just i)
checkFieldsReq (Notif ver m v) = checkFieldsReqNotif ver m v Nothing
checkFieldsRes :: Response -> Object -> Parser Bool
checkFieldsRes (Response ver v i) o = do
checkVerId ver (Just i) o >>= guard
o .: "result" >>= guard . (==v)
return True
checkFieldsRes (ResponseError ver e i) o = do
checkVerId ver (Just i) o >>= guard
o .: "error" >>= guard . (==e)
return True
checkFieldsRes (OrphanError ver e) o = do
checkVerId ver Nothing o >>= guard
o .: "error" >>= guard . (==e)
return True
testFields :: ToJSON r => (Object -> Parser Bool) -> r -> Bool
testFields ck r = fromMaybe False . parseMaybe f $ toJSON r where
f = withObject "json" ck
testEncodeDecode :: (Eq r, ToJSON r, FromJSON r) => r -> Bool
testEncodeDecode r = maybe False (==r) $ parseMaybe parseJSON (toJSON r)
reqFields :: Request -> Bool
reqFields rq = testFields (checkFieldsReq rq) rq
resFields :: Response -> Bool
resFields rs = testFields (checkFieldsRes rs) rs
createChans :: MonadIO m
=> m ((TBMChan a, TBMChan b), (Sink a m (), Source m b))
createChans = do
(bso, bsi) <- liftIO . atomically $ (,) <$> newTBMChan 16 <*> newTBMChan 16
let (snk, src) = (sinkTBMChan bso False, sourceTBMChan bsi)
return ((bso, bsi), (snk, src))
serverTest :: ([Request], Ver) -> Property
serverTest (reqs, ver) = monadicIO $ do
rt <- run $ runNoLoggingT $ do
((bso, bsi), (snk, src)) <- createChans
withAsync (server snk src) $ const $
withAsync (sender bsi) $ const $ receiver bso []
assert $ length rt == length nonotif
assert $ null rt || all isJust rt
assert $ params == reverse (results rt)
where
respond q = return $ Right (q :: Value)
server snk src = runJsonRpcT ver False
(encodeConduit =$ snk) (src =$ decodeConduit ver) (srv respond)
sender bsi = forM_ reqs $ liftIO . atomically .
writeTBMChan bsi . L.toStrict . encode . MsgRequest
receiver bso xs =
if length xs == length nonotif
then return xs
else liftIO (atomically $ readTBMChan bso) >>= \b -> case b of
Just x -> do
let res = decodeStrict' x :: Maybe Response
receiver bso (res:xs)
Nothing -> undefined
params = map getReqParams nonotif
results = map $ getResult . fromJust
nonotif = flip filter reqs $ \q -> case q of Request{} -> True
Notif{} -> False
clientTest :: ([Value], Ver) -> Property
clientTest (qs, ver) = monadicIO $ do
rt <- run $ runNoLoggingT $ do
((bso, bsi), (snk, src)) <- createChans
let csnk = sinkTBMChan bsi False
csrc = sourceTBMChan bso
withAsync (server snk src) $ const $ cli
(CL.map Right =$ csnk)
(csrc $= CL.map Right)
assert $ length rt == length qs
assert $ null rt || all correct rt
assert $ qs == results rt
where
respond q = return $ Right (q :: Value)
server snk src = runJsonRpcT ver False snk src (srv respond)
cli snk src = runJsonRpcT ver True snk src . forM qs $ sendRequest
results = map $ fromRight . fromJust
correct (Just (Right _)) = True
correct _ = False
notifTest :: ([Request], Ver) -> Property
notifTest (qs, ver) = monadicIO $ do
nt <- run $ runNoLoggingT $ do
((bso, bsi), (snk, src)) <- createChans
let csnk = sinkTBMChan bsi False
csrc = sourceTBMChan bso
(sig, notifs) <- liftIO . atomically $
(,) <$> newEmptyTMVar <*> newTVar []
withAsync (server snk src sig notifs) $ const $ cli sig
(CL.map Right =$ csnk)
(csrc $= CL.map Right)
liftIO . atomically $ readTVar notifs
assert $ length nt == length ntfs
assert $ reverse nt == ntfs
where
respond q = return $ Right (q :: Value)
server snk src sig notifs =
runJsonRpcT ver False snk src $ process sig notifs
process sig notifs = do
qM <- receiveRequest
case qM of
Nothing -> return ()
Just q -> do
case q of
Notif{} -> liftIO . atomically $
readTVar notifs >>= writeTVar notifs . (q:)
Request{ getReqParams = String "disconnect" } ->
liftIO . atomically $ putTMVar sig ()
Request{} -> return ()
rM <- buildResponse respond q
case rM of
Nothing -> process sig notifs
Just r -> sendResponse r >> process sig notifs
reqs = map MsgRequest qs
cli sig snk src = runJsonRpcT ver True snk src $ do
forM_ reqs sendMessage
_ <- sendRequest $ String "disconnect"
:: JsonRpcT (NoLoggingT IO) (Maybe (Either ErrorObj Value))
liftIO . atomically $ takeTMVar sig
ntfs = flip filter qs $ \q -> case q of Notif{} -> True; _ -> False
srv :: (MonadLoggerIO m, FromRequest q, ToJSON r)
=> Respond q (JsonRpcT m) r -> JsonRpcT m ()
srv respond = do
qM <- receiveRequest
case qM of
Nothing -> return ()
Just q -> do
rM <- buildResponse respond q
case rM of
Nothing -> srv respond
Just r -> sendResponse r >> srv respond
fromRight :: Either a b -> b
fromRight (Right x) = x
fromRight _ = undefined