script-monad-0.0.4: test/Control/Monad/Script/Http/Test.hs
{-# LANGUAGE OverloadedStrings, RecordWildCards, Rank2Types, BangPatterns #-}
module Control.Monad.Script.Http.Test (
tests
) where
import Control.Concurrent.MVar
import Control.Monad.Trans.Identity
import System.IO
import Data.Functor.Identity
( Identity(..) )
import Data.Proxy
import Data.String
import Data.ByteString.Lazy
( ByteString, pack, toStrict, fromChunks )
import Data.Text
( Text )
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Text.Extras
import Data.Typeable
import Data.List (isSuffixOf)
import Network.HTTP.Types
import Network.HTTP.Client
( HttpException(..), HttpExceptionContent(..) )
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Test.QuickCheck.Property
( failed, rejected )
import Control.Monad.Script.Http
import Data.MockIO
import Data.MockIO.FileSystem
import Data.MockIO.Test.Server
tests :: Int -> MVar () -> TestTree
tests num lock = testGroup "Http"
[ localOption (QuickCheckTests $ 10 * num) $ testGroup "Mock IO"
[ testGroup "Internal"
[ testProperty "comment: return ()" $
prop_comment_value pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "comment: state unchanged" $
prop_comment_state pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "wait: return ()" $
prop_wait_value pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "wait: state unchanged" $
prop_wait_state pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "httpGet: return" $
prop_httpGet pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "httpSilentGet: return" $
prop_httpSilentGet pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "httpPost: return ()" $
prop_httpPost pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "httpSilentPost: return ()" $
prop_httpSilentPost pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "throwError: is caught" $
prop_throwError pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "throwJsonError: is caught" $
prop_throwJsonError pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "parseJson: valid" $
prop_parseJson pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "lookupKeyJson: valid" $
prop_lookupKeyJson pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
, testProperty "logEntries: value" $
prop_logEntries_log pU pU pU pU (evalMockIO evalId) (toIO simpleMockWorld) undefined
]
, testGroup "External"
[ testProperty "comment: is logged" $
prop_comment_write pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "comment: silent" $
prop_comment_silent pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "wait: is logged" $
prop_wait_write pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "wait: silent" $
prop_wait_silent pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "httpGet: is logged" $
prop_httpGet_write pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "httpGet: json" $
prop_httpGet_json pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "httpGet: silent" $
prop_httpGet_silent pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "httpSilentGet: is not logged" $
prop_httpSilentGet_write pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "throwError: is logged" $
prop_throwError_write pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
, testProperty "logDebug: is logged" $
prop_logDebug_write pU pU pU pU (evalMockIO evalId) (toIOs simpleMockWorld) undefined
]
]
, localOption (QuickCheckTests num) $ testGroup "Real IO"
[ testGroup "Internal"
[ real_internal_tests_for pU pU pU pU
, real_internal_tests_for pI pI pI pI
]
]
]
real_internal_tests_for
:: ( Eq s, Eq e, Eq w
, Show s, Show r, Show e, Show w
, Arbitrary s, Arbitrary r, Arbitrary e, Arbitrary w
, Typeable e, Typeable r, Typeable w, Typeable s
)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s -> TestTree
real_internal_tests_for pe pr pw ps =
let
label =
"e: " ++ show (typeRep pe) ++ "; " ++
"r: " ++ show (typeRep pr) ++ "; " ++
"w: " ++ show (typeRep pw) ++ "; " ++
"s: " ++ show (typeRep ps) ++ " "
in
testGroup label
[ testProperty "comment: return ()" $
prop_comment_value pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "comment: state unchanged" $
prop_comment_state pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "wait: return ()" $
prop_wait_value pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "wait: state unchanged" $
prop_wait_state pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "httpGet: return" $
prop_httpGet pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "httpSilentGet: return" $
prop_httpSilentGet pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "httpPost: return ()" $
prop_httpPost pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "httpSilentPost: return ()" $
prop_httpSilentPost pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "throwError: is caught" $
prop_throwError pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "throwJsonError: is caught" $
prop_throwJsonError pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "parseJson: valid" $
prop_parseJson pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "lookupKeyJson: valid" $
prop_lookupKeyJson pe pr pw ps (evalIO evalId) runIdentityT undefined
, testProperty "logEntries: value" $
prop_logEntries_log pe pr pw ps (evalIO evalId) runIdentityT undefined
]
pU :: Proxy ()
pU = Proxy
pI :: Proxy Int
pI = Proxy
toIO :: MockWorld s -> IdentityT (MockIO s) a -> IO a
toIO u (IdentityT (MockIO x)) = do
let (a,_) = x u
return a
toIOs :: MockWorld s -> IdentityT (MockIO s) a -> IO (a, MockWorld s)
toIOs u (IdentityT (MockIO x)) = do
return $ x u
as :: a -> a -> a
as _ a = a
testEnv :: r -> R e w r
testEnv r = (trivialEnv r)
{ _logHandle = stdout
, _logOptions = trivialLogOptions
{ _logSilent = True
}
}
jsonEnv :: r -> R e w r
jsonEnv r = (trivialEnv r)
{ _logHandle = stdout
, _logOptions = trivialLogOptions
{ _logSilent = False
, _logJson = True
, _logColor = False
}
}
noisyEnv :: r -> R e w r
noisyEnv r = (trivialEnv r)
{ _logHandle = stdout
, _logOptions = trivialLogOptions
{ _logSilent = False
}
}
prop_comment_value
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> Text -> Property
prop_comment_value _ _ _ _ eval cond x s r msg =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== ())) $
as x $ do
comment msg
prop_comment_state
:: (Monad eff, Eq s, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> Text -> Property
prop_comment_state _ _ _ _ eval cond x s r msg =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasState (== s)) $
as x $ do
comment msg
prop_comment_write
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Text -> Property
prop_comment_write _ _ _ _ eval cond x s r str =
let msg = T.filter (/= '\n') str in
checkHttpTT (basicState s) (noisyEnv r) eval cond
(hasWorld $ outputContains msg) $
as x $ do
comment msg
prop_comment_silent
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Text -> Property
prop_comment_silent _ _ _ _ eval cond x s r str =
let msg = T.filter (/= '\n') str in
checkHttpTT (basicState s) (testEnv r) eval cond
(hasWorld outputIsEmpty) $
as x $ do
comment msg
hasWorld
:: (MockWorld u -> Bool)
-> (v, MockWorld u)
-> Bool
hasWorld p (_,w) = p w
outputContains
:: Text
-> MockWorld u
-> Bool
outputContains str world =
case getLines (Right stdout) $ _files world of
Nothing -> False
Just ls -> any (T.isSuffixOf str) ls
outputIsEmpty
:: MockWorld u -> Bool
outputIsEmpty world =
_files world == emptyFileSystem
prop_wait_value
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> Int -> Property
prop_wait_value _ _ _ _ eval cond x s r k =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== ())) $
as x $ do
wait k
prop_wait_state
:: (Monad eff, Eq s, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> Int -> Property
prop_wait_state _ _ _ _ eval cond x s r k =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasState (== s)) $
as x $ do
wait k
prop_wait_write
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Int -> Property
prop_wait_write _ _ _ _ eval cond x s r k =
checkHttpTT (basicState s) (noisyEnv r) eval cond
(hasWorld $ outputContains $ "Wait for " <> T.pack (show k) <> "μs") $
as x $ do
wait k
prop_wait_silent
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Int -> Property
prop_wait_silent _ _ _ _ eval cond x s r k =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasWorld outputIsEmpty) $
as x $ do
wait k
prop_httpGet
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff Int
-> s -> r -> Property
prop_httpGet _ _ _ _ eval cond x s r =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== 200)) $
as x $ do
response <- httpGet "http://example.com"
return (statusCode $ _responseStatus response)
prop_httpGet_json
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Property
prop_httpGet_json _ _ _ _ eval cond x s r =
checkHttpTT (basicState s) (jsonEnv r) eval cond
(hasWorld $ outputContains "True") $
as x $ do
val1 <- httpGet "http://example.com/json"
>>= (return . _responseBody)
>>= parseJson
val2 <- parseJson "{\"key\":\"value\"}"
comment $ T.pack $ show $ val1 == val2
return ()
prop_httpGet_write
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Property
prop_httpGet_write _ _ _ _ eval cond x s r =
checkHttpTT (basicState s) (noisyEnv r) eval cond
(hasWorld $ outputContains "GET http://example.com") $
as x $ do
httpGet "http://example.com"
return ()
prop_httpGet_silent
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Property
prop_httpGet_silent _ _ _ _ eval cond x s r =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasWorld outputIsEmpty) $
as x $ do
httpGet "http://example.com"
return ()
prop_httpSilentGet
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> Property
prop_httpSilentGet _ _ _ _ eval cond x s r =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== ())) $
as x $ do
httpSilentGet "http://example.com"
return ()
prop_httpSilentGet_write
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> Property
prop_httpSilentGet_write _ _ _ _ eval cond x s r =
checkHttpTT (basicState s) (noisyEnv r) eval cond
(hasWorld $ not . outputContains "http://example.com") $
as x $ do
httpSilentGet "http://example.com"
return ()
prop_httpPost
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff Int
-> s -> r -> Text -> Property
prop_httpPost _ _ _ _ eval cond x s r payload =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== 200)) $
as x $ do
response <- httpPost "http://example.com" $
fromChunks [T.encodeUtf8 payload]
return (statusCode $ _responseStatus response)
prop_httpSilentPost
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff Int
-> s -> r -> Text -> Property
prop_httpSilentPost _ _ _ _ eval cond x s r payload =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== 200)) $
as x $ do
response <- httpSilentPost "http://example.com" $
fromChunks [T.encodeUtf8 payload]
return (statusCode $ _responseStatus response)
prop_throwError
:: (Monad eff, Eq e, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff Int
-> s -> r -> Int -> e -> Property
prop_throwError _ _ _ _ eval cond x s r k err =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== k)) $
as x $ do
catchError (throwError err)
(\e -> if e == err then return k else return (k+1))
prop_logDebug_write
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> w -> Property
prop_logDebug_write _ _ _ _ eval cond x s r w =
checkHttpTT (basicState s) (noisyEnv r) eval cond
(hasWorld $ outputContains "LOG") $
as x $ do
logDebug w
return ()
prop_logEntries_log
:: (Monad eff, Eq w, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> w -> w -> w -> Property
prop_logEntries_log _ _ _ _ eval cond x s r w1 w2 w3 =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasLog $ \w -> [w1,w2,w3] == logEntries w) $
as x $ do
logDebug w1
comment "hey!"
logDebug w2
logDebug w3
return ()
prop_throwError_write
:: (Monad eff, Show e, Show w, Show s, Show u)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO ((Either (E e) t, S s, W e w), MockWorld u))
-> HttpT e r w s p eff ()
-> s -> r -> e -> Property
prop_throwError_write _ _ _ _ eval cond x s r e =
checkHttpTT (basicState s) (noisyEnv r) eval cond
(hasWorld $ outputContains "ERROR") $
as x $ do
throwError e
return ()
prop_throwJsonError
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff Int
-> s -> r -> Int -> JsonError -> Property
prop_throwJsonError _ _ _ _ eval cond x s r k err =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== k)) $
as x $ do
catchJsonError (throwJsonError err)
(\e -> if e == err then return k else return (k+1))
prop_parseJson
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff ()
-> s -> r -> Int -> Property
prop_parseJson _ _ _ _ eval cond x s r k =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== ())) $
as x $ do
parseJson $ fromString $ "{ \"key\":" ++ show k ++ " }"
return ()
prop_lookupKeyJson
:: (Monad eff, Show e, Show w, Show s)
=> Proxy e -> Proxy r -> Proxy w -> Proxy s
-> (forall u. P p u -> eff u)
-> (forall e s w t. IdentityT eff (Either (E e) t, S s, W e w) -> IO (Either (E e) t, S s, W e w))
-> HttpT e r w s p eff Int
-> s -> r -> Int -> Property
prop_lookupKeyJson _ _ _ _ eval cond x s r k =
checkHttpTT (basicState s) (testEnv r) eval cond
(hasValue (== k)) $
as x $ do
obj <- parseJson $ fromString $ "{ \"key\":" ++ show k ++ " }"
lookupKeyJson "key" obj >>= constructFromJson
hasValue
:: (Show e) => (t -> Bool)
-> (Either (E e) t, S s, W e w)
-> Property
hasValue p (x,_,_) = case x of
Left (E_Http (HttpExceptionRequest _ err)) ->
case err of
-- These errors are outside the control of this code, so it's better
-- to reject cases that trigger them than to fail the whole suite.
ConnectionTimeout -> property rejected
InternalException _ -> property rejected
-- These might be real errors. :)
e -> counterexample (show e) failed
Left e -> counterexample (show e) failed
Right a -> property $ p a
hasLog
:: (W e w -> Bool)
-> (Either (E e) t, S s, W e w)
-> Bool
hasLog p (_,_,w) = p w
hasState
:: (s -> Bool)
-> (Either (E e) t, S s, W e w)
-> Bool
hasState p (_,s,_) = p $ _userState s
data Id a = Id { unId :: a }
evalId :: (Monad m) => Id a -> m a
evalId (Id a) = return a