module Test.Gen.Session
( SessionInit (..)
, genSessionInit
, SessionGenOptions
, requireLive
, requireExpired
) where
import Internal.Prelude
import Data.Map.Strict qualified as Map
import Test.Gen.General
import Test.Gen.Mock
import Test.QuickCheck (Gen)
import Test.QuickCheck.Gen qualified as Gen
import Time
import Yesod.Core (SessionMap)
import Yesod.Session.Persist
import Prelude (error)
data SessionInit = SessionInit
{ time :: Time UTCTime
, map :: SessionMap
}
deriving stock (Eq, Show)
genSessionInit
:: (SessionGenOptions -> SessionGenOptions) -> MockInit -> Gen SessionInit
genSessionInit fsgo mockInit = do
let SessionGenOptions {liveness} = fsgo defaultSessionGenOptions
map <- genSessionData
let now = mockInit.time
let timeout = mockInit.timing.timeout
time <- case liveness of
Nothing -> whatever now
Just Live -> live timeout now
Just (Expired reasonMaybe) -> do
reason <- case reasonMaybe of
Nothing ->
case (nonEmpty . catMaybes)
[ timeout.idle $> IdleTimeout
, timeout.absolute $> AbsoluteTimeout
] of
Just xs -> Gen.elements $ toList xs
Nothing ->
error
"Cannot generate an expired session for a configuration \
\with no timeout limits"
Just x -> pure x
case reason of
IdleTimeout ->
fromMaybe
( error
"Cannot generate an expired-by-idle-timeout \
\session for a configuration with no idle timeout limit"
)
$ expiredViaIdleTimeout timeout now
AbsoluteTimeout ->
fromMaybe
( error
"Cannot generate an expired-by-absolute-timeout session \
\for a configuration with no absolute timeout limit"
)
$ expiredViaAbsoluteTimeout timeout now
pure SessionInit {..}
whatever :: UTCTime -> Gen (Time UTCTime)
whatever now = do
created <- chooseTime (subtractUTCTime nominalDay now, now)
accessed <- chooseTime (created, now)
pure Time {accessed, created}
-- | Generates times for a session that is still live
live :: Timeout NominalDiffTime -> UTCTime -> Gen (Time UTCTime)
live timeout now = do
accessed <-
chooseTime
( subtractUTCTime
( case (timeout.idle, timeout.absolute) of
(Just idleTimeout, _) -> pred idleTimeout
(_, Just absoluteTimeout) -> pred absoluteTimeout
_ -> nominalDay
)
now
, now
)
created <-
chooseTime
( case timeout.absolute of
Just absoluteTimeout -> subtractUTCTime (pred absoluteTimeout) now
Nothing -> subtractUTCTime nominalDay accessed
, accessed
)
pure Time {accessed, created}
-- | Generates times for a session that is expired due to idle timeout
-- (returns 'Nothing' if there is no idle timeout)
expiredViaIdleTimeout
:: Timeout NominalDiffTime -> UTCTime -> Maybe (Gen (Time UTCTime))
expiredViaIdleTimeout timeout now =
timeout.idle <&> \idleTimeout -> do
accessed <-
chooseTime
( case timeout.absolute of
Just absoluteTimeout -> subtractUTCTime absoluteTimeout now
Nothing -> subtractUTCTime (idleTimeout + nominalDay) now
, subtractUTCTime idleTimeout now
)
created <-
chooseTime
( case timeout.absolute of
Just absoluteTimeout -> subtractUTCTime absoluteTimeout now
Nothing -> subtractUTCTime nominalDay accessed
, accessed
)
pure Time {accessed, created}
-- | Generates times for a session that is expired due to absolute timeout
-- (returns 'Nothing' if there is no absolute timeout)
expiredViaAbsoluteTimeout
:: Timeout NominalDiffTime -> UTCTime -> Maybe (Gen (Time UTCTime))
expiredViaAbsoluteTimeout timeout now =
timeout.absolute <&> \absoluteTimeout -> do
created <-
let base = subtractUTCTime absoluteTimeout now
in chooseTime (subtractUTCTime nominalDay base, base)
accessed <-
chooseTime
( case timeout.idle of
Just idleTimeout -> subtractUTCTime idleTimeout now
Nothing -> created
, now
)
pure Time {accessed, created}
newtype SessionGenOptions = SessionGenOptions
{ liveness :: Maybe Liveness
}
defaultSessionGenOptions :: SessionGenOptions
defaultSessionGenOptions = SessionGenOptions {liveness = Nothing}
requireLive :: SessionGenOptions -> SessionGenOptions
requireLive x = x {liveness = Just Live}
requireExpired :: SessionGenOptions -> SessionGenOptions
requireExpired x = x {liveness = Just (Expired Nothing)}
data Liveness = Live | Expired (Maybe ExpirationReason)
data ExpirationReason = IdleTimeout | AbsoluteTimeout
deriving stock (Show)
genSessionData :: Gen (Map Text ByteString)
genSessionData = fmap Map.fromList $ do
k <- Gen.choose (0, 5)
Gen.vectorOf k
$ (,)
<$> Gen.elements ["", "a", "bc", "def", "ghij"]
<*> Gen.elements ["", "a", "\0", "what"]