packages feed

aws-eventbridge-cron-0.1.0.0: test/AWS/EventBridge/OneTimeSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module AWS.EventBridge.OneTimeSpec (tests) where

import AWS.EventBridge.OneTime
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time (UTCTime (..))
import Data.Time.Calendar (fromGregorian, gregorianMonthLength)
import Data.Time.Format (defaultTimeLocale, formatTime)
import Data.Time.LocalTime (TimeOfDay (..), timeOfDayToTime)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))
import Test.Tasty.QuickCheck as QC
import TestSupport (assertLeft)

tests :: TestTree
tests =
  testGroup "one-time"
    [ manualTests
    , propertyTests
    ]

manualTests :: TestTree
manualTests = testGroup "manual"
  [ testCase "parse at expression" $
      parseOneTimeText "at(2025-11-16T09:30:00)"
        @?= Right (OneTime (mkUtc 2025 11 16 9 30 0))
  , testCase "parse allows whitespace" $
      parseOneTimeText "  at(2025-01-01T00:00:00)  "
        @?= Right (OneTime (mkUtc 2025 1 1 0 0 0))
  , testCase "parse rejects Z suffix" $
      assertLeft (parseOneTimeText "at(2025-03-15T12:45:30Z)")
  , testCase "evaluator returns timestamp" $
      evaluateOneTimeT (OneTime (mkUtc 2024 5 20 18 0 0))
        @?= mkUtc 2024 5 20 18 0 0
  , testCase "invalid month rejected" $
      assertLeft (parseOneTimeText "at(2025-13-01T00:00:00)")
  , testCase "invalid day rejected" $
      assertLeft (parseOneTimeText "at(2025-02-30T00:00:00)")
  , testCase "invalid time rejected" $
      assertLeft (parseOneTimeText "at(2025-01-01T25:00:00)")
  , testCase "missing seconds rejected" $
      assertLeft (parseOneTimeText "at(2025-01-01T00:00)")
  ]

propertyTests :: TestTree
propertyTests = testGroup "properties"
  [ QC.testProperty "parser round-trips formatted timestamps" propRoundTrip
  ]

propRoundTrip :: QC.Property
propRoundTrip =
  QC.forAll genUtc $ \t ->
    let rendered = renderAt t
     in parseOneTimeText rendered QC.=== Right (OneTime t)

renderAt :: UTCTime -> Text
renderAt t =
  let payload = formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" t
   in T.pack ("at(" <> payload <> ")")

mkUtc :: Integer -> Int -> Int -> Int -> Int -> Int -> UTCTime
mkUtc year month day hour minute second =
  let day' = fromGregorian year month day
      tod = TimeOfDay hour minute (fromIntegral second)
   in UTCTime day' (timeOfDayToTime tod)

genUtc :: QC.Gen UTCTime
genUtc = do
  year <- QC.chooseInteger (1970, 2199)
  month <- QC.chooseInt (1, 12)
  let dim = gregorianMonthLength year month
  day <- QC.chooseInt (1, dim)
  hour <- QC.chooseInt (0, 23)
  minute <- QC.chooseInt (0, 59)
  second <- QC.chooseInt (0, 59)
  pure (mkUtc year month day hour minute second)