packages feed

utxorpc-client-0.0.4.1: test/logged_test.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE RankNTypes #-}

module Main (main) where

import Control.Monad (foldM)
import Data.IORef (newIORef, readIORef, writeIORef)
import Data.List (foldl')
import Data.ProtoLens.Message (defMessage)
import Network.GRPC.Client (HeaderList, RawReply)
import Network.GRPC.HTTP2.ProtoLens (RPC (RPC))
import Proto.Utxorpc.V1alpha.Sync.Sync
import Test.Hspec
import Utxorpc.Logged
import "http2-client" Network.HTTP2.Client (ClientError, TooMuchConcurrency)

type UnaryReq = FetchBlockRequest

unaryReq :: UnaryReq
unaryReq = defMessage

type UnaryReply = FetchBlockResponse

type UnaryRpc = RPC SyncService "fetchBlock"

unaryRpc :: UnaryRpc
unaryRpc = RPC

type StreamReq = FollowTipRequest

streamReq :: StreamReq
streamReq = defMessage

type StreamReply = FollowTipResponse

streamReplies :: Int -> [StreamReply]
streamReplies n = replicate n defMessage

type StreamRpc = RPC SyncService "followTip"

streamRpc :: StreamRpc
streamRpc = RPC

mockLogger :: (String -> IO ()) -> UtxorpcClientLogger IO
mockLogger f =
  UtxorpcClientLogger
    (mockRequestLogger f)
    (mockReplyLogger f)
    (mockStreamLogger f)
    (mockStreamEndLogger f)
    id

showIndex :: Int -> String
showIndex i = " (" ++ show i ++ ")"

mockRequestLogger :: (String -> IO ()) -> RequestLogger IO
mockRequestLogger f _ _ _ msg = f $ requestLoggerOut msg

requestLoggerOut :: (Show a) => a -> String
requestLoggerOut msg = "REQUEST_LOGGER" ++ show msg

mockReplyLogger :: (String -> IO ()) -> ReplyLogger IO
mockReplyLogger f _ _ _ msg = f $ replyLoggerOut msg

replyLoggerOut ::
  (Show a) =>
  Either ClientError (Either TooMuchConcurrency (RawReply a)) ->
  String
replyLoggerOut (Right (Right (Right (_, _, Right msg)))) = "REPLY_LOGGER" ++ show msg
replyLoggerOut _ = "REPLY_LOGGER: error"

mockStreamLogger :: (String -> IO ()) -> ServerStreamLogger IO
mockStreamLogger f _ _ (_, index) msg = f $ streamLoggerOut index msg

streamLoggerOut :: (Show a) => Int -> a -> String
streamLoggerOut index msg = "STREAM_LOGGER" ++ showIndex index ++ show msg

mockStreamEndLogger :: (String -> IO ()) -> ServerStreamEndLogger IO
mockStreamEndLogger f _ _ (_, index) _ = f $ streamEndLoggerOut index

streamEndLoggerOut :: Int -> String
streamEndLoggerOut index = "STREAM_END_LOGGER" ++ showIndex index

mockUnaryExecutor :: UnaryExecutor r i UnaryReply
mockUnaryExecutor _ _ _ = return $ Right $ Right ([], Nothing, Right defMessage)

mockServerStreamExecutor :: Int -> ServerStreamExecutor r i StreamReply
mockServerStreamExecutor n _ _ initStreamState _ inStream = do
  finalStreamState <- foldM run initStreamState $ streamReplies n
  return $ Right (finalStreamState, [], [])
  where
    run state = inStream state []

mockInStream :: (String -> IO ()) -> Int -> HeaderList -> StreamReply -> IO Int
mockInStream logF index _ msg = do
  logF $ inStreamOut index msg
  return $ index + 1

inStreamOut :: (Show o) => Int -> o -> String
inStreamOut index msg = "IN_STREAM_OUT" ++ showIndex index ++ show msg

testUnary :: IO ()
testUnary = do
  out <- newIORef ""
  let logF str = writeIORef out . (++ str ++ "\n") =<< readIORef out
  _reply <-
    loggedUnary'
      mockUnaryExecutor
      (Just $ mockLogger logF)
      unaryRpc
      undefined
      unaryReq
  out' <- readIORef out

  hspec $ describe "Unary Logging" $ do
    it "should log the request" $ do
      head (lines out') `shouldBe` requestLoggerOut unaryReq
    it "should log the reply" $ do
      tail (lines out')
        `shouldBe` [ replyLoggerOut $
                       Right (Right (Right ([], Nothing, Right (defMessage :: UnaryReply))))
                   ]

testStream :: IO ()
testStream = do
  out <- newIORef ""
  let logF str = writeIORef out . (++ str ++ "\n") =<< readIORef out
  let numStreamMsgs = 3

  _finalStreamState <-
    loggedSStream'
      (mockServerStreamExecutor numStreamMsgs)
      (Just $ mockLogger logF)
      streamRpc
      undefined
      0
      streamReq
      (mockInStream logF)
  out' <- readIORef out

  hspec $ describe "Stream logging" $ do
    it "should log the request" $ do
      head (lines out') `shouldBe` requestLoggerOut streamReq
    it "should log the stream" $ do
      let msgsWithIndex = zip [0 .. numStreamMsgs] (streamReplies numStreamMsgs)
      let targetStreamLogs (index, msg) = [streamLoggerOut index msg, inStreamOut index msg]
      let f logs (index, msg) = logs ++ targetStreamLogs (index, msg)
      init (tail (lines out'))
        `shouldBe` foldl' f [] msgsWithIndex
    it "should log the end of the stream" $ do
      last (lines out') `shouldBe` streamEndLoggerOut numStreamMsgs

main :: IO ()
main = do
  testUnary
  testStream