grapesy-1.0.0: interop/Interop/Client/Common.hs
-- | Functionality required by many test cases
module Interop.Client.Common (
-- * Setup
enableInitCompression
-- * Construct server inputs
, mkSimpleRequest
, mkStreamingInputCallRequest
, mkStreamingOutputCallRequest
-- * Verify server outputs
, verifySimpleResponse
, verifyStreamingOutputCallResponse
, verifyStreamingOutputs
, expectInvalidArgument
) where
import Data.ByteString qualified as BS.Strict
import Network.GRPC.Client
import Network.GRPC.Common
import Network.GRPC.Common.Compression qualified as Compr
import Network.GRPC.Common.Protobuf
import Interop.Util.Exceptions
import Interop.Util.Messages
import Proto.API.Interop
{-------------------------------------------------------------------------------
Config
-------------------------------------------------------------------------------}
-- | Enable compression of the first message
--
-- Some test specifications (@client_compressed_unary@,
-- @client_compressed_streaming@) tell us to
--
-- 1. First send a request that causes the server to send an error response
-- (which will use the gRPC Trailers-Only case)
-- 2. Then send a compressed message
-- 3. Finally, send an uncompressed message
--
-- This is problematic: we don't know which compression algorithms the server
-- supports until we have had a successful response from the server. We could
-- fix this by swapping (2) and (3), but this would be deviating from the test
-- specification. Instead, we override the initial compression to use, and
-- report the test as skipped if the server comes back with 'GrpcUnimplemented'.
enableInitCompression :: ConnParams
enableInitCompression = def {
connInitCompression = Just Compr.gzip
}
{-------------------------------------------------------------------------------
Construct server inputs
-------------------------------------------------------------------------------}
mkSimpleRequest ::
Bool -- ^ Should the server expect the message to be compressed?
-> Proto SimpleRequest
mkSimpleRequest expectCompressed =
defMessage
& #expectCompressed .~ boolValue expectCompressed
& #responseSize .~ 314159
& #payload .~ payloadOfZeroes 271828
mkStreamingInputCallRequest ::
Bool -- ^ Should the server expect the message to be compressed?
-> Int -- ^ Payload size
-> Proto StreamingInputCallRequest
mkStreamingInputCallRequest expectCompressed size =
defMessage
& #expectCompressed .~ boolValue expectCompressed
& #payload .~ payloadOfZeroes size
mkStreamingOutputCallRequest ::
[(Bool, Int)] -- ^ Size and compression of each expected response
-> Maybe Int -- ^ Size of the payload, if any
-> Proto StreamingOutputCallRequest
mkStreamingOutputCallRequest expectedSizes payload =
defMessage
& #responseParameters .~ [
defMessage
& #size .~ fromIntegral sz
& #compressed .~ boolValue compressed
| (compressed, sz) <- expectedSizes
]
& ( case payload of
Nothing -> id
Just sz -> #payload .~ payloadOfZeroes sz
)
{-------------------------------------------------------------------------------
Verify server outputs
-------------------------------------------------------------------------------}
verifySimpleResponse :: HasCallStack => Proto SimpleResponse -> IO ()
verifySimpleResponse resp = do
assertEqual 314159 $ BS.Strict.length (resp ^. #payload . #body)
assertBool $ BS.Strict.all (== 0) (resp ^. #payload . #body)
verifyStreamingOutputCallResponse ::
Int -- ^ Expected size
-> Proto StreamingOutputCallResponse -> IO ()
verifyStreamingOutputCallResponse expectedSize resp = do
assertEqual expectedSize $ BS.Strict.length (resp ^. #payload . #body)
assertBool $ BS.Strict.all (== 0) (resp ^. #payload . #body)
verifyStreamingOutputs :: forall rpc.
HasCallStack
=> Call rpc
-> (ProperTrailers' -> IO ()) -- ^ Verify trailers
-> [(InboundMeta, Output rpc) -> IO ()] -- ^ Verifier per expected output
-> IO ()
verifyStreamingOutputs call verifyTrailers = go
where
go :: [(InboundMeta, Output rpc) -> IO ()] -> IO ()
go verifiers = do
mResp <- recvOutputWithMeta call
case (mResp, verifiers) of
(NoMoreElems trailers, []) -> verifyTrailers trailers
(StreamElem{}, []) -> assertFailure "Too many outputs"
(FinalElem{}, []) -> assertFailure "Too many outputs"
(NoMoreElems{}, _:_) -> assertFailure "Not enough outputs"
(FinalElem{}, _:_:_) -> assertFailure "Not enough outputs"
(StreamElem resp, v:vs) -> v resp >> go vs
(FinalElem resp trailers, [v]) -> v resp >> verifyTrailers trailers
-- | Expect the server to respond with 'InvalidArgument'
--
-- Throws 'TestSkipped' if the server responds with normal response instead
-- (indicating that the server was unable to perform the required check on
-- the inputs we sent it).
--
-- Also throws 'TestSkipped' is the server returns with 'GrpcUnimplemented'.
expectInvalidArgument :: IO a -> IO ()
expectInvalidArgument = assertThrows $ \exception ->
case grpcError exception of
GrpcInvalidArgument ->
return ()
GrpcUnimplemented ->
throwIO $ TestSkipped "Server sent UNIMPLEMENTED"
err ->
assertFailure $ "Unexpected failure " ++ show err