packages feed

grapesy-1.2.0: test-grapesy/Test/Sanity/Metadata.hs

{-# OPTIONS_GHC -Wno-orphans #-}
module Test.Sanity.Metadata (tests) where

import Control.Monad
import Data.Binary (Binary)
import Data.ByteString qualified as BSS
import Data.ByteString qualified as Strict (ByteString)
import Data.String
import GHC.Generics (Generic)
import Test.Driver.ClientServer
import Test.Tasty
import Test.Tasty.HUnit
import Text.Printf

import Network.GRPC.Client qualified as Client
import Network.GRPC.Client.Binary qualified as Client.Binary
import Network.GRPC.Common
import Network.GRPC.Common.Binary
import Network.GRPC.Server qualified as Server
import Network.GRPC.Server.Binary qualified as Server.Binary
import Network.GRPC.Spec.Serialization qualified as Spec

tests :: TestTree
tests = testGroup "Test.Sanity.Metadata" [
      testCase "summarizeAndEcho" $ test_summarizeAndEcho 10
    ]

{-------------------------------------------------------------------------------
  Trailers
-------------------------------------------------------------------------------}

-- | Sanity check: test that the server can receive client metadata and can
-- echo it as trailing metadata. We also verify that the server can /announce/
-- that trailing metadata before it sends it.
test_summarizeAndEcho :: Int -> Assertion
test_summarizeAndEcho n = testClientServer $ ClientServerTest {
      config = def{serverPort = Right 50051}
    , server = [Server.someRpcHandler execInstr]
    , client = simpleTestClient $ \conn -> do
          Client.withRPC conn callParams (Proxy @ExecInstr) $ \call -> do
            Client.Binary.sendFinalInput call SummarizeAndEcho

            -- Check that trailers announced
            initResponse <- Client.recvInitialResponse call
            case initResponse of
              Left trailersOnly ->
                assertFailure $ "Unexpected trailers-only " ++ show trailersOnly
              Right x ->
                case Client.responseTrailerNames x of
                  Left err ->
                    assertFailure $ show err
                  Right Nothing ->
                    assertFailure "Trailer not present"
                  Right (Just names) ->
                    forM_ metadata $ \md ->
                      assertBool ("Missing " ++ show md) $ flip elem names $
                        Spec.buildHeaderName (customMetadataName md)

            -- Check summary and trailing metadata
            (summary, trailers) <- Client.Binary.recvFinalOutput call
            assertEqual "" (summarize metadata) $ summary
            assertEqual "" metadata             $ trailers

    }
  where
    metadata :: [CustomMetadata]
    metadata = [
          CustomMetadata
            (fromString $ "md-" ++ printf "%02d" i) -- for sorting purposes
            (fromString $ show i)
        | i <- [1 .. n]
        ]

    callParams :: Client.CallParams ExecInstr
    callParams = def{Client.callRequestMetadata = metadata}

{-------------------------------------------------------------------------------
  Server handler
-------------------------------------------------------------------------------}

type ExecInstr = RawRpc "TestMetadata" "ExecInstr"

type instance RequestMetadata          ExecInstr = [CustomMetadata]
type instance ResponseInitialMetadata  ExecInstr = [CustomMetadata]
type instance ResponseTrailingMetadata ExecInstr = [CustomMetadata]

data Instruction =
    -- | Summary the request metadata, and echo it as trailing metadata
    SummarizeAndEcho
  deriving stock (Generic)
  deriving anyclass (Binary)

execInstr :: Server.RpcHandler IO ExecInstr
execInstr = Server.mkRpcHandlerNoDefMetadata $ \call -> do
    requestMetadata <- Server.getRequestMetadata call
    instr <- Server.Binary.recvFinalInput call
    case instr of
      SummarizeAndEcho -> do
        -- We need to explicitly set the trailers, because they vary from one
        -- request to the next (that is, they aren't static)
        Server.setResponseInitialMetadataAndTrailers call [] . Just $
          map customMetadataName requestMetadata
        Server.Binary.sendFinalOutput @Summary call (
            summarize requestMetadata
          , requestMetadata
          )

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

type Summary = [(Strict.ByteString, Int)]

summarize :: [CustomMetadata] -> Summary
summarize = map aux
  where
    aux :: CustomMetadata -> (Strict.ByteString, Int)
    aux md = (
          getHeaderName $ customMetadataName  md
        , BSS.length    $ customMetadataValue md
        )