pgmq-hasql-0.1.0.0: test/AllFunctionsDecoderSpec.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Decoder tests for all pgmq functions that return messages
-- Each test verifies that the decoder works correctly for a specific function
module AllFunctionsDecoderSpec (tests) where
import Data.Aeson (Value, object, (.=))
import Data.Time (addUTCTime, getCurrentTime)
import Data.Vector qualified as V
import EphemeralDb (TestFixture (..), withTestFixture)
import Hasql.Pool qualified as Pool
import Pgmq.Hasql.Sessions qualified as Sessions
import Pgmq.Hasql.Statements.Types
( BatchVisibilityTimeoutQuery (..),
PopMessage (..),
ReadMessage (..),
ReadWithPollMessage (..),
SendMessage (..),
VisibilityTimeoutQuery (..),
)
import Pgmq.Types (Message (..), MessageBody (..), QueueName, unMessageId)
import Pgmq.Types qualified
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertEqual, testCase)
import TestUtils (assertSession, cleanupQueue)
-- | All per-function decoder tests
tests :: Pool.Pool -> TestTree
tests p =
testGroup
"All Functions Decoder Tests"
[ testReadDecoder p,
testPopDecoder p,
testSetVtDecoder p,
testBatchSetVtDecoder p,
testReadWithPollDecoder p
]
-- | Test pgmq.read() decoder
testReadDecoder :: Pool.Pool -> TestTree
testReadDecoder p = testCase "read() returns correctly decoded message" $ do
withTestFixture p $ \TestFixture {pool, queueName} -> do
assertSession pool (Sessions.createQueue queueName)
-- Send message with known body
let sentBody = object ["function" .= ("read" :: String), "value" .= (123 :: Int)]
_ <- assertSession pool (Sessions.sendMessage (mkSendMessage queueName sentBody))
-- Read via pgmq.read
let readQuery =
ReadMessage
{ queueName = queueName,
delay = 30,
batchSize = Just 1,
conditional = Nothing
}
messages <- assertSession pool (Sessions.readMessage readQuery)
assertEqual "Should return 1 message" 1 (V.length messages)
let msg = V.head messages
assertValidMessage msg sentBody
cleanupQueue pool queueName
-- | Test pgmq.pop() decoder
testPopDecoder :: Pool.Pool -> TestTree
testPopDecoder p = testCase "pop() returns correctly decoded message" $ do
withTestFixture p $ \TestFixture {pool, queueName} -> do
assertSession pool (Sessions.createQueue queueName)
-- Send message
let sentBody = object ["function" .= ("pop" :: String), "value" .= (456 :: Int)]
_ <- assertSession pool (Sessions.sendMessage (mkSendMessage queueName sentBody))
-- Pop message
let popQuery = PopMessage {queueName = queueName, qty = Just 1}
messages <- assertSession pool (Sessions.pop popQuery)
assertEqual "Should return 1 message" 1 (V.length messages)
let msg = V.head messages
assertValidMessage msg sentBody
-- Verify message is deleted (pop removes from queue)
let readQuery =
ReadMessage
{ queueName = queueName,
delay = 30,
batchSize = Just 1,
conditional = Nothing
}
messagesAfter <- assertSession pool (Sessions.readMessage readQuery)
assertEqual "Queue should be empty after pop" 0 (V.length messagesAfter)
cleanupQueue pool queueName
-- | Test pgmq.set_vt() decoder
testSetVtDecoder :: Pool.Pool -> TestTree
testSetVtDecoder p = testCase "set_vt() returns correctly decoded message" $ do
withTestFixture p $ \TestFixture {pool, queueName} -> do
assertSession pool (Sessions.createQueue queueName)
-- Send message
let sentBody = object ["function" .= ("set_vt" :: String)]
msgId <- assertSession pool (Sessions.sendMessage (mkSendMessage queueName sentBody))
beforeSetVt <- getCurrentTime
-- Change visibility timeout
let vtQuery =
VisibilityTimeoutQuery
{ queueName = queueName,
messageId = msgId,
visibilityTimeoutOffset = 60
}
msg <- assertSession pool (Sessions.changeVisibilityTimeout vtQuery)
-- Verify message fields
assertBool "messageId should be positive" (unMessageId (Pgmq.Types.messageId msg) > 0)
assertEqual "messageId should match" msgId (Pgmq.Types.messageId msg)
assertEqual "body should match" sentBody (unMessageBody (body msg))
-- Visibility time should be ~60 seconds in the future
let expectedMinVt = addUTCTime 55 beforeSetVt
assertBool "visibilityTime should be in future" (visibilityTime msg >= expectedMinVt)
cleanupQueue pool queueName
-- | Test batch set_vt decoder
testBatchSetVtDecoder :: Pool.Pool -> TestTree
testBatchSetVtDecoder p = testCase "batch set_vt returns correctly decoded messages" $ do
withTestFixture p $ \TestFixture {pool, queueName} -> do
assertSession pool (Sessions.createQueue queueName)
-- Send two messages
let body1 = object ["batch_vt" .= (1 :: Int)]
let body2 = object ["batch_vt" .= (2 :: Int)]
msgId1 <- assertSession pool (Sessions.sendMessage (mkSendMessage queueName body1))
msgId2 <- assertSession pool (Sessions.sendMessage (mkSendMessage queueName body2))
-- Batch change visibility timeout
let batchVtQuery =
BatchVisibilityTimeoutQuery
{ queueName = queueName,
messageIds = [msgId1, msgId2],
visibilityTimeoutOffset = 45
}
messages <- assertSession pool (Sessions.batchChangeVisibilityTimeout batchVtQuery)
assertEqual "Should return 2 messages" 2 (V.length messages)
-- Verify each message is valid
let msg1 = V.head messages
let msg2 = messages V.! 1
assertBool "first messageId should be positive" (unMessageId (Pgmq.Types.messageId msg1) > 0)
assertBool "second messageId should be positive" (unMessageId (Pgmq.Types.messageId msg2) > 0)
cleanupQueue pool queueName
-- | Test read_with_poll decoder
testReadWithPollDecoder :: Pool.Pool -> TestTree
testReadWithPollDecoder p = testCase "read_with_poll returns correctly decoded message" $ do
withTestFixture p $ \TestFixture {pool, queueName} -> do
assertSession pool (Sessions.createQueue queueName)
-- Send message first
let sentBody = object ["function" .= ("read_with_poll" :: String)]
_ <- assertSession pool (Sessions.sendMessage (mkSendMessage queueName sentBody))
-- Read with poll (should return immediately since message exists)
let pollQuery =
ReadWithPollMessage
{ queueName = queueName,
delay = 30,
batchSize = Just 1,
maxPollSeconds = 1,
pollIntervalMs = 100,
conditional = Nothing
}
messages <- assertSession pool (Sessions.readWithPoll pollQuery)
assertEqual "Should return 1 message" 1 (V.length messages)
let msg = V.head messages
assertValidMessage msg sentBody
cleanupQueue pool queueName
-- Helper functions
mkSendMessage :: QueueName -> Value -> SendMessage
mkSendMessage qName bodyVal =
SendMessage
{ queueName = qName,
messageBody = MessageBody bodyVal,
delay = Nothing
}
-- | Assert that a message has valid fields
assertValidMessage :: Message -> Value -> IO ()
assertValidMessage msg expectedBody = do
assertBool "messageId should be positive" (unMessageId (Pgmq.Types.messageId msg) > 0)
assertBool "readCount should be >= 0" (readCount msg >= 0)
assertEqual "body should match expected" expectedBody (unMessageBody (body msg))