packages feed

bloodhound-1.0.0.0: tests/Test/FieldCapsSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns -Wno-x-partial #-}

module Test.FieldCapsSpec (spec) where

import Data.Aeson (Value (..), object, (.=))
import Data.Aeson.Key (fromText)
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy.Char8 qualified as BL8
import TestsUtils.Common
import TestsUtils.Import

spec :: Spec
spec = do
  describe "FieldCapsRequest JSON" $ do
    it "encodes an empty body as {}" $
      let req = defaultFieldCapsRequest
          Object o = toJSON req
       in KM.toList o `shouldBe` []

    it "includes index_filter and runtime_mappings when provided" $
      let req =
            FieldCapsRequest
              { fieldCapsRequestIndexFilter = Just (MatchAllQuery Nothing),
                fieldCapsRequestRuntimeMappings = Just (KM.fromList [(fromText "rate", object ["type" .= ("double" :: Text)])])
              }
          Object o = toJSON req
       in do
            KM.lookup (fromText "index_filter") o `shouldBe` Just (toJSON (MatchAllQuery Nothing))
            KM.member (fromText "runtime_mappings") o `shouldBe` True
            KM.lookup (fromText "fields") o `shouldBe` (Nothing :: Maybe Value)

  describe "FieldCapsOptions params" $ do
    it "renders fields as a comma-joined URI param" $
      let opts =
            defaultFieldCapsOptions
              { fcoFields = Just [FieldPattern "user", FieldPattern "geo*"]
              }
       in lookup "fields" (fieldCapsOptionsParams opts) `shouldBe` Just (Just "user,geo*")

    it "omits fields when Nothing" $
      lookup "fields" (fieldCapsOptionsParams defaultFieldCapsOptions) `shouldBe` Nothing

    it "renders include_unmapped as a boolean string" $
      let opts = defaultFieldCapsOptions {fcoIncludeUnmapped = Just True}
       in lookup "include_unmapped" (fieldCapsOptionsParams opts) `shouldBe` Just (Just "true")

    it "renders allow_no_indices as a boolean string" $
      let opts = defaultFieldCapsOptions {fcoAllowNoIndices = Just False}
       in lookup "allow_no_indices" (fieldCapsOptionsParams opts) `shouldBe` Just (Just "false")

    it "renders expand_wildcards using Search.expandWildcardsText" $
      let opts =
            defaultFieldCapsOptions
              { fcoExpandWildcards = Just [ExpandWildcardsOpen, ExpandWildcardsClosed]
              }
       in lookup "expand_wildcards" (fieldCapsOptionsParams opts) `shouldBe` Just (Just "open,closed")

  describe "FieldCapsResponse JSON" $ do
    it "decodes the canonical ES doc example with multi-type field" $
      let bytes =
            BL8.pack
              "{\"indices\":[\"index1\",\"index2\",\"index3\",\"index4\",\"index5\"],\"fields\":{\"rating\":{\"long\":{\"type\":\"long\",\"metadata_field\":false,\"searchable\":true,\"aggregatable\":false,\"indices\":[\"index1\",\"index2\"],\"non_aggregatable_indices\":[\"index1\"]},\"keyword\":{\"type\":\"keyword\",\"metadata_field\":false,\"searchable\":false,\"aggregatable\":true,\"indices\":[\"index3\",\"index4\"],\"non_searchable_indices\":[\"index4\"]}},\"title\":{\"text\":{\"type\":\"text\",\"metadata_field\":false,\"searchable\":true,\"aggregatable\":false}}}}"
          Just resp = decode bytes
          fields = fieldCapsResponseFields resp
          Just rating = KM.lookup (fromText "rating") fields
          Just ratingLong = KM.lookup (fromText "long") rating
          Just ratingKeyword = KM.lookup (fromText "keyword") rating
          Just title = KM.lookup (fromText "title") fields
          Just titleText = KM.lookup (fromText "text") title
       in do
            fieldCapsResponseIndices resp
              `shouldBe` Just
                [ [qqIndexName|index1|],
                  [qqIndexName|index2|],
                  [qqIndexName|index3|],
                  [qqIndexName|index4|],
                  [qqIndexName|index5|]
                ]
            fieldCapabilityType ratingLong `shouldBe` "long"
            fieldCapabilitySearchable ratingLong `shouldBe` True
            fieldCapabilityAggregatable ratingLong `shouldBe` False
            fieldCapabilityIndices ratingLong
              `shouldBe` Just [[qqIndexName|index1|], [qqIndexName|index2|]]
            fieldCapabilityNonAggregatableIndices ratingLong
              `shouldBe` Just [[qqIndexName|index1|]]
            fieldCapabilityType ratingKeyword `shouldBe` "keyword"
            fieldCapabilitySearchable ratingKeyword `shouldBe` False
            fieldCapabilityAggregatable ratingKeyword `shouldBe` True
            fieldCapabilityNonSearchableIndices ratingKeyword
              `shouldBe` Just [[qqIndexName|index4|]]
            fieldCapabilityType titleText `shouldBe` "text"
            fieldCapabilitySearchable titleText `shouldBe` True
            fieldCapabilityAggregatable titleText `shouldBe` False
            fieldCapabilityIndices titleText `shouldBe` Nothing

    it "decodes a response with null indices" $
      let bytes =
            BL8.pack
              "{\"indices\":null,\"fields\":{\"user\":{\"text\":{\"type\":\"text\",\"searchable\":true,\"aggregatable\":false}}}}"
          Just resp = decode bytes
       in do
            fieldCapsResponseIndices resp `shouldBe` Nothing
            KM.size (fieldCapsResponseFields resp) `shouldBe` 1

    it "decodes a capability with metadata_field and meta map" $
      let bytes =
            BL8.pack
              "{\"indices\":[\"idx\"],\"fields\":{\"_id\":{\"_id\":{\"type\":\"_id\",\"searchable\":true,\"aggregatable\":true,\"metadata_field\":true,\"meta\":{\"_foo\":[\"bar\"]}}}}}"
          Just resp = decode bytes
          fields = fieldCapsResponseFields resp
          Just _id = KM.lookup (fromText "_id") fields
          Just _idCap = KM.lookup (fromText "_id") _id
       in do
            fieldCapabilityMetadataField _idCap `shouldBe` Just True
            fieldCapabilityMeta _idCap
              `shouldBe` Just (KM.fromList [(fromText "_foo", [String "bar"])])

    it "ignores unknown experimental fields (time_series_dimension etc.)" $
      let bytes =
            BL8.pack
              "{\"indices\":null,\"fields\":{\"metric\":{\"long\":{\"type\":\"long\",\"searchable\":true,\"aggregatable\":true,\"time_series_dimension\":true,\"time_series_metric\":\"gauge\"}}}}"
          Just resp = decode bytes
          fields = fieldCapsResponseFields resp
          Just metric = KM.lookup (fromText "metric") fields
          Just longCap = KM.lookup (fromText "long") metric
       in fieldCapabilityType longCap `shouldBe` "long"

  describe "Field Caps endpoint" $ do
    it "returns capabilities for a single field via POST /_field_caps" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCaps (Just [testIndex]) [FieldPattern "user"]
        liftIO $ do
          let fields = fieldCapsResponseFields resp
              Just user = KM.lookup (fromText "user") fields
          KM.size user `shouldBe` 1
          let Just userText = KM.lookup (fromText "text") user
          -- The TweetMapping in TestsUtils.Common sets fielddata=true
          -- on user, so it is both searchable and aggregatable.
          fieldCapabilitySearchable userText `shouldBe` True
          fieldCapabilityAggregatable userText `shouldBe` True

    it "reports integer fields as aggregatable" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCaps (Just [testIndex]) [FieldPattern "age"]
        liftIO $ do
          let fields = fieldCapsResponseFields resp
              Just age = KM.lookup (fromText "age") fields
              cap =
                case KM.lookup (fromText "long") age of
                  Just c -> c
                  Nothing -> case KM.lookup (fromText "integer") age of
                    Just c -> c
                    Nothing -> error "expected long or integer capability for age"
          fieldCapabilitySearchable cap `shouldBe` True
          fieldCapabilityAggregatable cap `shouldBe` True

    it "supports glob patterns" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCaps (Just [testIndex]) [FieldPattern "*"]
        liftIO $
          KM.size (fieldCapsResponseFields resp) `shouldSatisfy` (> 1)

    it "works without an explicit index (POST /_field_caps)" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCaps Nothing [FieldPattern "user"]
        liftIO $
          KM.member (fromText "user") (fieldCapsResponseFields resp) `shouldBe` True

    it "passes include_unmapped via FieldCapsOptions" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCapsWith
              (Just [testIndex])
              defaultFieldCapsOptions
                { fcoIncludeUnmapped = Just True,
                  fcoFields = Just [FieldPattern "nonexistent_*"]
                }
              defaultFieldCapsRequest
        liftIO $
          KM.size (fieldCapsResponseFields resp) `shouldBe` 0

    it "accepts Just [] as equivalent to Nothing for the index list" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCaps (Just []) [FieldPattern "user"]
        liftIO $
          KM.member (fromText "user") (fieldCapsResponseFields resp) `shouldBe` True

    it "passes expand_wildcards via FieldCapsOptions" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCapsWith
              (Just [testIndex])
              defaultFieldCapsOptions
                { fcoFields = Just [FieldPattern "user"],
                  fcoExpandWildcards = Just [ExpandWildcardsOpen]
                }
              defaultFieldCapsRequest
        liftIO $
          KM.member (fromText "user") (fieldCapsResponseFields resp) `shouldBe` True

    it "accepts an index_filter in the request body" $
      withTestEnv $ do
        _ <- insertData
        resp <-
          performBHRequest $
            getFieldCapsWith
              (Just [testIndex])
              defaultFieldCapsOptions {fcoFields = Just [FieldPattern "age"]}
              ( defaultFieldCapsRequest
                  { fieldCapsRequestIndexFilter = Just (MatchAllQuery Nothing)
                  }
              )
        liftIO $ do
          let fields = fieldCapsResponseFields resp
          KM.member (fromText "age") fields `shouldBe` True