packages feed

http-tower-hs-0.1.0.0: test/Network/HTTP/Tower/Middleware/HedgeSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NumericUnderscores #-}

module Network.HTTP.Tower.Middleware.HedgeSpec (spec) where

import Control.Concurrent (threadDelay)
import Data.IORef
import Test.Hspec

import Network.HTTP.Tower.Core
import Network.HTTP.Tower.Error
import Network.HTTP.Tower.Middleware.Hedge

spec :: Spec
spec = describe "Hedge middleware" $ do
  it "returns result from fast primary without hedging" $ do
    let svc :: Service () String
        svc = Service $ \_ -> pure (Right "fast")
        hedged = withHedge 1000 svc
    result <- runService hedged ()
    result `shouldBe` Right "fast"

  it "returns a result when primary is slow (hedge may win)" $ do
    callCount <- newIORef (0 :: Int)
    let svc :: Service () String
        svc = Service $ \_ -> do
          n <- atomicModifyIORef' callCount (\c -> (c + 1, c))
          if n == 0
            then do
              threadDelay 500_000  -- primary: slow
              pure (Right "primary")
            else pure (Right "hedge")  -- hedge: instant
        hedged = withHedge 50 svc
    result <- runService hedged ()
    case result of
      Right _ -> pure ()
      Left err -> expectationFailure $ "Expected Right, got: " ++ show err

  it "calls service at most twice" $ do
    callCount <- newIORef (0 :: Int)
    let svc :: Service () String
        svc = Service $ \_ -> do
          modifyIORef' callCount (+ 1)
          threadDelay 100_000
          pure (Right "done")
        hedged = withHedge 50 svc
    _ <- runService hedged ()
    threadDelay 200_000
    count <- readIORef callCount
    count `shouldSatisfy` (<= 2)

instance Eq ServiceError where
  CustomError a      == CustomError b      = a == b
  TimeoutError       == TimeoutError       = True
  CircuitBreakerOpen == CircuitBreakerOpen  = True
  RetryExhausted n _ == RetryExhausted m _ = n == m
  _                  == _                  = False