-- |
-- Module: Test.Hspec.NeedEnv
-- Description: Read environment variables for hspec tests
-- Maintainer: Toshio Ito <debug.ito@gmail.com>
-- 
-- <https://github.com/debug-ito/hspec-need-env/tree/master/test/Synopsis.hs Synopsis>:
--
-- > module Synopsis (main,spec) where
-- > 
-- > import Control.Applicative ((<$>), (<*>))
-- > import Test.Hspec (Spec, SpecWith, hspec, before, describe, it, shouldBe)
-- > import Test.Hspec.NeedEnv (EnvMode(Need), needEnv, needEnvRead)
-- > 
-- > main :: IO ()
-- > main = hspec spec
-- > 
-- > -- | Read environment variables for parameters necessary for testing.
-- > getEnvs :: IO (String, Int)
-- > getEnvs = (,)
-- >           <$> needEnv mode "TEST_USER_NAME"
-- >           <*> needEnvRead mode "TEST_SEED"
-- >   where
-- >     mode = Need
-- > 
-- > spec :: Spec
-- > spec = before getEnvs $ specWithUserAndSeed
-- >        -- ^ Use 'before' and similar functions to write 'SpecWith'
-- >        -- that takes parameters.
-- > 
-- > -- | Test spec that depends on the environment variables.
-- > specWithUserAndSeed :: SpecWith (String, Int)
-- > specWithUserAndSeed = describe "funcUnderTest" $ do
-- >   it "should do something" $ \(user_name, seed) -> do
-- >     funcUnderTest user_name seed `shouldBe` "SOMETHING"
-- > 
-- > funcUnderTest :: String -> Int -> String
-- > funcUnderTest = undefined
--
-- This module exports 'needEnv' and other similar functions that read
-- environment variables in hspec tests. They are useful to write
-- tests that depend on some external entities, e.g. Web servers,
-- database servers and random number generators.
module Test.Hspec.NeedEnv
       ( -- * Basics
         EnvMode(..),
         needEnv,
         needEnvParse,
         needEnvRead,
         -- * Utilities
         needEnvHostPort
       ) where

import Control.Applicative ((<$>), (<*>))
import Data.Monoid ((<>))
import System.Environment (lookupEnv)
import Test.Hspec.Core.Spec (pendingWith)
import Test.Hspec.Expectations (expectationFailure)
import Text.Read (readEither)

-- | How to treat missing environment variable.
data EnvMode = Need
               -- ^ If the environment variable is not set, the test
               -- fails.
             | Want
               -- ^ If the environment variable is not set, the test
               -- gets pending.
             deriving (Int -> EnvMode -> ShowS
[EnvMode] -> ShowS
EnvMode -> String
(Int -> EnvMode -> ShowS)
-> (EnvMode -> String) -> ([EnvMode] -> ShowS) -> Show EnvMode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [EnvMode] -> ShowS
$cshowList :: [EnvMode] -> ShowS
show :: EnvMode -> String
$cshow :: EnvMode -> String
showsPrec :: Int -> EnvMode -> ShowS
$cshowsPrec :: Int -> EnvMode -> ShowS
Show,EnvMode -> EnvMode -> Bool
(EnvMode -> EnvMode -> Bool)
-> (EnvMode -> EnvMode -> Bool) -> Eq EnvMode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: EnvMode -> EnvMode -> Bool
$c/= :: EnvMode -> EnvMode -> Bool
== :: EnvMode -> EnvMode -> Bool
$c== :: EnvMode -> EnvMode -> Bool
Eq,Eq EnvMode
Eq EnvMode
-> (EnvMode -> EnvMode -> Ordering)
-> (EnvMode -> EnvMode -> Bool)
-> (EnvMode -> EnvMode -> Bool)
-> (EnvMode -> EnvMode -> Bool)
-> (EnvMode -> EnvMode -> Bool)
-> (EnvMode -> EnvMode -> EnvMode)
-> (EnvMode -> EnvMode -> EnvMode)
-> Ord EnvMode
EnvMode -> EnvMode -> Bool
EnvMode -> EnvMode -> Ordering
EnvMode -> EnvMode -> EnvMode
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: EnvMode -> EnvMode -> EnvMode
$cmin :: EnvMode -> EnvMode -> EnvMode
max :: EnvMode -> EnvMode -> EnvMode
$cmax :: EnvMode -> EnvMode -> EnvMode
>= :: EnvMode -> EnvMode -> Bool
$c>= :: EnvMode -> EnvMode -> Bool
> :: EnvMode -> EnvMode -> Bool
$c> :: EnvMode -> EnvMode -> Bool
<= :: EnvMode -> EnvMode -> Bool
$c<= :: EnvMode -> EnvMode -> Bool
< :: EnvMode -> EnvMode -> Bool
$c< :: EnvMode -> EnvMode -> Bool
compare :: EnvMode -> EnvMode -> Ordering
$ccompare :: EnvMode -> EnvMode -> Ordering
$cp1Ord :: Eq EnvMode
Ord,Int -> EnvMode
EnvMode -> Int
EnvMode -> [EnvMode]
EnvMode -> EnvMode
EnvMode -> EnvMode -> [EnvMode]
EnvMode -> EnvMode -> EnvMode -> [EnvMode]
(EnvMode -> EnvMode)
-> (EnvMode -> EnvMode)
-> (Int -> EnvMode)
-> (EnvMode -> Int)
-> (EnvMode -> [EnvMode])
-> (EnvMode -> EnvMode -> [EnvMode])
-> (EnvMode -> EnvMode -> [EnvMode])
-> (EnvMode -> EnvMode -> EnvMode -> [EnvMode])
-> Enum EnvMode
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: EnvMode -> EnvMode -> EnvMode -> [EnvMode]
$cenumFromThenTo :: EnvMode -> EnvMode -> EnvMode -> [EnvMode]
enumFromTo :: EnvMode -> EnvMode -> [EnvMode]
$cenumFromTo :: EnvMode -> EnvMode -> [EnvMode]
enumFromThen :: EnvMode -> EnvMode -> [EnvMode]
$cenumFromThen :: EnvMode -> EnvMode -> [EnvMode]
enumFrom :: EnvMode -> [EnvMode]
$cenumFrom :: EnvMode -> [EnvMode]
fromEnum :: EnvMode -> Int
$cfromEnum :: EnvMode -> Int
toEnum :: Int -> EnvMode
$ctoEnum :: Int -> EnvMode
pred :: EnvMode -> EnvMode
$cpred :: EnvMode -> EnvMode
succ :: EnvMode -> EnvMode
$csucc :: EnvMode -> EnvMode
Enum,EnvMode
EnvMode -> EnvMode -> Bounded EnvMode
forall a. a -> a -> Bounded a
maxBound :: EnvMode
$cmaxBound :: EnvMode
minBound :: EnvMode
$cminBound :: EnvMode
Bounded)

-- | Get value of the specified environment variable. If the
-- environment variable is not set, it executes the action specified
-- by the 'EnvMode'.
needEnv :: EnvMode
        -> String -- ^ name of the environment variable
        -> IO String -- ^ value of the environment variable
needEnv :: EnvMode -> String -> IO String
needEnv EnvMode
mode String
envkey = do
  Maybe String
mval <- String -> IO (Maybe String)
lookupEnv String
envkey
  case Maybe String
mval of
   Maybe String
Nothing -> do
     String -> Expectation
signalMsg (String
"Environment variable " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
envkey String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" is not set.")
     String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
""
   Just String
str -> String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
str
  where
    signalMsg :: String -> Expectation
signalMsg = case EnvMode
mode of
      EnvMode
Need -> HasCallStack => String -> Expectation
String -> Expectation
expectationFailure
      EnvMode
Want -> HasCallStack => String -> Expectation
String -> Expectation
pendingWith

-- | Get environment variable by 'needEnv', and parse the value. If it
-- fails to parse, the test fails.
needEnvParse :: EnvMode
             -> (String -> Either String a) -- ^ the parser of the environment variable
             -> String
             -> IO a
needEnvParse :: EnvMode -> (String -> Either String a) -> String -> IO a
needEnvParse EnvMode
mode String -> Either String a
parseEnvVal String
envkey = do
  String
val_str <- EnvMode -> String -> IO String
needEnv EnvMode
mode String
envkey
  case String -> Either String a
parseEnvVal String
val_str of
   Right a
val -> a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
val
   Left String
e -> do
     let error_msg :: String
error_msg = String
"Fail to parse environment variable " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
envkey String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
": " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
e
     HasCallStack => String -> Expectation
String -> Expectation
expectationFailure String
error_msg
     String -> IO a
forall a. HasCallStack => String -> a
error String
error_msg

-- | Parse the environment variable with 'Read' class.
needEnvRead :: (Read a)
            => EnvMode -> String -> IO a
needEnvRead :: EnvMode -> String -> IO a
needEnvRead EnvMode
mode = EnvMode -> (String -> Either String a) -> String -> IO a
forall a. EnvMode -> (String -> Either String a) -> String -> IO a
needEnvParse EnvMode
mode String -> Either String a
forall a. Read a => String -> Either String a
readEither

-- | Get the pair of hostname and port number from environment
-- variables.
--
-- It reads environment variables @(prefix ++ \"_HOST\")@ and
-- @(prefix ++ \"_PORT\")@.
needEnvHostPort :: EnvMode
                -> String -- ^ prefix of environment variables
                -> IO (String,Int)
needEnvHostPort :: EnvMode -> String -> IO (String, Int)
needEnvHostPort EnvMode
mode String
prefix = (,) (String -> Int -> (String, Int))
-> IO String -> IO (Int -> (String, Int))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO String
needStr String
"_HOST" IO (Int -> (String, Int)) -> IO Int -> IO (String, Int)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> String -> IO Int
forall a. Read a => String -> IO a
needInt String
"_PORT"
  where
    needStr :: String -> IO String
needStr String
suffix = EnvMode -> String -> IO String
needEnv EnvMode
mode (String
prefix String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
suffix)
    needInt :: String -> IO a
needInt String
suffix = EnvMode -> String -> IO a
forall a. Read a => EnvMode -> String -> IO a
needEnvRead EnvMode
mode (String
prefix String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
suffix)