elocrypt-2.1.0: test/Test/ElocryptTest.hs
{-# LANGUAGE TemplateHaskell #-}
module Test.ElocryptTest where
import Control.Monad
import Control.Monad.Random
import Data.Bool
import Data.Char hiding (isSymbol)
import Data.List
import Data.Maybe
import Test.QuickCheck
import Test.Tasty
import Test.Tasty.QuickCheck (testProperty)
import Test.Tasty.TH
import Data.Elocrypt
import Data.Elocrypt.Trigraph
import Data.Elocrypt.Utils (isSymbol)
import Test.Elocrypt.QuickCheck
import Test.Elocrypt.Instances
tests :: TestTree
tests = $(testGroupGenerator)
-- |Passwords have the specified length
prop_newPasswordHasLen :: Positive Int -> GenOptions -> StdGen -> Bool
prop_newPasswordHasLen (Positive len) opts gen = length pass == len
where pass = newPassword len opts gen
-- |Passwords are all lowercase when caps is false
prop_newPasswordIsLower :: Positive Int -> StdGen -> Bool
prop_newPasswordIsLower (Positive len) gen = all isLower pass
where
pass = newPassword len opts gen
opts = genOptions { genCapitals = False }
-- |Passwords with caps have at least one capital
prop_newPasswordHasCaps :: Positive Int -> StdGen -> Bool
prop_newPasswordHasCaps (Positive len) gen = any isUpper pass
where
pass = newPassword (len + 2) opts gen
opts = genOptions { genCapitals = True }
-- |Most passwords have more than one capital
prop_newPasswordHasMultipleCaps :: Positive Int -> StdGen -> Property
prop_newPasswordHasMultipleCaps (Positive len) gen =
checkCoverage $
cover 50 (length (filter isUpper pass) > 1) "has multiple caps" True
where
pass = newPassword (len + 2) opts gen
opts = genOptions { genCapitals = True }
-- |Passwords are all letters when numbers is false
prop_newPasswordIsAlpha :: Positive Int -> StdGen -> Bool
prop_newPasswordIsAlpha (Positive len) gen = all isAlpha pass
where
pass = newPassword len opts gen
opts = genOptions { genDigits = False }
-- |Most passwords have at least one number
prop_newPasswordHasDigits :: Positive Int -> StdGen -> Property
prop_newPasswordHasDigits (Positive len) gen =
checkCoverage $
cover 50 (any isDigit pass) "has digits" True
where
pass = newPassword (len + 2) opts gen
opts = genOptions { genDigits = True }
-- |Most passwords have at least one special character
prop_newPasswordHasSpecials :: Positive Int -> StdGen -> Property
prop_newPasswordHasSpecials (Positive len) gen =
checkCoverage $
cover 50 (any isSymbol pass) "has specials" True
where
pass = newPassword (len + 2) opts gen
opts = genOptions { genSpecials = True }
-- |Third and each successive character is taken from the trigraph
prop_3rdCharHasPositiveFrequency
:: Positive Int -> GenOptions -> StdGen -> Property
prop_3rdCharHasPositiveFrequency (Positive len) opts gen = conjoin $ loop pass
where
pass = newPassword (len + 2) opts gen
loop (f : s : t : xs) = thirdCharIsInTrigraph [f, s, t] : loop (s : t : xs)
loop _ = []
thirdCharIsInTrigraph :: String -> Property
thirdCharIsInTrigraph pass = counterexample failMsg
$ property (t `elem` candidates)
where
(f : s : t : _) = map toLower pass
candidates = map fst . filter ((0 /=) . snd) $ frequencies
frequencies =
zip ['a' .. 'z'] . defaultFrequencies . fromJust . findFrequency $ [f, s]
failMsg = t : " not in [" ++ candidates ++ "]"
-- First 2 characters have total non-zero frequencies
prop_first2HavePositiveFrequencies
:: Positive Int -> GenOptions -> StdGen -> Property
prop_first2HavePositiveFrequencies (Positive len) opts gen =
counterexample failMsg $ property (sum frequencies > 0)
where
pass = newPassword (len + 1) opts gen
(f : s : _) = map toLower pass
frequencies =
zipWith (curry snd) ['a' .. 'z'] . fromJust . findFrequency $ [f, s]
failMsg = "no candidates for '" ++ [f, s] ++ "'"
-- (len . fst) (genPasswords _ x _ _) = x
prop_newPasswordsHasLen
:: Positive Int -> Positive Int -> GenOptions -> StdGen -> Property
prop_newPasswordsHasLen (Positive len) (Positive num) opts gen =
counterexample failMsg $ property (length passes == num)
where
passes = newPasswords len num opts gen
failMsg = show (length passes) ++ " /= " ++ show num
-- (all ((x==) . length) . fst) (genPasswords x _ _ _) = x
prop_newPasswordsAllHaveLen
:: Positive Int -> Positive Int -> GenOptions -> StdGen -> Bool
prop_newPasswordsAllHaveLen (Positive len) (Positive num) opts gen = all
((len ==) . length)
passes
where passes = newPasswords len num opts gen
-- |Given the same generator, newPassword and genPassword generates the
-- same password.
prop_newPasswordMatchesGenPassword
:: Positive Int -> GenOptions -> StdGen -> Property
prop_newPasswordMatchesGenPassword (Positive len) opts gen =
counterexample failMsg $ property (pass == pass')
where
(pass, _) = genPassword len opts gen
pass' = newPassword len opts gen
failMsg = show pass ++ " /= " ++ show pass'
-- |Given the same generator, newPasswords and genPasswords genereates
-- the same passwords.
prop_newPasswordsMatchesGenPasswords
:: Positive Int -> Positive Int -> GenOptions -> StdGen -> Property
prop_newPasswordsMatchesGenPasswords (Positive len) (Positive num) opts gen =
counterexample failMsg $ property (passes == passes')
where
(passes, _) = genPasswords len num opts gen
passes' = newPasswords len num opts gen
failMsg = show passes ++ " /= " ++ show passes'
-- |newPassphrase generates n words
prop_newPassphraseHasLen
:: Positive Int
-> Positive Int
-> Positive Int
-> GenOptions
-> StdGen
-> Property
prop_newPassphraseHasLen (Positive len) (Positive min) (Positive max) opts gen
= counterexample failMsg $ property (length words == len)
where
words = newPassphrase len min max opts gen
failMsg = show len ++ " /= length " ++ show words
-- |newPassphrase generates words in the allowed range
prop_newPassphraseWordsHaveLen
:: Positive Int
-> Positive Int
-> Positive Int
-> GenOptions
-> StdGen
-> Bool
prop_newPassphraseWordsHaveLen (Positive len) (Positive min) (Positive max) opts gen
= all (\w -> length w >= min && length w <= max') words
where
words = newPassphrase len min max' opts gen
max' = min + max
-- |Given the same generator, genPassphrase and newPassphrase generates
-- the same passphrase
prop_genPassphraseMatchesNewPassphrase
:: Positive Int
-> Positive Int
-> Positive Int
-> GenOptions
-> StdGen
-> Property
prop_genPassphraseMatchesNewPassphrase (Positive len) (Positive min) (Positive max) opts gen
= counterexample failMsg $ property (words == words')
where
(words, _) = genPassphrase len min max' opts gen
words' = newPassphrase len min max' opts gen
max' = min + max
failMsg = show words ++ " /= " ++ show words'