bloodhound-1.0.0.0: tests/Test/SyncedFlushSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns -Wno-warnings-deprecations #-}
module Test.SyncedFlushSpec (spec) where
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy qualified as LBS
import Data.List (sort)
import Database.Bloodhound.ElasticSearch7.Requests qualified as Requests
import Database.Bloodhound.ElasticSearch7.Types qualified as ES7Types
import Optics (set, view)
import TestsUtils.Common
import TestsUtils.Import
-- | Stable ordering for equality.
normalize :: [(Text, Maybe Text)] -> [(Text, Maybe Text)]
normalize = sort
-- | Documented success response: every shard sync-flushed cleanly, no
-- per-index @failures@ array.
sampleSuccessBytes :: LBS.ByteString
sampleSuccessBytes =
"{\"_shards\":{\"total\":2,\"successful\":2,\"failed\":0},\
\\"my-index-000001\":{\"total\":2,\"successful\":2,\"failed\":0}}"
-- | Documented partial-failure response: one shard group failed with a
-- per-shard @failures@ entry carrying a nested @routing@ object.
sampleFailureBytes :: LBS.ByteString
sampleFailureBytes =
"{\"_shards\":{\"total\":4,\"successful\":1,\"failed\":1},\
\\"my-index-000001\":{\"total\":4,\"successful\":3,\"failed\":1,\
\\"failures\":[{\"shard\":1,\"reason\":\"unexpected error\",\
\\"routing\":{\"state\":\"STARTED\",\"primary\":false,\
\\"node\":\"SZNr2J_ORxKTLUCydGX4zA\",\"relocating_node\":null,\
\\"shard\":1,\"index\":\"my-index-000001\"}}]}}"
spec :: Spec
spec = do
-- ------------------------------------------------------------------ --
-- Pure JSON decoder tests (no ES required). --
-- ------------------------------------------------------------------ --
describe "SyncedFlushResult JSON parsing" $ do
it "peels _shards and collects the per-index shard groups" $ do
let Just (r :: ES7Types.SyncedFlushResult) = decode sampleSuccessBytes
shards = ES7Types.sfrShards r
shardTotal shards `shouldBe` 2
shardsSuccessful shards `shouldBe` 2
case KM.lookup "my-index-000001" (ES7Types.sfrIndices r) of
Just grp -> do
ES7Types.sfsgTotal grp `shouldBe` 2
ES7Types.sfsgSuccessful grp `shouldBe` 2
ES7Types.sfsgFailed grp `shouldBe` 0
ES7Types.sfsgFailures grp `shouldBe` []
Nothing -> expectationFailure "expected my-index-000001 entry"
it "parses a per-index failures array with nested routing" $ do
let Just (r :: ES7Types.SyncedFlushResult) = decode sampleFailureBytes
shards = ES7Types.sfrShards r
shardsFailed shards `shouldBe` 1
case KM.lookup "my-index-000001" (ES7Types.sfrIndices r) of
Just grp -> do
ES7Types.sfsgFailed grp `shouldBe` 1
case ES7Types.sfsgFailures grp of
[fail_] -> do
ES7Types.sffShard fail_ `shouldBe` 1
ES7Types.sffReason fail_ `shouldBe` "unexpected error"
ES7Types.sffRouting fail_ `shouldNotBe` Nothing
_ -> expectationFailure "expected exactly one failure"
Nothing -> expectationFailure "expected my-index-000001 entry"
it "round-trips the success response via ToJSON/FromJSON" $ do
let Just (original :: ES7Types.SyncedFlushResult) = decode sampleSuccessBytes
reDecoded = decode (encode original) :: Maybe ES7Types.SyncedFlushResult
Just original `shouldBe` reDecoded
it "round-trips the failure response via ToJSON/FromJSON" $ do
let Just (original :: ES7Types.SyncedFlushResult) = decode sampleFailureBytes
reDecoded = decode (encode original) :: Maybe ES7Types.SyncedFlushResult
Just original `shouldBe` reDecoded
it "tolerates a response with only _shards (empty index map)" $ do
let Just (r :: ES7Types.SyncedFlushResult) =
decode "{\"_shards\":{\"total\":0,\"successful\":0,\"failed\":0}}"
ES7Types.sfrIndices r `shouldBe` mempty
it "tolerates a per-index group missing the failures array" $ do
let Just (r :: ES7Types.SyncedFlushResult) =
decode
"{\"_shards\":{\"total\":1,\"successful\":1,\"failed\":0},\
\\"idx\":{\"total\":1,\"successful\":1,\"failed\":0}}"
case KM.lookup "idx" (ES7Types.sfrIndices r) of
Just grp -> ES7Types.sfsgFailures grp `shouldBe` []
Nothing -> expectationFailure "expected idx entry"
describe "SyncedFlushFailure JSON parsing" $ do
it "parses a minimal failure with only shard and reason" $ do
let Just (f :: ES7Types.SyncedFlushFailure) =
decode "{\"shard\":3,\"reason\":\"[2] ongoing operations on primary\"}"
ES7Types.sffShard f `shouldBe` 3
ES7Types.sffReason f `shouldBe` "[2] ongoing operations on primary"
ES7Types.sffRouting f `shouldBe` Nothing
it "round-trips via ToJSON/FromJSON" $ do
let Just (original :: ES7Types.SyncedFlushFailure) =
decode "{\"shard\":1,\"reason\":\"x\"}"
reDecoded = decode (encode original) :: Maybe ES7Types.SyncedFlushFailure
Just original `shouldBe` reDecoded
-- ------------------------------------------------------------------ --
-- Optics. --
-- ------------------------------------------------------------------ --
describe "SyncedFlush lenses" $ do
it "sfoIgnoreUnavailableLens is a lawful get/set" $ do
let opts = set ES7Types.sfoIgnoreUnavailableLens (Just True) ES7Types.defaultSyncedFlushOptions
view ES7Types.sfoIgnoreUnavailableLens opts `shouldBe` Just True
it "sffShardLens is a lawful get/set" $ do
let f = ES7Types.SyncedFlushFailure {ES7Types.sffShard = 0, ES7Types.sffReason = "", ES7Types.sffRouting = Nothing}
f' = set ES7Types.sffShardLens 7 f
view ES7Types.sffShardLens f' `shouldBe` (7 :: Int)
it "sfsgFailedLens is a lawful get/set" $ do
let g = ES7Types.SyncedFlushShardGroup {ES7Types.sfsgTotal = 0, ES7Types.sfsgSuccessful = 0, ES7Types.sfsgFailed = 0, ES7Types.sfsgFailures = []}
g' = set ES7Types.sfsgFailedLens 2 g
view ES7Types.sfsgFailedLens g' `shouldBe` (2 :: Int)
-- ------------------------------------------------------------------ --
-- Options rendering. --
-- ------------------------------------------------------------------ --
describe "SyncedFlushOptions URI param rendering" $ do
it "defaultSyncedFlushOptions emits no query string" $
ES7Types.syncedFlushOptionsParams ES7Types.defaultSyncedFlushOptions
`shouldBe` []
it "renders the optional params when set" $ do
let opts =
ES7Types.defaultSyncedFlushOptions
{ ES7Types.sfoIgnoreUnavailable = Just True,
ES7Types.sfoAllowNoIndices = Just False,
ES7Types.sfoExpandWildcards = Just (ExpandWildcardsOpen :| [])
}
normalize (ES7Types.syncedFlushOptionsParams opts)
`shouldBe` [ ("allow_no_indices", Just "false"),
("expand_wildcards", Just "open"),
("ignore_unavailable", Just "true")
]
-- ------------------------------------------------------------------ --
-- Endpoint shape (BHRequest, no ES required). --
-- ------------------------------------------------------------------ --
describe "syncedFlushIndex endpoint shape" $ do
let idx = [qqIndexName|my-idx|]
it "POSTs /{index}/_flush/synced with no query string by default" $ do
let req = Requests.syncedFlushIndex idx
bhRequestMethod req `shouldBe` "POST"
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["my-idx", "_flush", "synced"]
getRawEndpointQueries (bhRequestEndpoint req)
`shouldBe` []
it "sends an empty body" $ do
let req = Requests.syncedFlushIndex idx
bhRequestBody req `shouldBe` Just ""
it "forwards options via the With variant" $ do
let opts = ES7Types.defaultSyncedFlushOptions {ES7Types.sfoIgnoreUnavailable = Just True}
req = Requests.syncedFlushIndexWith opts idx
lookup "ignore_unavailable" (getRawEndpointQueries (bhRequestEndpoint req))
`shouldBe` Just (Just "true")