packages feed

bloodhound-1.0.0.0: tests/Test/FleetSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.FleetSpec (spec) where

import Data.Aeson
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy.Char8 qualified as LBS
import TestsUtils.Import
import Prelude

-- | Canonical @GET /{index}/_fleet/global_checkpoints@ response.
sampleGlobalCheckpointsBytes :: LBS.ByteString
sampleGlobalCheckpointsBytes =
  "{\
  \  \"global_checkpoints\": [0, 42, 100],\
  \  \"timed_out\": false\
  \}"

-- | A polling response that @timed_out@ before advancing, plus an unknown
-- sibling field (@future_field@) the @fgcrExtras@ catch-all must keep.
sampleGlobalCheckpointsWithExtrasBytes :: LBS.ByteString
sampleGlobalCheckpointsWithExtrasBytes =
  "{\
  \  \"global_checkpoints\": [42],\
  \  \"timed_out\": true,\
  \  \"future_field\": \"experimental\"\
  \}"

spec :: Spec
spec = describe "Fleet API" $ do
  describe "GlobalCheckpoint JSON" $ do
    it "round-trips as a JSON number" $ do
      let Just decoded = decode "42" :: Maybe GlobalCheckpoint
      unGlobalCheckpoint decoded `shouldBe` 42
      encode (42 :: GlobalCheckpoint) `shouldBe` "42"

    it "decodes the documented -1 'no checkpoint' sentinel" $ do
      let Just decoded = decode "-1" :: Maybe GlobalCheckpoint
      unGlobalCheckpoint decoded `shouldBe` (-1)

    it "rejects a non-number value" $ do
      let decoded = decode "\"x\"" :: Maybe GlobalCheckpoint
      decoded `shouldBe` Nothing

  describe "FleetGlobalCheckpointsResponse JSON" $ do
    it "decodes the canonical shape" $ do
      let Just decoded =
            decode sampleGlobalCheckpointsBytes ::
              Maybe FleetGlobalCheckpointsResponse
      fgcrCheckpoints decoded `shouldBe` [0, 42, 100]
      fgcrTimedOut decoded `shouldBe` False
      fgcrExtras decoded `shouldBe` KM.empty

    it "preserves unknown sibling fields in extras" $ do
      let Just decoded =
            decode sampleGlobalCheckpointsWithExtrasBytes ::
              Maybe FleetGlobalCheckpointsResponse
      fgcrTimedOut decoded `shouldBe` True
      KM.lookup "future_field" (fgcrExtras decoded) `shouldSatisfy` isJust

    it "round-trips through encode . decode" $ do
      let Just decoded =
            decode sampleGlobalCheckpointsWithExtrasBytes ::
              Maybe FleetGlobalCheckpointsResponse
          Just roundTripped =
            decode (encode decoded) :: Maybe FleetGlobalCheckpointsResponse
      fgcrCheckpoints roundTripped `shouldBe` [42]
      fgcrTimedOut roundTripped `shouldBe` True
      -- The @future_field@ extra survives the round-trip.
      KM.lookup "future_field" (fgcrExtras roundTripped) `shouldSatisfy` isJust
      -- The known keys are not duplicated into extras.
      KM.lookup "global_checkpoints" (fgcrExtras roundTripped) `shouldBe` Nothing

    it "rejects a response missing 'global_checkpoints'" $ do
      let decoded =
            decode "{\"timed_out\":false}" ::
              Maybe FleetGlobalCheckpointsResponse
      decoded `shouldBe` Nothing

    it "decodes an empty global_checkpoints array" $ do
      let Just decoded =
            decode "{\"global_checkpoints\":[],\"timed_out\":false}" ::
              Maybe FleetGlobalCheckpointsResponse
      fgcrCheckpoints decoded `shouldBe` []
      fgcrTimedOut decoded `shouldBe` False

  describe "FleetGlobalCheckpointsOptions params" $ do
    it "default options render no query string" $ do
      fleetGlobalCheckpointsOptionsParams defaultFleetGlobalCheckpointsOptions
        `shouldBe` []

    it "renders boolean wait_for_advance / wait_for_index" $ do
      let opts =
            defaultFleetGlobalCheckpointsOptions
              { fgcoWaitForAdvance = Just True,
                fgcoWaitForIndex = Just False
              }
      fleetGlobalCheckpointsOptionsParams opts
        `shouldContain` [("wait_for_advance", Just "true")]
      fleetGlobalCheckpointsOptionsParams opts
        `shouldContain` [("wait_for_index", Just "false")]

    it "renders checkpoints as a comma-separated list" $ do
      let opts =
            defaultFleetGlobalCheckpointsOptions
              { fgcoCheckpoints = [1, 2, 3]
              }
      fleetGlobalCheckpointsOptionsParams opts
        `shouldContain` [("checkpoints", Just "1,2,3")]

    it "omits an empty checkpoints list" $ do
      let opts =
            defaultFleetGlobalCheckpointsOptions
              { fgcoCheckpoints = []
              }
      fleetGlobalCheckpointsOptionsParams opts
        `shouldBe` []

    it "renders timeout as magnitude plus time-unit suffix" $ do
      let opts =
            defaultFleetGlobalCheckpointsOptions
              { fgcoTimeout = Just (TimeUnitSeconds, 30)
              }
      fleetGlobalCheckpointsOptionsParams opts
        `shouldContain` [("timeout", Just "30s")]

  describe "FleetSearchOptions params" $ do
    it "default options render no query string" $ do
      fleetSearchOptionsParams defaultFleetSearchOptions
        `shouldBe` []

    it "renders wait_for_checkpoints as a comma-separated list" $ do
      let opts =
            defaultFleetSearchOptions
              { fsoWaitForCheckpoints = [7, 14]
              }
      fleetSearchOptionsParams opts
        `shouldContain` [("wait_for_checkpoints", Just "7,14")]

    it "renders allow_partial_search_results as a bool string" $ do
      let opts =
            defaultFleetSearchOptions
              { fsoAllowPartialSearchResults = Just False
              }
      fleetSearchOptionsParams opts
        `shouldContain` [("allow_partial_search_results", Just "false")]

    it "omits an empty wait_for_checkpoints list" $ do
      let opts =
            defaultFleetSearchOptions
              { fsoWaitForCheckpoints = []
              }
      fleetSearchOptionsParams opts
        `shouldNotContain` [("wait_for_checkpoints", Just "")]