mysql-haskell-1.3.0: test/FieldRoundtrip.hs
{-# LANGUAGE TypeApplications #-}
-- | Roundtrip law for every 'Field' instance
-- (@fromField (toField x) == Right x@), plus the decode-failure behaviour
-- that a roundtrip cannot exercise.
module FieldRoundtrip (tests) where
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LazyByteString
import Data.Int (Int16, Int32, Int64, Int8)
import Data.Scientific (Scientific)
import Data.Text (Text)
import qualified Data.Text.Lazy as LazyText
import Data.Time.Calendar (Day)
import Data.Time.LocalTime (LocalTime,
TimeOfDay (..))
import Data.Word (Word16, Word32, Word64,
Word8)
import Database.MySQL.Field
import Database.MySQL.Protocol.MySQLValue (MySQLValue (..))
import Test.QuickCheck.Instances ()
import Test.Tasty
import Test.Tasty.HUnit (testCase, (@?=))
import Test.Tasty.QuickCheck (Property, testProperty,
(===))
roundtrips :: (Field a, Eq a, Show a) => a -> Property
roundtrips value = fromField (toField value) === Right value
tests :: TestTree
tests = testGroup "Field"
[ testGroup "roundtrip"
[ testProperty "Int8" (roundtrips @Int8)
, testProperty "Word8" (roundtrips @Word8)
, testProperty "Int16" (roundtrips @Int16)
, testProperty "Word16" (roundtrips @Word16)
, testProperty "Int32" (roundtrips @Int32)
, testProperty "Word32" (roundtrips @Word32)
, testProperty "Int64" (roundtrips @Int64)
, testProperty "Word64" (roundtrips @Word64)
, testProperty "Float" (roundtrips @Float)
, testProperty "Double" (roundtrips @Double)
, testProperty "Scientific" (roundtrips @Scientific)
, testProperty "Text" (roundtrips @Text)
, testProperty "lazy Text" (roundtrips @LazyText.Text)
, testProperty "String" (roundtrips @String)
, testProperty "ByteString" (roundtrips @ByteString)
, testProperty "lazy ByteString" (roundtrips @LazyByteString.ByteString)
, testProperty "Day" (roundtrips @Day)
, testProperty "LocalTime" (roundtrips @LocalTime)
, testProperty "TimeOfDay" (roundtrips @TimeOfDay)
, testProperty "Bool" (roundtrips @Bool)
, testProperty "Maybe Int32" (roundtrips @(Maybe Int32))
, testProperty "Maybe Text" (roundtrips @(Maybe Text))
]
, testGroup "decode failures"
[ testCase "NULL into a non-Maybe type is DecodeUnexpectedNull" $
decodeText MySQLNull
@?= Left (DecodeUnexpectedNull (ExpectedTypeName "Text"))
, testCase "wrong constructor is DecodeTypeMismatch" $
decodeInt8 (MySQLText "hi")
@?= Left (DecodeTypeMismatch (ExpectedTypeName "Int8")
(MySQLText "hi"))
, testCase "no implicit widening: INT32 value into Int64 fails" $
decodeInt64 (MySQLInt32 42)
@?= Left (DecodeTypeMismatch (ExpectedTypeName "Int64")
(MySQLInt32 42))
, testCase "negative TIME into TimeOfDay fails" $
decodeTimeOfDay (MySQLTime 1 (TimeOfDay 1 2 3))
@?= Left (DecodeTypeMismatch (ExpectedTypeName "TimeOfDay")
(MySQLTime 1 (TimeOfDay 1 2 3)))
]
, testGroup "decode alternatives"
[ testCase "NULL into Maybe is Nothing" $
decodeMaybe @Int32 MySQLNull @?= Right Nothing
, testCase "TIMESTAMP decodes into LocalTime" $
decodeLocalTime (MySQLTimeStamp (read "2026-07-21 12:00:00"))
@?= Right (read "2026-07-21 12:00:00")
, testCase "nonzero TINYINT decodes as True" $
decodeBool (MySQLInt8 5) @?= Right True
, testCase "unsigned TINYINT decodes into Bool" $
decodeBool (MySQLInt8U 0) @?= Right False
, testCase "nested Maybe collapses Just Nothing to Nothing (documented NULL lossiness)" $
fromField @(Maybe (Maybe Int32)) (toField (Just (Nothing :: Maybe Int32)))
@?= Right Nothing
, testCase "NaN encodes losslessly (roundtrip == fails only since NaN /= NaN)" $
fmap isNaN (decodeDouble (encodeDouble (0 / 0))) @?= Right True
, testCase "lone surrogate in String is rejected by encodeString" $
encodeString ['\xD800'] @?= Left (EncodeStringLoneSurrogate ['\xD800'])
]
]