Test adherence to typeclass-laws much more extensively
This commit is contained in:
parent
7936a64f09
commit
e66f8919a4
@ -160,6 +160,7 @@ default-extensions:
|
|||||||
- BinaryLiterals
|
- BinaryLiterals
|
||||||
- PolyKinds
|
- PolyKinds
|
||||||
- PackageImports
|
- PackageImports
|
||||||
|
- TypeApplications
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
@ -223,6 +224,10 @@ tests:
|
|||||||
- conduit-extra
|
- conduit-extra
|
||||||
- quickcheck-classes
|
- quickcheck-classes
|
||||||
- quickcheck-instances
|
- quickcheck-instances
|
||||||
|
- generic-arbitrary
|
||||||
|
- http-types
|
||||||
|
ghc-options:
|
||||||
|
- -fno-warn-orphans
|
||||||
hlint:
|
hlint:
|
||||||
main: Hlint.hs
|
main: Hlint.hs
|
||||||
other-modules: []
|
other-modules: []
|
||||||
|
|||||||
@ -97,6 +97,8 @@ import Data.Universe.Instances.Reverse ()
|
|||||||
import Data.Universe.Instances.Reverse.JSON ()
|
import Data.Universe.Instances.Reverse.JSON ()
|
||||||
import Data.Universe.Instances.Reverse.Hashable ()
|
import Data.Universe.Instances.Reverse.Hashable ()
|
||||||
|
|
||||||
|
import GHC.Exts (IsList)
|
||||||
|
|
||||||
makeLenses_ ''Mail
|
makeLenses_ ''Mail
|
||||||
makeLenses_ ''Part
|
makeLenses_ ''Part
|
||||||
|
|
||||||
@ -122,7 +124,7 @@ instance Monoid (MailSmtpData) where
|
|||||||
|
|
||||||
newtype MailLanguages = MailLanguages { mailLanguages :: [Lang] }
|
newtype MailLanguages = MailLanguages { mailLanguages :: [Lang] }
|
||||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||||
deriving newtype (FromJSON, ToJSON)
|
deriving newtype (FromJSON, ToJSON, IsList)
|
||||||
|
|
||||||
instance Default MailLanguages where
|
instance Default MailLanguages where
|
||||||
def = MailLanguages []
|
def = MailLanguages []
|
||||||
@ -158,7 +160,7 @@ instance MonadHandler m => MonadMail (MailT m) where
|
|||||||
|
|
||||||
data VerpMode = VerpNone
|
data VerpMode = VerpNone
|
||||||
| Verp { verpSeparator, verpAtReplacement :: Char }
|
| Verp { verpSeparator, verpAtReplacement :: Char }
|
||||||
deriving (Eq, Show, Read)
|
deriving (Eq, Show, Read, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
{ constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
||||||
|
|||||||
@ -94,10 +94,11 @@ instance (CI.FoldCase s, PathMultiPiece s) => PathMultiPiece (CI s) where
|
|||||||
toPathMultiPiece = toPathMultiPiece . CI.foldedCase
|
toPathMultiPiece = toPathMultiPiece . CI.foldedCase
|
||||||
|
|
||||||
instance ToHttpApiData (CI Text) where
|
instance ToHttpApiData (CI Text) where
|
||||||
toUrlPiece = CI.original
|
toUrlPiece = toUrlPiece . CI.original
|
||||||
|
toEncodedUrlPiece = toEncodedUrlPiece . CI.original
|
||||||
|
|
||||||
instance FromHttpApiData (CI Text) where
|
instance FromHttpApiData (CI Text) where
|
||||||
parseUrlPiece = return . CI.mk
|
parseUrlPiece = fmap CI.mk . parseUrlPiece
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ data SheetGradeSummary = SheetGradeSummary
|
|||||||
, numGradePasses :: Sum Int
|
, numGradePasses :: Sum Int
|
||||||
, achievedPoints :: Maybe (Sum Points)
|
, achievedPoints :: Maybe (Sum Points)
|
||||||
, achievedPasses :: Maybe (Sum Int)
|
, achievedPasses :: Maybe (Sum Int)
|
||||||
} deriving (Generic)
|
} deriving (Generic, Read, Show, Eq)
|
||||||
|
|
||||||
instance Monoid SheetGradeSummary where
|
instance Monoid SheetGradeSummary where
|
||||||
mempty = memptydefault
|
mempty = memptydefault
|
||||||
@ -175,7 +176,7 @@ derivePersistFieldJSON ''SheetType
|
|||||||
data SheetTypeSummary = SheetTypeSummary
|
data SheetTypeSummary = SheetTypeSummary
|
||||||
{ normalSummary, bonusSummary, informationalSummary :: SheetGradeSummary
|
{ normalSummary, bonusSummary, informationalSummary :: SheetGradeSummary
|
||||||
, numNotGraded :: Sum Int
|
, numNotGraded :: Sum Int
|
||||||
} deriving (Generic)
|
} deriving (Generic, Read, Show, Eq)
|
||||||
|
|
||||||
instance Monoid SheetTypeSummary where
|
instance Monoid SheetTypeSummary where
|
||||||
mempty = memptydefault
|
mempty = memptydefault
|
||||||
@ -191,14 +192,14 @@ data SheetGroup
|
|||||||
= Arbitrary { maxParticipants :: Natural }
|
= Arbitrary { maxParticipants :: Natural }
|
||||||
| RegisteredGroups
|
| RegisteredGroups
|
||||||
| NoGroups
|
| NoGroups
|
||||||
deriving (Show, Read, Eq)
|
deriving (Show, Read, Eq, Generic)
|
||||||
deriveJSON defaultOptions ''SheetGroup
|
deriveJSON defaultOptions ''SheetGroup
|
||||||
derivePersistFieldJSON ''SheetGroup
|
derivePersistFieldJSON ''SheetGroup
|
||||||
|
|
||||||
makeLenses_ ''SheetGroup
|
makeLenses_ ''SheetGroup
|
||||||
|
|
||||||
data SheetFileType = SheetExercise | SheetHint | SheetSolution | SheetMarking
|
data SheetFileType = SheetExercise | SheetHint | SheetSolution | SheetMarking
|
||||||
deriving (Show, Read, Eq, Ord, Enum, Bounded)
|
deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic)
|
||||||
derivePersistField "SheetFileType"
|
derivePersistField "SheetFileType"
|
||||||
|
|
||||||
instance Universe SheetFileType where universe = universeDef
|
instance Universe SheetFileType where universe = universeDef
|
||||||
@ -225,7 +226,7 @@ partitionFileType :: Ord a => [(SheetFileType,a)] -> SheetFileType -> Set a
|
|||||||
partitionFileType fs t = Map.findWithDefault Set.empty t . Map.fromListWith Set.union $ map (over _2 Set.singleton) fs
|
partitionFileType fs t = Map.findWithDefault Set.empty t . Map.fromListWith Set.union $ map (over _2 Set.singleton) fs
|
||||||
|
|
||||||
data SubmissionFileType = SubmissionOriginal | SubmissionCorrected
|
data SubmissionFileType = SubmissionOriginal | SubmissionCorrected
|
||||||
deriving (Show, Read, Eq, Ord, Enum, Bounded)
|
deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic)
|
||||||
|
|
||||||
instance Universe SubmissionFileType where universe = universeDef
|
instance Universe SubmissionFileType where universe = universeDef
|
||||||
instance Finite SubmissionFileType
|
instance Finite SubmissionFileType
|
||||||
@ -256,7 +257,7 @@ instance DisplayAble DA where
|
|||||||
|
|
||||||
|
|
||||||
data UploadMode = NoUpload | Upload { unpackZips :: Bool }
|
data UploadMode = NoUpload | Upload { unpackZips :: Bool }
|
||||||
deriving (Show, Read, Eq, Ord)
|
deriving (Show, Read, Eq, Ord, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions ''UploadMode
|
deriveJSON defaultOptions ''UploadMode
|
||||||
derivePersistFieldJSON ''UploadMode
|
derivePersistFieldJSON ''UploadMode
|
||||||
@ -275,7 +276,7 @@ instance PathPiece UploadMode where
|
|||||||
data SheetSubmissionMode = NoSubmissions
|
data SheetSubmissionMode = NoSubmissions
|
||||||
| CorrectorSubmissions
|
| CorrectorSubmissions
|
||||||
| UserSubmissions
|
| UserSubmissions
|
||||||
deriving (Show, Read, Eq, Ord, Enum, Bounded)
|
deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = camelToPathPiece
|
{ constructorTagModifier = camelToPathPiece
|
||||||
@ -288,7 +289,7 @@ instance Finite SheetSubmissionMode
|
|||||||
nullaryPathPiece ''SheetSubmissionMode camelToPathPiece
|
nullaryPathPiece ''SheetSubmissionMode camelToPathPiece
|
||||||
|
|
||||||
data ExamStatus = Attended | NoShow | Voided
|
data ExamStatus = Attended | NoShow | Voided
|
||||||
deriving (Show, Read, Eq, Ord, Enum, Bounded)
|
deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic)
|
||||||
derivePersistField "ExamStatus"
|
derivePersistField "ExamStatus"
|
||||||
|
|
||||||
-- | Specify a corrector's workload
|
-- | Specify a corrector's workload
|
||||||
@ -296,7 +297,7 @@ data Load -- = ByTutorial { countsToLoad :: Bool } | ByProportion { load :: Rati
|
|||||||
= Load { byTutorial :: Maybe Bool -- ^ Just all from Tutorial, True if counting towards overall workload
|
= Load { byTutorial :: Maybe Bool -- ^ Just all from Tutorial, True if counting towards overall workload
|
||||||
, byProportion :: Rational -- ^ workload proportion of all submission not assigned to tutorial leaders
|
, byProportion :: Rational -- ^ workload proportion of all submission not assigned to tutorial leaders
|
||||||
}
|
}
|
||||||
deriving (Show, Read, Eq, Ord)
|
deriving (Show, Read, Eq, Ord, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions ''Load
|
deriveJSON defaultOptions ''Load
|
||||||
derivePersistFieldJSON ''Load
|
derivePersistFieldJSON ''Load
|
||||||
@ -343,6 +344,11 @@ data TermIdentifier = TermIdentifier
|
|||||||
, season :: Season
|
, season :: Season
|
||||||
} deriving (Show, Read, Eq, Ord, Generic, Typeable)
|
} deriving (Show, Read, Eq, Ord, Generic, Typeable)
|
||||||
|
|
||||||
|
instance Enum TermIdentifier where
|
||||||
|
-- ^ Do not use for conversion – Enumeration only
|
||||||
|
toEnum int = let (toInteger -> year, toEnum -> season) = int `divMod` 2 in TermIdentifier{..}
|
||||||
|
fromEnum TermIdentifier{..} = fromInteger year * 2 + fromEnum season
|
||||||
|
|
||||||
-- Conversion TermId <-> TermIdentifier::
|
-- Conversion TermId <-> TermIdentifier::
|
||||||
-- from_TermId_to_TermIdentifier = unTermKey
|
-- from_TermId_to_TermIdentifier = unTermKey
|
||||||
-- from_TermIdentifier_to_TermId = TermKey
|
-- from_TermIdentifier_to_TermId = TermKey
|
||||||
@ -436,7 +442,7 @@ time `withinTerm` term = timeYear `mod` 100 == termYear `mod` 100
|
|||||||
|
|
||||||
|
|
||||||
data StudyFieldType = FieldPrimary | FieldSecondary
|
data StudyFieldType = FieldPrimary | FieldSecondary
|
||||||
deriving (Eq, Ord, Enum, Show, Read, Bounded)
|
deriving (Eq, Ord, Enum, Show, Read, Bounded, Generic)
|
||||||
derivePersistField "StudyFieldType"
|
derivePersistField "StudyFieldType"
|
||||||
|
|
||||||
instance PersistField UUID where
|
instance PersistField UUID where
|
||||||
@ -458,7 +464,7 @@ data Theme
|
|||||||
| ThemeAberdeenReds
|
| ThemeAberdeenReds
|
||||||
| ThemeMossGreen
|
| ThemeMossGreen
|
||||||
| ThemeSkyLove
|
| ThemeSkyLove
|
||||||
deriving (Eq, Ord, Bounded, Enum, Show, Read)
|
deriving (Eq, Ord, Bounded, Enum, Show, Read, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = fromJust . stripPrefix "Theme"
|
{ constructorTagModifier = fromJust . stripPrefix "Theme"
|
||||||
@ -483,7 +489,7 @@ instance PathPiece obj => PathPiece (ZIPArchiveName obj) where
|
|||||||
|
|
||||||
|
|
||||||
data CorrectorState = CorrectorNormal | CorrectorMissing | CorrectorExcused
|
data CorrectorState = CorrectorNormal | CorrectorMissing | CorrectorExcused
|
||||||
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = fromJust . stripPrefix "Corrector"
|
{ constructorTagModifier = fromJust . stripPrefix "Corrector"
|
||||||
@ -499,7 +505,7 @@ derivePersistField "CorrectorState"
|
|||||||
|
|
||||||
data AuthenticationMode = AuthLDAP
|
data AuthenticationMode = AuthLDAP
|
||||||
| AuthPWHash { authPWHash :: Text }
|
| AuthPWHash { authPWHash :: Text }
|
||||||
deriving (Eq, Ord, Read, Show)
|
deriving (Eq, Ord, Read, Show, Generic)
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
{ constructorTagModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
||||||
@ -577,7 +583,7 @@ type PseudonymWord = CI Text
|
|||||||
|
|
||||||
newtype Pseudonym = Pseudonym Word24
|
newtype Pseudonym = Pseudonym Word24
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Data)
|
deriving (Eq, Ord, Read, Show, Generic, Data)
|
||||||
deriving newtype (Bounded, Enum, Integral, Num, Real, Bits, FiniteBits, Ix)
|
deriving newtype (Bounded, Enum, Integral, Num, Real, Ix)
|
||||||
|
|
||||||
|
|
||||||
instance PersistField Pseudonym where
|
instance PersistField Pseudonym where
|
||||||
@ -632,7 +638,7 @@ _PseudonymWords = prism' pToWords pFromWords
|
|||||||
| Just i1 <- elemIndex w1 pseudonymWordlist
|
| Just i1 <- elemIndex w1 pseudonymWordlist
|
||||||
, Just i2 <- elemIndex w2 pseudonymWordlist
|
, Just i2 <- elemIndex w2 pseudonymWordlist
|
||||||
, i1 <= maxWord, i2 <= maxWord
|
, i1 <= maxWord, i2 <= maxWord
|
||||||
= Just $ shiftL (fromIntegral i1) 12 .|. fromIntegral i2
|
= Just . Pseudonym $ shiftL (fromIntegral i1) 12 .|. fromIntegral i2
|
||||||
pFromWords _ = Nothing
|
pFromWords _ = Nothing
|
||||||
|
|
||||||
pToWords :: Pseudonym -> [PseudonymWord]
|
pToWords :: Pseudonym -> [PseudonymWord]
|
||||||
|
|||||||
@ -42,4 +42,7 @@ extra-deps:
|
|||||||
|
|
||||||
- pkcs7-1.0.0.1
|
- pkcs7-1.0.0.1
|
||||||
|
|
||||||
|
- quickcheck-classes-0.4.14
|
||||||
|
- semirings-0.2.1.1
|
||||||
|
|
||||||
resolver: lts-10.5
|
resolver: lts-10.5
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
||||||
|
|
||||||
module Handler.Utils.ZipSpec where
|
module Handler.Utils.ZipSpec where
|
||||||
|
|
||||||
import TestImport
|
import TestImport
|
||||||
@ -27,8 +23,9 @@ instance Arbitrary File where
|
|||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = describe "Zip file handling" $ do
|
spec = describe "Zip file handling" $ do
|
||||||
it "has compatible encoding/decoding to/from zip files" . property $
|
it "has compatible encoding/decoding to/from zip files" . property $ do
|
||||||
\zipFiles -> do
|
zipFiles <- listOf $ scale (`div` 2) arbitrary
|
||||||
|
return . property $ do
|
||||||
zipFiles' <- runConduit $ Conduit.sourceList zipFiles =$= produceZip def =$= void consumeZip =$= Conduit.consume
|
zipFiles' <- runConduit $ Conduit.sourceList zipFiles =$= produceZip def =$= void consumeZip =$= Conduit.consume
|
||||||
forM_ (zipFiles `zip` zipFiles') $ \(file, file') -> do
|
forM_ (zipFiles `zip` zipFiles') $ \(file, file') -> do
|
||||||
let acceptableFilenameChanges
|
let acceptableFilenameChanges
|
||||||
|
|||||||
33
test/MailSpec.hs
Normal file
33
test/MailSpec.hs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
module MailSpec where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
import Utils.DateTimeSpec ()
|
||||||
|
|
||||||
|
import Mail
|
||||||
|
|
||||||
|
instance Arbitrary MailSmtpData where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary MailLanguages where
|
||||||
|
arbitrary = MailLanguages <$> arbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary MailContext where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary VerpMode where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
lawsCheckHspec (Proxy @MailSmtpData)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, monoidLaws ]
|
||||||
|
lawsCheckHspec (Proxy @MailLanguages)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, isListLaws, jsonLaws, hashableLaws ]
|
||||||
|
lawsCheckHspec (Proxy @MailContext)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, jsonLaws, hashableLaws ]
|
||||||
|
lawsCheckHspec (Proxy @VerpMode)
|
||||||
|
[ eqLaws, showReadLaws, jsonLaws ]
|
||||||
@ -1,19 +1,16 @@
|
|||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
||||||
|
|
||||||
module Model.TypesSpec where
|
module Model.TypesSpec where
|
||||||
|
|
||||||
import TestImport
|
import TestImport
|
||||||
|
|
||||||
import Control.Lens (review, preview)
|
import Control.Lens (review, preview)
|
||||||
|
import Data.Aeson (Value)
|
||||||
|
import qualified Data.Aeson as Aeson
|
||||||
|
|
||||||
import Data.Aeson (encode, decode)
|
import MailSpec ()
|
||||||
|
|
||||||
|
|
||||||
instance Arbitrary Season where
|
instance Arbitrary Season where
|
||||||
arbitrary = elements [minBound..maxBound]
|
arbitrary = genericArbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary TermIdentifier where
|
instance Arbitrary TermIdentifier where
|
||||||
@ -38,21 +35,156 @@ instance Arbitrary SheetGrading where
|
|||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary SheetType where
|
instance Arbitrary SheetType where
|
||||||
arbitrary = oneof
|
arbitrary = genericArbitrary
|
||||||
[ return NotGraded
|
shrink = genericShrink
|
||||||
, Normal <$> arbitrary
|
|
||||||
, Bonus <$> arbitrary
|
instance Arbitrary SheetGradeSummary where
|
||||||
, Informational <$> arbitrary
|
arbitrary = genericArbitrary
|
||||||
]
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary SheetGroup where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary SheetTypeSummary where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary SheetFileType where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary SubmissionFileType where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary UploadMode where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary SheetSubmissionMode where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary ExamStatus where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary Load where
|
||||||
|
arbitrary = do
|
||||||
|
byTutorial <- arbitrary
|
||||||
|
byProportion <- getNonNegative <$> arbitrary
|
||||||
|
return Load{..}
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary StudyFieldType where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary Theme where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary CorrectorState where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary AuthenticationMode where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary NotificationTrigger where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
instance CoArbitrary NotificationTrigger where
|
||||||
|
coarbitrary = genericCoarbitrary
|
||||||
|
|
||||||
|
instance Arbitrary NotificationSettings where
|
||||||
|
arbitrary = NotificationSettings <$> arbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary AuthTag where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
instance CoArbitrary AuthTag where
|
||||||
|
coarbitrary = genericCoarbitrary
|
||||||
|
|
||||||
|
instance Arbitrary AuthTagActive where
|
||||||
|
arbitrary = AuthTagActive <$> arbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary Value where
|
||||||
|
arbitrary = sized $ \n -> if
|
||||||
|
| n <= 0 -> oneof [ Aeson.Number <$> arbitrary, Aeson.Bool <$> arbitrary, pure Aeson.Null ]
|
||||||
|
| otherwise -> oneof
|
||||||
|
[ Aeson.Object <$> arbitrary'
|
||||||
|
, Aeson.Array <$> arbitrary'
|
||||||
|
, Aeson.String <$> arbitrary'
|
||||||
|
, resize 0 arbitrary
|
||||||
|
]
|
||||||
|
where
|
||||||
|
arbitrary' :: forall a. Arbitrary a => Gen a
|
||||||
|
arbitrary' = scale (`div` 2) arbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = do
|
spec = do
|
||||||
describe "UUID" $
|
lawsCheckHspec (Proxy @UUID)
|
||||||
lawsCheckHspec (Proxy :: Proxy UUID)
|
[ persistFieldLaws, pathPieceLaws, eqLaws, ordLaws, showReadLaws, hashableLaws, jsonLaws, storableLaws, jsonKeyLaws, httpApiDataLaws ]
|
||||||
[ persistFieldLaws, pathPieceLaws, eqLaws, ordLaws, showReadLaws, hashableLaws, jsonLaws, storableLaws, jsonKeyLaws
|
lawsCheckHspec (Proxy @FilePath)
|
||||||
]
|
[ pathMultiPieceLaws ]
|
||||||
|
lawsCheckHspec (Proxy @(CI Text))
|
||||||
|
[ httpApiDataLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetGrading)
|
||||||
|
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetGradeSummary)
|
||||||
|
[ eqLaws, showReadLaws, commutativeMonoidLaws, commutativeSemigroupLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetType)
|
||||||
|
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetTypeSummary)
|
||||||
|
[ eqLaws, showReadLaws, commutativeMonoidLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetGroup)
|
||||||
|
[ eqLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetFileType)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, persistFieldLaws, pathPieceLaws, finiteLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SubmissionFileType)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, pathPieceLaws, finiteLaws ]
|
||||||
|
lawsCheckHspec (Proxy @UploadMode)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, pathPieceLaws, finiteLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SheetSubmissionMode)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, jsonLaws, persistFieldLaws, finiteLaws, pathPieceLaws ]
|
||||||
|
lawsCheckHspec (Proxy @ExamStatus)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @Load)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, commutativeSemigroupLaws, commutativeMonoidLaws ]
|
||||||
|
lawsCheckHspec (Proxy @Season)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, boundedEnumLaws ]
|
||||||
|
lawsCheckHspec (Proxy @TermIdentifier)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, enumLaws, persistFieldLaws, jsonLaws, httpApiDataLaws, pathPieceLaws ]
|
||||||
|
lawsCheckHspec (Proxy @StudyFieldType)
|
||||||
|
[ eqLaws, ordLaws, boundedEnumLaws, showReadLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @Theme)
|
||||||
|
[ eqLaws, ordLaws, boundedEnumLaws, showReadLaws, jsonLaws, finiteLaws, pathPieceLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @CorrectorState)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, boundedEnumLaws, jsonLaws, finiteLaws, pathPieceLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @AuthenticationMode)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @Value)
|
||||||
|
[ persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @NotificationTrigger)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, jsonLaws, boundedEnumLaws, finiteLaws, hashableLaws, jsonLaws, jsonKeyLaws ]
|
||||||
|
lawsCheckHspec (Proxy @NotificationSettings)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @MailLanguages)
|
||||||
|
[ persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @Pseudonym)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, boundedEnumLaws, integralLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
lawsCheckHspec (Proxy @AuthTag)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, boundedEnumLaws, finiteLaws, hashableLaws, jsonLaws, pathPieceLaws, jsonKeyLaws ]
|
||||||
|
lawsCheckHspec (Proxy @AuthTagActive)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, jsonLaws, persistFieldLaws ]
|
||||||
|
|
||||||
describe "TermIdentifier" $ do
|
describe "TermIdentifier" $ do
|
||||||
it "has compatible encoding/decoding to/from Text" . property $
|
it "has compatible encoding/decoding to/from Text" . property $
|
||||||
\term -> termFromText (termToText term) == Right term
|
\term -> termFromText (termToText term) == Right term
|
||||||
@ -61,6 +193,8 @@ spec = do
|
|||||||
, (TermIdentifier 1995 Winter, "W95")
|
, (TermIdentifier 1995 Winter, "W95")
|
||||||
, (TermIdentifier 3068 Winter, "W3068")
|
, (TermIdentifier 3068 Winter, "W3068")
|
||||||
]
|
]
|
||||||
|
it "has compatbile encoding/decoding to/from Rational" . property $
|
||||||
|
\term -> termFromRational (termToRational term) == term
|
||||||
describe "Pseudonym" $ do
|
describe "Pseudonym" $ do
|
||||||
it "has sufficient vocabulary" $
|
it "has sufficient vocabulary" $
|
||||||
(length pseudonymWordlist ^ 2) `shouldBe` (succ $ fromIntegral (maxBound - minBound :: Pseudonym))
|
(length pseudonymWordlist ^ 2) `shouldBe` (succ $ fromIntegral (maxBound - minBound :: Pseudonym))
|
||||||
@ -68,9 +202,6 @@ spec = do
|
|||||||
\pseudonym -> preview _PseudonymText (review _PseudonymText pseudonym) == Just pseudonym
|
\pseudonym -> preview _PseudonymText (review _PseudonymText pseudonym) == Just pseudonym
|
||||||
it "encodes to Text injectively" . property $
|
it "encodes to Text injectively" . property $
|
||||||
\p1 p2 -> p1 /= p2 ==> ((/=) `on` review _PseudonymText) p1 p2
|
\p1 p2 -> p1 /= p2 ==> ((/=) `on` review _PseudonymText) p1 p2
|
||||||
describe "SheetType" $ do
|
|
||||||
it "has compatible encoding/decoding to/from JSON" . property $
|
|
||||||
\sg -> decode (encode sg) == Just (sg :: SheetType)
|
|
||||||
|
|
||||||
termExample :: (TermIdentifier, Text) -> Expectation
|
termExample :: (TermIdentifier, Text) -> Expectation
|
||||||
termExample (term, encoded) = example $ do
|
termExample (term, encoded) = example $ do
|
||||||
|
|||||||
25
test/Test/QuickCheck/Classes/HttpApiData.hs
Normal file
25
test/Test/QuickCheck/Classes/HttpApiData.hs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
module Test.QuickCheck.Classes.HttpApiData
|
||||||
|
( httpApiDataLaws
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.QuickCheck.Classes
|
||||||
|
import Web.HttpApiData
|
||||||
|
import Data.Proxy
|
||||||
|
import Network.HTTP.Types.URI (encodePathSegmentsRelative)
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Lazy.Char8 as LCBS
|
||||||
|
import qualified Data.ByteString.Builder as BS
|
||||||
|
|
||||||
|
httpApiDataLaws :: forall a. (Arbitrary a, ToHttpApiData a, FromHttpApiData a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
httpApiDataLaws _ = Laws "ToHttpApiData/FromHttpApiData"
|
||||||
|
[ ("Partial Isomorphism (UrlPiece)", property $ \(a :: a) -> parseUrlPiece (toUrlPiece a) == Right a)
|
||||||
|
, ("Encoding Equals Value (UrlPiece)", property $ \(a :: a) -> BS.toLazyByteString (toEncodedUrlPiece a) == BS.toLazyByteString (encodePathSegmentsRelative . pure $ toUrlPiece a))
|
||||||
|
, ("Produces only valid characters (UrlPiece)", property $ \(a :: a) -> LCBS.all validUrlChar (BS.toLazyByteString $ toEncodedUrlPiece a) )
|
||||||
|
, ("Partial Isomorphism (Header)", property $ \(a :: a) -> parseHeader (toHeader a) == Right a)
|
||||||
|
, ("Partial Isomorphism (QueryParam)", property $ \(a :: a) -> parseQueryParam (toQueryParam a) == Right a)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
validUrlChar :: Char -> Bool
|
||||||
|
validUrlChar = flip elem $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "-_.~:@&=+$," ++ "%"
|
||||||
@ -1,5 +1,6 @@
|
|||||||
module Test.QuickCheck.Classes.PathPiece
|
module Test.QuickCheck.Classes.PathPiece
|
||||||
( pathPieceLaws
|
( pathPieceLaws
|
||||||
|
, pathMultiPieceLaws
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
@ -12,3 +13,8 @@ pathPieceLaws :: forall a. (Arbitrary a, PathPiece a, Eq a, Show a) => Proxy a -
|
|||||||
pathPieceLaws _ = Laws "PathPiece"
|
pathPieceLaws _ = Laws "PathPiece"
|
||||||
[ ("Partial Isomorphism", property $ \(a :: a) -> fromPathPiece (toPathPiece a) == Just a)
|
[ ("Partial Isomorphism", property $ \(a :: a) -> fromPathPiece (toPathPiece a) == Just a)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
pathMultiPieceLaws :: forall a. (Arbitrary a, PathMultiPiece a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
pathMultiPieceLaws _ = Laws "PathMultiPiece"
|
||||||
|
[ ("Partial Isomorphism", property $ \(a :: a) -> fromPathMultiPiece (toPathMultiPiece a) == Just a)
|
||||||
|
]
|
||||||
|
|||||||
24
test/Test/QuickCheck/Classes/Universe.hs
Normal file
24
test/Test/QuickCheck/Classes/Universe.hs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module Test.QuickCheck.Classes.Universe
|
||||||
|
( universeLaws
|
||||||
|
, finiteLaws
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.QuickCheck.Classes
|
||||||
|
import Data.Proxy
|
||||||
|
import Data.Universe
|
||||||
|
import Data.List (genericLength, elemIndices)
|
||||||
|
|
||||||
|
universeLaws :: forall a. (Arbitrary a, Universe a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
universeLaws _ = Laws "Universe"
|
||||||
|
[ ("universe contains everything", property $ \(a :: a) -> a `elem` universe)
|
||||||
|
]
|
||||||
|
|
||||||
|
finiteLaws :: forall a. (Arbitrary a, Finite a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
finiteLaws _ = Laws "Finite" $
|
||||||
|
[ ("universeF is small", property $ genericLength (universeF :: [a]) <= toInteger (maxBound :: Word32))
|
||||||
|
, ("universeF contains everything once", property $ \(a :: a) -> length (elemIndices a universeF) == 1)
|
||||||
|
, ("universe is permutation of universeF", property $ all (\(a :: a) -> length (elemIndices a universeF) == 1) universeF)
|
||||||
|
]
|
||||||
|
|
||||||
@ -18,11 +18,14 @@ import Test.QuickCheck as X
|
|||||||
import Test.QuickCheck.Gen as X
|
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 Test.QuickCheck.Arbitrary.Generic as X
|
||||||
import Test.QuickCheck.Classes as X
|
import Test.QuickCheck.Classes as X
|
||||||
import Test.QuickCheck.Classes.PathPiece as X
|
import Test.QuickCheck.Classes.PathPiece as X
|
||||||
import Test.QuickCheck.Classes.PersistField as X
|
import Test.QuickCheck.Classes.PersistField as X
|
||||||
import Test.QuickCheck.Classes.Hashable as X
|
import Test.QuickCheck.Classes.Hashable as X
|
||||||
import Test.QuickCheck.Classes.JSON as X
|
import Test.QuickCheck.Classes.JSON as X
|
||||||
|
import Test.QuickCheck.Classes.HttpApiData as X
|
||||||
|
import Test.QuickCheck.Classes.Universe as X
|
||||||
import Data.Proxy as X
|
import Data.Proxy as X
|
||||||
import Data.UUID as X (UUID)
|
import Data.UUID as X (UUID)
|
||||||
import System.IO as X (hPrint, hPutStrLn, stderr)
|
import System.IO as X (hPrint, hPutStrLn, stderr)
|
||||||
@ -36,9 +39,11 @@ import Data.Pool (destroyAllResources)
|
|||||||
|
|
||||||
import Settings
|
import Settings
|
||||||
|
|
||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive as X (CI)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
|
||||||
|
import Data.Typeable
|
||||||
|
|
||||||
|
|
||||||
runDB :: SqlPersistM a -> YesodExample UniWorX a
|
runDB :: SqlPersistM a -> YesodExample UniWorX a
|
||||||
runDB query = do
|
runDB query = do
|
||||||
@ -113,8 +118,8 @@ createUser userIdent = do
|
|||||||
userNotificationSettings = def
|
userNotificationSettings = def
|
||||||
runDB $ insertEntity User{..}
|
runDB $ insertEntity User{..}
|
||||||
|
|
||||||
lawsCheckHspec :: Proxy a -> [Proxy a -> Laws] -> Spec
|
lawsCheckHspec :: Typeable a => Proxy a -> [Proxy a -> Laws] -> Spec
|
||||||
lawsCheckHspec p = mapM_ (checkHspec . ($ p))
|
lawsCheckHspec p = describe (show $ typeRep p) . mapM_ (checkHspec . ($ p))
|
||||||
where
|
where
|
||||||
checkHspec (Laws className properties) = describe className $
|
checkHspec (Laws className properties) = describe className $
|
||||||
forM_ properties $ \(name, prop) -> it name $ property prop
|
forM_ properties $ \(name, prop) -> it name $ property prop
|
||||||
|
|||||||
20
test/Utils/DateTimeSpec.hs
Normal file
20
test/Utils/DateTimeSpec.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module Utils.DateTimeSpec where
|
||||||
|
|
||||||
|
import TestImport
|
||||||
|
|
||||||
|
instance Arbitrary DateTimeFormat where
|
||||||
|
arbitrary = DateTimeFormat <$> arbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
|
||||||
|
instance Arbitrary SelDateTimeFormat where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
shrink = genericShrink
|
||||||
|
instance CoArbitrary SelDateTimeFormat where
|
||||||
|
coarbitrary = genericCoarbitrary
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
lawsCheckHspec (Proxy @DateTimeFormat)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, jsonLaws, persistFieldLaws, hashableLaws ]
|
||||||
|
lawsCheckHspec (Proxy @SelDateTimeFormat)
|
||||||
|
[ eqLaws, ordLaws, showReadLaws, boundedEnumLaws, finiteLaws, hashableLaws, jsonLaws, jsonKeyLaws ]
|
||||||
@ -1,5 +1,3 @@
|
|||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
||||||
|
|
||||||
module UtilsSpec where
|
module UtilsSpec where
|
||||||
|
|
||||||
import TestImport
|
import TestImport
|
||||||
|
|||||||
Reference in New Issue
Block a user