-- | 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