packages feed

launchdarkly-server-sdk-1.0.0: test/Spec/StoreInterface.hs

module Spec.StoreInterface (allTests) where

import           Control.Monad                      (void)
import           Data.Function                      ((&))
import           Data.IORef                         (newIORef, readIORef, atomicModifyIORef', writeIORef)
import           Data.Either                        (isLeft)
import           Data.HashMap.Strict                (HashMap)
import qualified Data.HashMap.Strict as             HM
import           Data.ByteString                    ()
import           Test.HUnit
import           System.Clock                       (TimeSpec(..))

import           Util.Features                      (makeTestFlag)

import           LaunchDarkly.Server.Store.Internal

makeTestStore :: Maybe StoreInterface -> IO (StoreHandle IO)
makeTestStore backend = makeStoreIO backend $ TimeSpec 10 0

makeStoreInterface :: StoreInterface
makeStoreInterface = StoreInterface
    { storeInterfaceAllFeatures   = const $ assertFailure "allFeatures should not be called"
    , storeInterfaceGetFeature    = const $ const $ assertFailure "getFeatures should not be called"
    , storeInterfaceUpsertFeature = const $ const $ const $ assertFailure "upsertFeature should not be called"
    , storeInterfaceIsInitialized = assertFailure "isInitialized should not be called"
    , storeInterfaceInitialize    = const $ assertFailure "initialize should not be called"
    }

testFailInit :: Test
testFailInit = TestCase $ do
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceInitialize = \_ -> pure $ Left "err"
        }
    initializeStore store HM.empty HM.empty >>= (Left "err" @?=)

testFailGet :: Test
testFailGet = TestCase $ do
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceGetFeature = \_ _ -> pure $ Left "err"
        }
    getFlagC store "abc" >>= (Left "err" @?=)

testFailAll :: Test
testFailAll = TestCase $ do
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceAllFeatures = \_ -> pure $ Left "err"
        }
    getAllFlagsC store >>= (Left "err" @?=)

testFailIsInitialized :: Test
testFailIsInitialized = TestCase $ do
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceIsInitialized = pure $ Left "err"
        }
    getInitializedC store >>= (Left "err" @?=)

testFailUpsert :: Test
testFailUpsert = TestCase $ do
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceUpsertFeature = \_ _ _ -> pure $ Left "err"
        }
    insertFlag store (makeTestFlag "test" 123) >>= (Left "err" @?=)

testFailGetInvalidJSON :: Test
testFailGetInvalidJSON = TestCase $ do
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceGetFeature = \_ _ -> pure $ Right $ RawFeature (pure "invalid json") 0
        }
    getFlagC store "abc" >>= (\v -> True @?= isLeft v)

testGetAllInvalidJSON :: Test
testGetAllInvalidJSON = TestCase $ do
    let flag = makeTestFlag "abc" 52
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceAllFeatures = \_ -> pure $ Right $ HM.empty
            & HM.insert "abc" (versionedToRaw $ Versioned (pure flag) 52)
            & HM.insert "xyz" (RawFeature (pure "invalid json") 64)
        }
    getAllFlagsC store >>= (Right (HM.singleton "abc" flag) @?=)

testInitializedCache :: Test
testInitializedCache = TestCase $ do
    counter <- newIORef 0
    value   <- newIORef False
    store   <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceIsInitialized = do
            atomicModifyIORef' counter (\c -> (c + 1, ()))
            Right <$> readIORef value
        }
    getInitializedC store      >>= (Right False @=?)
    readIORef counter          >>= (1 @=?)
    getInitializedC store      >>= (Right False @=?)
    readIORef counter          >>= (1 @=?)
    storeHandleExpireAll store >>= (Right () @=?)
    getInitializedC store      >>= (Right False @=?)
    readIORef counter          >>= (2 @=?)
    writeIORef value True
    storeHandleExpireAll store >>= (Right () @=?)
    getInitializedC store      >>= (Right True @=?)
    readIORef counter          >>= (3 @=?)
    getInitializedC store      >>= (Right True @=?)
    readIORef counter          >>= (3 @=?)

testGetCache :: Test
testGetCache = TestCase $ do
    counter <- newIORef 0
    value   <- newIORef $ RawFeature Nothing 0
    store   <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceGetFeature = \_ _ -> do
            atomicModifyIORef' counter (\c -> (c + 1, ()))
            Right <$> readIORef value
        }
    getFlagC store "abc"       >>= (Right Nothing @?=)
    readIORef counter          >>= (1 @=?)
    getFlagC store "abc"       >>= (Right Nothing @?=)
    readIORef counter          >>= (1 @=?)
    storeHandleExpireAll store >>= (Right () @=?)
    let flag = pure $ makeTestFlag "abc" 12
    writeIORef value $ versionedToRaw $ Versioned flag 12
    getFlagC store "abc"       >>= (Right flag @=?)
    readIORef counter          >>= (2 @=?)
    getFlagC store "abc"       >>= (Right flag @=?)
    readIORef counter          >>= (2 @=?)

testUpsertInvalidatesAllFlags :: Test
testUpsertInvalidatesAllFlags = TestCase $ do
    allCounter    <- newIORef 0
    upsertCounter <- newIORef 0
    upsertResult  <- newIORef $ Right True
    store         <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceUpsertFeature = \_ _ _ -> do
            atomicModifyIORef' upsertCounter (\c -> (c + 1, ()))
            readIORef upsertResult
        , storeInterfaceAllFeatures = \_ -> do
            atomicModifyIORef' allCounter (\c -> (c + 1, ()))
            pure $ Right HM.empty
        }
    getAllFlagsC store        >>= (Right HM.empty @=?)
    readIORef allCounter      >>= (1 @=?)
    deleteFlag store "abc" 52 >>= (Right () @=?)
    readIORef upsertCounter   >>= (1 @=?)
    getAllFlagsC store        >>= (Right HM.empty @=?)
    readIORef allCounter      >>= (2 @=?)
    writeIORef upsertResult $ Right False
    deleteFlag store "abc" 53 >>= (Right () @=?)
    readIORef upsertCounter   >>= (2 @=?)
    getAllFlagsC store        >>= (Right HM.empty @=?)
    readIORef allCounter      >>= (2 @=?)

testAllFlagsCache :: Test
testAllFlagsCache = TestCase $ do
    counter <- newIORef 0
    value   <- newIORef HM.empty
    store   <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceAllFeatures = \_ -> do
            atomicModifyIORef' counter (\c -> (c + 1, ()))
            pure $ Right HM.empty
        }
    getAllFlagsC store         >>= (Right HM.empty @=?)
    readIORef counter          >>= (1 @=?)
    getAllFlagsC store         >>= (Right HM.empty @=?)
    readIORef counter          >>= (1 @=?)
    storeHandleExpireAll store >>= (Right () @=?)
    getAllFlagsC store         >>= (Right HM.empty @=?)
    readIORef counter          >>= (2 @=?)

testAllFlagsUpdatesRegularCache :: Test
testAllFlagsUpdatesRegularCache = TestCase $ do
    let flag = makeTestFlag "abc" 12
    store <- makeTestStore $ pure $ makeStoreInterface
        { storeInterfaceAllFeatures = \_ -> pure $ Right $
            HM.singleton "abc" (versionedToRaw $ Versioned (pure flag) 12)
        }
    getAllFlagsC store   >>= (Right (HM.singleton "abc" flag) @=?)
    getFlagC store "abc" >>= (Right (pure flag) @=?)

allTests :: Test
allTests = TestList
    [ testFailInit
    , testFailGet
    , testFailAll
    , testFailIsInitialized
    , testFailUpsert
    , testFailGetInvalidJSON
    , testGetAllInvalidJSON
    , testInitializedCache
    , testGetCache
    , testUpsertInvalidatesAllFlags
    , testAllFlagsCache
    , testAllFlagsUpdatesRegularCache
    ]