data-validation-0.1.2.0: test/Data/ValidationSpec.hs
{-# LANGUAGE
FlexibleInstances
, MultiParamTypeClasses
, TemplateHaskellQuotes
, TypeSynonymInstances
#-}
module Data.ValidationSpec where
------------------------------------------------------------------------------------------------------------------------
import Test.Hspec
import Data.Bool (bool)
import Data.Map (fromList)
import Text.Read (readEither)
------------------------------------------------------------------------------------------------------------------------
import Data.Validation
import Data.Validation.Internal
------------------------------------------------------------------------------------------------------------------------
spec :: Spec
spec = parallel $ do
describe "Semigroup (Proof f a)" $ do
it "If applied to two valid proofs, it concatenate the inhabitants." $ do
Valid [1] <> Valid [2] `shouldBe` (Valid [1,2] :: Proof String [Int])
it "If applied to an invalid and valid proof, it results in the invalid proof." $ do
let
vi = Invalid ["Failure"] (fromList [([mkName "Field1"], ["Field Failure"])])
(vi :: Proof String [Int]) <> Valid [2] `shouldBe` vi
it "If applied to a valid and invalid proof, it results in the invalid proof." $ do
let
vi = Invalid ["Failure"] (fromList [([mkName "Field1"], ["Field Failure"])])
Valid [2] <> vi `shouldBe` (vi :: Proof String [Int])
it "If applied to two invalid proofs, it concatenates the errors." $ do
let
vi1 = Invalid ["Failure 1"]
(fromList [([mkName "Field1"], ["Field Failure 1"])])
vi2 = Invalid ["Failure 2"]
(fromList [([mkName "Field1"], ["Field Failure 1.1"]), ([mkName "Field2"], ["Field Failure 2"])])
vi3 = Invalid ["Failure 1", "Failure 2"]
(fromList [([mkName "Field1"], ["Field Failure 1", "Field Failure 1.1"]), ([mkName "Field2"], ["Field Failure 2"])])
(vi1 :: Proof String [Int]) <> vi2 `shouldBe` vi3
describe "Functor (Proof f)" $ do
it "Converts a `Proof f a` to a `Proof f b`." $ do
fmap show (Valid 1 :: Proof String Int) `shouldBe` Valid "1"
it "Does not change the contents of an invalid proof." $ do
let vi = Invalid ["Failure"] (fromList [([mkName "Field1"], ["Field Failure"])])
fmap show (vi :: Proof String Int) `shouldBe` (vi :: Proof String String)
describe "Applicative (Proof f)" $ do
let
vap = Valid show :: Proof String (Int -> String)
iap = Invalid ["Applicative Failure"] mempty
vp = Valid 1 :: Proof String Int
ip = Invalid ["Failure"] (fromList [([mkName "Field1"], ["Field Failure"])])
it "Constructs a new Valid proof" $ do
pure 1 `shouldBe` (Valid 1 :: Proof String Int)
it "If applied to two invalid proofs, it concatenates the errors." $ do
(iap :: Proof String (Int -> String)) <*> ip
`shouldBe` Invalid ["Applicative Failure", "Failure"] (fromList [([mkName "Field1"], ["Field Failure"])])
it "If applied to an invalid and valid proof, it results in the invalid proof." $ do
(iap :: Proof String (Int -> String)) <*> vp
`shouldBe` (iap :: Proof String String)
it "If applied to a valid and invalid proof, it results in the invalid proof." $ do
vap <*> (ip :: Proof String Int) `shouldBe` (ip :: Proof String String)
it "If applied to two valid proofs, it should transform the second proof." $ do
vap <*> vp `shouldBe` Valid "1"
describe "Monad (Proof f)" $ do
it "Binds a valid proof to a function." $ do
((Valid 1 :: Proof String Int) >>= pure . show) `shouldBe` Valid "1"
it "Does not change the contents of an invalid proof." $ do
((Invalid ["Failure"] mempty :: Proof String Int) >>= pure . show) `shouldBe` Invalid ["Failure"] mempty
describe "fromVCtx" $ do
let one = 1 :: Int
it "Transforms a valid context to a valid proof." $ do
fromVCtx (ValidCtx one :: VCtx String Int) `shouldBe` Valid one
it "Transforms a disputed context to an invalid proof." $ do
let
v1 = DisputedCtx ["Failure"] (fromList [([mkName "Field1"],["Field Failure 1"])]) one
v2 = Invalid ["Failure"] (fromList [([mkName "Field1"],["Field Failure 1"])])
fromVCtx v1 `shouldBe` v2
it "Transforms a refuted context to an invalid proof." $ do
let
v1 = RefutedCtx ["Failure"] (fromList [([mkName "Field1"],["Field Failure 1"])])
v2 = Invalid ["Failure"] (fromList [([mkName "Field1"],["Field Failure 1"])])
fromVCtx (v1 :: VCtx String Int) `shouldBe` v2
describe "Functor ValueCtx" $ do
it "transforms a global context." $ do
fmap show (Global 1 :: ValueCtx Int) `shouldBe` Global "1"
it "transforms a field context while preserving the field name." $ do
fmap show (Field (mkName "Field1") 1 :: ValueCtx Int) `shouldBe` Field (mkName "Field1") "1"
describe "getValue" $ do
it "Retrieves the value from a global context." $ do
getValue (Global "1") `shouldBe` "1"
it "Retrieves the value from a field context." $ do
getValue (Field (mkName "Field1") "1") `shouldBe` "1"
describe "setValue" $ do
it "Creates a global context based on the given context." $ do
setValue (Global "1") (1 :: Int) `shouldBe` Global 1
it "Retrieves the value from a field context." $ do
setValue (Field (mkName "Field1") "1") (1 :: Int) `shouldBe` (Field (mkName "Field1") 1)
describe "isRequired" $ do
it "Adds a failure to the context if the value is Nothing." $ do
let
v = Global (Nothing :: Maybe Bool)
r = isRequired "Failed" v
r `shouldBe` refute v "Failed"
it "Should add no failure if the value is Just." $ do
let
v = Global $ Just True
v2 = Global True
r = isRequired "Failed" v
r `shouldBe` pure v2
describe "isRequiredWhen" $ do
it "Adds a failure to the context if the condition is True and the value is Nothing." $ do
let
v = Global (Nothing :: Maybe Bool)
r = isRequiredWhen "Failed" True v
r `shouldBe` dispute v "Failed"
it "Should add no failure when the condition is False and the value is Nothing." $ do
let
v = Global (Nothing :: Maybe Bool)
r = isRequiredWhen "Failed" False v
r `shouldBe` pure v
it "Should add no failure if the condition is True and the value is Just." $ do
let
v = Global $ Just True
r = isRequiredWhen "Failed" True v
r `shouldBe` pure v
it "Should add no failure if the condition is False and the value is Just." $ do
let
v = Global $ Just True
r = isRequiredWhen "Failed" False v
r `shouldBe` pure v
describe "isLeft" $ do
it "Adds a failure to the context if the value is Right." $ do
let
v = Global (Right True :: Either Bool Bool)
r = isLeft "Failed" v
r `shouldBe` refute v "Failed"
it "Should add no failure if the value is Left." $ do
let
v = Global $ (Left True :: Either Bool Bool)
v2 = Global True
r = isLeft "Failed" v
r `shouldBe` pure v2
describe "isRight" $ do
it "Adds a failure to the context if the value is Left." $ do
let
v = Global (Left True :: Either Bool Bool)
r = isRight "Failed" v
r `shouldBe` refute v "Failed"
it "Should add no failure if the value is Right." $ do
let
v = Global $ (Right True :: Either Bool Bool)
v2 = Global True
r = isRight "Failed" v
r `shouldBe` pure v2
describe "isNull" $ do
it "Adds a failure to the context if the value is not null." $ do
let
v = Global "not null"
r = isNull "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value is null." $ do
let
v = Global ""
r = isNull "Failed" v
r `shouldBe` pure v
describe "isNotNull" $ do
it "Adds a failure to the context if the value is null." $ do
let
v = Global ""
r = isNotNull "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value is not null." $ do
let
v = Global "not null"
r = isNotNull "Failed" v
r `shouldBe` pure v
describe "minLength" $ do
it "Adds a failure to the context if the value has a length less than the given value." $ do
let
v = Global "TW"
r = minLength 3 "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value has a length equal to the given value." $ do
let
v = Global "THR"
r = minLength 3 "Failed" v
r `shouldBe` pure v
it "Should add no failure if the value has a length greater than the given value." $ do
let
v = Global "THREE"
r = minLength 3 "Failed" v
r `shouldBe` pure v
describe "maxLength" $ do
it "Adds a failure to the context if the value has a length greater than the given value." $ do
let
v = Global "FOUR"
r = maxLength 3 "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value has a length equal to the given value." $ do
let
v = Global "THR"
r = maxLength 3 "Failed" v
r `shouldBe` pure v
it "Should add no failure if the value has a length less than the given value." $ do
let
v = Global "TW"
r = maxLength 3 "Failed" v
r `shouldBe` pure v
describe "isLength" $ do
it "Adds a failure to the context if the value has a length greater than the given value." $ do
let
v = Global "FOUR"
r = isLength 3 "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value has a length equal to the given value." $ do
let
v = Global "THR"
r = isLength 3 "Failed" v
r `shouldBe` pure v
it "Adds a failure to the context if the value has a length less than the given value." $ do
let
v = Global "TW"
r = isLength 3 "Failed" v
r `shouldBe` dispute v "Failed"
describe "isEqual" $ do
it "Adds a failure to the context if the value is not equal to the given value." $ do
let
v = Global "1"
r = isEqual "2" "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value is equal to the given value." $ do
let
v = Global "1"
r = isEqual "1" "Failed" v
r `shouldBe` pure v
describe "isNotEqual" $ do
it "Adds a failure to the context if the value is equal to the given value." $ do
let
v = Global "1"
r = isNotEqual "1" "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value is not equal to the given value." $ do
let
v = Global "1"
r = isNotEqual "2" "Failed" v
r `shouldBe` pure v
describe "isLessThan" $ do
let
one = 1 :: Int
two = 2 :: Int
three = 3 :: Int
it "Adds a failure to the context if the value is greater than the given value." $ do
let
v = Global three
r = isLessThan two "Failed" v
r `shouldBe` dispute v "Failed"
it "Adds a failure to the context if the value is equal to the given value." $ do
let
v = Global two
r = isLessThan two "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value is less than the given value." $ do
let
v = Global one
r = isLessThan two "Failed" v
r `shouldBe` pure v
describe "isLessThanOrEqual" $ do
let
one = 1 :: Int
two = 2 :: Int
three = 3 :: Int
it "Adds a failure to the context if the value is greater than the given value." $ do
let
v = Global three
r = isLessThanOrEqual two "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value is equal to the given value." $ do
let
v = Global two
r = isLessThanOrEqual two "Failed" v
r `shouldBe` pure v
it "Should add no failure if the value is less than the given value." $ do
let
v = Global one
r = isLessThanOrEqual two "Failed" v
r `shouldBe` pure v
describe "isGreaterThan" $ do
let
one = 1 :: Int
two = 2 :: Int
three = 3 :: Int
it "Should add no failure if the value is greater than the given value." $ do
let
v = Global three
r = isGreaterThan two "Failed" v
r `shouldBe` pure v
it "Adds a failure to the context if the value is equal to the given value." $ do
let
v = Global two
r = isGreaterThan two "Failed" v
r `shouldBe` dispute v "Failed"
it "Adds a failure to the context if the value is less than the given value." $ do
let
v = Global one
r = isGreaterThan two "Failed" v
r `shouldBe` dispute v "Failed"
describe "isGreaterThanOrEqual" $ do
let
one = 1 :: Int
two = 2 :: Int
three = 3 :: Int
it "Should add no failure if the value is greater than the given value." $ do
let
v = Global three
r = isGreaterThanOrEqual two "Failed" v
r `shouldBe` pure v
it "Should add no failure if the value is equal to the given value." $ do
let
v = Global two
r = isGreaterThanOrEqual two "Failed" v
r `shouldBe` pure v
it "Adds a failure to the context if the value is less than the given value." $ do
let
v = Global one
r = isGreaterThanOrEqual two "Failed" v
r `shouldBe` dispute v "Failed"
describe "hasElem" $ do
it "Should add no failure if the value contains the given element." $ do
let
v = Global "test@example.com"
r = hasElem '@' "Failed" v
r `shouldBe` pure v
it "Adds a failure to the context if the value does not contain the given element." $ do
let
v = Global "test.example.com"
r = hasElem '@' "Failed" v
r `shouldBe` dispute v "Failed"
describe "doesNotHaveElem" $ do
it "Adds a failure to the context if the value contains the given element." $ do
let
v = Global "test@example site.com"
r = doesNotHaveElem ' ' "Failed" v
r `shouldBe` dispute v "Failed"
it "Should add no failure if the value does not contain the given element." $ do
let
v = Global "test@example-site.com"
r = doesNotHaveElem ' ' "Failed" v
r `shouldBe` pure v
describe "ifAny" $ do
it "Adds a failure to the context if no element in the list passes the check." $ do
let
v = Global ["foo", "bar", "baz", "bat"]
r = ifAny (\e -> bool (Just (e ++ " does not equal buz")) Nothing $ e == "buz") v
r `shouldBe` disputeMany v ["foo does not equal buz","bar does not equal buz","baz does not equal buz","bat does not equal buz"]
it "Should add no failure if any element in the list passed the check." $ do
let
v = Global ["foo", "bar", "baz", "bat"]
r = ifAny (\e -> bool (Just (e ++ " does not equal foo")) Nothing $ e == "foo") v
r `shouldBe` pure v
describe "ifAll" $ do
it "Adds a failure to the context if any element in the list does not pass the check." $ do
let
v = Global ["foo", "bar", "baz", "bat"]
r = ifAll (\e -> bool (Just (e ++ " does not have an 'a'")) Nothing $ elem 'a' e) v
r `shouldBe` disputeMany v ["foo does not have an 'a'"]
it "Should add no failure if every element in the list passed the check." $ do
let
v = Global ["bar", "baz", "bat"]
r = ifAll (\e -> bool (Just (e ++ " does not have an 'a'")) Nothing $ elem 'a' e) v
r `shouldBe` pure v
describe "ifEach" $ do
it "Adds a failure to the context if any element in the list does not pass the check." $ do
let
v = Global ["1", "2", "3", "bat"]
r = ifEach (\e -> readEither e :: Either String Int) v
r `shouldBe` refute v "Prelude.read: no parse"
it "Should add no failure if every element in the list passed the check." $ do
let
v = Global ["1", "2", "3"]
r = ifEach (\e -> readEither e :: Either String Int) v
v2 = Global [1, 2, 3]
r `shouldBe` pure v2
describe "ifEachProven" $ do
it "Adds a failure to the context if any element in the list does not pass the check." $ do
let
v = Global ["1234567", "7654321", "5555555", "bat"]
r = ifEachProven mkPhoneNumber v
r `shouldBe` refute v "Phone number is the wrong length."
it "Should add no failure if every element in the list passed the check." $ do
let
v = Global ["1234567", "7654321", "5555555"]
r = ifEachProven mkPhoneNumber v
v2 = Global [PhoneNumber "1234567", PhoneNumber "7654321", PhoneNumber "5555555"]
r `shouldBe` pure v2
describe "isMatch" $ do
it "Adds a failure to the context if value does not match the given value." $ do
let
v = Global "asdf"
vm = pure "fdsa"
r = isMatch "Failure" vm v
r `shouldBe` dispute v "Failure"
it "Should add no failure if the value matches the given value." $ do
let
v = Global "asdf"
vm = pure "asdf"
r = isMatch "Failure" vm v
r `shouldBe` pure v
describe "validateField" $ do
it "Uses the `Validatable` instance on a subfield for validation." $ do
let
c = Field (mkName "contact") (ContactVM "")
fromVCtx (validateField c) `shouldBe` Invalid [] (fromList [
([(mkName "contact"), 'phoneNumber], ["Phone Number cannot be empty."])
])
describe "optional" $ do
it "Nothing value is valid if optional" $ do
let proof = fromVCtx $ optional (Nothing :: Maybe ContactVM) $ \v -> withValue v $ validateField
proof `shouldBe` Valid Nothing
it "Just value is will validate if optional" $ do
let c = (ContactVM "")
let proof = fromVCtx $ optional (Just c) $ \v -> withValue v $ validateField
proof `shouldBe` Invalid [] (fromList [(['phoneNumber], ["Phone Number cannot be empty."])])
describe "whenJust" $ do
it "Nothing value is valid if optional" $ do
let proof = fromVCtx $ withValue (Nothing :: Maybe ContactVM) $ whenJust validateField
proof `shouldBe` Valid Nothing
it "Just value is will validate if optional" $ do
let c = (ContactVM "")
let proof = fromVCtx $ withValue (Just c) $ whenJust validateField
proof `shouldBe` Invalid [] (fromList [(['phoneNumber], ["Phone Number cannot be empty."])])
data Contact
= Contact
{ validPhoneNumber :: String
} deriving (Show, Eq)
data ContactVM
= ContactVM
{ phoneNumber :: String
} deriving (Show, Eq)
newtype PhoneNumber = PhoneNumber { unPhoneNumber :: String }
deriving (Show, Eq)
mkPhoneNumber :: String -> Proof String PhoneNumber
mkPhoneNumber s = fromVCtx $ do
v <- withValue s (isLength 7 "Phone number is the wrong length.")
return $ PhoneNumber v
instance Validatable String ContactVM Contact where
validation c =
let
vp = withField 'phoneNumber (phoneNumber c) $ \v ->
isNotNull "Phone Number cannot be empty." v
in pure Contact <*> vp