-- | Coverage for 'Pqi.notifies': @LISTEN@\/@NOTIFY@ delivery, queueing,
-- and that @UNLISTEN@ stops delivery.
--
-- The backend PID carried by a notification is connection-specific — the
-- candidate and the reference are distinct backends — so 'bePid' is omitted
-- from the cross-adapter comparison. Each scenario that receives a
-- notification instead asserts independently (per adapter) that
-- @Pqi.bePid notification == backendPID connection@, verifying that the PID field
-- is correctly populated without comparing it across adapters.
module Pqi.Conformance.Operation.Notifies
  ( spec,
  )
where

import Pqi (Notify (..))
import qualified Pqi
import Pqi.Conformance.Harness
import Pqi.Conformance.Prelude
import Test.Hspec

spec :: Pqi.Adapter -> SpecWith ByteString
spec :: Adapter -> SpecWith ByteString
spec Adapter
adapter =
  String -> SpecWith ByteString -> SpecWith ByteString
forall a. HasCallStack => String -> SpecWith a -> SpecWith a
describe String
"notifies" do
    String
-> (ByteString -> IO ()) -> SpecWith (Arg (ByteString -> IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"is empty with no pending notifications" \ByteString
conninfo ->
      Adapter
-> ByteString
-> (Connection -> IO (Maybe (ByteString, ByteString)))
-> IO ()
forall a.
(Eq a, Show a, HasCallStack) =>
Adapter -> ByteString -> (Connection -> IO a) -> IO ()
differential Adapter
adapter ByteString
conninfo \Connection
connection ->
        (Notify -> (ByteString, ByteString))
-> Maybe Notify -> Maybe (ByteString, ByteString)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Notify -> (ByteString, ByteString)
channelAndPayload (Maybe Notify -> Maybe (ByteString, ByteString))
-> IO (Maybe Notify) -> IO (Maybe (ByteString, ByteString))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Connection -> IO (Maybe Notify)
Pqi.notifies Connection
connection

    String
-> (ByteString -> IO ()) -> SpecWith (Arg (ByteString -> IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"delivers a listen/notify round-trip and then drains" \ByteString
conninfo ->
      Adapter
-> ByteString
-> (Connection
    -> IO
         (Maybe (ByteString, ByteString), Maybe (ByteString, ByteString)))
-> IO ()
forall a.
(Eq a, Show a, HasCallStack) =>
Adapter -> ByteString -> (Connection -> IO a) -> IO ()
differential Adapter
adapter ByteString
conninfo \Connection
connection -> do
        _ <- Connection -> ByteString -> IO (Maybe Result)
Pqi.exec Connection
connection ByteString
"listen conformance_channel"
        _ <- Pqi.exec connection "notify conformance_channel, 'payload-1'"
        notification <- Pqi.notifies connection
        pid <- Pqi.backendPID connection
        for_ notification \Notify
n -> Notify -> Int32
Pqi.bePid Notify
n Int32 -> Int32 -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` Int32
pid
        drained <- fmap channelAndPayload <$> Pqi.notifies connection
        pure (fmap channelAndPayload notification, drained)

    String
-> (ByteString -> IO ()) -> SpecWith (Arg (ByteString -> IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"queues notifications in order" \ByteString
conninfo ->
      Adapter
-> ByteString
-> (Connection
    -> IO
         (Maybe (ByteString, ByteString), Maybe (ByteString, ByteString),
          Maybe (ByteString, ByteString)))
-> IO ()
forall a.
(Eq a, Show a, HasCallStack) =>
Adapter -> ByteString -> (Connection -> IO a) -> IO ()
differential Adapter
adapter ByteString
conninfo \Connection
connection -> do
        _ <- Connection -> ByteString -> IO (Maybe Result)
Pqi.exec Connection
connection ByteString
"listen conformance_channel"
        _ <- Pqi.exec connection "notify conformance_channel, 'first'"
        _ <- Pqi.exec connection "notify conformance_channel, 'second'"
        first <- Pqi.notifies connection
        second <- Pqi.notifies connection
        third <- Pqi.notifies connection
        pid <- Pqi.backendPID connection
        for_ first \Notify
n -> Notify -> Int32
Pqi.bePid Notify
n Int32 -> Int32 -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` Int32
pid
        for_ second \Notify
n -> Notify -> Int32
Pqi.bePid Notify
n Int32 -> Int32 -> IO ()
forall a. (HasCallStack, Show a, Eq a) => a -> a -> IO ()
`shouldBe` Int32
pid
        pure (fmap channelAndPayload first, fmap channelAndPayload second, fmap channelAndPayload third)

    String
-> (ByteString -> IO ()) -> SpecWith (Arg (ByteString -> IO ()))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"stops delivery after unlisten" \ByteString
conninfo ->
      Adapter
-> ByteString
-> (Connection -> IO (Maybe (ByteString, ByteString)))
-> IO ()
forall a.
(Eq a, Show a, HasCallStack) =>
Adapter -> ByteString -> (Connection -> IO a) -> IO ()
differential Adapter
adapter ByteString
conninfo \Connection
connection -> do
        _ <- Connection -> ByteString -> IO (Maybe Result)
Pqi.exec Connection
connection ByteString
"listen conformance_channel"
        _ <- Pqi.exec connection "unlisten conformance_channel"
        _ <- Pqi.exec connection "notify conformance_channel, 'lost'"
        fmap channelAndPayload <$> Pqi.notifies connection
  where
    channelAndPayload :: Notify -> (ByteString, ByteString)
channelAndPayload Notify
notification = (Notify -> ByteString
Pqi.relname Notify
notification, Notify -> ByteString
Pqi.extra Notify
notification)