packages feed

greskell-websocket-1.0.0.6: test/ServerTest/Client.hs

{-# LANGUAGE OverloadedStrings #-}
module ServerTest.Client
    ( main
    , spec
    ) where

import           Control.Concurrent                    (threadDelay)
import           Control.Concurrent.Async              (mapConcurrently, withAsync)
import           Control.Exception.Safe                (bracket)
import           Control.Monad                         (forM_)
import           Data.Aeson                            (Value (Number))
import qualified Data.Aeson.KeyMap                     as KM
import           Data.HashMap.Strict                   (HashMap)
import           Data.Maybe                            (fromJust, isJust, isNothing)
import           Data.Text                             (Text)
import qualified Data.Vector                           as V
import qualified Network.WebSockets                    as WS
import           Test.Hspec

import           Data.Greskell.GMap                    (GMap, GMapEntry, unGMapEntry)
import           Data.Greskell.Greskell                (Greskell)
import qualified Data.Greskell.Greskell                as G

import           Network.Greskell.WebSocket.Client     (Client, Host, Options, Port,
                                                        SubmitException (ParseError, ResponseError),
                                                        batchSize, close, connectWith,
                                                        connectionSettings, defOptions, nextResult,
                                                        responseTimeout, slurpResults, submit)
import           Network.Greskell.WebSocket.Connection (RequestException (ResponseTimeout))
import           Network.Greskell.WebSocket.Request    (RequestMessage (requestId))
import qualified Network.Greskell.WebSocket.Response   as Res

import           TestUtil.Env                          (withEnvForExtServer, withEnvForIntServer)
import           TestUtil.MockServer                   (receiveRequest, simpleRawResponse,
                                                        waitForServer, wsServer)

main :: IO ()
main = hspec spec

spec :: Spec
spec = describe "Client" $ do
  withEnvForExtServer $ do
    client_basic_spec
  withEnvForIntServer $ do
    bad_server_spec

withClient :: (Client -> IO a) -> (Host, Port) -> IO a
withClient act (host, port) = forClient' defOptions host port act

forClient :: Host -> Port -> (Client -> IO a) -> IO a
forClient = forClient' defOptions

forClient' :: Options -> Host -> Port -> (Client -> IO a) -> IO a
forClient' opt host port act = bracket (connectWith opt host port) close act

client_basic_spec :: SpecWith (Host, Port)
client_basic_spec = do
  specify "eval Int" $ withClient $ \client -> do
    let g = 108 :: Greskell Int
    rh <- submit client g Nothing
    nextResult rh `shouldReturn` Just 108
    nextResult rh `shouldReturn` Nothing
    nextResult rh `shouldReturn` Nothing
  specify "eval Text" $ withClient $ \client -> do
    let g = "hoge" :: Greskell Text
    rh <- submit client g Nothing
    nextResult rh `shouldReturn` Just "hoge"
    nextResult rh `shouldReturn` Nothing
    nextResult rh `shouldReturn` Nothing
  specify "eval [Int]" $ withClient $ \client -> do
    let g = G.list $ map fromInteger [1..20] :: Greskell [Int]
    rh <- submit client g Nothing
    forM_  [1..20] $ \n ->
      nextResult rh `shouldReturn` Just n
    nextResult rh `shouldReturn` Nothing
    nextResult rh `shouldReturn` Nothing
  specify "eval (GMap HashMap Int Text)" $ withClient $ \client -> do
    let g :: Greskell (GMap HashMap Int Text)
        g = G.unsafeGreskell "[100: 'hoge', 200: 'foo', 300: 'bar']"
    rh <- submit client g Nothing
    got <- fmap (fmap unGMapEntry) $ slurpResults rh
    (V.toList got) `shouldMatchList` [(100, "hoge"), (200, "foo"), (300, "bar")]
  specify "eval (Maybe Int)" $ withClient $ \client -> do
    let g :: Greskell (Maybe Int)
        g = G.unsafeGreskell "100"
    rh <- submit client g Nothing
    slurpResults rh `shouldReturn` V.fromList [Just 100]
  specify "eval (bound Double)" $ withClient $ \client -> do
    let gx = G.unsafeGreskell "x"
        g = 92.125 + gx :: Greskell Double
        b = KM.fromList [("x", Number 22.25)]
    rh <- submit client g (Just b)
    slurpResults rh `shouldReturn` V.fromList [114.375]
  specify "multiple response messages" $ \(host, port) -> do
    let opt = defOptions { batchSize = Just 3
                         }
        g = G.list $ map fromInteger [1..31] :: Greskell [Int]
    forClient' opt host port $ \client -> do
      rh <- submit client g Nothing
      slurpResults rh `shouldReturn` V.fromList [1..31]
  specify "ParseError exception" $ withClient $ \client -> do
    let g :: Greskell Int
        g = G.unsafeGreskell "\"some string\""
        expEx (ParseError _ _) = True
        expEx _                = False
    rh <- submit client g Nothing
    nextResult rh `shouldThrow` expEx
    nextResult rh `shouldThrow` expEx
  specify "multiple threads reading on a single ResultHandle" $ \(host, port) -> do
    let opt = defOptions { batchSize = Just 3
                         }
        g = G.list $ map fromInteger [1..50] :: Greskell [Int]
    forClient' opt host port $ \client -> do
      rh <- submit client g Nothing
      mgot <- mapConcurrently (const $ nextResult rh) ([1..52] :: [Int])
      nextResult rh `shouldReturn` Nothing
      (length $ filter isNothing mgot) `shouldBe` 2
      let got = map fromJust $ filter isJust mgot
      got `shouldMatchList` [1..50]
  specify "Gremlin script throwing exception" $ withClient $ \client -> do
    let g :: Greskell Int
        g = G.unsafeGreskell "throw new Exception(\"BOOM\")"
        expEx (ResponseError res) = (Res.code $ Res.status res) == Res.ScriptEvaluationError
        expEx _                   = False
    rh <- submit client g Nothing
    nextResult rh `shouldThrow` expEx
    nextResult rh `shouldThrow` expEx


bad_server_spec :: SpecWith Port
bad_server_spec = do
  specify "error ResponseMessage" $ \port -> do
    let server = wsServer port $ \wsconn -> do
          req <- receiveRequest wsconn
          WS.sendBinaryData wsconn $ simpleRawResponse (requestId req) 500 "null"
        expEx (ResponseError res) = (Res.code $ Res.status res) == Res.ServerError
        expEx _                   = False
    withAsync server $ \_ -> do
      waitForServer
      forClient "localhost" port $ \client -> do
        let g = 100 :: Greskell Int
        rh <- submit client g Nothing
        nextResult rh `shouldThrow` expEx
        nextResult rh `shouldThrow` expEx
        nextResult rh `shouldThrow` expEx
  specify "response timeout" $ \port -> do
    let server = wsServer port $ \wsconn -> do
          req <- receiveRequest wsconn
          WS.sendBinaryData wsconn $ simpleRawResponse (requestId req) 206 "[99]"
          threadDelay 10000000
        opt = defOptions { connectionSettings = sett
                         }
        sett = (connectionSettings defOptions)
               { responseTimeout = 1
               }
        expEx ResponseTimeout = True
        expEx _               = False
    withAsync server $ \_ -> do
      waitForServer
      forClient' opt "localhost" port $ \client -> do
        let g = G.list [99, 100, 101, 102] :: Greskell [Int]
        rh <- submit client g Nothing
        nextResult rh `shouldReturn` Just 99
        nextResult rh `shouldThrow` expEx
        nextResult rh `shouldThrow` expEx