{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Main
( main
) where
import Control.Exception
import qualified Data.ByteString.Char8 as B
import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI
import qualified Data.Map as Map
import qualified Data.Text.Lazy as L
import Data.Time.Calendar
import Data.Time.LocalTime
import Test.QuickCheck
import Test.QuickCheck.Property
import Test.Tasty
import Test.Tasty.QuickCheck
import Network.Email.Charset
import qualified Network.Email.Header.Read as H
import qualified Network.Email.Header.Render as R
import Network.Email.Header.Types
instance Eq ZonedTime where
ZonedTime t1 z1 == ZonedTime t2 z2 = (t1, z1) == (t2, z2)
instance Arbitrary ZonedTime where
arbitrary = ZonedTime <$> local <*> zone
where
local = do
h <- choose (0, 23)
m <- choose (0, 59)
s <- fromInteger <$> choose (0, 60)
d <- choose (0, 50000)
return $ LocalTime (ModifiedJulianDay d) (TimeOfDay h m s)
zone = minutesToTimeZone <$> choose (-12*60, 14*60)
option :: Gen a -> Gen (Maybe a)
option gen = frequency [(1, pure Nothing), (3, Just <$> gen)]
char :: Gen Char
char = frequency
[ (9, elements [' ' .. '~'])
, (1, arbitrary)
]
text :: Gen L.Text
text = L.pack <$> listOf char
phrase :: Gen L.Text
phrase = L.pack <$> listOf1 char
token :: Gen B.ByteString
token = resize 20 $ B.pack <$> listOf1 (elements ttext) -- TODO
where
ttext = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "!#$%&'*+-^_`{|}~"
tokenCI :: Gen (CI B.ByteString)
tokenCI = CI.mk <$> token
addrSpec :: Gen B.ByteString
addrSpec = B.concat <$> sequence [token, pure "@", token] -- TODO
mimeVersion :: Gen (Int, Int)
mimeVersion = (,) <$> digit <*> digit
where
digit = choose (0, 9)
encoding :: Gen (CI B.ByteString)
encoding = elements
["base64", "quoted-printable", "8bit", "7bit", "binary", "x-token"]
contentType :: Gen (MimeType, Parameters)
contentType = (,) <$> arbitrary <*> params
where
params = Map.fromList <$> listOf param
param = (,) <$> tokenCI <*> token
instance Arbitrary Address where
arbitrary = Address <$> addrSpec
instance Arbitrary Mailbox where
arbitrary = Mailbox <$> option phrase <*> arbitrary
instance Arbitrary Recipient where
arbitrary = oneof
[Individual <$> arbitrary, Group <$> phrase <*> listOf1 arbitrary]
instance Arbitrary MessageID where
arbitrary = MessageID <$> addrSpec
instance Arbitrary MimeType where
arbitrary = MimeType <$> tokenCI <*> tokenCI
instance Arbitrary R.RenderOptions where
arbitrary = R.RenderOptions
<$> choose (20, 80)
<*> choose (1, 8)
<*> arbitrary
<*> arbitrary
instance Arbitrary Charset where
arbitrary = pure defaultCharset -- TODO
instance Arbitrary R.Encoding where
arbitrary = arbitraryBoundedEnum
list1 :: Arbitrary a => Gen [a]
list1 = listOf1 arbitrary
roundTrip
:: (Eq a, Show a)
=> String
-> Gen a
-> (a -> (HeaderName, R.Doc))
-> (Headers -> Either SomeException a)
-> TestTree
roundTrip name gen renderer parser = testProperty name $
forAll gen $ \a opts ->
let hs = R.renderHeaders opts [renderer a]
in case parser hs of
Left e -> exception "exception" e
Right b -> liftBool (b == a)
tests :: TestTree
tests = testGroup "round trip"
[ -- Origination date
roundTrip "Date" arbitrary R.date H.date
-- Originator
, roundTrip "From" list1 R.from H.from
, roundTrip "Sender" arbitrary R.sender H.sender
, roundTrip "Reply-To" list1 R.replyTo H.replyTo
-- Destination address
, roundTrip "To" list1 R.to H.to
, roundTrip "Cc" list1 R.cc H.cc
, roundTrip "Bcc" (option list1) R.bcc H.bcc
-- Identification
, roundTrip "Message-ID" arbitrary R.messageID H.messageID
, roundTrip "In-Reply-To" list1 R.inReplyTo H.inReplyTo
, roundTrip "References" list1 R.references H.references
-- Informational
, roundTrip "Subject" text R.subject H.subject
, roundTrip "Comments" text R.comments H.comments
, roundTrip "Keywords" (listOf1 phrase) R.keywords H.keywords
-- Resent
, roundTrip "Resent-Date" arbitrary R.resentDate H.resentDate
, roundTrip "Resent-From" list1 R.resentFrom H.resentFrom
, roundTrip "Resent-Sender" arbitrary R.resentSender H.resentSender
, roundTrip "Resent-To" list1 R.resentTo H.resentTo
, roundTrip "Resent-Cc" list1 R.resentCc H.resentCc
, roundTrip "Resent-Bcc" (option list1) R.resentBcc H.resentBcc
, roundTrip "Resent-Message-ID" arbitrary
R.resentMessageID H.resentMessageID
-- MIME
, roundTrip "MIME-Version" mimeVersion (uncurry R.mimeVersion) H.mimeVersion
, roundTrip "Content-Type" contentType (uncurry R.contentType) H.contentType
, roundTrip "Content-Transfer-Encoding" encoding
R.contentTransferEncoding H.contentTransferEncoding
, roundTrip "Content-ID" arbitrary R.contentID H.contentID
]
main :: IO ()
main = defaultMain tests