packages feed

spire-0.1.0.0: test/Properties.hs

{-# LANGUAGE TemplateHaskell #-}

module Main (main) where

import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

import Spire


-- | Identity layer does not change service behavior.
prop_identityLayer :: Property
prop_identityLayer = property $ do
  input <- forAll $ Gen.string (Range.linear 0 100) Gen.unicode
  let echo = Service pure :: Service IO String String
      wrapped = applyLayer identity echo
  result <- evalIO $ runService wrapped input
  expected <- evalIO $ runService echo input
  result === expected


-- | mapResponse composes covariantly: mapResponse g . mapResponse f == mapResponse (g . f)
prop_mapResponseComposition :: Property
prop_mapResponseComposition = property $ do
  input <- forAll $ Gen.int (Range.linear (-1000) 1000)
  let svc = Service pure :: Service IO Int Int
      f = (+ 1)
      g = (* 2)
      composed = mapResponse g (mapResponse f svc)
      fused    = mapResponse (g . f) svc
  r1 <- evalIO $ runService composed input
  r2 <- evalIO $ runService fused input
  r1 === r2


-- | mapRequest composes contravariantly: mapRequest f . mapRequest g == mapRequest (g . f)
prop_mapRequestContravariant :: Property
prop_mapRequestContravariant = property $ do
  input <- forAll $ Gen.int (Range.linear (-1000) 1000)
  let svc = Service pure :: Service IO Int Int
      f = (+ 1)
      g = (* 2)
      composed = mapRequest f (mapRequest g svc)
      fused    = mapRequest (g . f) svc
  r1 <- evalIO $ runService composed input
  r2 <- evalIO $ runService fused input
  r1 === r2


-- | dimap fusion: dimap f g (dimap h k svc) == dimap (h . f) (g . k) svc
prop_dimapFusion :: Property
prop_dimapFusion = property $ do
  input <- forAll $ Gen.int (Range.linear (-1000) 1000)
  let svc = Service pure :: Service IO Int Int
      f = (+ 1)
      g = (* 2)
      h = (+ 3)
      k = (* 5)
      nested = dimap f g (dimap h k svc)
      fused  = dimap (h . f) (g . k) svc
  r1 <- evalIO $ runService nested input
  r2 <- evalIO $ runService fused input
  r1 === r2


-- | Layer application order: (svc |> layer1) |> layer2 processes correctly.
-- Use 'before' layers that prepend to a list to verify ordering.
prop_layerApplicationOrder :: Property
prop_layerApplicationOrder = property $ do
  input <- forAll $ Gen.string (Range.linear 0 50) Gen.unicode
  let svc = Service (\req -> pure [req, "base"]) :: Service IO String [String]
      layer1 = middleware $ \(Service inner) -> Service $ \req -> do
        rs <- inner req
        pure ("layer1" : rs)
      layer2 = middleware $ \(Service inner) -> Service $ \req -> do
        rs <- inner req
        pure ("layer2" : rs)
      composed = (svc |> layer1) |> layer2
  result <- evalIO $ runService composed input
  -- layer2 wraps layer1 wraps svc, so layer2 prepends last (outermost)
  result === ["layer2", "layer1", input, "base"]


-- | before does not change the service's response.
prop_beforeDoesNotChangeResponse :: Property
prop_beforeDoesNotChangeResponse = property $ do
  input <- forAll $ Gen.int (Range.linear (-1000) 1000)
  let svc = Service pure :: Service IO Int Int
      wrapped = applyLayer (before (\_ -> pure ())) svc
  r1 <- evalIO $ runService svc input
  r2 <- evalIO $ runService wrapped input
  r1 === r2


-- | after does not change the service's response.
prop_afterDoesNotChangeResponse :: Property
prop_afterDoesNotChangeResponse = property $ do
  input <- forAll $ Gen.int (Range.linear (-1000) 1000)
  let svc = Service pure :: Service IO Int Int
      wrapped = applyLayer (after (\_ _ -> pure ())) svc
  r1 <- evalIO $ runService svc input
  r2 <- evalIO $ runService wrapped input
  r1 === r2


main :: IO Bool
main = checkParallel $$(discover)