packages feed

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

-- | Interpreter-level tests that need no broker.
--
-- librdkafka connects lazily, so @newConsumer@ against an unreachable broker
-- address succeeds and returns a usable handle. That is enough to exercise the
-- operations whose defects are local to the interpreter rather than to any
-- broker interaction — in particular the idle-commit path, where
-- @commitAllOffsets@ with nothing assigned yields
-- @RdKafkaRespErrNoOffset@, which hw-kafka-client documents as not an error.
--
-- In-band benign poll classification (partition EOF, offset reset,
-- unknown topic) cannot be forced this way, because an offline client never
-- emits those conditions. That path is covered by
-- "Kafka.Effectful.Consumer.ClassifyTest" plus the fact that both interpreters
-- route through the same exported classifier.
module Kafka.Effectful.Consumer.InterpreterTest (tests) where

import Effectful (Eff, IOE, runEff, (:>))
import Effectful.Error.Static (Error, runErrorNoCallStack)
import Kafka.Consumer.ConsumerProperties
  ( ConsumerProperties,
    brokersList,
    groupId,
  )
import Kafka.Consumer.Subscription (Subscription, topics)
import Kafka.Consumer.Types (ConsumerGroupId (..), OffsetCommit (OffsetCommit))
import Kafka.Effectful.Consumer.Effect
  ( KafkaConsumer,
    commitAllOffsets,
    pollMessage,
  )
import Kafka.Effectful.Consumer.Interpreter (runKafkaConsumer)
import Kafka.Types
  ( BrokerAddress (..),
    KafkaError,
    Timeout (..),
    TopicName (..),
  )
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase, (@?=))

-- | Point at a port nothing listens on. librdkafka will keep trying to
-- connect in the background; none of the operations under test wait for it.
offlineProps :: ConsumerProperties
offlineProps =
  brokersList [BrokerAddress "localhost:1"]
    <> groupId (ConsumerGroupId "kafka-effectful-test-group")

offlineSubscription :: Subscription
offlineSubscription = topics [TopicName "kafka-effectful-test-topic"]

-- | Run an action under the plain interpreter against the offline consumer.
runOffline ::
  (forall es. (IOE :> es, Error KafkaError :> es, KafkaConsumer :> es) => Eff es a) ->
  IO (Either KafkaError a)
runOffline action =
  runEff . runErrorNoCallStack $
    runKafkaConsumer offlineProps offlineSubscription action

tests :: TestTree
tests =
  testGroup
    "Interpreter (brokerless)"
    [ -- Pins the half of pollMessage's contract that is not changing: a
      -- timeout is not an error.
      testCase "pollMessage returns Nothing on timeout" $ do
        result <- runOffline (pollMessage (Timeout 100))
        case result of
          Right Nothing -> pure ()
          Right (Just _) ->
            assertFailure "expected no record from an offline consumer"
          Left err ->
            assertFailure ("expected a timeout to be swallowed, got: " <> show err),
      -- The KSC-5 regression. With nothing assigned there is nothing to
      -- commit, and librdkafka reports that as RdKafkaRespErrNoOffset --
      -- which hw-kafka-client's own callback documentation calls out as
      -- "not to be considered an error". Before the fix this threw, so an
      -- idle consumer could die on a shutdown commit.
      testCase "commitAllOffsets succeeds when there is nothing to commit" $ do
        result <- runOffline (commitAllOffsets OffsetCommit)
        result @?= Right ()
    ]