packages feed

http-date-0.0.13: test/DateSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module DateSpec where

import Control.Monad
import Data.Maybe (isJust)
import Model
import Network.HTTP.Date
import System.Posix.Types
import Test.Hspec

----------------------------------------------------------------

-- Every 100000 seconds hits few boundaries of a day, so add a step
-- coprime to 86400 from before 1970 to beyond year 9999.
epochs :: [EpochTime]
epochs =
    [0, 100000 .. 10000000000]
        ++ [-62135596800, -62135596800 + 9999991 .. 253402300799]

hd :: HTTPDate
hd =
    defaultHTTPDate
        { hdYear = 1994
        , hdMonth = 11
        , hdDay = 6
        , hdHour = 8
        , hdMinute = 49
        , hdSecond = 37
        , hdWkday = 7
        }

spec :: Spec
spec = do
    describe "epochTimeToHTTPDate" $ do
        it "behaves like utcToHTTPDate" $
            forM_ (epochs ++ [-1, -86400, 253402300800, 10 ^ (15 :: Int)]) $ \e ->
                epochTimeToHTTPDate e `shouldBe` utcToHTTPDate (epochTimeToUtcTime e)
        it "handles the extremes of EpochTime in constant time" $ do
            hdYear (epochTimeToHTTPDate 9223372036854775807) `shouldBe` 292277026596
            hdYear (epochTimeToHTTPDate (-9223372036854775808)) `shouldBe` -292277022657

    describe "formatHTTPDate" $ do
        it "behaves like the model" $
            forM_ epochs $ \e ->
                formatHTTPDate (epochTimeToHTTPDate e)
                    `shouldBe` utcToDate (epochTimeToUtcTime e)
        it "renders out-of-range fields as underscores" $ do
            formatHTTPDate hd{hdYear = 10000} `shouldBe` "Sun, 06 Nov ____ 08:49:37 GMT"
            formatHTTPDate hd{hdYear = -1} `shouldBe` "Sun, 06 Nov ____ 08:49:37 GMT"
            formatHTTPDate hd{hdDay = 0, hdHour = 24, hdMinute = 60, hdSecond = 61}
                `shouldBe` "Sun, __ Nov 1994 __:__:__ GMT"
            formatHTTPDate hd{hdDay = 123, hdHour = -1}
                `shouldBe` "Sun, __ Nov 1994 __:49:37 GMT"

    describe "parseHTTPDate" $ do
        it "behaves like the model" $
            forM_ epochs $ \e -> do
                let m = epochTimeToHTTPDate e
                parseHTTPDate (formatHTTPDate m) `shouldBe` Just m
        it "accepts the three formats of RFC 9110" $ do
            parseHTTPDate "Sun, 06 Nov 1994 08:49:37 GMT" `shouldBe` Just hd
            parseHTTPDate "Sunday, 06-Nov-94 08:49:37 GMT" `shouldBe` Just hd
            parseHTTPDate "Sun Nov  6 08:49:37 1994" `shouldBe` Just hd
            parseHTTPDate "Sun Nov 06 08:49:37 1994" `shouldBe` Just hd
        it "ignores trailing whitespace" $ do
            parseHTTPDate "Sun, 06 Nov 1994 08:49:37 GMT \t " `shouldBe` Just hd
            parseHTTPDate "Sun Nov  6 08:49:37 1994 " `shouldBe` Just hd
        it "maps a two-digit year to 1970-2069" $ do
            hdYear <$> parseHTTPDate "Thursday, 01-Jan-70 00:00:00 GMT" `shouldBe` Just 1970
            hdYear <$> parseHTTPDate "Tuesday, 31-Dec-69 00:00:00 GMT" `shouldBe` Just 2069
        it "accepts a leap second only at 23:59" $ do
            parseHTTPDate "Sat, 31 Dec 2016 23:59:60 GMT" `shouldSatisfy` isJust
            parseHTTPDate "Sat, 31 Dec 2016 23:58:60 GMT" `shouldBe` Nothing
        it "rejects invalid dates and times"
            $ forM_
                [ "Sun, 06 Nov 1994 08:49:37 GMTgarbage"
                , " Sun, 06 Nov 1994 08:49:37 GMT"
                , "Sun, 00 Nov 1994 08:49:37 GMT"
                , "Sun, 31 Nov 1994 08:49:37 GMT"
                , "Sun, 29 Feb 1900 08:49:37 GMT"
                , "Sun, 06 Nov 1994 24:00:00 GMT"
                , "Sun, 06 Nov 1994 08:60:37 GMT"
                , "Sun, 06 Nov 1994 08:49:61 GMT"
                , "Sun, 06 Nov 1994 08:49:37"
                , "Sun, 6 Nov 1994 08:49:37 GMT"
                , "Sun,06 Nov 1994 08:49:37 GMT"
                , "Sun Nov  6 08:49:37 1994 GMT"
                , "Sunday, 06 Nov 1994 08:49:37 GMT"
                , ""
                ]
            $ \s -> parseHTTPDate s `shouldBe` Nothing

    describe "httpDateToUTC" $ do
        it "behaves like the model" $
            forM_ epochs $ \e ->
                httpDateToUTC (epochTimeToHTTPDate e) `shouldBe` epochTimeToUtcTime e
        it "keeps a leap second at 23:59" $ do
            let ls = hd{hdHour = 23, hdMinute = 59, hdSecond = 60}
            show (httpDateToUTC ls) `shouldBe` "1994-11-06 23:59:60 UTC"
            utcToHTTPDate (httpDateToUTC ls) `shouldBe` ls
        it "clips out-of-range fields" $ do
            show (httpDateToUTC hd{hdMonth = 2, hdDay = 31, hdHour = 25})
                `shouldBe` "1994-02-28 23:49:37 UTC"
            show (httpDateToUTC hd{hdMinute = -5, hdSecond = 60})
                `shouldBe` "1994-11-06 08:00:59 UTC"

    describe "httpDateToUTCValid" $ do
        it "agrees with httpDateToUTC on valid values" $
            forM_ epochs $ \e -> do
                let m = epochTimeToHTTPDate e
                httpDateToUTCValid m `shouldBe` Just (httpDateToUTC m)
        it "rejects invalid values"
            $ forM_
                [ hd{hdMonth = 2, hdDay = 30}
                , hd{hdMonth = 13}
                , hd{hdHour = 24}
                , hd{hdMinute = -1}
                , hd{hdSecond = 60}
                ]
            $ \x -> httpDateToUTCValid x `shouldBe` Nothing

    describe "utcToHTTPDate" $ do
        it "behaves like the model" $
            forM_ epochs $ \e ->
                utcToHTTPDate (epochTimeToUtcTime e) `shouldBe` epochTimeToHTTPDate e