{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Property (testProperties) where import Control.Applicative (liftA2) import Data.Either (isRight) import Database.Persist.Migration import Test.QuickCheck import Test.Tasty (TestTree, testGroup) import Test.Tasty.QuickCheck (testProperty) import Utils.QuickCheck (CreateTable'(..), Identifier(..), mapSome, toOperation) -- | Run tests related to migration validaton. testProperties :: TestTree testProperties = testGroup "properties" [ testProperty "Valid migration" $ forAll arbitrary isValidOperationPaths , testProperty "Invalid decreasing migration" $ forAll arbitrary $ \(OperationPaths opPaths) -> do opPaths' <- mapSome (\(a, b) -> (b, a)) opPaths return . not . isValidOperationPaths $ OperationPaths opPaths' , testProperty "Invalid duplicate operation path" $ forAll arbitrary $ \(OperationPaths opPaths) -> do opPaths' <- mapSomeDupl opPaths return . not . isValidOperationPaths $ OperationPaths opPaths' , testProperty "Valid CreateTable" $ forAll arbitrary $ isValidOperation . toOperation , testProperty "Duplicate ColumnProps in CreateTable" $ do colsMaybeProps <- listOf arbitrary colsWithProps <- listOf1 (arbitrary `suchThat` (not . null . colProps)) let duplProps = mapM $ \col@Column{colProps} -> do colProps' <- mapSomeDupl colProps return col{colProps = colProps'} cols <- liftA2 (++) (duplProps colsMaybeProps) (duplProps colsWithProps) let ct = CreateTable "foo" cols [] return . not . isValidOperation $ ct , testProperty "Duplicate Constraints in CreateTable" $ forAll arbitrary $ \ct@CreateTable'{..} -> do constraints <- mapSomeDupl ctConstraints return . not . isValidOperation . toOperation $ ct{ctConstraints = constraints} , testProperty "Constraint references non-existent column" $ forAll arbitrary $ \ct@CreateTable'{..} -> do let existing = map (Identifier . colName) ctSchema genConstraint = do Identifier name' <- arbitrary cols <- map unIdent <$> listOf1 (arbitrary `suchThat` (`notElem` existing)) elements [PrimaryKey cols, Unique name' cols] newConstraints <- listOf1 genConstraint return . not . isValidOperation . toOperation $ ct{ctConstraints = ctConstraints ++ newConstraints} , testProperty "Duplicate ColumnProps in AddColumn" $ forAll arbitrary $ \col@Column{colProps} -> do Identifier table <- arbitrary colProps' <- mapSomeDupl colProps let column = col{colProps = colProps'} colDefault = Nothing return $ not (null colProps) ==> not (isValidOperation AddColumn{..}) , testProperty "Non-null AddColumn without default" $ forAll arbitrary $ \col@Column{colProps} -> do Identifier table <- arbitrary let colProps' = if NotNull `elem` colProps then colProps else NotNull : colProps column = col{colProps = colProps'} colDefault = Nothing return . not . isValidOperation $ AddColumn{..} ] newtype OperationPaths = OperationPaths { getOpPaths :: [OperationPath] } deriving (Show) instance Arbitrary OperationPaths where arbitrary = do Positive maxVersion <- arbitrary let versions = [0..maxVersion] opPaths = zip versions $ tail versions -- order should not matter OperationPaths <$> shuffle opPaths -- | Validate an Operation. isValidOperation :: Operation -> Bool isValidOperation = isRight . validateOperation -- | Validate OperationPaths in a Migration. isValidOperationPaths :: OperationPaths -> Bool isValidOperationPaths = isRight . validateMigration . map mkOperation . getOpPaths where mkOperation path = path := [] -- | Duplicate some elements in the given list. mapSomeDupl :: [a] -> Gen [a] mapSomeDupl = fmap concat . mapSome dupl . map pure where dupl [x] = [x, x] dupl _ = error "unreachable"