packages feed

grapesy-1.0.0: test-grapesy/Test/Sanity/Compression.hs

{-# LANGUAGE OverloadedLabels  #-}
{-# LANGUAGE OverloadedStrings #-}

module Test.Sanity.Compression (tests) where

import Control.Monad
import Data.IORef
import Data.Maybe (isJust)
import Data.Text (Text)
import Test.Tasty
import Test.Tasty.HUnit

import Network.GRPC.Client qualified as Client
import Network.GRPC.Common
import Network.GRPC.Common.Protobuf
import Network.GRPC.Common.StreamElem qualified as StreamElem
import Network.GRPC.Server qualified as Server

import Test.Driver.ClientServer

import Proto.API.Helloworld

{-------------------------------------------------------------------------------
  Top-level
-------------------------------------------------------------------------------}

tests :: TestTree
tests = testGroup "Test.Sanity.Compression" [
      testCase "multipleRPC"  test_multipleRPC
    , testCase "multipleMsgs" test_multipleMsgs
    ]

{-------------------------------------------------------------------------------
  Individual tests
-------------------------------------------------------------------------------}

-- | Test that compression is enabled for the /second/ RPC
test_multipleRPC :: Assertion
test_multipleRPC = do
    counter <- newIORef 0
    testClientServer $ ClientServerTest {
        config = def
      , server = [Server.someRpcHandler $ handleNonStreaming counter]
      , client = simpleTestClient $ \conn ->
            replicateM_ 2 $ do
              Client.withRPC conn def (Proxy @SayHello) $ \call -> do
                Client.sendFinalInput call req
                mResp <- StreamElem.value <$> Client.recvOutputWithMeta call
                case mResp of
                  Nothing -> assertFailure "Expected response"
                  Just (meta, resp) -> do
                    -- /All/ responses from the server should be compressed
                    -- (the request tells the server what the client supports)
                    assertEqual "" True $
                      isJust (inboundCompressedSize meta)
                    assertEqual "" compressibleName $
                      resp ^. #message
    }
  where
    req :: Proto HelloRequest
    req = defMessage & #name .~ compressibleName

-- | Test that multiple messages on /one/ RPC will either all be compressed or
-- all uncompressed.
test_multipleMsgs :: Assertion
test_multipleMsgs = do
    counter <- newIORef 0
    testClientServer $ ClientServerTest {
        config = def
      , server = [Server.someRpcHandler $ handleBidiStreaming counter]
      , client = simpleTestClient $ \conn ->
          replicateM_ 2 $
            Client.withRPC conn def (Proxy @SayHelloBidiStream) $ \call -> do
              replicateM_ 2 $ do
                Client.sendNextInput call req
                mResp <- StreamElem.value <$> Client.recvOutputWithMeta call
                case mResp of
                  Nothing -> assertFailure "Expected response"
                  Just (meta, resp) -> do
                    -- /All/ responses from the server should be compressed
                    -- (the request tells the server what the client supports)
                    assertEqual "" True $
                      isJust (inboundCompressedSize meta)
                    assertEqual "" compressibleName $
                      resp ^. #message
              Client.sendEndOfInput call
      }
  where
    req :: Proto HelloRequest
    req = defMessage & #name .~ compressibleName

{-------------------------------------------------------------------------------
  Server handlers
-------------------------------------------------------------------------------}

handleNonStreaming :: IORef Int -> Server.RpcHandler IO SayHello
handleNonStreaming counter = Server.mkRpcHandler $ \call -> do
    mElem <- Server.recvInputWithMeta call
    case mElem of
      FinalElem (meta, req) NoMetadata -> do
        callNo <- atomicModifyIORef counter $ \i -> (succ i, i)

        -- We expect all messages to be compressed except the first (the client
        -- does not yet know which compression algorithms the server supports)
        let expectCompression :: Bool
            expectCompression = callNo > 0
        assertEqual "" expectCompression $
          isJust (inboundCompressedSize meta)

        Server.sendFinalOutput call (
            defMessage & #message .~ (req ^. #name)
          , NoMetadata
          )
      _otherwise ->
        assertFailure "expected FinalElem"

handleBidiStreaming :: IORef Int -> Server.RpcHandler IO SayHelloBidiStream
handleBidiStreaming counter = Server.mkRpcHandler $ \call -> do
    isFirstCall <- atomicModifyIORef counter $ \i -> (succ i, i == 0)

    let loop :: IO ()
        loop = do
          mElem <- Server.recvInputWithMeta call
          case mElem of
            NoMoreElems NoMetadata ->
              Server.sendTrailers call NoMetadata
            StreamElem (meta, req) -> do
              -- The compression algorithm is established once at the start of
              -- the request; we cannot start compression halfway a conversation
              let expectCompression :: Bool
                  expectCompression = not isFirstCall
              assertEqual "" expectCompression $
                isJust (inboundCompressedSize meta)

              Server.sendNextOutput call $
                defMessage & #message .~ (req ^. #name)
              loop
            FinalElem{} ->
              assertFailure "Unexpected FinalElem"

    loop

{-------------------------------------------------------------------------------
  Auxiliary
-------------------------------------------------------------------------------}

compressibleName :: Text
compressibleName = mconcat (replicate 100 "John")