packages feed

glue-core-0.4.2: test/Glue/CircuitBreakerSpec.hs

{-# LANGUAGE OverloadedStrings, DeriveDataTypeable, ScopedTypeVariables #-}

module Glue.CircuitBreakerSpec where

import Data.Traversable
import Data.Typeable
import Glue.CircuitBreaker
import Test.Hspec
import Data.IORef
import Test.QuickCheck
import Control.Exception.Base hiding (throw, throwIO, try)
import Control.Exception.Lifted

data CircuitBreakerTestException = CircuitBreakerTestException deriving (Eq, Show, Typeable)
instance Exception CircuitBreakerTestException

requests :: [Int]
requests = [1..10]

spec :: Spec
spec = do
  describe "circuitBreaker" $ do
    it "Multiple failures prevent subsequent calls" $ do
      property $ \failureMax -> do
        let positiveFailureMax      = (abs failureMax) `mod` 5
        let options                 = defaultCircuitBreakerOptions { maxBreakerFailures = positiveFailureMax }
        ref                         <- newIORef (0 :: Int)
        let service _               = atomicModifyIORef' ref (\c -> (c + 1, ())) >> throwIO CircuitBreakerTestException :: IO Int
        (s, circuitBreakerService)  <- circuitBreaker options service
        results                     <- traverse (\req -> try $ try $ circuitBreakerService req) requests :: IO [Either CircuitBreakerException (Either CircuitBreakerTestException Int)]
        let expectedResults         = (replicate (positiveFailureMax + 1) (Right $ Left $ CircuitBreakerTestException)) ++ (replicate (10 - positiveFailureMax - 1) (Left $ CircuitBreakerException "Circuit breaker open."))
        results `shouldBe` expectedResults
        (isCircuitBreakerOpen s) `shouldReturn` True
        (isCircuitBreakerClosed s) `shouldReturn` False
        (readIORef ref) `shouldReturn` (positiveFailureMax + 1)
    it "Successful calls pass straight through" $ do
      property $ \(failureMax :: Int, requests :: [Int]) -> do
        let positiveFailureMax      = abs failureMax
        let options                 = defaultCircuitBreakerOptions { maxBreakerFailures = positiveFailureMax }
        let service req             = return $ req + 1
        (s, circuitBreakerService)  <- circuitBreaker options service
        results                     <- traverse (\req -> circuitBreakerService req) requests 
        let expectedResults         = fmap (+ 1) requests
        results `shouldBe` expectedResults
        (isCircuitBreakerOpen s) `shouldReturn` False
        (isCircuitBreakerClosed s) `shouldReturn` True