Cron
This commit is contained in:
parent
0ca12cecdb
commit
4598b38242
222
src/Cron.hs
Normal file
222
src/Cron.hs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude
|
||||||
|
, RecordWildCards
|
||||||
|
, PatternGuards
|
||||||
|
, ViewPatterns
|
||||||
|
, DeriveFunctor
|
||||||
|
, TemplateHaskell
|
||||||
|
, NamedFieldPuns
|
||||||
|
#-}
|
||||||
|
|
||||||
|
module Cron
|
||||||
|
( matchesCron
|
||||||
|
, CronNextMatch(..)
|
||||||
|
, nextCronMatch
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Prelude (lcm)
|
||||||
|
import Cron.Types
|
||||||
|
|
||||||
|
import Data.Time
|
||||||
|
import Data.Time.Calendar.OrdinalDate (toOrdinalDate, fromOrdinalDateValid)
|
||||||
|
import Data.Time.Calendar.WeekDate (toWeekDate, fromWeekDate, fromWeekDateValid)
|
||||||
|
import Data.Time.Zones
|
||||||
|
|
||||||
|
import Numeric.Natural
|
||||||
|
import Data.Ratio ((%))
|
||||||
|
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
import Data.List.NonEmpty (NonEmpty(..))
|
||||||
|
import qualified Data.List.NonEmpty as NonEmpty
|
||||||
|
|
||||||
|
import Utils.Lens.TH
|
||||||
|
import Control.Lens
|
||||||
|
|
||||||
|
|
||||||
|
data CronDate = CronDate
|
||||||
|
{ cdYear, cdWeekOfYear, cdDayOfYear
|
||||||
|
, cdMonth, cdWeekOfMonth, cdDayOfMonth
|
||||||
|
, cdDayOfWeek
|
||||||
|
, cdHour, cdMinute, cdSecond :: Natural
|
||||||
|
} deriving (Eq, Show, Read)
|
||||||
|
|
||||||
|
makeLenses_ ''CronDate
|
||||||
|
|
||||||
|
|
||||||
|
evalCronMatch :: CronMatch -> Natural -> Bool
|
||||||
|
evalCronMatch CronMatchAny _ = True
|
||||||
|
evalCronMatch CronMatchNone _ = False
|
||||||
|
evalCronMatch (CronMatchSome set) x = Set.member x $ toNullable set
|
||||||
|
evalCronMatch (CronMatchStep step) x = (x `mod` step) == 0
|
||||||
|
evalCronMatch (CronMatchContiguous from to) x = from <= x && x <= to
|
||||||
|
evalCronMatch (CronMatchIntersect a b) x = evalCronMatch a x && evalCronMatch b x
|
||||||
|
evalCronMatch (CronMatchUnion a b) x = evalCronMatch a x || evalCronMatch b x
|
||||||
|
|
||||||
|
toCronDate :: LocalTime -> CronDate
|
||||||
|
toCronDate LocalTime{..} = CronDate{..}
|
||||||
|
where
|
||||||
|
(fromInteger -> cdYear, fromIntegral -> cdMonth, fromIntegral -> cdDayOfMonth)
|
||||||
|
= toGregorian localDay
|
||||||
|
(_, fromIntegral -> cdDayOfYear)
|
||||||
|
= toOrdinalDate localDay
|
||||||
|
(_, fromIntegral -> cdWeekOfYear, fromIntegral -> cdDayOfWeek)
|
||||||
|
= toWeekDate localDay
|
||||||
|
cdWeekOfMonth = go 1 localDay
|
||||||
|
where
|
||||||
|
go :: Natural -> Day -> Natural
|
||||||
|
go n day
|
||||||
|
| dow /= 4 = go n $ fromWeekDate y w 4 -- According to ISO week of month is determined by Thursday
|
||||||
|
| m == m' = go (succ n) day'
|
||||||
|
| otherwise = n
|
||||||
|
where
|
||||||
|
(y, w, dow) = toWeekDate day
|
||||||
|
day'
|
||||||
|
| w /= 0 = fromWeekDate y (pred w) dow
|
||||||
|
| otherwise = fromWeekDate (pred y) 53 dow
|
||||||
|
(_, m, _) = toGregorian day
|
||||||
|
(_, m', _) = toGregorian day'
|
||||||
|
TimeOfDay
|
||||||
|
{ todHour = fromIntegral -> cdHour
|
||||||
|
, todMin = fromIntegral -> cdMinute
|
||||||
|
, todSec = round -> cdSecond
|
||||||
|
} = localTimeOfDay
|
||||||
|
|
||||||
|
consistentCronDate :: CronDate -> Bool
|
||||||
|
consistentCronDate cd@CronDate{ cdWeekOfMonth = _, ..} = fromMaybe False $ do
|
||||||
|
gDay <- fromGregorianValid (fromIntegral cdYear) (fromIntegral cdMonth) (fromIntegral cdDayOfMonth)
|
||||||
|
wDay <- fromWeekDateValid (fromIntegral cdYear) (fromIntegral cdWeekOfYear) (fromIntegral cdDayOfWeek)
|
||||||
|
guard $ gDay == wDay
|
||||||
|
oDay <- fromOrdinalDateValid (fromIntegral cdYear) (fromIntegral cdDayOfYear)
|
||||||
|
guard $ wDay == oDay
|
||||||
|
guard $ ((==) `on` cdWeekOfMonth) cd (toCronDate $ LocalTime wDay (error "TimeOfDay inspected in toCronDate"))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
data CronNextMatch a = MatchAsap | MatchAt a | MatchNone
|
||||||
|
deriving (Eq, Ord, Show, Read, Functor)
|
||||||
|
|
||||||
|
instance Applicative CronNextMatch where
|
||||||
|
pure = MatchAt
|
||||||
|
_ <*> MatchNone = MatchNone
|
||||||
|
MatchNone <*> _ = MatchNone
|
||||||
|
_ <*> MatchAsap = MatchAsap
|
||||||
|
MatchAsap <*> _ = MatchAsap
|
||||||
|
MatchAt f <*> MatchAt x = MatchAt $ f x
|
||||||
|
|
||||||
|
instance Alternative CronNextMatch where
|
||||||
|
empty = MatchNone
|
||||||
|
x <|> MatchNone = x
|
||||||
|
MatchNone <|> x = x
|
||||||
|
_ <|> MatchAsap = MatchAsap
|
||||||
|
MatchAsap <|> _ = MatchAsap
|
||||||
|
(MatchAt a) <|> (MatchAt _) = MatchAt a
|
||||||
|
|
||||||
|
|
||||||
|
listToMatch :: [a] -> CronNextMatch a
|
||||||
|
listToMatch [] = MatchNone
|
||||||
|
listToMatch (t:_) = MatchAt t
|
||||||
|
|
||||||
|
genMatch :: Int -- ^ Period
|
||||||
|
-> Natural -- ^ Start value
|
||||||
|
-> CronMatch
|
||||||
|
-> [Natural]
|
||||||
|
genMatch p st CronMatchAny = take p [st..]
|
||||||
|
genMatch _ _ CronMatchNone = []
|
||||||
|
genMatch p _ (CronMatchSome set) = take p . Set.toAscList $ toNullable set
|
||||||
|
genMatch p st (CronMatchStep step) = do
|
||||||
|
start <- [st..st + step]
|
||||||
|
guard $ (start `mod` step) == 0
|
||||||
|
take (ceiling $ fromIntegral p % step) [start,start + step..]
|
||||||
|
genMatch p st (CronMatchContiguous from to) = take p $ [max st from..to]
|
||||||
|
genMatch _ _ (CronMatchIntersect CronMatchNone _) = []
|
||||||
|
genMatch _ _ (CronMatchIntersect _ CronMatchNone) = []
|
||||||
|
genMatch p st (CronMatchIntersect CronMatchAny other) = genMatch p st other
|
||||||
|
genMatch p st (CronMatchIntersect other CronMatchAny) = genMatch p st other
|
||||||
|
genMatch p st (CronMatchIntersect (CronMatchStep st1) (CronMatchStep st2))
|
||||||
|
= genMatch p st . CronMatchStep $ lcm st1 st2
|
||||||
|
genMatch p st (CronMatchIntersect aGen bGen)
|
||||||
|
| [] <- as' = []
|
||||||
|
| (a:as) <- as' = mergeAnd (a:as) (genMatch p a bGen)
|
||||||
|
where
|
||||||
|
as' = genMatch p st aGen
|
||||||
|
mergeAnd [] _ = []
|
||||||
|
mergeAnd _ [] = []
|
||||||
|
mergeAnd (a:as) (b:bs)
|
||||||
|
| a < b = mergeAnd as (b:bs)
|
||||||
|
| a == b = a : mergeAnd as bs
|
||||||
|
| a > b = mergeAnd (a:as) bs
|
||||||
|
genMatch p st (CronMatchUnion CronMatchNone other) = genMatch p st other
|
||||||
|
genMatch p st (CronMatchUnion other CronMatchNone) = genMatch p st other
|
||||||
|
genMatch p st (CronMatchUnion CronMatchAny _) = genMatch p st CronMatchAny
|
||||||
|
genMatch p st (CronMatchUnion _ CronMatchAny) = genMatch p st CronMatchAny
|
||||||
|
genMatch p st (CronMatchUnion aGen bGen) = merge (genMatch p st aGen) (genMatch p st bGen)
|
||||||
|
where
|
||||||
|
merge [] bs = bs
|
||||||
|
merge as [] = as
|
||||||
|
merge (a:as) (b:bs)
|
||||||
|
| a < b = a : merge as (b:bs)
|
||||||
|
| a == b = a : merge as bs
|
||||||
|
| a > b = b : merge (a:as) bs
|
||||||
|
|
||||||
|
nextCronMatch :: TZ -- ^ Timezone of the `Cron`-Entry
|
||||||
|
-> Maybe UTCTime -- ^ Time of last execution of the job
|
||||||
|
-> UTCTime -- ^ Current time, used only for `CronCalendar`
|
||||||
|
-> Cron
|
||||||
|
-> CronNextMatch UTCTime
|
||||||
|
nextCronMatch tz mPrev now c@Cron{..}
|
||||||
|
| isNothing mPrev
|
||||||
|
= execRef now False cronInitial
|
||||||
|
| isJust mPrev
|
||||||
|
, isNothing cronRepeat
|
||||||
|
= MatchNone
|
||||||
|
| Just prevT <- mPrev
|
||||||
|
, Just CronPeriod{..} <- cronRepeat
|
||||||
|
= case cronNext of
|
||||||
|
CronAsap
|
||||||
|
| addUTCTime cronMinInterval prevT <= now
|
||||||
|
-> MatchAsap
|
||||||
|
| otherwise
|
||||||
|
-> MatchAt $ addUTCTime cronMinInterval prevT
|
||||||
|
cronNext -> execRef (addUTCTime cronMinInterval prevT) True cronNext
|
||||||
|
where
|
||||||
|
execRef ref wasExecd cronAbsolute = case cronAbsolute of
|
||||||
|
CronAsap -> MatchAsap
|
||||||
|
CronTimestamp{ cronTimestamp = localTimeToUTCTZ tz -> ts }
|
||||||
|
| ref <= ts -> MatchAt ts
|
||||||
|
| not wasExecd -> MatchAsap
|
||||||
|
| otherwise -> MatchNone
|
||||||
|
CronCalendar{..} -> listToMatch $ do
|
||||||
|
let CronDate{..} = toCronDate $ utcToLocalTimeTZ tz ref
|
||||||
|
cronYear <- genMatch 400 cdYear cronYear
|
||||||
|
cronWeekOfYear <- genMatch 53 cdWeekOfYear cronWeekOfYear
|
||||||
|
cronDayOfYear <- genMatch 366 cdDayOfYear cronDayOfYear
|
||||||
|
cronMonth <- genMatch 12 cdMonth cronMonth
|
||||||
|
cronWeekOfMonth <- genMatch 5 cdWeekOfMonth cronWeekOfMonth
|
||||||
|
cronDayOfMonth <- genMatch 31 cdDayOfMonth cronDayOfMonth
|
||||||
|
cronDayOfWeek <- genMatch 7 cdDayOfWeek cronDayOfWeek
|
||||||
|
cronHour <- genMatch 24 cdHour cronHour
|
||||||
|
cronMinute <- genMatch 60 cdMinute cronMinute
|
||||||
|
cronSecond <- genMatch 60 cdSecond cronSecond
|
||||||
|
guard $ consistentCronDate CronDate{..}
|
||||||
|
localDay <- maybeToList $ fromGregorianValid (fromIntegral cronYear) (fromIntegral cronMonth) (fromIntegral cronDayOfMonth)
|
||||||
|
let localTimeOfDay = TimeOfDay (fromIntegral cronHour) (fromIntegral cronMinute) (fromIntegral cronSecond)
|
||||||
|
return $ localTimeToUTCTZ tz LocalTime{..}
|
||||||
|
|
||||||
|
matchesCron :: TZ -- ^ Timezone of the `Cron`-Entry
|
||||||
|
-> Maybe UTCTime -- ^ Previous execution of the job
|
||||||
|
-> NominalDiffTime -- ^ Approximate time until next check
|
||||||
|
-> UTCTime -- ^ "Current" time
|
||||||
|
-> Cron
|
||||||
|
-> Bool
|
||||||
|
-- ^ @matchesCron tz prev prec now c@ determines whether the given `Cron`
|
||||||
|
-- specification @c@ should match @now@, under the assumption that the next
|
||||||
|
-- check will occur no earlier than @now + prec@.
|
||||||
|
matchesCron tz mPrev prec now cron@Cron{cronOffset} = case nextCronMatch tz mPrev now cron of
|
||||||
|
MatchAsap -> True
|
||||||
|
MatchNone -> False
|
||||||
|
MatchAt ts -> ts < toT
|
||||||
|
where
|
||||||
|
toT = case cronOffset of
|
||||||
|
CronScheduleBefore -> addUTCTime prec now
|
||||||
|
CronScheduleAfter -> now
|
||||||
74
src/Cron/Types.hs
Normal file
74
src/Cron/Types.hs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude
|
||||||
|
, TemplateHaskell
|
||||||
|
, DuplicateRecordFields
|
||||||
|
#-}
|
||||||
|
|
||||||
|
module Cron.Types
|
||||||
|
( Cron(..), Crontab
|
||||||
|
, CronScheduleOffset(..)
|
||||||
|
, CronMatch(..)
|
||||||
|
, CronAbsolute(..)
|
||||||
|
, CronPeriod(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
|
||||||
|
import Utils.Lens.TH
|
||||||
|
import Control.Lens
|
||||||
|
|
||||||
|
import Data.Time
|
||||||
|
|
||||||
|
import Numeric.Natural
|
||||||
|
|
||||||
|
|
||||||
|
-- | When the scheduled time for a job falls between two wakeups of the timing
|
||||||
|
-- thread, execute the job on the wakeup before or after the scheduled time
|
||||||
|
data CronScheduleOffset
|
||||||
|
= CronScheduleBefore | CronScheduleAfter
|
||||||
|
deriving (Eq, Ord, Show, Read, Enum, Bounded)
|
||||||
|
|
||||||
|
makePrisms ''CronScheduleOffset
|
||||||
|
|
||||||
|
data CronMatch
|
||||||
|
= CronMatchAny
|
||||||
|
| CronMatchNone
|
||||||
|
| CronMatchSome (NonNull (Set Natural))
|
||||||
|
| CronMatchStep Natural
|
||||||
|
| CronMatchContiguous Natural Natural
|
||||||
|
| CronMatchIntersect CronMatch CronMatch
|
||||||
|
| CronMatchUnion CronMatch CronMatch
|
||||||
|
deriving (Show, Read)
|
||||||
|
|
||||||
|
data CronAbsolute
|
||||||
|
= CronAsap
|
||||||
|
| CronTimestamp
|
||||||
|
{ cronTimestamp :: LocalTime
|
||||||
|
}
|
||||||
|
| CronCalendar
|
||||||
|
{ cronYear, cronWeekOfYear, cronDayOfYear
|
||||||
|
, cronMonth, cronWeekOfMonth, cronDayOfMonth
|
||||||
|
, cronDayOfWeek
|
||||||
|
, cronHour, cronMinute, cronSecond :: CronMatch
|
||||||
|
}
|
||||||
|
deriving (Show, Read)
|
||||||
|
|
||||||
|
makeLenses_ ''CronAbsolute
|
||||||
|
|
||||||
|
data CronPeriod = CronPeriod
|
||||||
|
{ cronMinInterval :: NominalDiffTime
|
||||||
|
, cronNext :: CronAbsolute
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
makeLenses_ ''CronPeriod
|
||||||
|
|
||||||
|
data Cron = Cron
|
||||||
|
{ cronInitial :: CronAbsolute
|
||||||
|
, cronRepeat :: Maybe CronPeriod
|
||||||
|
, cronOffset :: CronScheduleOffset
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
makeLenses_ ''Cron
|
||||||
|
|
||||||
|
type Crontab a = Map a Cron
|
||||||
@ -29,3 +29,7 @@ import Data.Pool as Import (Pool)
|
|||||||
import Network.HaskellNet.SMTP as Import (SMTPConnection)
|
import Network.HaskellNet.SMTP as Import (SMTPConnection)
|
||||||
|
|
||||||
import Mail as Import
|
import Mail as Import
|
||||||
|
|
||||||
|
import Data.Data as Import (Data)
|
||||||
|
import Data.Typeable as Import (Typeable)
|
||||||
|
import GHC.Generics as Import (Generic)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{-# LANGUAGE TemplateHaskell
|
{-# LANGUAGE TemplateHaskell
|
||||||
, NoImplicitPrelude
|
, NoImplicitPrelude
|
||||||
|
, DeriveGeneric
|
||||||
|
, DeriveDataTypeable
|
||||||
#-}
|
#-}
|
||||||
|
|
||||||
module Jobs.Types
|
module Jobs.Types
|
||||||
@ -15,9 +17,9 @@ import Data.Aeson.TH (deriveJSON)
|
|||||||
|
|
||||||
data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notification }
|
data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notification }
|
||||||
| JobSendTestEmail { jEmail :: Text, jLanguages :: MailLanguages }
|
| JobSendTestEmail { jEmail :: Text, jLanguages :: MailLanguages }
|
||||||
deriving (Eq, Ord, Show, Read)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId, nTimestamp :: UTCTime }
|
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
|
||||||
deriving (Eq, Ord, Show, Read)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
{ constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
||||||
@ -36,4 +38,4 @@ deriveJSON defaultOptions
|
|||||||
|
|
||||||
data JobCtl = JobCtlFlush
|
data JobCtl = JobCtlFlush
|
||||||
| JobCtlPerform QueuedJobId
|
| JobCtlPerform QueuedJobId
|
||||||
deriving (Eq, Ord, Read, Show)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
module Model
|
module Model
|
||||||
( module Model
|
( module Model
|
||||||
, module Model.Types
|
, module Model.Types
|
||||||
|
, module Cron.Types
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod
|
import ClassyPrelude.Yesod
|
||||||
@ -23,6 +24,7 @@ import Database.Persist.Quasi
|
|||||||
-- import Data.Time
|
-- import Data.Time
|
||||||
-- import Data.ByteString
|
-- import Data.ByteString
|
||||||
import Model.Types
|
import Model.Types
|
||||||
|
import Cron.Types
|
||||||
|
|
||||||
import Data.Aeson (Value)
|
import Data.Aeson (Value)
|
||||||
import Data.Aeson.TH (deriveJSON, defaultOptions)
|
import Data.Aeson.TH (deriveJSON, defaultOptions)
|
||||||
|
|||||||
14
test.sh
Executable file
14
test.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
move-back() {
|
||||||
|
mv -v .stack-work .stack-work-test
|
||||||
|
[[ -d .stack-work-run ]] && mv -v .stack-work-run .stack-work
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -d .stack-work-test ]]; then
|
||||||
|
[[ -d .stack-work ]] && mv -v .stack-work .stack-work-run
|
||||||
|
mv -v .stack-work-test .stack-work
|
||||||
|
trap move-back EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
|
stack test --flag uniworx:dev --flag uniworx:library-only ${@}
|
||||||
40
test/CronSpec.hs
Normal file
40
test/CronSpec.hs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
|
|
||||||
|
module CronSpec where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
import Cron
|
||||||
|
import Numeric.Natural
|
||||||
|
|
||||||
|
import Data.Time
|
||||||
|
import Data.Time.Clock.System
|
||||||
|
import Data.Time.Zones
|
||||||
|
|
||||||
|
import Data.List (iterate)
|
||||||
|
|
||||||
|
|
||||||
|
baseTime :: UTCTime
|
||||||
|
baseTime = UTCTime (addDays 58400 systemEpochDay) 50000
|
||||||
|
|
||||||
|
|
||||||
|
sampleCron :: Natural -> Cron -> [UTCTime]
|
||||||
|
sampleCron n = go n baseTime Nothing
|
||||||
|
where
|
||||||
|
go 0 _ _ _ = []
|
||||||
|
go n t mPrev cron = case nextCronMatch utcTZ mPrev t cron of
|
||||||
|
MatchAsap -> t : go (pred n) t (Just t) cron
|
||||||
|
MatchAt t' -> t' : go (pred n) t' (Just t') cron
|
||||||
|
MatchNone -> []
|
||||||
|
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
describe "Cron" $ do
|
||||||
|
it "generates correct example series" . mapM_ seriesExample $
|
||||||
|
[ (Cron CronAsap Nothing CronScheduleBefore, [baseTime])
|
||||||
|
, (Cron CronAsap (Just $ CronPeriod 10 CronAsap) CronScheduleBefore, iterate (addUTCTime 10) baseTime)
|
||||||
|
]
|
||||||
|
|
||||||
|
seriesExample :: (Cron, [UTCTime]) -> Expectation
|
||||||
|
seriesExample (cron, res) = example $ sampleCron 10 cron `shouldBe` take 10 res
|
||||||
@ -4,6 +4,8 @@ module Handler.ProfileSpec (spec) where
|
|||||||
|
|
||||||
import TestImport
|
import TestImport
|
||||||
|
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = withApp $ do
|
spec = withApp $ do
|
||||||
|
|
||||||
@ -13,16 +15,16 @@ spec = withApp $ do
|
|||||||
statusIs 403
|
statusIs 403
|
||||||
|
|
||||||
it "asserts access to my-account for authenticated users" $ do
|
it "asserts access to my-account for authenticated users" $ do
|
||||||
userEntity <- createUser "dummy" "foo"
|
userEntity <- createUser "foo"
|
||||||
authenticateAs userEntity
|
authenticateAs userEntity
|
||||||
|
|
||||||
get ProfileR
|
get ProfileR
|
||||||
statusIs 200
|
statusIs 200
|
||||||
|
|
||||||
it "asserts user's information is shown" $ do
|
it "asserts user's information is shown" $ do
|
||||||
userEntity <- createUser "dummy" "bar"
|
userEntity <- createUser "bar"
|
||||||
authenticateAs userEntity
|
authenticateAs userEntity
|
||||||
|
|
||||||
get ProfileR
|
get ProfileR
|
||||||
let (Entity _ user) = userEntity
|
let (Entity _ user) = userEntity
|
||||||
htmlAnyContain ".username" . unpack $ userIdent user
|
htmlAnyContain ".username" . unpack . CI.original $ userIdent user
|
||||||
|
|||||||
@ -25,6 +25,13 @@ import Test.QuickCheck.Gen as X
|
|||||||
import Data.Default as X
|
import Data.Default as X
|
||||||
import Test.QuickCheck.Instances as X
|
import Test.QuickCheck.Instances as X
|
||||||
|
|
||||||
|
|
||||||
|
import Settings
|
||||||
|
|
||||||
|
import Data.CaseInsensitive (CI)
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
|
||||||
runDB :: SqlPersistM a -> YesodExample UniWorX a
|
runDB :: SqlPersistM a -> YesodExample UniWorX a
|
||||||
runDB query = do
|
runDB query = do
|
||||||
app <- getTestYesod
|
app <- getTestYesod
|
||||||
@ -79,12 +86,24 @@ authenticateAs :: Entity User -> YesodExample UniWorX ()
|
|||||||
authenticateAs (Entity _ User{..}) = do
|
authenticateAs (Entity _ User{..}) = do
|
||||||
request $ do
|
request $ do
|
||||||
setMethod "POST"
|
setMethod "POST"
|
||||||
addPostParam "ident" $ userPlugin <> ":" <> userIdent
|
addPostParam "ident" $ CI.original userIdent
|
||||||
setUrl $ AuthR $ PluginR "dummy" []
|
setUrl $ AuthR $ PluginR "dummy" []
|
||||||
|
|
||||||
-- | Create a user. The dummy email entry helps to confirm that foreign-key
|
-- | Create a user. The dummy email entry helps to confirm that foreign-key
|
||||||
-- checking is switched off in wipeDB for those database backends which need it.
|
-- checking is switched off in wipeDB for those database backends which need it.
|
||||||
createUser :: Text -> Text -> YesodExample UniWorX (Entity User)
|
createUser :: CI Text -> YesodExample UniWorX (Entity User)
|
||||||
createUser userPlugin userIdent = runDB $ insertEntity User{..}
|
createUser userIdent = do
|
||||||
where
|
UserDefaultConf{..} <- appUserDefaults . appSettings <$> getTestYesod
|
||||||
userMatrikelnummer = "DummyMatrikelnummer"
|
let
|
||||||
|
userMatrikelnummer = Nothing
|
||||||
|
userAuthentication = AuthLDAP
|
||||||
|
userEmail = "dummy@example.invalid"
|
||||||
|
userDisplayName = "Dummy Example"
|
||||||
|
userSurname = "Example"
|
||||||
|
userTheme = userDefaultTheme
|
||||||
|
userMaxFavourites = userDefaultMaxFavourites
|
||||||
|
userDateTimeFormat = userDefaultDateTimeFormat
|
||||||
|
userDateFormat = userDefaultDateFormat
|
||||||
|
userTimeFormat = userDefaultTimeFormat
|
||||||
|
userDownloadFiles = userDefaultDownloadFiles
|
||||||
|
runDB $ insertEntity User{..}
|
||||||
|
|||||||
Reference in New Issue
Block a user