packages feed

kafka-effectful-0.3.1.0: test/Kafka/Effectful/Consumer/ClassifyTest.hs

-- | Pins the in-band error taxonomy the consumer interpreters apply.
--
-- Laid out as label\/expectation tables deliberately, mirroring
-- @hw-kafka-streamly@\'s @test\/Kafka\/Streamly\/StreamTest.hs@, so the two
-- projects\' classifications of the same librdkafka codes can be read side by
-- side when either changes.
module Kafka.Effectful.Consumer.ClassifyTest (tests) where

import Kafka.Consumer (RdKafkaRespErrT (..))
import Kafka.Effectful.Consumer.Classify
  ( PollErrorDisposition (..),
    classifyPollError,
    isBenignCommitError,
  )
import Kafka.Types (KafkaError (..))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))

-- | Conditions a healthy consumer meets in normal operation. Swallowed.
benignPollErrors :: [(String, KafkaError)]
benignPollErrors =
  [ ("RdKafkaRespErrPartitionEof", KafkaResponseError RdKafkaRespErrPartitionEof),
    ("RdKafkaRespErrAutoOffsetReset", KafkaResponseError RdKafkaRespErrAutoOffsetReset),
    ("RdKafkaRespErrUnknownTopicOrPart", KafkaResponseError RdKafkaRespErrUnknownTopicOrPart)
  ]

-- | Conditions that must reach the caller as a thrown error.
-- 'RdKafkaRespErrFatal' is the important one: it is what librdkafka delivers
-- once a fatal error has been raised on the client, after which the consumer is
-- permanently dead.
throwingPollErrors :: [(String, KafkaError)]
throwingPollErrors =
  [ ("RdKafkaRespErrFatal", KafkaResponseError RdKafkaRespErrFatal),
    ("RdKafkaRespErrSaslAuthenticationFailed", KafkaResponseError RdKafkaRespErrSaslAuthenticationFailed),
    ("RdKafkaRespErrAuthentication", KafkaResponseError RdKafkaRespErrAuthentication),
    ("RdKafkaRespErrDestroy", KafkaResponseError RdKafkaRespErrDestroy),
    ("RdKafkaRespErrAllBrokersDown", KafkaResponseError RdKafkaRespErrAllBrokersDown),
    ("KafkaBadConfiguration", KafkaBadConfiguration),
    ("KafkaBadSpecification", KafkaBadSpecification ""),
    -- A commit-only condition must not be mistaken for a benign poll
    -- condition; the two tables are independent.
    ("RdKafkaRespErrNoOffset", KafkaResponseError RdKafkaRespErrNoOffset)
  ]

tests :: TestTree
tests =
  testGroup
    "Classify"
    [ testGroup "classifyPollError" classifyPollErrorTests,
      testGroup "isBenignCommitError" isBenignCommitErrorTests
    ]

classifyPollErrorTests :: [TestTree]
classifyPollErrorTests =
  [ testCase "timeout: RdKafkaRespErrTimedOut" $
      classifyPollError (KafkaResponseError RdKafkaRespErrTimedOut) @?= PollTimeout
  ]
    <> [ testCase ("benign: " <> label) (classifyPollError err @?= PollBenign)
       | (label, err) <- benignPollErrors
       ]
    <> [ testCase ("throws: " <> label) (classifyPollError err @?= PollThrow)
       | (label, err) <- throwingPollErrors
       ]

isBenignCommitErrorTests :: [TestTree]
isBenignCommitErrorTests =
  [ testCase "accepts RdKafkaRespErrNoOffset" $
      isBenignCommitError (KafkaResponseError RdKafkaRespErrNoOffset) @?= True,
    testCase "rejects RdKafkaRespErrAllBrokersDown" $
      isBenignCommitError (KafkaResponseError RdKafkaRespErrAllBrokersDown) @?= False,
    testCase "rejects RdKafkaRespErrFatal" $
      isBenignCommitError (KafkaResponseError RdKafkaRespErrFatal) @?= False,
    testCase "rejects a partition-EOF poll condition" $
      isBenignCommitError (KafkaResponseError RdKafkaRespErrPartitionEof) @?= False
  ]