bloodhound-1.0.0.0: tests/Test/SecurityRolesSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.SecurityRolesSpec (spec) where
import Data.Aeson
import Data.Aeson.KeyMap qualified as KM
import Data.ByteString.Lazy.Char8 qualified as L
import TestsUtils.Import
import Prelude
-- | A role body exercising every typed field, matching the ES 7.17 docs
-- example shape.
sampleRoleBytes :: L.ByteString
sampleRoleBytes =
"{\
\ \"cluster\": [\"monitor\", \"manage\"],\
\ \"indices\": [\
\ {\
\ \"names\": [\"events-*\"],\
\ \"privileges\": [\"read\"],\
\ \"field_security\": {\"grant\": [\"category\"], \"except\": [\"secret\"]},\
\ \"query\": {\"match_all\": {}}\
\ }\
\ ],\
\ \"applications\": [\
\ {\"application\": \"myapp\", \"privileges\": [\"read\"], \"resources\": [\"*\"]}\
\ ],\
\ \"run_as\": [\"other_user\"],\
\ \"metadata\": {\"version\": 1}\
\}"
-- | A role returned by @GET /_security/role@ — note @transient_metadata@
-- which is present only on retrieval.
sampleRoleWithTransientBytes :: L.ByteString
sampleRoleWithTransientBytes =
"{\
\ \"cluster\": [\"monitor\"],\
\ \"transient_metadata\": {\"_reserved\": true},\
\ \"metadata\": {\"version\": 1},\
\ \"description\": \"doc string\"\
\}"
spec :: Spec
spec = do
describe "Role" $ do
it "round-trips a full role body" $ do
let Just decoded = decode sampleRoleBytes :: Maybe Role
rCluster decoded `shouldBe` ["monitor", "manage"]
length (rIndices decoded) `shouldBe` 1
rRunAs decoded `shouldBe` ["other_user"]
KM.lookup "version" (rMetadata decoded) `shouldSatisfy` isJust
-- Stable round-trip.
let Just rt = decode (encode decoded) :: Maybe Role
rCluster rt `shouldBe` ["monitor", "manage"]
rRunAs rt `shouldBe` ["other_user"]
it "captures transient_metadata and extras on decode" $ do
let Just decoded = decode sampleRoleWithTransientBytes :: Maybe Role
rTransientMetadata decoded `shouldSatisfy` isJust
KM.lookup "description" (rExtras decoded) `shouldSatisfy` isJust
-- transient_metadata is NOT dumped into extras.
KM.lookup "transient_metadata" (rExtras decoded) `shouldBe` Nothing
it "decodes an empty role" $ do
let Just decoded = decode "{}" :: Maybe Role
rCluster decoded `shouldBe` []
rIndices decoded `shouldBe` []
describe "RoleCreatedResponse" $ do
it "decodes the nested {role:{created}} envelope" $ do
let Just rc = decode "{\"role\":{\"created\":true}}" :: Maybe RoleCreatedResponse
rcCreated rc `shouldBe` True
it "encodes back to the nested envelope" $ do
encode (RoleCreatedResponse False) `shouldBe` "{\"role\":{\"created\":false}}"
describe "RoleDeletedResponse" $ do
it "decodes found + acknowledged" $ do
let Just rd = decode "{\"found\":true,\"acknowledged\":true}" :: Maybe RoleDeletedResponse
rdFound rd `shouldBe` Just True
rdAcknowledged rd `shouldBe` True
it "tolerates an absent found field" $ do
let Just rd = decode "{\"acknowledged\":true}" :: Maybe RoleDeletedResponse
rdFound rd `shouldBe` Nothing
describe "RolesListResponse" $ do
it "decodes an object keyed by role name" $ do
let bytes =
"{\
\ \"admin\": {\
\ \"cluster\": [\"all\"]\
\ },\
\ \"reader\": {\
\ \"indices\": [{\"names\": [\"*\"], \"privileges\": [\"read\"]}]\
\ }\
\}"
let Just resp = decode bytes :: Maybe RolesListResponse
length (rolesListEntries resp) `shouldBe` 2
-- Round-trip reproduces the object-keyed shape.
let Just rt = decode (encode resp) :: Maybe RolesListResponse
length (rolesListEntries rt) `shouldBe` 2
describe "ClearRoleCacheResponse" $ do
it "decodes the cleared list" $ do
let Just c = decode "{\"cleared\":[\"admin\",\"reader\"]}" :: Maybe ClearRoleCacheResponse
clearedRoles c `shouldBe` ["admin", "reader"]