packages feed

bloodhound-1.0.0.0: tests/Test/ExplainOptionsSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.ExplainOptionsSpec (spec) where

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

-- | Stable ordering for equality. The renderer preserves field-declaration
-- order but tests should 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 "ExplainOptions URI param rendering" $ do
    it "defaultExplainOptions emits no params" $
      explainOptionsParams defaultExplainOptions
        `shouldBe` []

    it "routing renders verbatim" $ do
      let opts = defaultExplainOptions {eoRouting = Just "user-42"}
      explainOptionsParams opts `shouldBe` [("routing", Just "user-42")]

    it "preference renders verbatim" $ do
      let opts = defaultExplainOptions {eoPreference = Just "_local"}
      explainOptionsParams opts `shouldBe` [("preference", Just "_local")]

    it "stored_fields renders verbatim" $ do
      let opts = defaultExplainOptions {eoStoredFields = Just "name,age"}
      explainOptionsParams opts `shouldBe` [("stored_fields", Just "name,age")]

    describe "_source" $ do
      it "Just True renders as _source=true" $ do
        let opts = defaultExplainOptions {eoSource = Just True}
        explainOptionsParams opts `shouldBe` [("_source", boolTrue)]
      it "Just False renders as _source=false" $ do
        let opts = defaultExplainOptions {eoSource = Just False}
        explainOptionsParams opts `shouldBe` [("_source", boolFalse)]

    it "_source_includes renders verbatim" $ do
      let opts = defaultExplainOptions {eoSourceIncludes = Just "name"}
      explainOptionsParams opts `shouldBe` [("_source_includes", Just "name")]

    it "_source_excludes renders verbatim" $ do
      let opts = defaultExplainOptions {eoSourceExcludes = Just "age"}
      explainOptionsParams opts `shouldBe` [("_source_excludes", Just "age")]

    describe "lenient" $ do
      it "Just True renders as lenient=true" $ do
        let opts = defaultExplainOptions {eoLenient = Just True}
        explainOptionsParams opts `shouldBe` [("lenient", boolTrue)]
      it "Just False renders as lenient=false" $ do
        let opts = defaultExplainOptions {eoLenient = Just False}
        explainOptionsParams opts `shouldBe` [("lenient", boolFalse)]

    -- The ES _explain URI parameter documents AND/OR (uppercase), even
    -- though BooleanOperator's JSON encoding is lowercase. Pin the
    -- uppercase wire form so a future refactor that reuses ToJSON is
    -- caught here.
    describe "default_operator" $ do
      it "And renders as default_operator=AND (uppercase)" $ do
        let opts = defaultExplainOptions {eoDefaultOperator = Just And}
        explainOptionsParams opts `shouldBe` [("default_operator", Just "AND")]
      it "Or renders as default_operator=OR (uppercase)" $ do
        let opts = defaultExplainOptions {eoDefaultOperator = Just Or}
        explainOptionsParams opts `shouldBe` [("default_operator", Just "OR")]

    it "df renders verbatim" $ do
      let opts = defaultExplainOptions {eoDf = Just "message"}
      explainOptionsParams opts `shouldBe` [("df", Just "message")]

    it "analyzer renders verbatim" $ do
      let opts = defaultExplainOptions {eoAnalyzer = Just "standard"}
      explainOptionsParams opts `shouldBe` [("analyzer", Just "standard")]

    describe "analyze_wildcard" $ do
      it "Just True renders as analyze_wildcard=true" $ do
        let opts = defaultExplainOptions {eoAnalyzeWildcard = Just True}
        explainOptionsParams opts `shouldBe` [("analyze_wildcard", boolTrue)]
      it "Just False renders as analyze_wildcard=false" $ do
        let opts = defaultExplainOptions {eoAnalyzeWildcard = Just False}
        explainOptionsParams opts `shouldBe` [("analyze_wildcard", boolFalse)]

    it "renders every field together when all are populated" $ do
      let opts =
            defaultExplainOptions
              { eoRouting = Just "user-42",
                eoPreference = Just "_local",
                eoStoredFields = Just "name,age",
                eoSource = Just False,
                eoSourceIncludes = Just "name",
                eoSourceExcludes = Just "age",
                eoLenient = Just True,
                eoDefaultOperator = Just And,
                eoDf = Just "message",
                eoAnalyzer = Just "standard",
                eoAnalyzeWildcard = Just True
              }
      let rendered = explainOptionsParams opts
      -- Exactly the 11 documented _explain URI parameters.
      length rendered `shouldBe` 11
      rendered `shouldSatisfy` all (isJust . snd)
      normalize rendered
        `shouldBe` sort
          [ ("routing", Just "user-42"),
            ("preference", Just "_local"),
            ("stored_fields", Just "name,age"),
            ("_source", Just "false"),
            ("_source_includes", Just "name"),
            ("_source_excludes", Just "age"),
            ("lenient", Just "true"),
            ("default_operator", Just "AND"),
            ("df", Just "message"),
            ("analyzer", Just "standard"),
            ("analyze_wildcard", Just "true")
          ]

    it "explainDocument delegates to explainDocumentWith defaultExplainOptions (no params)" $
      -- defaultExplainOptions emits an empty param list, so the base
      -- endpoint is wire-identical to the legacy parameterless form.
      explainOptionsParams defaultExplainOptions
        `shouldBe` []
  where
    isJust (Just _) = True
    isJust Nothing = False