bloodhound-1.0.0.0: tests/Test/TextStructureSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.TextStructureSpec (spec) where
import Data.Aeson (decode, encode)
import Data.ByteString.Lazy.Char8 qualified as LBS
import Data.List (sort)
import Database.Bloodhound.Client.Cluster (BackendType (..))
import Database.Bloodhound.ElasticSearch8.Client qualified as ClientES8
import Database.Bloodhound.ElasticSearch8.Requests qualified as RequestsES8
import Database.Bloodhound.ElasticSearch8.Types qualified as Types
import TestsUtils.Common
import TestsUtils.Import
normalize :: [(Text, Maybe Text)] -> [(Text, Maybe Text)]
normalize = sort
spec :: Spec
spec =
describe "Text structure API (find_message_structure, find_field_structure, test_grok_pattern)" $ do
describe "FindMessageStructureRequest JSON" $ do
it "encodes as {messages: [...]}" $ do
let req = Types.FindMessageStructureRequest ["line one", "line two"]
encode req
`shouldBe` "{\"messages\":[\"line one\",\"line two\"]}"
decode (encode req) `shouldBe` Just req
it "encodes an empty message list as {messages: []}" $
encode (Types.FindMessageStructureRequest [])
`shouldBe` "{\"messages\":[]}"
it "decodes a response missing the messages key as []" $
decode "{}" `shouldBe` Just (Types.FindMessageStructureRequest [])
describe "TestGrokPatternRequest JSON" $ do
it "encodes as {grok_pattern, text}" $ do
let req =
Types.TestGrokPatternRequest
"%{IP:ip}"
["127.0.0.1"]
encode req
`shouldBe` "{\"grok_pattern\":\"%{IP:ip}\",\"text\":[\"127.0.0.1\"]}"
decode (encode req) `shouldBe` Just req
it "decodes a body missing the text key as []" $ do
let raw = LBS.pack "{\"grok_pattern\":\"%{IP:ip}\"}"
decode raw
`shouldBe` Just (Types.TestGrokPatternRequest "%{IP:ip}" [])
describe "TestGrokPatternResponse JSON" $ do
it "decodes a populated matches array" $ do
let raw =
LBS.pack
"{\"matches\":[{\"ip\":\"127.0.0.1\"},{}]}"
case decode raw :: Maybe Types.TestGrokPatternResponse of
Just resp -> length (Types.tgprMatches resp) `shouldBe` 2
Nothing -> expectationFailure "failed to decode TestGrokPatternResponse"
it "decodes a missing matches key as []" $
decode "{}"
`shouldBe` Just (Types.TestGrokPatternResponse [])
it "round-trips a populated response" $ do
let resp = Types.TestGrokPatternResponse {Types.tgprMatches = []}
decode (encode resp) `shouldBe` Just resp
describe "findMessageStructureOptionsParams URI rendering" $ do
it "defaultFindMessageStructureOptions emits no params" $
Types.findMessageStructureOptionsParams Types.defaultFindMessageStructureOptions
`shouldBe` []
it "renders the message-classifier overrides" $ do
let opts =
Types.defaultFindMessageStructureOptions
{ Types.fmsoFormat = Just "ndjson",
Types.fmsoExplain = Just True,
Types.fmsoEcsCompatibility = Just "v1"
}
normalize (Types.findMessageStructureOptionsParams opts)
`shouldBe` sort
[ ("ecs_compatibility", Just "v1"),
("explain", Just "true"),
("format", Just "ndjson")
]
describe "findFieldStructureOptionsParams URI rendering" $ do
it "defaultFindFieldStructureOptions emits no params" $
Types.findFieldStructureOptionsParams Types.defaultFindFieldStructureOptions
`shouldBe` []
it "renders the required index/field plus overrides" $ do
let opts =
Types.defaultFindFieldStructureOptions
{ Types.ffsoIndex = Just "logs",
Types.ffsoField = Just "message",
Types.ffsoDocumentsToSample = Just 1000,
Types.ffsoExplain = Just True
}
normalize (Types.findFieldStructureOptionsParams opts)
`shouldBe` sort
[ ("documents_to_sample", Just "1000"),
("explain", Just "true"),
("field", Just "message"),
("index", Just "logs")
]
describe "testGrokPatternOptionsParams URI rendering" $ do
it "defaultTestGrokPatternOptions emits no params" $
Types.testGrokPatternOptionsParams Types.defaultTestGrokPatternOptions
`shouldBe` []
it "renders ecs_compatibility" $
Types.testGrokPatternOptionsParams
Types.defaultTestGrokPatternOptions
{ Types.tgpoEcsCompatibility = Just "v1"
}
`shouldBe` [("ecs_compatibility", Just "v1")]
describe "endpoint shape" $ do
it "POSTs /_text_structure/find_message_structure with a JSON body" $ do
let req =
RequestsES8.findMessageStructure
(Types.FindMessageStructureRequest ["a", "b"])
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_text_structure", "find_message_structure"]
bhRequestBody req `shouldSatisfy` isJust
it "findMessageStructureWith appends query params" $ do
let opts =
Types.defaultFindMessageStructureOptions
{ Types.fmsoFormat = Just "ndjson"
}
req =
RequestsES8.findMessageStructureWith
opts
(Types.FindMessageStructureRequest ["a"])
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_text_structure", "find_message_structure"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` [("format", Just "ndjson")]
it "GETs /_text_structure/find_field_structure with no body" $ do
let req = RequestsES8.findFieldStructure "logs" "message"
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_text_structure", "find_field_structure"]
bhRequestBody req `shouldSatisfy` isNothing
normalize (getRawEndpointQueries (bhRequestEndpoint req))
`shouldBe` sort [("field", Just "message"), ("index", Just "logs")]
it "findFieldStructureWith forwards extra overrides" $ do
let opts =
Types.defaultFindFieldStructureOptions
{ Types.ffsoIndex = Just "logs",
Types.ffsoField = Just "message",
Types.ffsoDocumentsToSample = Just 500
}
req = RequestsES8.findFieldStructureWith opts
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_text_structure", "find_field_structure"]
bhRequestBody req `shouldSatisfy` isNothing
normalize (getRawEndpointQueries (bhRequestEndpoint req))
`shouldBe` sort
[ ("documents_to_sample", Just "500"),
("field", Just "message"),
("index", Just "logs")
]
it "POSTs /_text_structure/test_grok_pattern with a JSON body" $ do
let req =
RequestsES8.testGrokPattern
(Types.TestGrokPatternRequest "%{IP:ip}" ["1.2.3.4"])
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_text_structure", "test_grok_pattern"]
bhRequestBody req `shouldSatisfy` isJust
it "testGrokPatternWith appends ecs_compatibility" $ do
let req =
RequestsES8.testGrokPatternWith
Types.defaultTestGrokPatternOptions
{ Types.tgpoEcsCompatibility = Just "v1"
}
(Types.TestGrokPatternRequest "%{IP:ip}" ["1.2.3.4"])
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_text_structure", "test_grok_pattern"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` [("ecs_compatibility", Just "v1")]
describe "live integration (requires ES8+ backend)" $
backendSpecific [ElasticSearch8] $ do
it "test_grok_pattern classifies a simple IP pattern" $
withTestEnv $ do
resp <-
ClientES8.testGrokPattern
(Types.TestGrokPatternRequest "%{IP:ip}" ["127.0.0.1"])
liftIO $ length (Types.tgprMatches resp) `shouldBe` 1