effectful-tracing-0.1.0.0: test/Effectful/Tracing/Instrumentation/AmqpSpec.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : Effectful.Tracing.Instrumentation.AmqpSpec
-- Description : Tests for the broker-free helpers in the RabbitMQ binding.
--
-- The RabbitMQ wrappers ('Effectful.Tracing.Instrumentation.Amqp.publishMsgTraced',
-- 'Effectful.Tracing.Instrumentation.Amqp.getMsgTraced', and
-- 'Effectful.Tracing.Instrumentation.Amqp.withProcessSpan') all need a live
-- broker @Channel@ (or an @Envelope@ that embeds one), so they are covered by the
-- compile-only mirror in "Effectful.Tracing.CompileTest". 'messageHeaders' is the
-- one piece that is pure and broker-free: it decodes the trace-context headers a
-- consumer reads back, so it is exercised here directly.
--
-- Only present when built with @+amqp@.
module Effectful.Tracing.Instrumentation.AmqpSpec
( tests
) where
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import Hedgehog (Gen, forAll, property, (===))
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)
import Test.Tasty.HUnit (testCase, (@?=))
import Network.AMQP (Message (msgHeaders), newMsg)
import Network.AMQP.Types
( FieldTable (FieldTable)
, FieldValue (FVBool, FVInt32, FVString)
)
import Effectful.Tracing.Instrumentation.Amqp (messageHeaders)
tests :: TestTree
tests =
testGroup
"Effectful.Tracing.Instrumentation.Amqp"
[ testGroup
"messageHeaders"
[ testCase "a message with no headers yields no pairs" $
messageHeaders newMsg @?= []
, testCase "an empty header table yields no pairs" $
messageHeaders (withHeaders []) @?= []
, testCase "string-valued headers are decoded to text pairs" $
messageHeaders
(withHeaders [("traceparent", str traceparent), ("tracestate", str "k=v")])
@?= [("traceparent", traceparent), ("tracestate", "k=v")]
, testCase "non-string field values are dropped" $
messageHeaders
( withHeaders
[ ("traceparent", str traceparent)
, ("priority", FVInt32 5)
, ("redelivered", FVBool True)
]
)
@?= [("traceparent", traceparent)]
, testProperty "every string header round-trips through messageHeaders" $
property $ do
entries <- forAll genStringEntries
let fields = [(name, str value) | (name, value) <- entries]
-- Map keying dedupes and orders by key, so compare as sorted maps.
Map.fromList (messageHeaders (withHeaders fields))
=== Map.fromList entries
]
]
where
traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
-- | A 'Message' carrying the given AMQP header table.
withHeaders :: [(Text, FieldValue)] -> Message
withHeaders fields = newMsg {msgHeaders = Just (FieldTable (Map.fromList fields))}
-- | A string-valued AMQP header from UTF-8 text, the way the binding writes them.
str :: Text -> FieldValue
str = FVString . encodeUtf8
-- | Header entries with distinct ASCII keys and arbitrary text values. ASCII
-- keys keep the generator from colliding on UTF-8 normalization while still
-- exercising decoding of arbitrary text values.
genStringEntries :: Gen [(Text, Text)]
genStringEntries =
Gen.list (Range.linear 0 8) $
(,)
<$> Gen.text (Range.linear 1 12) Gen.alphaNum
<*> Gen.text (Range.linear 0 24) Gen.unicode