{-# LANGUAGE QuasiQuotes #-}
{-
Testing strategies:
fromString . toString == id ** Todo?
toString . fromString == almost id ** Todo?
postgresql -> haskell -> postgresql * Done
haskell -> postgresql -> haskell ** Todo?
But still, what we really want to establish is that the two values
correspond; for example, a conversion that consistently added hour
when printed to a string and subtracted an hour when parsed from string
would still pass these tests.
Right now, we are checking that 1400+ timestamps in the range of 1860 to
2060 round trip from postgresql to haskell and back in 5 different timezones.
In addition to UTC, the four timezones were selected so that 2 have a positive
offset, and 2 have a negative offset, and that 2 have an offset of a
whole number of hours, while the other two do not.
It may be worth adding a few more timezones to ensure better test coverage.
We are checking a handful of selected timestamps to ensure we hit
various corner-cases in the code, in addition to 1400 timestamps randomly
generated with granularity of seconds down to microseconds in powers of ten.
-}
module Interval (testInterval) where
import Common
import Control.Monad(forM_, replicateM_)
import Data.Time.Compat
import Data.Time.LocalTime.Compat (CalendarDiffTime(..))
import Data.ByteString(ByteString)
import Database.PostgreSQL.Simple.SqlQQ
data IntervalTestCase = IntervalTestCase
{ label :: String
, inputMonths :: Integer
, inputSeconds :: NominalDiffTime
, asText :: String
}
deriving (Eq, Show)
testInterval :: TestEnv -> Assertion
testInterval env@TestEnv{..} = do
initializeTable env
let milliseconds = 0.001
seconds = 1
minutes = 60 * seconds
hours = 60 * minutes
days = 24 * hours
weeks = 7 * days
months = 1
years = 12 * months
mapM (checkRoundTrip env)
[ IntervalTestCase
{ label = "zero"
, inputMonths = 0
, inputSeconds = 0
, asText = "PT0"
}
, IntervalTestCase
{ label = "1 year"
, inputMonths = 1 * years
, inputSeconds = 0
, asText = "P1Y"
}
, IntervalTestCase
{ label = "2 months"
, inputMonths = 2 * months
, inputSeconds = 0
, asText = "P2M"
}
, IntervalTestCase
{ label = "3 weeks"
, inputMonths = 0
, inputSeconds = 3 * weeks
, asText = "P3W"
}
, IntervalTestCase
{ label = "4 days"
, inputMonths = 0
, inputSeconds = 4 * days
, asText = "P4D"
}
, IntervalTestCase
{ label = "5 hours"
, inputMonths = 0
, inputSeconds = 5 * hours
, asText = "PT5H"
}
, IntervalTestCase
{ label = "6 minutes"
, inputMonths = 0
, inputSeconds = 6 * minutes
, asText = "PT6M"
}
, IntervalTestCase
{ label = "7 seconds"
, inputMonths = 0
, inputSeconds = 7 * seconds
, asText = "PT7S"
}
, IntervalTestCase
{ label = "8 milliseconds"
, inputMonths = 0
, inputSeconds = 8 * milliseconds
, asText = "PT0.008S"
}
, IntervalTestCase
{ label = "combination of intervals (day-size or bigger)"
, inputMonths = 2 * years + 4 * months
, inputSeconds = 3 * weeks + 5 * days
, asText = "P2Y4M3W5D"
}
, IntervalTestCase
{ label = "combination of intervals (smaller than day-size)"
, inputMonths = 0
, inputSeconds = 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
, asText = "PT18H56M23.563S"
}
, IntervalTestCase
{ label = "full combination of intervals"
, inputMonths = 2 * years + 4 * months
, inputSeconds = 3 * weeks + 5 * days + 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
, asText = "P2Y4M3W5DT18H56M23.563S"
}
]
return ()
initializeTable :: TestEnv -> IO ()
initializeTable TestEnv{..} = withTransaction conn $ do
execute_ conn
[sql| CREATE TEMPORARY TABLE testinterval
( id serial, sample interval, PRIMARY KEY(id) ) |]
return ()
checkRoundTrip :: TestEnv -> IntervalTestCase -> IO ()
checkRoundTrip TestEnv{..} IntervalTestCase{..} = do
let input = CalendarDiffTime
{ ctMonths = inputMonths
, ctTime = inputSeconds
}
[(returnedId :: Int, output :: CalendarDiffTime)] <- query conn [sql|
INSERT INTO testinterval (sample)
VALUES (?)
RETURNING id, sample
|] (Only input)
assertBool ("CalendarDiffTime did not round-trip from Haskell to SQL and back (" ++ label ++ ")") $
output == input
[(Only isExpectedIso)] <- query conn [sql|
SELECT sample = (?)::interval
FROM testinterval
WHERE id = ?
|] (asText, returnedId)
assertBool ("CalendarDiffTime inserted did not match ISO8601 equivalent \"" ++ asText ++ "\". (" ++ label ++ ")")
isExpectedIso