packages feed

railroad-0.1.0.1: test/Railroad/MonadErrorSpec.hs

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE TypeApplications #-}

module Railroad.MonadErrorSpec where

import           Control.Monad.Except
import           Data.Functor.Identity
import           Data.Validation
import           Railroad.MonadError
import           Test.Hspec

-- Helper to run MonadError computations in pure Either
runMonadError :: ExceptT String Identity a -> Either String a
runMonadError = runIdentity . runExceptT

spec :: Spec
spec = do
  describe "MonadError version of Operators" $ do
    describe "Basic Operators (? and ??)" $ do
      it "unwraps success values with (?)" $ do
        runMonadError (pure (Just 10 :: Maybe Int) ? "missing") `shouldBe` Right 10
      it "throws constant error on failure with (?)" $ do
        runMonadError (pure (Nothing :: Maybe ()) ? "missing") `shouldBe` Left "missing"
      it "maps internal errors with (??)" $ do
        let action = pure (Left "original" :: Either String String)
        runMonadError (action ?? reverse) `shouldBe` Left "lanigiro"

    describe "Predicate Operator (?>)" $ do
      it "passes when predicate is met" $ do
        runMonadError (pure 10 ?> (> 5) $ const "too small") `shouldBe` Right 10
      it "fails when predicate is not met" $ do
        runMonadError (pure 4 ?> (> 5) $ const "too small") `shouldBe` Left "too small"

    describe "Recovery Operators (?~ and ??~)" $ do
      it "recovers to a constant value with (?~)" $ do
        runMonadError (pure Nothing ?~ 0) `shouldBe` Right (0 :: Int)
      it "recovers using a function with (??~)" $ do
        runMonadError (pure (Left "err") ??~ length) `shouldBe` Right 3

    describe "Cardinality Operators" $ do
      describe "(?+)" $ do
        it "succeeds on non-empty list" $ do
          runMonadError (pure [1, 2, 3] ?+ "empty") `shouldBe` Right [1, 2, 3]
        it "fails on empty list" $ do
          runMonadError (pure ([] :: [Int]) ?+ "empty") `shouldBe` Left "empty"

      describe "(?!)" $ do
        let toErr = cardinalityErr "none" (const "too many")
        it "extracts the single element" $ do
          runMonadError (pure [42] ?! toErr) `shouldBe` Right 42
        it "fails on empty" $ do
          runMonadError (pure ([] :: [Int]) ?! toErr) `shouldBe` Left "none"
        it "fails on multiple elements" $ do
          runMonadError (pure [1, 2] ?! toErr) `shouldBe` Left "too many"

      describe "(?∅)" $ do
        it "succeeds on empty" $ do
          runMonadError (pure [] ?∅ const "not empty") `shouldBe` Right ()
        it "fails on non-empty" $ do
          runMonadError (pure [1] ?∅ const "not empty") `shouldBe` Left "not empty"