packages feed

settei-0.2.0.0: test/Settei/ConfigTest.hs

module Settei.ConfigTest (tests) where

import Control.Selective (select)
import Data.Bifunctor (first)
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text qualified as Text
import Settei
import Settei.Internal.Config (Request (..), runConfig)
import Settei.Prelude
import Settei.Prototype.Free (freeNecessaryKeys, freePossibleKeys)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, testCase, (@?=))

tests :: TestTree
tests =
  testGroup
    "Settei.Config"
    [ testCase "applicative settings are necessary" $ do
        let schema = describe ((,) <$> required environmentSetting <*> required passwordSetting)
        keySet (schemaNecessary schema)
          @?= Set.fromList [environmentKey, passwordKey],
      testCase "selective production secret is conditional" $ do
        let schema = describe productionOnly
        keySet (schemaPossible schema)
          @?= Set.fromList [environmentKey, passwordKey]
        keySet (schemaNecessary schema)
          @?= Set.singleton environmentKey
        case schemaConditions schema of
          [condition] -> do
            conditionDependencies condition @?= Set.singleton environmentKey
            conditionSettings condition @?= Set.singleton passwordKey
          conditions -> fail ("expected one condition, found " <> show (length conditions)),
      testCase "whenEq matches the raw selective schema" $ do
        let schema = describe sugaredProductionOnly
        schema @?= describe productionOnly
        assertProductionSchema schema,
      testCase "whenConfig preserves the conditional schema footprint" $
        assertProductionSchema (describe whenConfigProductionOnly),
      testCase "fallbackTo makes only the fallback key conditional" $ do
        let schema = describe endpointWithFallback
        keySet (schemaPossible schema)
          @?= Set.fromList [serviceEndpointKey, serviceUrlKey]
        keySet (schemaNecessary schema)
          @?= Set.singleton serviceEndpointKey
        case schemaConditions schema of
          [condition] -> do
            conditionDependencies condition @?= Set.singleton serviceEndpointKey
            conditionSettings condition @?= Set.singleton serviceUrlKey
          conditions -> fail ("expected one condition, found " <> show (length conditions)),
      testCase "free-selective prototype agrees on possible effects" $
        Set.fromList (freePossibleKeys environmentKey passwordKey)
          @?= Set.fromList [environmentKey, passwordKey],
      testCase "free-selective prototype agrees on necessary effects" $
        Set.fromList (freeNecessaryKeys environmentKey passwordKey)
          @?= Set.singleton environmentKey,
      testCase "development skips production secret" $
        runConfig (interpret developmentValues) productionOnly
          @?= Right Nothing,
      testCase "production requests its secret" $
        runConfig (interpret productionValues) productionOnly
          @?= Left (Missing passwordKey),
      testCase "whenEq skips, requires, and returns its conditional value" $ do
        runConfig (interpret developmentValues) sugaredProductionOnly
          @?= Right Nothing
        runConfig (interpret productionValues) sugaredProductionOnly
          @?= Left (Missing passwordKey)
        runConfig (interpret productionWithPasswordValues) sugaredProductionOnly
          @?= Right (Just "fleet-secret"),
      testCase "fallbackTo prefers the primary and otherwise evaluates the fallback" $ do
        runConfig (interpret primaryEndpointValues) endpointWithFallback
          @?= Right "new.example"
        runConfig (interpret fallbackEndpointValues) endpointWithFallback
          @?= Right "old.example"
        runConfig (interpret Map.empty) endpointWithFallback
          @?= Left (Missing serviceUrlKey),
      testCase "duplicate sensitivity is secret-biased in either applicative order" $ do
        schemaSettingSensitivity (schemaEntry passwordKey (describe secretThenPublic))
          @?= Secret
        schemaSettingSensitivity (schemaEntry passwordKey (describe publicThenSecret))
          @?= Secret
        let rendered = renderSchemaText (describe publicThenSecret)
        assertBool "schema did not mark the duplicate key secret" ("secret" `Text.isInfixOf` rendered)
        assertBool "schema marked the duplicate key public" (not ("public" `Text.isInfixOf` rendered)),
      testCase "duplicate sensitivity is secret-biased through a selective branch" $
        schemaSettingSensitivity (schemaEntry passwordKey (describe selectiveDuplicate))
          @?= Secret,
      testCase "duplicate sensitivity is secret-biased through a default dependency" $
        schemaSettingSensitivity (schemaEntry passwordKey (describe defaultDuplicate))
          @?= Secret
    ]

data RuntimeError
  = Missing !Key
  | Decode !DecodeFailure
  deriving stock (Generic, Eq, Show)

productionOnly :: Config (Maybe Text)
productionOnly = select selector productionBranch
  where
    selector =
      (\environment -> if environment == "production" then Left () else Right Nothing)
        <$> required environmentSetting
    productionBranch = (\password _ -> Just password) <$> required passwordSetting

sugaredProductionOnly :: Config (Maybe Text)
sugaredProductionOnly =
  whenEq (required environmentSetting) "production" (required passwordSetting)

whenConfigProductionOnly :: Config (Maybe Text)
whenConfigProductionOnly =
  whenConfig
    ((== "production") <$> required environmentSetting)
    (required passwordSetting)

endpointWithFallback :: Config Text
endpointWithFallback =
  optional endpointSetting `fallbackTo` required urlSetting

secretThenPublic :: Config (Text, Text)
secretThenPublic =
  (,) <$> required passwordSetting <*> required publicPasswordSetting

publicThenSecret :: Config (Text, Text)
publicThenSecret =
  (,) <$> required publicPasswordSetting <*> required passwordSetting

selectiveDuplicate :: Config Text
selectiveDuplicate =
  select
    (Left <$> required passwordSetting)
    (const <$> required publicPasswordSetting)

defaultDuplicate :: Config Text
defaultDuplicate =
  withDefault
    passwordSetting
    ( derivedDefault
        (RuleName "password-from-public-duplicate")
        "Exercise duplicate sensitivity in a default dependency"
        (required publicPasswordSetting)
        id
    )

interpret :: Map Key RawValue -> Request a -> Either RuntimeError a
interpret values = \case
  RequiredRequest settingSpec ->
    case Map.lookup (settingKey settingSpec) values of
      Nothing -> Left (Missing (settingKey settingSpec))
      Just rawValue -> first Decode (decodeSetting settingSpec rawValue)
  OptionalRequest settingSpec ->
    case Map.lookup (settingKey settingSpec) values of
      Nothing -> Right Nothing
      Just rawValue -> Just <$> first Decode (decodeSetting settingSpec rawValue)

developmentValues :: Map Key RawValue
developmentValues = Map.singleton environmentKey (RawText "development")

productionValues :: Map Key RawValue
productionValues = Map.singleton environmentKey (RawText "production")

productionWithPasswordValues :: Map Key RawValue
productionWithPasswordValues =
  Map.fromList
    [ (environmentKey, RawText "production"),
      (passwordKey, RawText "fleet-secret")
    ]

primaryEndpointValues :: Map Key RawValue
primaryEndpointValues = Map.singleton serviceEndpointKey (RawText "new.example")

fallbackEndpointValues :: Map Key RawValue
fallbackEndpointValues = Map.singleton serviceUrlKey (RawText "old.example")

environmentSetting :: Setting Text
environmentSetting =
  publicSetting environmentKey "Runtime environment" textDecoder

passwordSetting :: Setting Text
passwordSetting =
  secretSetting passwordKey "Database password" textDecoder

publicPasswordSetting :: Setting Text
publicPasswordSetting =
  publicSetting passwordKey "Metrics password label" textDecoder

endpointSetting :: Setting Text
endpointSetting =
  publicSetting serviceEndpointKey "Service endpoint" textDecoder

urlSetting :: Setting Text
urlSetting =
  publicSetting serviceUrlKey "Legacy service URL" textDecoder

environmentKey :: Key
environmentKey = validKey "runtime.environment"

passwordKey :: Key
passwordKey = validKey "database.password"

serviceEndpointKey :: Key
serviceEndpointKey = validKey "service.endpoint"

serviceUrlKey :: Key
serviceUrlKey = validKey "service.url"

validKey :: Text -> Key
validKey value = either (error . show) id (parseKey value)

keySet :: [SchemaSetting] -> Set Key
keySet = Set.fromList . fmap schemaSettingKey

assertProductionSchema :: Schema -> IO ()
assertProductionSchema schema = do
  keySet (schemaPossible schema)
    @?= Set.fromList [environmentKey, passwordKey]
  keySet (schemaNecessary schema)
    @?= Set.singleton environmentKey
  case schemaConditions schema of
    [condition] -> do
      conditionDependencies condition @?= Set.singleton environmentKey
      conditionSettings condition @?= Set.singleton passwordKey
    conditions -> fail ("expected one condition, found " <> show (length conditions))

schemaEntry :: Key -> Schema -> SchemaSetting
schemaEntry key schema =
  case filter ((== key) . schemaSettingKey) (schemaPossible schema) of
    [entry] -> entry
    entries -> error ("expected one schema entry, found " <> show (length entries))