bloodhound-1.0.0.0: tests/Test/SimulateIngestSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.SimulateIngestSpec (spec) where
import Data.Aeson
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy.Char8 qualified as LBS
import Database.Bloodhound.ElasticSearch9.Requests qualified as RequestsES9
import Database.Bloodhound.ElasticSearch9.Types qualified as Types
import TestsUtils.Import
hasKey :: Key -> LBS.ByteString -> Bool
hasKey k bs = case decode bs of
Just (Object obj) -> k `KM.member` obj
_ -> False
-- | Extract the inner doc of the first response entry; the decode tests
-- feed known non-empty responses so the empty case is unreachable.
firstRespDoc :: Types.SimulateIngestResponse -> Types.SimulateIngestResponseInner
firstRespDoc r = case Types.sirResponseDocs r of
(d : _) -> Types.srdDoc d
[] -> error "firstRespDoc: empty SimulateIngestResponse"
spec :: Spec
spec =
describe "Simulate ingest v2 API (GET/POST /_ingest/_simulate, /_ingest/{index}/_simulate)" $ do
describe "SimulateIngestMergeType" $ do
it "renders index/template as text" $ do
Types.simulateIngestMergeTypeToText Types.SimulateIngestMergeIndex
`shouldBe` ("index" :: Text)
Types.simulateIngestMergeTypeToText Types.SimulateIngestMergeTemplate
`shouldBe` ("template" :: Text)
it "round-trips via fromText" $
mapM_
( \mt ->
Types.simulateIngestMergeTypeFromText
(Types.simulateIngestMergeTypeToText mt)
`shouldBe` Just mt
)
[minBound .. maxBound]
it "rejects unknown values" $
Types.simulateIngestMergeTypeFromText "bogus"
`shouldBe` (Nothing :: Maybe Types.SimulateIngestMergeType)
describe "simulateIngestOptionsParams URI rendering" $ do
it "defaultSimulateIngestOptions emits no params" $
Types.simulateIngestOptionsParams Types.defaultSimulateIngestOptions
`shouldBe` []
it "renders pipeline and merge_type" $ do
let opts =
Types.defaultSimulateIngestOptions
{ Types.sioPipeline = Just "my-pipeline",
Types.sioMergeType = Just Types.SimulateIngestMergeTemplate
}
Types.simulateIngestOptionsParams opts
`shouldBe` [("pipeline", Just "my-pipeline"), ("merge_type", Just "template")]
it "renders only the set field" $ do
let opts =
Types.defaultSimulateIngestOptions
{ Types.sioMergeType = Just Types.SimulateIngestMergeIndex
}
Types.simulateIngestOptionsParams opts
`shouldBe` [("merge_type", Just "index")]
describe "SimulateIngestDoc JSON" $ do
it "encodes a doc, omitting optional id/index" $ do
let doc =
Types.SimulateIngestDoc
{ Types.sidId = Nothing,
Types.sidIndex = Nothing,
Types.sidSource = object ["foo" .= ("bar" :: Text)]
}
encode doc `shouldBe` "{\"_source\":{\"foo\":\"bar\"}}"
it "encodes id and index when present" $ do
let doc =
Types.SimulateIngestDoc
{ Types.sidId = Just "123",
Types.sidIndex = Just "my-index",
Types.sidSource = object ["foo" .= ("bar" :: Text)]
}
decode (encode doc) `shouldBe` Just doc
it "decodes the documented example" $ do
let raw =
LBS.pack
"{\"_id\":\"123\",\"_index\":\"my-index\",\"_source\":{\"foo\":\"bar\"}}"
case decode raw :: Maybe Types.SimulateIngestDoc of
Just doc -> Types.sidId doc `shouldBe` Just "123"
Nothing -> expectationFailure "failed to decode SimulateIngestDoc"
describe "SimulateIngestRequest JSON" $ do
it "encodes a minimal request (docs only)" $ do
let req =
Types.SimulateIngestRequest
{ Types.sirDocs =
[ Types.SimulateIngestDoc
{ Types.sidId = Just "123",
Types.sidIndex = Just "my-index",
Types.sidSource = object ["foo" .= ("bar" :: Text)]
}
],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
hasKey "component_template_substitutions" (encode req) `shouldBe` False
decode (encode req) `shouldBe` Just req
it "always emits the required docs key, even when empty" $ do
let req =
Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
hasKey "docs" (encode req) `shouldBe` True
it "decodes a request, defaulting docs to []" $
case decode (LBS.pack "{}") :: Maybe Types.SimulateIngestRequest of
Just req -> length (Types.sirDocs req) `shouldBe` 0
Nothing -> expectationFailure "failed to decode empty SimulateIngestRequest"
describe "SimulateIngestResponse JSON" $ do
it "decodes a documented response entry" $ do
let raw =
LBS.pack
"{\"docs\":[{\"doc\":{\"_id\":\"123\",\"_index\":\"my-index\",\"_source\":{\"foo\":\"bar\"},\"executed_pipelines\":[\"my-pipeline\"]}}]}"
case decode raw :: Maybe Types.SimulateIngestResponse of
Just resp -> do
length (Types.sirResponseDocs resp) `shouldBe` 1
let inner = firstRespDoc resp
Types.siriExecutedPipelines inner
`shouldBe` ["my-pipeline"]
Nothing -> expectationFailure "failed to decode SimulateIngestResponse"
it "defaults executed_pipelines to [] when omitted" $ do
let raw =
LBS.pack
"{\"docs\":[{\"doc\":{\"_id\":\"1\",\"_index\":\"i\",\"_source\":{}}}]}"
case decode raw :: Maybe Types.SimulateIngestResponse of
Just resp ->
Types.siriExecutedPipelines
(firstRespDoc resp)
`shouldBe` []
Nothing -> expectationFailure "failed to decode SimulateIngestResponse"
it "round-trips an inner entry, preserving executed_pipelines" $ do
let inner =
Types.SimulateIngestResponseInner
{ Types.siriId = Just "1",
Types.siriIndex = Just "i",
Types.siriSource = Just (object ["foo" .= ("bar" :: Text)]),
Types.siriExecutedPipelines = [],
Types.siriIgnoredFields = Nothing,
Types.siriError = Nothing,
Types.siriEffectiveMapping = Nothing
}
hasKey "executed_pipelines" (encode inner) `shouldBe` True
decode (encode inner) `shouldBe` Just inner
it "decodes an entry carrying an error object" $ do
let raw =
LBS.pack
"{\"docs\":[{\"doc\":{\"_source\":{},\"executed_pipelines\":[],\"error\":{\"type\":\"x\",\"reason\":\"y\"}}}]}"
case decode raw :: Maybe Types.SimulateIngestResponse of
Just resp ->
Types.siriError
(firstRespDoc resp)
`shouldSatisfy` isJust
Nothing -> expectationFailure "failed to decode SimulateIngestResponse"
describe "endpoint shape" $ do
it "POSTs /_ingest/_simulate with a body" $ do
let req =
RequestsES9.simulateIngest
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "_simulate"]
bhRequestBody req `shouldSatisfy` isJust
getRawEndpointQueries (bhRequestEndpoint req) `shouldBe` []
it "simulateIngestWith appends pipeline/merge_type params" $ do
let opts =
Types.defaultSimulateIngestOptions
{ Types.sioPipeline = Just "p"
}
req =
RequestsES9.simulateIngestWith
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
opts
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "_simulate"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` [("pipeline", Just "p")]
it "POSTs /_ingest/<index>/_simulate" $
case mkIndexName "my-index" of
Left _ -> expectationFailure "mkIndexName rejected a valid name"
Right idx -> do
let req =
RequestsES9.simulateIngestIndex
idx
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "my-index", "_simulate"]
bhRequestBody req `shouldSatisfy` isJust
it "simulateIngestIndexWith appends pipeline/merge_type params" $
case mkIndexName "my-index" of
Left _ -> expectationFailure "mkIndexName rejected a valid name"
Right idx -> do
let opts =
Types.defaultSimulateIngestOptions
{ Types.sioMergeType = Just Types.SimulateIngestMergeIndex
}
req =
RequestsES9.simulateIngestIndexWith
idx
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
opts
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "my-index", "_simulate"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` [("merge_type", Just "index")]
it "GETs /_ingest/_simulate with a body" $ do
let req =
RequestsES9.simulateIngestGet
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "_simulate"]
-- GET-form still carries the required docs body (getWithBody).
bhRequestBody req `shouldSatisfy` isJust
getRawEndpointQueries (bhRequestEndpoint req) `shouldBe` []
it "simulateIngestWithGet appends pipeline/merge_type params" $ do
let opts =
Types.defaultSimulateIngestOptions
{ Types.sioPipeline = Just "p"
}
req =
RequestsES9.simulateIngestWithGet
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
opts
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "_simulate"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` [("pipeline", Just "p")]
it "GETs /_ingest/<index>/_simulate with a body" $
case mkIndexName "my-index" of
Left _ -> expectationFailure "mkIndexName rejected a valid name"
Right idx -> do
let req =
RequestsES9.simulateIngestIndexGet
idx
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "my-index", "_simulate"]
bhRequestBody req `shouldSatisfy` isJust
it "simulateIngestIndexWithGet appends pipeline/merge_type params" $
case mkIndexName "my-index" of
Left _ -> expectationFailure "mkIndexName rejected a valid name"
Right idx -> do
let opts =
Types.defaultSimulateIngestOptions
{ Types.sioMergeType = Just Types.SimulateIngestMergeIndex
}
req =
RequestsES9.simulateIngestIndexWithGet
idx
( Types.SimulateIngestRequest
{ Types.sirDocs = [],
Types.sirComponentTemplateSubstitutions = Nothing,
Types.sirIndexTemplateSubstitutions = Nothing,
Types.sirMappingAddition = Nothing
}
)
opts
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_ingest", "my-index", "_simulate"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` [("merge_type", Just "index")]