packages feed

feature-flipper-postgres-0.1.1.1: test/Control/Flipper/PostgresSpec.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Control.Flipper.PostgresSpec (main, spec) where

import           Control.Monad                            (void)
import           Control.Monad.Reader
import           Control.Monad.State
import qualified Data.ByteString.Char8 as C8
import           Data.Map.Strict                          as Map
import qualified Data.Set                                 as Set
import           Test.Hspec

import           Control.Flipper.Adapters.Postgres        as FP
import           Control.Flipper.Adapters.Postgres.Models (ConnectionPool)
import           Control.Flipper.Adapters.Postgres.Query  as Q
import qualified Helpers.Config                           as Cfg

main :: IO ()
main = hspec spec

newtype MyContext m a = MyContext { unContext :: StateT MyState (FlipperT m) a }
    deriving ( Functor
             , Applicative
             , Monad
             , MonadIO
             , MonadState MyState
             , MonadReader Config
             , HasFeatureFlags
             , ModifiesFeatureFlags
             )

newtype MyState = MyState Int
    deriving (Show, Eq)

runMyContext :: (MonadIO m)
             => ConnectionPool -> MyState -> MyContext m a -> m (a, MyState)
runMyContext pool initialState f =
    let flipperT = runStateT (unContext f) initialState
    in runFlipperT pool flipperT

spec :: Spec
spec = around Cfg.withConfig $ do
    describe "control flow with feature flags" $ do
        describe "a non-existant feature" $ do
            it "is disabled by default" $ \(Config pool _) -> do
                (_, st) <- runMyContext pool (MyState 0) $ do
                    whenEnabled "non-existant feature" (void $ put (MyState 1))

                st `shouldBe` MyState 0

        describe "a persisted feature" $ do
            it "runs a feature when it is enabled" $ \(Config pool dbAccess) -> do
                let f = (FP.mkFeature "enabled-feature") { isEnabled = True }
                void $ Q.addFeature f dbAccess

                (_, st) <- runMyContext pool (MyState 0) $ do
                    whenEnabled "enabled-feature" (void $ put (MyState 1))

                st `shouldBe` MyState 1

            it "does not run disabled features" $ \(Config pool dbAccess) -> do
                let f = (FP.mkFeature "disabled-feature") { isEnabled = False }
                void $ Q.addFeature f dbAccess

                (_, st) <- runMyContext pool (MyState 0) $ do
                    whenEnabled "disabled-feature" (void $ put (MyState 1))

                st `shouldBe` MyState 0

    describe "modifying feature flags" $ do
        describe "adding new feature flags" $ do
            it "creates new records" $ \(Config pool dbAccess) -> do
                featureCount dbAccess `shouldReturn` 0

                (_, _) <- runMyContext pool (MyState 0) $ do
                    let feature1 = (FP.mkFeature "my-new-feature") { isEnabled = True }
                    let feature2 = (FP.mkFeature "some-other-feature") { isEnabled = True }
                    let fs = Features $ Map.fromList [ (featureName feature1, feature1), (featureName feature2, feature2) ]
                    updateFeatures fs

                featureCount dbAccess `shouldReturn` 2

            it "updating existing feature records" $ \(Config pool dbAccess) -> do
                featureCount dbAccess `shouldReturn` 0

                void $ runMyContext pool (MyState 0) $ do
                    let feature1 = (FP.mkFeature "my-new-feature") { isEnabled = True }
                    let feature2 = (FP.mkFeature "some-other-feature") { isEnabled = True }
                    let featureList = [ (featureName feature1, feature1), (featureName feature2, feature2) ]
                    let fs = Features $ Map.fromList featureList
                    updateFeatures fs
                    liftIO $ featureCount dbAccess `shouldReturn` 2

                    let feature1' = (FP.mkFeature "my-new-feature") { isEnabled = False }
                    let feature2' = (FP.mkFeature "some-other-feature") { isEnabled = False }
                    let feature3' = (FP.mkFeature "hi-there") { isEnabled = False }
                    let featureList' = [ (featureName feature1', feature1'), (featureName feature2', feature2'), (featureName feature3', feature3') ]
                    let fs' = Features $ Map.fromList featureList'
                    updateFeatures fs'
                    liftIO $ featureCount dbAccess `shouldReturn` 3

                    fs'' <- FP.getFeatures
                    liftIO $ all (\f -> isEnabled f == False) (Map.elems (unFeatures fs'')) `shouldBe` True

    describe "enabling a feature for a specific actor" $ do
        it "runs a feature for enabled users" $ \(Config pool _) -> do
            let actor1 = User 1
            let actor2 = User 2

            -- setup the features
            runFlipperT pool $ do
                -- here, we only enable the feature for actor1
                let feature = (FP.mkFeature "vrry-special-feature") { isEnabled = False, enabledActors = Set.singleton (actorId actor1) }
                let fs = Features $ Map.singleton (featureName feature) feature
                updateFeatures fs

            -- run some computation with feature flippers
            (_, st) <- runMyContext pool (MyState 0) $ do
                whenEnabledFor "vrry-special-feature" actor1 $
                    (void $ put (MyState 1))

                whenEnabledFor "vrry-special-feature" actor2 $
                    (void $ put (MyState 2))

            st `shouldBe` MyState 1

data User = User { userId :: Int }
    deriving (Show, Eq)

instance HasActorId User where
    actorId = ActorId . C8.pack . show . userId