bloodhound-1.0.0.0: tests/Test/AutoscalingSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.AutoscalingSpec (spec) where
import Data.Aeson
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy.Char8 qualified as LBS
import Data.Maybe (fromJust, isNothing)
import TestsUtils.Import
import Prelude
------------------------------------------------------------------------------
-- Policy samples
------------------------------------------------------------------------------
-- | The PUT body from the ES 7.17 docs example: a @fixed@ decider governing
-- the @data_hot@ node role.
samplePolicyDocsBytes :: LBS.ByteString
samplePolicyDocsBytes =
"{\
\ \"roles\": [\"data_hot\"],\
\ \"deciders\": {\
\ \"fixed\": {}\
\ }\
\}"
-- | A policy with empty @roles@ and several deciders; both are valid wire
-- shapes the decoder must accept.
samplePolicyEmptyRolesBytes :: LBS.ByteString
samplePolicyEmptyRolesBytes =
"{\
\ \"roles\": [],\
\ \"deciders\": {\
\ \"proactive_storage\": {},\
\ \"decider_two\": {\"some\": \"setting\"}\
\ }\
\}"
-- | A policy carrying an unknown sibling field (@description@) that the
-- @apExtras@ catch-all must preserve across @encode . decode@.
samplePolicyWithExtrasBytes :: LBS.ByteString
samplePolicyWithExtrasBytes =
"{\
\ \"roles\": [\"data_hot\", \"data_content\"],\
\ \"deciders\": {\"fixed\": {}},\
\ \"description\": \"tier autoscaling\"\
\}"
------------------------------------------------------------------------------
-- Capacity samples
------------------------------------------------------------------------------
-- | @GET /_autoscaling/capacity@ with no policies configured — the empty-map
-- shape the docs use as their worked example.
sampleCapacityEmptyBytes :: LBS.ByteString
sampleCapacityEmptyBytes =
"{\
\ \"policies\": {}\
\}"
-- | A full nested capacity response: required\/current capacity, two
-- current_nodes (one with an unknown sibling field), a decider result with
-- @reason_summary@ and @reason_details@, plus an unknown top-level policy
-- field — exercising every typed field and every @extras@ catch-all.
sampleCapacityFullBytes :: LBS.ByteString
sampleCapacityFullBytes =
"{\
\ \"policies\": {\
\ \"my_policy\": {\
\ \"required_capacity\": {\
\ \"node\": {\"storage\": 1000, \"memory\": 2000},\
\ \"total\": {\"storage\": 5000, \"memory\": 10000}\
\ },\
\ \"current_capacity\": {\
\ \"node\": {\"storage\": 800, \"memory\": 1600},\
\ \"total\": {\"storage\": 4000, \"memory\": 8000}\
\ },\
\ \"current_nodes\": [\
\ {\"name\": \"node-1\"},\
\ {\"name\": \"node-2\", \"foo\": \"bar\"}\
\ ],\
\ \"deciders\": {\
\ \"proactive_storage\": {\
\ \"required_capacity\": {\
\ \"node\": {\"storage\": 1000},\
\ \"total\": {\"storage\": 5000}\
\ },\
\ \"reason_summary\": \"need more space\",\
\ \"reason_details\": {\"shard\": {\"index\": \"logs\", \"size\": 1234}}\
\ }\
\ },\
\ \"unknown_policy_field\": 42\
\ }\
\ }\
\}"
-- | A large byte count (just under 1 EiB) — guards that @storage@\/@memory@
-- parse as Int64 without overflow.
sampleLargeResourceBytes :: LBS.ByteString
sampleLargeResourceBytes =
"{\
\ \"storage\": 9223372036854775000,\
\ \"memory\": 0\
\}"
spec :: Spec
spec = describe "Autoscaling API" $ do
describe "AutoscalingPolicyName JSON" $ do
it "round-trips as a bare JSON string" $ do
let Just decoded = decode "\"my-policy\"" :: Maybe AutoscalingPolicyName
unAutoscalingPolicyName decoded `shouldBe` "my-policy"
encode (AutoscalingPolicyName "my-policy") `shouldBe` "\"my-policy\""
it "rejects a non-string value" $ do
let decoded = decode "42" :: Maybe AutoscalingPolicyName
decoded `shouldSatisfy` isNothing
describe "AutoscalingPolicy JSON" $ do
it "decodes the docs example into typed roles and deciders" $ do
let Just p = decode samplePolicyDocsBytes :: Maybe AutoscalingPolicy
apRoles p `shouldBe` ["data_hot"]
KM.keys (apDeciders p) `shouldSatisfy` elem "fixed"
apExtras p `shouldBe` KM.empty
it "decodes empty roles and multiple deciders" $ do
let Just p = decode samplePolicyEmptyRolesBytes :: Maybe AutoscalingPolicy
apRoles p `shouldBe` []
length (KM.toList (apDeciders p)) `shouldBe` 2
it "preserves unknown sibling fields in apExtras" $ do
let Just p = decode samplePolicyWithExtrasBytes :: Maybe AutoscalingPolicy
apRoles p `shouldBe` ["data_hot", "data_content"]
KM.lookup "description" (apExtras p) `shouldBe` Just (String "tier autoscaling")
it "round-trips through encode . decode (extras survive)" $ do
let Just p = decode samplePolicyWithExtrasBytes :: Maybe AutoscalingPolicy
let Just again = decode (encode p) :: Maybe AutoscalingPolicy
again `shouldBe` p
it "rejects a non-object policy body" $ do
let decoded = decode "[1,2,3]" :: Maybe AutoscalingPolicy
decoded `shouldSatisfy` isNothing
describe "AutoscalingCapacity JSON" $ do
it "decodes the empty-policies response" $ do
let Just c = decode sampleCapacityEmptyBytes :: Maybe AutoscalingCapacity
acPolicies c `shouldBe` KM.empty
it "decodes a full nested response into typed fields" $ do
let Just c = decode sampleCapacityFullBytes :: Maybe AutoscalingCapacity
acPolicies c `shouldSatisfy` (not . KM.null)
let policy = fromJust (KM.lookup "my_policy" (acPolicies c))
-- required_capacity.node.memory is typed and present
let reqCap = fromJust (acpRequiredCapacity policy)
arMemory (fromJust (capNode reqCap)) `shouldBe` Just 2000
arStorage (fromJust (capTotal reqCap)) `shouldBe` Just 5000
-- current_nodes: two entries, the second carrying an unknown field
let nodes = fromJust (acpCurrentNodes policy)
length nodes `shouldBe` 2
acndName (nodes !! 1) `shouldBe` "node-2"
KM.lookup "foo" (acndExtras (nodes !! 1)) `shouldBe` Just (String "bar")
-- deciders: reason_summary typed, reason_details opaque, preserved
let deciders = fromJust (acpDeciders policy)
let decider = fromJust (KM.lookup "proactive_storage" deciders)
adrReasonSummary decider `shouldBe` Just "need more space"
let Just (Object details) = adrReasonDetails decider
KM.size details `shouldBe` 1
-- unknown top-level policy field survives in extras
KM.lookup "unknown_policy_field" (acpExtras policy) `shouldBe` Just (Number 42)
it "round-trips a full response through encode . decode" $ do
let Just c = decode sampleCapacityFullBytes :: Maybe AutoscalingCapacity
let Just again = decode (encode c) :: Maybe AutoscalingCapacity
again `shouldBe` c
it "rejects a non-object capacity body" $ do
let decoded = decode "\"oops\"" :: Maybe AutoscalingCapacity
decoded `shouldSatisfy` isNothing
describe "AutoscalingResource JSON" $ do
it "decodes large byte counts without overflow" $ do
let Just r = decode sampleLargeResourceBytes :: Maybe AutoscalingResource
arStorage r `shouldBe` Just 9223372036854775000
arMemory r `shouldBe` Just 0
it "current_nodes element rejects a missing name" $ do
let decoded = decode "{\"foo\": \"bar\"}" :: Maybe AutoscalingCurrentNode
decoded `shouldSatisfy` isNothing