scrape-changes-0.1.0.0: test/Network/ScrapeChanges/Internal/Test.hs
{-# LANGUAGE FlexibleInstances, QuasiQuotes, OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Network.ScrapeChanges.Internal.Test where
import Prelude hiding (filter)
import Network.ScrapeChanges.Internal as SUT
import Network.ScrapeChanges.Domain as Domain
import qualified Network.ScrapeChanges as SC
import qualified Data.Maybe as M
import qualified Data.List as L
import Data.List.NonEmpty
import Test.HUnit hiding (assertFailure)
import Test.QuickCheck
import Test.Framework as TF
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2
import Control.Lens
import qualified Data.ByteString.Lens as ByteStringLens
import qualified Data.Validation as V
import qualified Text.Email.Validate as EmailValidate
import qualified Data.Text.Lazy as TextLazy
import qualified Data.Hashable as Hashable
import Text.Shakespeare.Text
newtype NCronScheduleString = NCronScheduleString { nCronScheduleStringRun :: String } deriving Show
instance Show ScrapeConfig where
show (ScrapeConfig url (MailConfig mail))= show [lt|
ScrapeConfig { _scrapeInfoUrl = #{url}
, _scrapeInfoCallbackConfig = MailConfig #{show mail}
}
|]
show (ScrapeConfig url (OtherConfig _))= show [lt|
ScrapeConfig { _scrapeInfoUrl = #{url}
, _scrapeInfoCallbackConfig = OtherConfig (\x -> return ())
}
|]
instance Arbitrary NCronScheduleString where
arbitrary = NCronScheduleString <$> oneof [pure correctCronScheduleString, arbitrary]
correctCronScheduleString :: String
correctCronScheduleString = "*/2 * 3 * 4,5,6"
emailAddressGen :: Gen String
emailAddressGen = oneof [pure correctMailAddr, arbitrary]
instance Arbitrary MailAddr where
arbitrary = MailAddr <$> arbitrary <*> emailAddressGen
instance Arbitrary TextLazy.Text where
arbitrary = TextLazy.pack <$> arbitrary
instance Arbitrary Mail where
arbitrary = Mail <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary CallbackConfig where
arbitrary = oneof [otherConfigGen, mailConfigGen]
where otherConfigGen = return $ OtherConfig (const $ return ())
mailConfigGen = MailConfig <$> arbitrary
instance Arbitrary ScrapeConfig where
arbitrary = helper <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
where helper scrapeInfoUrl' config' mailFromAddr mailToAddrs =
let setConfig = scrapeInfoCallbackConfig .~ config'
in setConfig $ SC.mailScrapeConfig scrapeInfoUrl' mailFromAddr mailToAddrs
instance Arbitrary a => Arbitrary (NonEmpty a) where
arbitrary = (:|) <$> arbitrary <*> arbitrary
tMailAddr :: MailAddr
tMailAddr = MailAddr (Just $ TextLazy.pack "Max Mustermann") "max@mustermann.com"
correctMailAddr :: String
correctMailAddr = "correct@mail.com"
correctUrl :: String
correctUrl = "http://www.google.de"
correctMailScrapeConfig :: ScrapeConfig
correctMailScrapeConfig = SC.mailScrapeConfig correctUrl tMailAddr (tMailAddr :| [])
correctOtherScrapeConfig :: ScrapeConfig
correctOtherScrapeConfig = let setCallbackConfig = scrapeInfoCallbackConfig .~ OtherConfig (const $ return ())
in setCallbackConfig correctMailScrapeConfig
validateScrapeConfigWithBadInfoUrlShouldNotValidate :: Assertion
validateScrapeConfigWithBadInfoUrlShouldNotValidate =
let wrongUrl = "httpp://www.google.de"
scrapeInfo = scrapeInfoUrl .~ wrongUrl $ correctMailScrapeConfig
result = SUT.validateScrapeConfig scrapeInfo
in Just [UrlProtocolInvalid] @=? result ^? V._Failure
validateScrapeConfigShouldValidateOnValidInput :: Assertion
validateScrapeConfigShouldValidateOnValidInput =
let scrapeInfo = scrapeInfoUrl .~ correctUrl $ correctMailScrapeConfig
result = SUT.validateScrapeConfig scrapeInfo
in result @=? V.AccSuccess scrapeInfo
validateScrapeConfigWithOtherConfigShouldSatisfyAllInvariants :: ScrapeConfig -> Property
validateScrapeConfigWithOtherConfigShouldSatisfyAllInvariants si = M.isJust (si ^? scrapeInfoCallbackConfig . _OtherConfig) ==>
let result = SUT.validateScrapeConfig si
p1 = property $ M.isJust (result ^? V._Success)
badUrlErrorsOnly = (null . (L.\\ [UrlNotAbsolute, UrlProtocolInvalid])) <$> (result ^? V._Failure)
p2 = property $ False `M.fromMaybe` badUrlErrorsOnly
in p1 .||. p2
validateScrapeConfigWithMailConfigShouldSatisfyAllInvariants :: ScrapeConfig -> Property
validateScrapeConfigWithMailConfigShouldSatisfyAllInvariants si = M.isJust (si ^? scrapeInfoCallbackConfig . _MailConfig) ==>
let result = SUT.validateScrapeConfig si
failure = result ^? V._Failure
mailConfigLens = scrapeInfoCallbackConfig . _MailConfig
(Just mailConfig) = si ^? mailConfigLens
invalidMailAddr = not . EmailValidate.isValid . (^. ByteStringLens.packedChars)
invalidMailAddrsProp es = let expected = (\errors' -> (`elem` errors') `L.all` es) <$> failure
in True `M.fromMaybe` expected
mailFromAddr = mailConfig ^. mailFrom . mailAddr
mailToAddrs = mailConfig ^.. mailTo . traverse . mailAddr
invalidMailFromAddrsProp = invalidMailAddrsProp $ MailConfigInvalidMailFromAddr <$> [mailFromAddr | invalidMailAddr mailFromAddr]
invalidMailToAddrs = invalidMailAddr `L.filter` mailToAddrs
invalidMailToAddrsProp = invalidMailAddrsProp $ MailConfigInvalidMailToAddr <$> invalidMailToAddrs
in property invalidMailFromAddrsProp .&&. property invalidMailToAddrsProp
validateCronScheduleShouldSatisfyAllInvariants :: NCronScheduleString -> Property
validateCronScheduleShouldSatisfyAllInvariants c =
let result = SUT.validateCronSchedule $ nCronScheduleStringRun c
isCorrect = nCronScheduleStringRun c /= correctCronScheduleString
|| M.isJust (result ^? V._Success)
containsExpectedError = False `M.fromMaybe` ((CronScheduleInvalid "" `elem`) <$> (result ^? V._Failure))
in property isCorrect .||. property containsExpectedError
differentScrapeConfigsShouldYieldToDifferentHashes :: ScrapeConfig -> ScrapeConfig -> Property
differentScrapeConfigsShouldYieldToDifferentHashes c1 c2 =
let isOtherConfig = M.isJust . (^? scrapeInfoCallbackConfig . _OtherConfig)
in not (isOtherConfig c1 && isOtherConfig c2) ==>
let hashedC1 = Hashable.hash c1
hashedC2 = Hashable.hash c2
correspondenceBetweenEqualsAndHashable =
label "correspondenceBetweenEqualsAndHashable" $ if c1 == c2 then hashedC1 == hashedC2
else hashedC1 /= hashedC2
differentMailConfigAttribute = let setMailSubject = set $ scrapeInfoCallbackConfig . _MailConfig . mailSubject
c1' = c1 & setMailSubject (TextLazy.pack "sub1")
c2' = c2 & setMailSubject (TextLazy.pack "sub2")
in label "differentMailConfigAttribute" $ c1' /= c2'
in correspondenceBetweenEqualsAndHashable .&&. differentMailConfigAttribute
tests :: [TF.Test]
tests =
[
testGroup "Network.ScrapeChanges.Internal"
[
testCase "validateScrapeConfig with bad info url should not validate"
validateScrapeConfigWithBadInfoUrlShouldNotValidate
, testCase "validateScrapeConfig should validate on valid input"
validateScrapeConfigShouldValidateOnValidInput
, testProperty "validateScrapeConfig with mail config should satisfy all invariants"
validateScrapeConfigWithMailConfigShouldSatisfyAllInvariants
, testProperty "validateScrapeConfig with other config should satisfy all invariants"
validateScrapeConfigWithOtherConfigShouldSatisfyAllInvariants
, testProperty "validateCronScheduleString should satisfy all invariants"
validateCronScheduleShouldSatisfyAllInvariants
, testProperty "Different ScrapeConfig's should yield to different hashes"
differentScrapeConfigsShouldYieldToDifferentHashes
]
]