packages feed

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"]