elocrypt-2.0.1: test/Test/ElocryptTest.hs
{-# LANGUAGE TemplateHaskell #-}
module Test.ElocryptTest where
import Control.Monad
import Control.Monad.Random
import Data.Bool
import Data.Char
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 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 =
cover 50 (length (filter isUpper pass) > 1) "has multiple caps"
$ any isLower pass
where
pass = newPassword (len + 2) opts gen
opts = genOptions { genCapitals = 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'