packages feed

conftrack-0.0.1: test/Main.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE QuasiQuotes #-}
module Main (main) where

import Conftrack
import Conftrack.Source.Trivial (mkTrivialSource)
import Conftrack.Source.Aeson (mkJsonSource)

import Data.Text (Text)
import qualified Data.Aeson as A
import Test.QuickCheck
import Test.QuickCheck.Monadic
import Test.QuickCheck.Instances ()
import System.Exit (exitFailure, exitSuccess)
import qualified Data.Text.Encoding as BS
import Data.List ((\\))
import Data.Maybe (isNothing)


data TestFlat = TestFlat { testFoo :: Text, testBar :: Integer }
  deriving (Show, Eq)

instance Arbitrary TestFlat where
  arbitrary = TestFlat <$> arbitrary <*> arbitrary

data TestNested = TestNested { nestedFoo :: Text, nestedTest :: TestFlat }
  deriving (Show, Eq)

data TestOptionalNested = TestOptionalNested { opNestedFoo :: Text, opNestedTest :: Maybe TestFlat }
  deriving (Show, Eq)

instance Arbitrary TestNested where
  arbitrary = TestNested <$> arbitrary <*> arbitrary

instance Config TestFlat where
  readConfig = TestFlat
    <$> readRequiredValue (Key ["foo"])
    <*> readRequiredValue (Key ["bar"])

instance Config TestNested where
  readConfig = do
    a <- readRequiredValue (Key ["foo"])
    b <- readNested (Key ["nested"])
    pure (TestNested a b)

instance Config TestOptionalNested where
  readConfig = do
    a <- readRequiredValue (Key ["foo"])
    b <- readNestedOptional (Key ["nested"])
    pure (TestOptionalNested a b)

testTypeToTrivial :: TestFlat -> SomeSource
testTypeToTrivial (TestFlat foo bar) = mkTrivialSource
  [(Key ["foo"], ConfigString (BS.encodeUtf8 foo)), (Key ["bar"], ConfigInteger bar)]

testTypeToJson :: TestFlat -> A.Value
testTypeToJson (TestFlat foo bar) =
  A.object ["foo" A..= foo, "bar" A..= bar]

nestedToTrivial :: TestNested -> SomeSource
nestedToTrivial (TestNested nfoo (TestFlat foo bar)) =
  mkTrivialSource [ (Key ["foo"], ConfigString (BS.encodeUtf8 nfoo))
                  , (Key ["nested", "foo"], ConfigString (BS.encodeUtf8 foo))
                  , (Key ["nested", "bar"], ConfigInteger bar)]

nestedToJson :: TestNested -> SomeSource
nestedToJson (TestNested nfoo (TestFlat foo bar)) =
  mkJsonSource $ A.object
    [ "foo" A..= nfoo
    , "nested" A..= A.object
      [ "foo" A..= foo
      , "bar" A..=bar
      ]
    ]

roundtripVia :: (Eq a, Config a) => (a -> SomeSource) -> a -> Property
roundtripVia f val = monadicIO $ do
  let trivial = f val
  Right (config :: a, _, _) <- run $ runFetchConfig [trivial]
  assert (config == val)

prop_flat :: TestFlat -> Property
prop_flat = roundtripVia testTypeToTrivial

prop_nested :: TestNested -> Property
prop_nested = roundtripVia nestedToTrivial

prop_aeson_flat :: TestFlat -> Property
prop_aeson_flat = roundtripVia (mkJsonSource . testTypeToJson)

prop_aeson_nested :: TestNested -> Property
prop_aeson_nested = roundtripVia nestedToJson

prop_flat_keys :: Property
prop_flat_keys = monadicIO $ do
  keys <- run $ configKeysOf @TestFlat
  assert (null (keys \\ [ [key|foo|], [key|bar|] ]))

prop_nested_keys :: Property
prop_nested_keys = monadicIO $ do
  keys <- run $ configKeysOf @TestNested
  assert (null (keys \\ [ [key|foo|], [key|nested.bar|], [key|nested.foo|] ]))

prop_nested_optional_nothing :: Property
prop_nested_optional_nothing = monadicIO $ do
  Right (conf, _, warnings) <- run $ runFetchConfig [ mkJsonSource (A.object ["foo" A..= ("bar" :: Text)]) ]
  assert (null warnings)
  assert (isNothing (opNestedTest conf))

prop_nested_optional_partial :: Property
prop_nested_optional_partial = monadicIO $ do
  Left errors <- run $ runFetchConfig @TestOptionalNested [ mkJsonSource (A.object ["foo" A..= ("bar" :: Text), "nested" A..= A.object [ "foo" A..= ("bar" :: Text) ]]) ]
  assert (not (null errors))

prop_nested_optional_just :: TestFlat -> Property
prop_nested_optional_just nested = monadicIO $ do
  Right (conf, _, warnings) <- run $ runFetchConfig [
    mkJsonSource (A.object ["foo" A..= ("bar" :: Text), "nested" A..= testTypeToJson nested ])
    ]
  assert (null warnings)
  assert (opNestedTest conf == Just nested)

-- see quickcheck docs for why this return is here
return []
runTests :: IO Bool
runTests = $quickCheckAll

main :: IO ()
main = do
  good <- runTests
  if good then exitSuccess else exitFailure