bloodhound-1.0.0.0: tests/Test/PendingTasksSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.PendingTasksSpec where
import Data.Aeson.KeyMap qualified as KM
import Database.Bloodhound.Common.Client qualified as Client
import Database.Bloodhound.Common.Types
import TestsUtils.Common
import TestsUtils.Import
spec :: Spec
spec = do
describe "cluster pending tasks API" $ do
-- The pending queue is almost always empty on a quiet test cluster,
-- so the live assertion only validates that the response decodes
-- (proving the @{"tasks": [...]}@ envelope is handled correctly).
it "getPendingTasks decodes the live response without error" $
withTestEnv $ do
tasks <- Client.getPendingTasks
liftIO $ tasks `shouldSatisfy` (all pendingTaskPriorityMatches)
-- ------------------------------------------------------------------ --
-- Pure FromJSON/ToJSON round-trip (no ES required) --
-- ------------------------------------------------------------------ --
describe "PendingTask JSON codec" $ do
let sampleTask :: Value
sampleTask =
object
[ "insert_order" .= (42 :: Int),
"priority" .= ("URGENT" :: Text),
"source" .= ("create-index [foo-12], message [...]" :: Text),
"executing" .= (False :: Bool),
"time_in_queue_millis" .= (1500 :: Int),
"time_in_queue" .= ("1.5s" :: Text)
]
it "decodes a fully-populated task" $
case fromJSON @PendingTask sampleTask of
Success pt -> do
pendingTaskInsertOrder pt `shouldBe` 42
pendingTaskPriority pt `shouldBe` PendingTaskUrgent
pendingTaskSource pt `shouldBe` "create-index [foo-12], message [...]"
pendingTaskExecuting pt `shouldBe` False
pendingTaskTimeInQueueMillis pt `shouldBe` 1500
pendingTaskTimeInQueue pt `shouldBe` "1.5s"
Error err -> expectationFailure ("decode failed: " <> err)
it "round-trips without dropping typed fields" $
case fromJSON @PendingTask sampleTask of
Success pt ->
case toJSON pt of
Object reencoded ->
case fromJSON @PendingTask (Object reencoded) of
Success again -> again `shouldBe` pt
Error err -> expectationFailure ("re-decode failed: " <> err)
other -> expectationFailure ("re-encode produced non-object: " <> show other)
Error err -> expectationFailure ("initial decode failed: " <> err)
it "preserves extra (untyped) fields through the round-trip" $
let withExtra =
object
[ "insert_order" .= (1 :: Int),
"priority" .= ("HIGH" :: Text),
"source" .= ("shard-start [...]" :: Text),
"executing" .= (True :: Bool),
"time_in_queue_millis" .= (10 :: Int),
"time_in_queue" .= ("10ms" :: Text),
"future_field" .= ("forward-compatible value" :: Text)
]
in case fromJSON @PendingTask withExtra of
Success pt ->
case toJSON pt of
Object reencoded ->
case fromJSON @PendingTask (Object reencoded) of
Success again -> do
again `shouldBe` pt
case pendingTaskOther again of
Object o -> KM.lookup "future_field" o `shouldBe` Just (String "forward-compatible value")
_ -> expectationFailure "future_field dropped from *Other round-trip"
Error err -> expectationFailure ("re-decode failed: " <> err)
other -> expectationFailure ("re-encode produced non-object: " <> show other)
Error err -> expectationFailure ("initial decode failed: " <> err)
it "decodes every documented priority value" $
mapM_
( \(wire, expected) ->
case fromJSON @PendingTaskPriority (String wire) of
Success got -> got `shouldBe` expected
Error err -> expectationFailure ("priority " <> show wire <> " failed: " <> err)
)
[ ("IMMEDIATE", PendingTaskImmediate),
("URGENT", PendingTaskUrgent),
("HIGH", PendingTaskHigh),
("NORMAL", PendingTaskNormal),
("LOW", PendingTaskLow)
]
it "rejects an unknown priority value" $
case fromJSON @PendingTaskPriority (String "NONSENSICAL") of
Success _ -> expectationFailure "expected unknown priority to fail decoding"
Error _ -> pure ()
it "decodes a minimal payload using sensible defaults for missing fields" $
let minimal = object ["source" .= ("only-source" :: Text)]
in case fromJSON @PendingTask minimal of
Success pt -> do
pendingTaskSource pt `shouldBe` "only-source"
pendingTaskInsertOrder pt `shouldBe` 0
pendingTaskPriority pt `shouldBe` PendingTaskNormal
pendingTaskExecuting pt `shouldBe` False
Error err -> expectationFailure ("decode failed: " <> err)
-- | Every pending task carries one of the documented 'PendingTaskPriority'
-- values — used by the live test to confirm the decoded priority is in
-- the expected set.
pendingTaskPriorityMatches :: PendingTask -> Bool
pendingTaskPriorityMatches pt =
pendingTaskPriority pt
`elem` [ PendingTaskImmediate,
PendingTaskUrgent,
PendingTaskHigh,
PendingTaskNormal,
PendingTaskLow
]