tower-hs-0.3.0.0: test/Tower/ServiceSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Tower.ServiceSpec (spec) where
import Test.Hspec
import Control.Arrow (Arrow(..), ArrowChoice(..))
import qualified Control.Category
import Control.Category ((>>>))
import Tower.Service
import Tower.Error
import Data.Function ((&))
import Tower.Error.Testing ()
spec :: Spec
spec = describe "Core" $ do
describe "Service" $ do
it "runs a simple service" $ do
let svc = Service $ \n -> pure (Right (n * 2 :: Int))
result <- runService svc 21
result `shouldBe` Right 42
it "returns errors in Left" $ do
let svc :: Service () String
svc = Service $ \_ -> pure (Left (CustomError "boom"))
result <- runService svc ()
result `shouldBe` Left (CustomError "boom")
describe "mapService" $ do
it "transforms successful responses" $ do
let svc :: Service () Int
svc = Service $ \_ -> pure (Right 10)
mapped = svc & mapService (* 3)
result <- runService mapped ()
result `shouldBe` Right 30
it "passes through errors unchanged" $ do
let svc :: Service () Int
svc = Service $ \_ -> pure (Left TimeoutError)
mapped = svc & mapService (* 3)
result <- runService mapped ()
result `shouldBe` Left TimeoutError
describe "Category" $ do
it "id passes through unchanged" $ do
let svc = Control.Category.id :: Service Int Int
result <- runService svc 42
result `shouldBe` Right 42
it "composes services with >>>" $ do
let double = Service $ \n -> pure (Right (n * 2 :: Int))
addOne = Service $ \n -> pure (Right (n + 1 :: Int))
composed = double >>> addOne
result <- runService composed 10
result `shouldBe` Right 21
it "short-circuits on error in first service" $ do
let failing :: Service Int Int
failing = Service $ \_ -> pure (Left (CustomError "fail"))
addOne = Service $ \n -> pure (Right (n + 1 :: Int))
composed = failing >>> addOne
result <- runService composed 10
result `shouldBe` Left (CustomError "fail")
it "short-circuits on error in second service" $ do
let double = Service $ \n -> pure (Right (n * 2 :: Int))
failing :: Service Int Int
failing = Service $ \_ -> pure (Left (CustomError "fail"))
composed = double >>> failing
result <- runService composed 10
result `shouldBe` Left (CustomError "fail")
describe "Arrow" $ do
it "arr lifts a pure function" $ do
let svc = arr (* 3) :: Service Int Int
result <- runService svc 7
result `shouldBe` Right 21
it "first applies to the first element of a pair" $ do
let double = Service $ \n -> pure (Right (n * 2 :: Int))
paired = first double
result <- runService paired (10, "hello")
result `shouldBe` Right (20, "hello")
it "first propagates errors" $ do
let failing :: Service Int Int
failing = Service $ \_ -> pure (Left TimeoutError)
paired = first failing
result <- runService paired (10, "hello")
result `shouldBe` Left TimeoutError
describe "ArrowChoice" $ do
it "left applies to Left values" $ do
let double = Service $ \n -> pure (Right (n * 2 :: Int))
choice = left double :: Service (Either Int String) (Either Int String)
result <- runService choice (Left 5)
result `shouldBe` Right (Left 10)
it "left passes through Right values" $ do
let double = Service $ \n -> pure (Right (n * 2 :: Int))
choice = left double :: Service (Either Int String) (Either Int String)
result <- runService choice (Right "hello")
result `shouldBe` Right (Right "hello")
it "left propagates errors" $ do
let failing :: Service Int Int
failing = Service $ \_ -> pure (Left (CustomError "boom"))
choice = left failing :: Service (Either Int String) (Either Int String)
result <- runService choice (Left 5)
result `shouldBe` Left (CustomError "boom")
describe "composeMiddleware" $ do
it "applies outer then inner" $ do
let addTag tag (Service run) = Service $ \req ->
run (req ++ tag)
mw1 = addTag "[1]"
mw2 = addTag "[2]"
composed = composeMiddleware mw1 mw2
svc = Service $ \req -> pure (Right req)
result <- runService (composed svc) "start"
result `shouldBe` Right "start[1][2]"