packages feed

bloodhound-1.0.0.0: tests/Test/VotingConfigExclusionsSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.VotingConfigExclusionsSpec where

import Data.ByteString.Lazy.Char8 qualified as BL
import Data.List qualified as L
import Data.Text qualified as T
import Database.Bloodhound.Client.Cluster (BackendType (..))
import Database.Bloodhound.Common.Requests qualified as Common
import TestsUtils.Common
import TestsUtils.Import

spec :: Spec
spec = do
  -- ---------------------------------------------------------------- --
  -- votingConfigExclusionOptionsParams: pure URI rendering (no ES).   --
  -- ---------------------------------------------------------------- --
  describe "votingConfigExclusionOptionsParams URI rendering" $ do
    let normalize :: [(T.Text, Maybe T.Text)] -> [(T.Text, Maybe T.Text)]
        normalize = L.sort

    it "defaultVotingConfigExclusionOptions emits no params" $
      votingConfigExclusionOptionsParams defaultVotingConfigExclusionOptions
        `shouldBe` []

    it "renders node_ids as a comma-separated list" $ do
      let opts =
            defaultVotingConfigExclusionOptions
              { votingConfigExclusionOptionsNodeIds =
                  [FullNodeId "node-a", FullNodeId "node-b"]
              }
      votingConfigExclusionOptionsParams opts
        `shouldBe` [("node_ids", Just "node-a,node-b")]

    it "renders node_names as a comma-separated list" $ do
      let opts =
            defaultVotingConfigExclusionOptions
              { votingConfigExclusionOptionsNodeNames =
                  [NodeName "node-1", NodeName "node-2", NodeName "node-3"]
              }
      votingConfigExclusionOptionsParams opts
        `shouldBe` [("node_names", Just "node-1,node-2,node-3")]

    it "renders both node_ids and node_names together" $ do
      let opts =
            defaultVotingConfigExclusionOptions
              { votingConfigExclusionOptionsNodeIds = [FullNodeId "a1"],
                votingConfigExclusionOptionsNodeNames = [NodeName "n1"]
              }
      normalize (votingConfigExclusionOptionsParams opts)
        `shouldBe` [ ("node_ids", Just "a1"),
                     ("node_names", Just "n1")
                   ]

    it "omits an empty list rather than rendering an empty string" $ do
      let opts =
            defaultVotingConfigExclusionOptions
              { votingConfigExclusionOptionsNodeIds = [],
                votingConfigExclusionOptionsNodeNames = [NodeName "only-name"]
              }
      votingConfigExclusionOptionsParams opts
        `shouldBe` [("node_names", Just "only-name")]

  -- ---------------------------------------------------------------- --
  -- updateVotingConfigExclusions endpoint shape: pure checks against  --
  -- the BHRequest value; no live backend needed.                     --
  -- ---------------------------------------------------------------- --
  describe "updateVotingConfigExclusions endpoint shape" $ do
    it "POSTs /_cluster/voting_config_exclusions" $ do
      let opts =
            defaultVotingConfigExclusionOptions
              { votingConfigExclusionOptionsNodeIds = [FullNodeId "x"]
              }
          req = Common.updateVotingConfigExclusions opts
      getRawEndpoint (bhRequestEndpoint req)
        `shouldBe` ["_cluster", "voting_config_exclusions"]
      bhRequestMethod req `shouldBe` "POST"

    it "forwards node_ids and node_names as query parameters" $ do
      let opts =
            defaultVotingConfigExclusionOptions
              { votingConfigExclusionOptionsNodeIds = [FullNodeId "a", FullNodeId "b"],
                votingConfigExclusionOptionsNodeNames = [NodeName "n"]
              }
          req = Common.updateVotingConfigExclusions opts
      getRawEndpointQueries (bhRequestEndpoint req)
        `shouldBe` [("node_ids", Just "a,b"), ("node_names", Just "n")]

    it "sends the empty body (ES requires no body on this endpoint)" $ do
      let req =
            Common.updateVotingConfigExclusions
              defaultVotingConfigExclusionOptions
                { votingConfigExclusionOptionsNodeIds = [FullNodeId "x"]
                }
      -- emptyBody is "" — distinct from no body at all (which would
      -- be 'Nothing'); POSTs to this endpoint carry an empty string
      -- so the server sees a body-shaped request without content.
      bhRequestBody req `shouldBe` Just (BL.pack "")

  -- ---------------------------------------------------------------- --
  -- clearVotingConfigExclusions endpoint shape: pure checks.          --
  -- ---------------------------------------------------------------- --
  describe "clearVotingConfigExclusions endpoint shape" $ do
    it "DELETEs /_cluster/voting_config_exclusions" $ do
      let req = Common.clearVotingConfigExclusions
      getRawEndpoint (bhRequestEndpoint req)
        `shouldBe` ["_cluster", "voting_config_exclusions"]
      bhRequestMethod req `shouldBe` "DELETE"

    it "sends no query string" $ do
      let req = Common.clearVotingConfigExclusions
      getRawEndpointQueries (bhRequestEndpoint req) `shouldBe` []

    it "sends no body" $ do
      let req = Common.clearVotingConfigExclusions
      bhRequestBody req `shouldBe` Nothing

  -- ---------------------------------------------------------------- --
  -- Distinctness guard: prevents a copy-paste regression that       --
  -- re-points updateVotingConfigExclusions at clearVotingConfig-    --
  -- Exclusions's DELETE (or vice versa). Same precedent as the      --
  -- clearKnnCache/warmupKnnIndex guard in Test.KnnClearCacheSpec.    --
  -- ---------------------------------------------------------------- --
  describe "POST/DELETE distinctness" $
    it "the two endpoints differ in HTTP method" $ do
      let postReq =
            Common.updateVotingConfigExclusions
              defaultVotingConfigExclusionOptions
                { votingConfigExclusionOptionsNodeIds = [FullNodeId "x"]
                }
          deleteReq = Common.clearVotingConfigExclusions
      bhRequestMethod postReq `shouldNotBe` bhRequestMethod deleteReq

  -- ---------------------------------------------------------------- --
  -- Live round-trip: gated on Elasticsearch backends only (OpenSearch  --
  -- does not expose voting_config_exclusions). Verifies the empty-body  --
  -- 200 -> Acknowledged True mapping that 'parseEsResponse' now handles --
  -- via null-substitution (see the Haddock on                          --
  -- 'updateVotingConfigExclusions' and 'Test.ParseEsResponseSpec').     --
  --                                                                     --
  -- The endpoint is self-cleaning: calling 'clearVotingConfigExclusions' --
  -- in the test body removes whatever the prior 'updateVotingConfig-    --
  -- Exclusions' call added. We still wrap the round-trip in a           --
  -- 'bracket_' that issues a defensive clear on teardown so a thrown    --
  -- exception or unexpected 4xx from the POST can't leak test state.    --
  -- ---------------------------------------------------------------- --
  describe "voting_config_exclusions live round-trip" $
    backendSpecific [ElasticSearch7, ElasticSearch8, ElasticSearch9] $ do
      let clearAll =
            withTestEnv $
              void $
                tryPerformBHRequest Common.clearVotingConfigExclusions

      before_ clearAll . after_ clearAll $
        it "adds an exclusion for a fake node id, then sees the clear land as Acknowledged" $ do
          withTestEnv $ do
            Acknowledged added <-
              performBHRequest $
                Common.updateVotingConfigExclusions
                  defaultVotingConfigExclusionOptions
                    { votingConfigExclusionOptionsNodeIds = [FullNodeId "fake-node-id"]
                    }
            liftIO $ added `shouldBe` True

            Acknowledged cleared <-
              performBHRequest Common.clearVotingConfigExclusions
            liftIO $ cleared `shouldBe` True