packages feed

bloodhound-1.0.0.0: tests/Test/ReindexOptionsSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.ReindexOptionsSpec (spec) where

import Data.List (sort)
import Data.Scientific (Scientific)
import Data.Text qualified as T
import Database.Bloodhound.Common.Types
import Test.Hspec (Spec, describe, it, shouldBe)

-- | Stable ordering for equality. The renderer preserves field-declaration
-- order but tests compare the set of params, not the order.
normalize :: [(T.Text, Maybe T.Text)] -> [(T.Text, Maybe T.Text)]
normalize = sort

-- | Boolean helpers rendered as @true@/@false@ strings.
boolTrue :: Maybe T.Text
boolTrue = Just "true"

boolFalse :: Maybe T.Text
boolFalse = Just "false"

spec :: Spec
spec =
  describe "ReindexOptions URI param rendering" $ do
    it "defaultReindexOptions emits no params" $
      reindexOptionsParams defaultReindexOptions
        `shouldBe` []

    describe "conflicts" $ do
      it "ConflictsProceed renders as conflicts=proceed" $ do
        let opts = defaultReindexOptions {rioConflicts = Just ConflictsProceed}
        reindexOptionsParams opts `shouldBe` [("conflicts", Just "proceed")]
      it "ConflictsAbort renders as conflicts=abort" $ do
        let opts = defaultReindexOptions {rioConflicts = Just ConflictsAbort}
        reindexOptionsParams opts `shouldBe` [("conflicts", Just "abort")]

    it "refresh renders via RefreshPolicy" $ do
      let opts = defaultReindexOptions {rioRefresh = Just RefreshTrue}
      reindexOptionsParams opts `shouldBe` [("refresh", Just "true")]

    it "timeout renders verbatim" $ do
      let opts = defaultReindexOptions {rioTimeout = Just "5s"}
      reindexOptionsParams opts `shouldBe` [("timeout", Just "5s")]

    it "scroll renders verbatim" $ do
      let opts = defaultReindexOptions {rioScroll = Just "1m"}
      reindexOptionsParams opts `shouldBe` [("scroll", Just "1m")]

    describe "wait_for_active_shards" $ do
      it "AllActiveShards renders as wait_for_active_shards=all" $ do
        let opts = defaultReindexOptions {rioWaitForActiveShards = Just AllActiveShards}
        reindexOptionsParams opts `shouldBe` [("wait_for_active_shards", Just "all")]
      it "ActiveShards n renders as wait_for_active_shards=<n>" $ do
        let opts = defaultReindexOptions {rioWaitForActiveShards = Just (ActiveShards 2)}
        reindexOptionsParams opts `shouldBe` [("wait_for_active_shards", Just "2")]

    it "requests_per_second renders as decimal" $ do
      let opts = defaultReindexOptions {rioRequestsPerSecond = Just 500}
      reindexOptionsParams opts `shouldBe` [("requests_per_second", Just "500")]

    it "requests_per_second renders 0 (no throttling)" $ do
      let opts = defaultReindexOptions {rioRequestsPerSecond = Just 0}
      reindexOptionsParams opts `shouldBe` [("requests_per_second", Just "0")]

    describe "slices" $ do
      it "SlicesAuto renders as slices=auto" $ do
        let opts = defaultReindexOptions {rioSlices = Just SlicesAuto}
        reindexOptionsParams opts `shouldBe` [("slices", Just "auto")]
      it "SlicesCount n renders as slices=<n>" $ do
        let opts = defaultReindexOptions {rioSlices = Just (SlicesCount 4)}
        reindexOptionsParams opts `shouldBe` [("slices", Just "4")]

    it "pipeline renders verbatim" $ do
      let opts = defaultReindexOptions {rioPipeline = Just "my-pipe"}
      reindexOptionsParams opts `shouldBe` [("pipeline", Just "my-pipe")]

    describe "require_alias" $ do
      it "Just True renders as require_alias=true" $ do
        let opts = defaultReindexOptions {rioRequireAlias = Just True}
        reindexOptionsParams opts `shouldBe` [("require_alias", boolTrue)]
      it "Just False renders as require_alias=false" $ do
        let opts = defaultReindexOptions {rioRequireAlias = Just False}
        reindexOptionsParams opts `shouldBe` [("require_alias", boolFalse)]

    it "max_docs renders as decimal" $ do
      let opts = defaultReindexOptions {rioMaxDocs = Just 100}
      reindexOptionsParams opts `shouldBe` [("max_docs", Just "100")]

    it "renders every field together when all are populated" $ do
      let opts =
            defaultReindexOptions
              { rioConflicts = Just ConflictsProceed,
                rioRefresh = Just RefreshWaitFor,
                rioTimeout = Just "5s",
                rioScroll = Just "1m",
                rioWaitForActiveShards = Just (ActiveShards 1),
                rioRequestsPerSecond = Just 100,
                rioSlices = Just (SlicesCount 4),
                rioPipeline = Just "my-pipe",
                rioRequireAlias = Just True,
                rioMaxDocs = Just 500
              }
      normalize (reindexOptionsParams opts)
        `shouldBe` normalize
          [ ("conflicts", Just "proceed"),
            ("refresh", Just "wait_for"),
            ("timeout", Just "5s"),
            ("scroll", Just "1m"),
            ("wait_for_active_shards", Just "1"),
            ("requests_per_second", Just "100"),
            ("slices", Just "4"),
            ("pipeline", Just "my-pipe"),
            ("require_alias", boolTrue),
            ("max_docs", Just "500")
          ]

    -- RethrottleRate renders the value of the requests_per_second URI
    -- parameter carried by 'rethrottleReindex'. Mirrors the examples in
    -- the ES docs: -1 for unlimited, plain integers for whole rates
    -- (no trailing .0), fractional form for decimals.
    describe "RethrottleRate rendering" $ do
      it "RethrottleUnlimited renders as -1" $
        renderRethrottleRate RethrottleUnlimited `shouldBe` "-1"
      it "integer-valued rate renders without trailing .0" $
        renderRethrottleRate (RethrottlePerSecond 12) `shouldBe` "12"
      it "fractional rate renders as a decimal" $
        renderRethrottleRate (RethrottlePerSecond (1.7 :: Scientific))
          `shouldBe` "1.7"
      it "zero rate renders as 0" $
        renderRethrottleRate (RethrottlePerSecond 0) `shouldBe` "0"
      -- A negative 'RethrottlePerSecond' is not validated (see the
      -- Haddock on 'RethrottleRate') — it renders verbatim and collides
      -- with 'RethrottleUnlimited' on the wire. ES treats any negative
      -- value as unlimited, so the wire-level effect is the same; this
      -- test pins down the chosen behaviour so future refactors notice
      -- if they change it.
      it "negative rate renders verbatim (collides with Unlimited)" $
        renderRethrottleRate (RethrottlePerSecond (-1)) `shouldBe` "-1"