packages feed

conferer-1.1.0.0: test/Conferer/FromConfig/Extended.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Conferer.FromConfig.Extended
  ( module Conferer.FromConfig
  , module GHC.Generics
  , configWith
  , anyConfigParserError
  , aConfigParserError
  , aMissingRequiredKey
  , aMissingRequiredKeys
  , ensureEmptyConfigThrows
  , ensureUsingDefaultReturnsSameValue
  , ensureSingleConfigParsesTheRightThing
  , ensureSingleConfigThrowsParserError
  , ensureSingleConfigThrows
  , ensureFetchParses
  , ensureFetchThrows
  , toDyn
  , module Conferer.Config
  ) where

import Data.Text
import Data.Typeable
import Test.Hspec
import GHC.Generics
import Control.Exception
import Data.Dynamic
import Data.Function

import Conferer.Config
import Conferer.FromConfig.Internal
  ( MissingRequiredKey(..)
  , ConfigParsingError(..)
  )
import Conferer.FromConfig
import qualified Conferer.Source.InMemory as InMemory

configWith :: [(Key, Text)] -> IO Config
configWith keyValues =
  emptyConfig
  & addSource (InMemory.fromConfig keyValues)

anyConfigParserError :: ConfigParsingError -> Bool
anyConfigParserError _ = True

aConfigParserError :: Key -> Text -> ConfigParsingError -> Bool
aConfigParserError key txt (ConfigParsingError k t _) =
  key == k && t == txt

aMissingRequiredKey :: forall t. Typeable t => Key -> MissingRequiredKey -> Bool
aMissingRequiredKey key (MissingRequiredKey k t) =
  [key] == k && typeRep (Proxy :: Proxy t) == t

aMissingRequiredKeys :: forall t. Typeable t => [Key] -> MissingRequiredKey -> Bool
aMissingRequiredKeys keys (MissingRequiredKey k t) =
  keys == k && typeRep (Proxy :: Proxy t) == t

data InvalidThing = InvalidThing deriving (Show, Eq)

ensureEmptyConfigThrows :: forall a. (Typeable a, FromConfig a) => SpecWith ()
ensureEmptyConfigThrows =
  context "with empty config"  $ do
    it "throws an exception" $ do
      config <- configWith []
      fetchFromConfig @a "some.key" config
        `shouldThrow` aMissingRequiredKey @a "some.key"

ensureSingleConfigThrowsParserError ::
    forall a. (FromConfig a, Typeable a) =>
    Text -> SpecWith ()
ensureSingleConfigThrowsParserError keyContent =
  context "with invalid types in the defaults"  $ do
    it "throws an exception" $ do
      config <- configWith [("some.key", keyContent)]
      fetchFromConfig @a "some.key" config
        `shouldThrow` aConfigParserError "some.key" keyContent

ensureUsingDefaultReturnsSameValue ::
    forall a. (Typeable a, Eq a, Show a, FromConfig a) =>
    a -> SpecWith ()
ensureUsingDefaultReturnsSameValue value =
  context ("with a default of '" ++ show value ++ "'") $ do
    it "gets that same value" $ do
      config <- configWith []
      fetchedValue <- fetchFromConfig @a "some.key"
        (config & addDefault "some.key" value)
      fetchedValue `shouldBe` value

ensureSingleConfigParsesTheRightThing ::
    forall a. (Eq a, Show a, FromConfig a, Typeable a) =>
    Text -> a -> SpecWith ()
ensureSingleConfigParsesTheRightThing keyContent value =
  context ("with a config value of '" ++ unpack keyContent ++ "'" ) $ do
    it "gets the right value" $ do
      config <- configWith [("some.key", keyContent)]
      fetchedValue <- fetchFromConfig @a "some.key" config
      fetchedValue `shouldBe` value

ensureSingleConfigThrows ::
    forall a e. (FromConfig a, Typeable a, Exception e) =>
    Text -> (e -> Bool) -> SpecWith ()
ensureSingleConfigThrows keyContent checkException =
  it "gets the right value" $ do
    config <- configWith [("some.key", keyContent)]
    fetchFromConfig @a "some.key" config
      `shouldThrow` checkException

ensureFetchParses ::
    forall a.
    ( FromConfig a
    , Typeable a
    , Eq a
    , Show a
    )
    => [(Key, Text)]
    -> [(Key, Dynamic)]
    -> a
    -> SpecWith ()
ensureFetchParses configs defaults expectedValue =
  it "gets the right value" $ do
    config <- addDefaults (mapKeys defaults) <$> configWith (mapKeys configs)
    fetchedValue <- fetchFromConfig @a "some.key" config
    fetchedValue `shouldBe` expectedValue
  where
    mapKeys :: [(Key, any)] -> [(Key, any)]
    mapKeys = fmap (\(k, x) -> ("some.key" /. k, x))

ensureFetchThrows ::
    forall a e.
    ( FromConfig a
    , Typeable a
    , Exception e
    )
    => [(Key, Text)]
    -> [(Key, Dynamic)]
    -> (e -> Bool)
    -> SpecWith ()
ensureFetchThrows configs defaults exceptionCheck =
  it "throws an exception" $ do
    config <- addDefaults (mapKeys defaults) <$> configWith (mapKeys configs)
    fetchFromConfig @a "some.key" config
      `shouldThrow` exceptionCheck
  where
    mapKeys :: forall x. [(Key, x)] -> [(Key, x)]
    mapKeys = fmap (\(k, x) -> ("some.key" /. k, x))