packages feed

json-rpc-client-0.1.0.0: tests/Tests.hs

{-# LANGUAGE CPP,
             OverloadedStrings,
             TypeOperators #-}

#if MIN_VERSION_mtl(2,2,1)
{-# OPTIONS_GHC -fno-warn-deprecations #-}
#endif

module Tests (tests) where

import Network.JsonRpc.Client
import Network.JsonRpc.ServerAdapter
import Network.JsonRpc.Server (toMethods, rpcError, callWithBatchStrategy)
import qualified Data.Aeson as A
import Data.Aeson ((.=))
import qualified Data.ByteString.Lazy as B
import Data.Text (unpack)
import Data.List (isInfixOf)
import Control.Applicative (pure, empty, (<$>), (<$), (<*>), (<|>))
import Control.Monad.Error (runErrorT, throwError)
import Control.Monad.State (State, runState, evalState, modify, when)
import Test.HUnit hiding (State, Test)
import Test.Framework (Test)
import Test.Framework.Providers.HUnit (testCase)
import Prelude hiding (subtract)

tests :: [Test]
tests = [ testCase "single request" $ runServer (subtract 22 1) @?= (Right 21, 1)

        , testCase "batch with one request" $ myRunBatch (subtractB 1 2) @?= (Right (-1), 1)

        , let result = myRunBatch $ pure (,) <*> subtractB 7 3 <*> divideB 15 12
          in testCase "batch 1" $ result @?= (Right (4, 1.25), 2)

        , let result = myRunBatch $ (+) <$> subtractB 5 3 <*> ((*) <$> subtractB 11 2 <*> subtractB 15 10)
          in testCase "batch 2" $ result @?= (Right 47, 3)

        , let result = myRunBatch $ (*) <$> ((+) <$> subtractB 5 3 <*> subtractB 11 2) <*> subtractB 15 10
          in testCase "batch 3" $ result @?= (Right 55, 3)

        , testCase "single notification" $
                   runServer (subtract_ 1 2) @?= (Right (), 1)

        , testCase "batch with one notification" $
                   myRunBatch (subtractB_ 1 2) @?= (Right (), 1)

        , let result = myRunBatch $ (,) <$> subtractB_ 5 4 <*> subtractB 20 16
          in testCase "notification at start of batch" $ result @?= (Right ((), 4), 2)

        , let result = myRunBatch $ (,) <$> subtractB 5 4 <*> subtractB_ 20 16
          in testCase "notification at end of batch" $ result @?= (Right (1, ()), 2)

        , let result = myRunBatch $ (:) <$> divideB 4 2 <*> ((:[]) <$> divideB 1 0)
          in testCase "batch with error" $ result @?= (Left (-32050), 1)

        , testCase "single request with error" $ runServer (divide 10 0) @?= (Left (-32050), 0)

        , let result = runServer $ runBatch badServer $ (||) <$> pure True <*> pure False
              badServer = error "unnecessarily sent to server"
          in testCase "batch with no requests" $ result @?= (Right True, 0)

        , let result = myRunBatch $ divideB 1 0 <|> divideB 2 1
          in testCase "batch alternative with failure first" $ result @?= (Right 2, 1)

        , let result = myRunBatch $ divideB 2 1 <|> divideB 1 0
          in testCase "batch alternative with failure last" $ result @?= (Right 2, 1)

        , let result = myRunBatch $ divideB 2 0 <|> divideB 1 0
          in testCase "batch alternative with all failures" $ result @?= (Left (-32050), 0)

        , let result = myRunBatch $ divideB 2 1 <|> divideB 1 1
          in testCase "batch alternative with no failures" $ result @?= (Right 2, 2)

        , testCase "empty batch" $ myRunBatch (empty :: Batch String) @?= (Left (-31999), 0)

        , testCase "bad JSON response" $
                   assertErrorMsg (A.encode $ A.Number 3) ["Client cannot parse JSON response"]

        , let response = A.encode $ A.object [ "result" .= A.Number 3
                                             , "jsonrpc" .= A.String "2.0" ]
          in testCase "missing response id attribute" $ assertErrorMsg response
                 [ "Client cannot parse JSON response"
                 , "key \"id\" not present" ]

        , let response = A.encode [A.String "element"]
          in testCase "non-object response" $
             assertErrorMsg response ["expecting a JSON-RPC response"]

        , let response = A.encode $ A.object [ "id" .= A.Number 3
                                             , "result" .= True
                                             , "jsonrpc" .= A.String "2.0"]
          in testCase "wrong response result type" $
             assertErrorMsg response ["wrong result type"]
        ]

type Result r = RpcResult (State Int) r

subtractSig :: Signature (Int ::: Int ::: ()) Int
subtractSig = Signature "subtract" ("x" ::: "y" ::: ())

divideSig :: Signature (Double ::: Double ::: ()) Double
divideSig = Signature "divide" ("x" ::: "y" ::: ())

subtract = toFunction myServer subtractSig

subtract_ = toFunction_ myServer subtractSig

subtractB = toBatchFunction subtractSig

subtractB_ = toBatchFunction_ subtractSig

divide = toFunction myServer divideSig

divideB = toBatchFunction divideSig

runServer :: Result a -> (Either Int a, Int)
runServer server = runState (mapLeft errCode <$> runErrorT server) 0
    where mapLeft f (Left x) = Left $ f x
          mapLeft _ (Right x) = Right x

myRunBatch :: A.FromJSON a => Batch a -> (Either Int a, Int)
myRunBatch = runServer . runBatch myServer

myServer :: B.ByteString -> State Int (Maybe B.ByteString)
myServer = callWithBatchStrategy (sequence . reverse) methods

assertErrorMsg :: B.ByteString -> [String] -> Assertion
assertErrorMsg response expected = (runServer result @?= (Left (-31999), 0)) >>
                                   mapM_ (assertErrorMsgContains result) expected
                 where result = toFunction badServer subtractSig 10 1
                       badServer = constServer response

constServer :: B.ByteString -> B.ByteString -> State Int (Maybe B.ByteString)
constServer = const . return . Just

assertErrorMsgContains :: Result a -> String -> Assertion
assertErrorMsgContains server expected = case evalState (runErrorT server) 0 of
                                           Right _ -> assertFailure "Expected an error, but got a result."
                                           Left err -> assertBool msg $ expected `isInfixOf` errorMsg
                                               where errorMsg = unpack $ errMsg err
                                                     msg = "Wrong error message: " ++ errorMsg

methods = toMethods [subtractMethod, divideMethod]

subtractMethod = toServerMethod subtractSig f
    where f :: Int -> Int -> RpcResult (State Int) Int
          f x y = (x - y) <$ modify (+1)

divideMethod = toServerMethod divideSig f
    where f :: Double -> Double -> RpcResult (State Int) Double
          f x y = do
            when (y == 0) $ throwError $ rpcError (-32050) "divide by zero"
            x / y <$ modify (+1)