rel8/tests/Main.hs

711 lines
24 KiB
Haskell
Raw Normal View History

2021-02-28 20:54:50 +03:00
{-# LANGUAGE DerivingStrategies #-}
2020-06-12 20:03:52 +03:00
{-# language BlockArguments #-}
{-# language DeriveAnyClass #-}
{-# language DeriveGeneric #-}
2020-06-12 20:03:52 +03:00
{-# language DisambiguateRecordFields #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language GADTs #-}
{-# language NamedFieldPuns #-}
2020-06-12 20:03:52 +03:00
{-# language OverloadedStrings #-}
{-# language QuasiQuotes #-}
2020-06-12 20:03:52 +03:00
{-# language RecordWildCards #-}
{-# language ScopedTypeVariables #-}
2020-06-12 20:03:52 +03:00
{-# language StandaloneDeriving #-}
2020-06-14 14:47:08 +03:00
{-# language TypeApplications #-}
2020-06-12 20:03:52 +03:00
2021-02-28 20:54:50 +03:00
{-# options -Weverything -Wno-prepositive-qualified-module -Wno-unsafe -Wno-missing-import-lists -Wno-missing-safe-haskell-mode -Wno-implicit-prelude #-}
module Main (main) where
2020-06-12 20:03:52 +03:00
2020-06-14 14:31:12 +03:00
import Control.Applicative ( liftA2, liftA3 )
2020-06-14 14:14:24 +03:00
import Control.Exception.Lifted ( bracket, throwIO, finally )
2020-06-13 23:03:09 +03:00
import Control.Monad.IO.Class ( MonadIO, liftIO )
import Control.Monad.Trans.Control ( MonadBaseControl, liftBaseOp_ )
2020-06-19 12:59:36 +03:00
import qualified Data.ByteString.Lazy
import Data.CaseInsensitive (mk)
2020-06-13 12:51:02 +03:00
import Data.Foldable ( for_ )
2021-02-28 20:45:19 +03:00
import Data.Functor.Identity ( Identity )
2020-06-19 12:59:36 +03:00
import Data.Function ( on )
import Data.Int ( Int32, Int64 )
2020-06-19 14:49:09 +03:00
import Data.Bifunctor ( bimap )
2020-06-13 16:26:14 +03:00
import Data.List ( nub, sort )
2020-06-19 14:49:09 +03:00
import qualified Data.Map.Strict as Map
2020-06-19 13:11:45 +03:00
import Data.Maybe ( catMaybes )
2020-06-19 12:59:36 +03:00
import Data.Scientific ( Scientific )
import Data.String ( fromString )
import qualified Data.Text.Lazy
import Data.Time
import qualified Data.UUID
2020-06-12 20:03:52 +03:00
import Database.PostgreSQL.Simple ( Connection, connectPostgreSQL, close, withTransaction, execute_, executeMany, rollback )
import Database.PostgreSQL.Simple.SqlQQ ( sql )
import qualified Database.Postgres.Temp as TmpPostgres
2020-06-13 23:03:09 +03:00
import GHC.Generics ( Generic )
2021-03-02 20:58:38 +03:00
import Hedgehog ( property, (===), forAll, cover, diff, evalM, PropertyT, TestT, test, Gen, annotate )
2020-06-12 20:03:52 +03:00
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Rel8
import Test.Tasty
import Test.Tasty.Hedgehog ( testProperty )
2021-02-28 20:54:50 +03:00
import Control.Monad (void)
import Data.Word (Word32)
2020-06-12 20:03:52 +03:00
main :: IO ()
main = defaultMain tests
tests :: TestTree
tests =
withResource startTestDatabase stopTestDatabase \getTestDatabase ->
2020-06-13 12:51:02 +03:00
testGroup "rel8"
[ testSelectTestTable getTestDatabase
, testWhere_ getTestDatabase
2020-06-19 13:30:41 +03:00
, testFilter getTestDatabase
2020-06-13 12:51:02 +03:00
, testLimit getTestDatabase
, testUnion getTestDatabase
2020-06-13 16:26:14 +03:00
, testDistinct getTestDatabase
2020-06-13 23:03:09 +03:00
, testExists getTestDatabase
2020-06-14 13:24:57 +03:00
, testOptional getTestDatabase
2020-06-14 14:27:55 +03:00
, testAnd getTestDatabase
, testOr getTestDatabase
2020-06-14 14:28:51 +03:00
, testNot getTestDatabase
2020-06-14 14:31:12 +03:00
, testIfThenElse getTestDatabase
2020-06-14 14:35:56 +03:00
, testAp getTestDatabase
2020-06-14 14:47:08 +03:00
, testDBType getTestDatabase
2020-06-14 14:57:14 +03:00
, testDBEq getTestDatabase
2020-06-18 16:07:38 +03:00
-- , TODO testTableEquality getTestDatabase
2020-06-14 15:05:59 +03:00
, testFromString getTestDatabase
2020-06-14 15:25:33 +03:00
, testCatMaybeTable getTestDatabase
2020-06-19 13:11:45 +03:00
, testCatMaybe getTestDatabase
2020-06-14 17:55:03 +03:00
, testMaybeTable getTestDatabase
2020-06-14 18:00:00 +03:00
, testNestedTables getTestDatabase
2020-06-15 11:49:39 +03:00
, testMaybeTableApplicative getTestDatabase
2020-06-19 13:23:50 +03:00
, testLogicalFixities getTestDatabase
2020-06-19 14:49:09 +03:00
, testUpdate getTestDatabase
2020-06-19 14:55:40 +03:00
, testDelete getTestDatabase
2021-02-28 18:29:18 +03:00
, testSelectNestedPairs getTestDatabase
2021-03-02 20:58:38 +03:00
, testSelectUnaggregatedArray getTestDatabase
, testSelectArray getTestDatabase
, testAggregateArrayLit getTestDatabase
2020-06-13 12:51:02 +03:00
]
2020-06-12 20:03:52 +03:00
where
startTestDatabase = do
db <- TmpPostgres.start >>= either throwIO return
2021-02-28 20:54:50 +03:00
bracket (connectPostgreSQL (TmpPostgres.toConnectionString db)) close \conn -> void do
2020-06-12 20:03:52 +03:00
execute_ conn [sql|
2020-06-19 12:59:36 +03:00
CREATE EXTENSION citext;
2020-06-12 20:03:52 +03:00
CREATE TABLE test_table ( column1 text not null, column2 bool not null );
|]
2021-02-28 20:54:50 +03:00
return db
2020-06-12 20:03:52 +03:00
stopTestDatabase = TmpPostgres.stop
2021-02-28 20:54:50 +03:00
2020-06-19 14:47:45 +03:00
databasePropertyTest
:: TestName
-> (((Connection -> TestT IO ()) -> PropertyT IO ()) -> PropertyT IO ())
-> IO TmpPostgres.DB -> TestTree
2020-06-12 20:03:52 +03:00
databasePropertyTest testName f getTestDatabase =
2020-06-13 16:20:04 +03:00
withResource connect close $ \c ->
testProperty testName $ property do
connection <- liftIO c
2020-06-19 14:47:45 +03:00
f \g -> test $ rollingBack connection $ g connection
2020-06-12 20:03:52 +03:00
where
connect = connectPostgreSQL . TmpPostgres.toConnectionString =<< getTestDatabase
data TestTable f = TestTable
{ testTableColumn1 :: Rel8.Column f String
, testTableColumn2 :: Rel8.Column f Bool
}
2021-02-28 20:54:50 +03:00
deriving stock Generic
deriving anyclass Rel8.HigherKindedTable
2020-06-12 20:03:52 +03:00
2021-02-28 20:54:50 +03:00
deriving stock instance Eq (TestTable Identity)
deriving stock instance Ord (TestTable Identity)
deriving stock instance Show (TestTable Identity)
2020-06-12 20:03:52 +03:00
testTableSchema :: Rel8.TableSchema ( TestTable Rel8.ColumnSchema )
testTableSchema =
Rel8.TableSchema
{ tableName = "test_table"
, tableSchema = Nothing
, tableColumns = TestTable
{ testTableColumn1 = "column1"
, testTableColumn2 = "column2"
}
}
testSelectTestTable :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testSelectTestTable = databasePropertyTest "Can SELECT TestTable" \transaction -> do
2020-06-13 12:51:02 +03:00
rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
2020-06-12 20:03:52 +03:00
2020-06-19 14:47:45 +03:00
transaction \connection -> do
2021-02-28 20:54:50 +03:00
void do
Rel8.insert connection
Rel8.Insert
{ into = testTableSchema
, rows = map Rel8.lit rows
, onConflict = Rel8.DoNothing
, returning = Rel8.NumberOfRowsInserted
}
2020-06-12 20:03:52 +03:00
2020-06-19 14:47:45 +03:00
selected <- Rel8.select connection do
Rel8.each testTableSchema
2020-06-12 20:03:52 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort rows
2020-06-12 20:03:52 +03:00
2020-06-19 14:47:45 +03:00
cover 1 "Empty" $ null rows
cover 1 "Singleton" $ null $ drop 1 rows
cover 1 ">1 row" $ not $ null $ drop 1 rows
2020-06-13 12:51:02 +03:00
testWhere_ :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testWhere_ = databasePropertyTest "WHERE (Rel8.where_)" \transaction -> do
2020-06-13 12:51:02 +03:00
rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable
magicBool <- forAll Gen.bool
let expected = filter (\t -> testTableColumn2 t == magicBool) rows
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- Rel8.select connection do
t <- Rel8.values $ Rel8.lit <$> rows
2020-06-19 14:47:45 +03:00
Rel8.where_ $ testTableColumn2 t Rel8.==. Rel8.lit magicBool
return t
2020-06-13 12:51:02 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort expected
2020-06-19 13:30:41 +03:00
2020-06-19 14:47:45 +03:00
cover 1 "No results" $ null expected
cover 1 "Some results" $ not $ null expected
cover 1 "All results" $ expected == rows
2020-06-19 13:30:41 +03:00
testFilter :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testFilter = databasePropertyTest "filter" \transaction -> do
2020-06-19 13:30:41 +03:00
rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
let expected = filter testTableColumn2 rows
2020-06-19 13:30:41 +03:00
2020-06-19 14:47:45 +03:00
selected <- Rel8.select connection
$ Rel8.filter testTableColumn2 =<< Rel8.values (Rel8.lit <$> rows)
2020-06-19 13:30:41 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort expected
2020-06-13 12:51:02 +03:00
2020-06-19 14:47:45 +03:00
cover 1 "No results" $ null expected
cover 1 "Some results" $ not $ null expected
cover 1 "All results" $ expected == rows
2020-06-13 12:51:02 +03:00
testLimit :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testLimit = databasePropertyTest "LIMIT (Rel8.limit)" \transaction -> do
2020-06-13 12:51:02 +03:00
rows <- forAll $ Gen.list (Range.linear 1 10) genTestTable
n <- forAll $ Gen.integral (Range.linear 0 10)
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- Rel8.select connection do
Rel8.limit n $ Rel8.values (Rel8.lit <$> rows)
2020-06-13 12:51:02 +03:00
2020-06-19 14:47:45 +03:00
diff (length selected) (<=) (fromIntegral n)
2020-06-13 12:51:02 +03:00
2020-06-19 14:47:45 +03:00
for_ selected \row ->
diff row elem rows
2020-06-13 12:51:02 +03:00
2020-06-19 14:47:45 +03:00
cover 1 "n == 0" $ n == 0
cover 1 "n < length rows" $ fromIntegral n < length rows
cover 1 "n == length rows" $ fromIntegral n == length rows
cover 1 "n >= length rows" $ fromIntegral n >= length rows
2020-06-13 12:51:02 +03:00
testUnion :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testUnion = databasePropertyTest "UNION (Rel8.union)" \transaction -> evalM do
2020-06-13 12:51:02 +03:00
left <- forAll $ Gen.list (Range.linear 0 10) genTestTable
right <- forAll $ Gen.list (Range.linear 0 10) genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- Rel8.select connection do
Rel8.values (Rel8.lit <$> nub left) `Rel8.union` Rel8.values (Rel8.lit <$> nub right)
2020-06-13 12:51:02 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort (nub (left ++ right))
2020-06-13 12:51:02 +03:00
2020-06-12 20:03:52 +03:00
2020-06-13 16:26:14 +03:00
testDistinct :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testDistinct = databasePropertyTest "DISTINCT (Rel8.distinct)" \transaction -> do
2020-06-13 16:26:14 +03:00
rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- Rel8.select connection $ Rel8.distinct do
Rel8.values (Rel8.lit <$> rows)
2020-06-13 16:26:14 +03:00
2020-06-19 14:47:45 +03:00
sort selected === nub (sort rows)
2020-06-13 16:26:14 +03:00
2020-06-19 14:47:45 +03:00
cover 1 "Empty" $ null rows
cover 1 "Duplicates" $ not (null rows) && rows /= nub rows
cover 1 "No duplicates" $ not (null rows) && rows == nub rows
2020-06-13 16:26:14 +03:00
2020-06-13 23:03:09 +03:00
testExists :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testExists = databasePropertyTest "EXISTS (Rel8.exists)" \transaction -> do
2021-03-02 22:28:43 +03:00
rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
2020-06-13 23:03:09 +03:00
2020-06-19 14:47:45 +03:00
transaction \connection -> do
2021-03-02 22:28:43 +03:00
exists <- Rel8.select connection $ Rel8.exists $ Rel8.values $ Rel8.lit <$> rows
2020-06-13 23:03:09 +03:00
2021-03-02 22:28:43 +03:00
case rows of
[] -> exists === [False]
_ -> exists === [True]
2020-06-13 23:03:09 +03:00
2020-06-14 13:24:57 +03:00
testOptional :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testOptional = databasePropertyTest "Rel8.optional" \transaction -> do
2020-06-14 13:24:57 +03:00
rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
2021-02-28 20:54:50 +03:00
void $ liftIO do
2020-06-19 14:47:45 +03:00
executeMany connection
[sql| INSERT INTO test_table (column1, column2) VALUES (?, ?) |]
[ ( testTableColumn1, testTableColumn2 ) | TestTable{..} <- rows ]
2020-06-14 14:14:20 +03:00
2020-06-19 14:47:45 +03:00
selected <- Rel8.select connection do
Rel8.optional $ Rel8.each testTableSchema
2020-06-14 13:24:57 +03:00
2020-06-19 14:47:45 +03:00
case rows of
[] -> selected === [Nothing]
_ -> sort selected === fmap Just (sort rows)
2020-06-14 13:24:57 +03:00
2020-06-14 14:27:55 +03:00
testAnd :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testAnd = databasePropertyTest "AND (&&.)" \transaction -> do
2020-06-14 14:27:55 +03:00
(x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[result] <- Rel8.select connection $ pure $
Rel8.lit x Rel8.&&. Rel8.lit y
2020-06-14 14:27:55 +03:00
2020-06-19 14:47:45 +03:00
result === (x && y)
2020-06-14 14:27:55 +03:00
testOr :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testOr = databasePropertyTest "OR (||.)" \transaction -> do
2020-06-14 14:27:55 +03:00
(x, y) <- forAll $ liftA2 (,) Gen.bool Gen.bool
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[result] <- Rel8.select connection $ pure $
Rel8.lit x Rel8.||. Rel8.lit y
2020-06-14 14:27:55 +03:00
2020-06-19 14:47:45 +03:00
result === (x || y)
2020-06-14 14:27:55 +03:00
2020-06-19 13:23:50 +03:00
testLogicalFixities :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testLogicalFixities = databasePropertyTest "Logical operator fixities" \transaction -> do
2020-06-19 13:23:50 +03:00
(u, v, w, x) <- forAll $ (,,,) <$> Gen.bool <*> Gen.bool <*> Gen.bool <*> Gen.bool
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[result] <- Rel8.select connection $ pure $
Rel8.lit u Rel8.||. Rel8.lit v Rel8.&&. Rel8.lit w Rel8.==. Rel8.lit x
2020-06-19 13:23:50 +03:00
2020-06-19 14:47:45 +03:00
result === (u || v && w == x)
2020-06-19 13:23:50 +03:00
2020-06-14 14:28:51 +03:00
testNot :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testNot = databasePropertyTest "NOT (not_)" \transaction -> do
2020-06-14 14:28:51 +03:00
x <- forAll Gen.bool
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[result] <- Rel8.select connection $ pure $
Rel8.not_ $ Rel8.lit x
2020-06-14 14:28:51 +03:00
2020-06-19 14:47:45 +03:00
result === not x
2020-06-14 14:28:51 +03:00
2020-06-14 14:35:56 +03:00
2020-06-14 14:31:12 +03:00
testIfThenElse :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testIfThenElse = databasePropertyTest "ifThenElse_" \transaction -> do
2020-06-14 14:31:12 +03:00
(x, y, z) <- forAll $ liftA3 (,,) Gen.bool Gen.bool Gen.bool
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[result] <- Rel8.select connection $ pure $
Rel8.ifThenElse_ (Rel8.lit x) (Rel8.lit y) (Rel8.lit z)
2020-06-14 14:31:12 +03:00
2020-06-19 14:47:45 +03:00
result === if x then y else z
2020-06-14 14:31:12 +03:00
2020-06-14 14:28:51 +03:00
2020-06-14 14:35:56 +03:00
testAp :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testAp = databasePropertyTest "Cartesian product (<*>)" \transaction -> do
2020-06-14 14:35:56 +03:00
(rows1, rows2) <- forAll $
liftA2 (,)
(Gen.list (Range.linear 1 10) genTestTable)
(Gen.list (Range.linear 1 10) genTestTable)
2020-06-19 14:47:45 +03:00
transaction \connection -> do
result <- Rel8.select connection $ do
liftA2 (,) (Rel8.values (Rel8.lit <$> rows1)) (Rel8.values (Rel8.lit <$> rows2))
2020-06-14 14:35:56 +03:00
2020-06-19 14:47:45 +03:00
sort result === sort (liftA2 (,) rows1 rows2)
2020-06-14 14:35:56 +03:00
2020-06-14 14:47:08 +03:00
testDBType :: IO TmpPostgres.DB -> TestTree
testDBType getTestDatabase = testGroup "DBType instances"
[ dbTypeTest "Bool" Gen.bool
2020-06-19 12:59:36 +03:00
, dbTypeTest "ByteString" $ Gen.bytes (Range.linear 0 128)
, dbTypeTest "CI Lazy Text" $ mk . Data.Text.Lazy.fromStrict <$> Gen.text (Range.linear 0 10) Gen.unicode
, dbTypeTest "CI Text" $ mk <$> Gen.text (Range.linear 0 10) Gen.unicode
, dbTypeTest "Day" genDay
2021-02-28 20:54:50 +03:00
, dbTypeTest "Double" $ (/10) . fromIntegral @Int @Double <$> Gen.integral (Range.linear (-100) 100)
, dbTypeTest "Float" $ (/10) . fromIntegral @Int @Float <$> Gen.integral (Range.linear (-100) 100)
2020-06-14 14:47:08 +03:00
, dbTypeTest "Int32" $ Gen.integral @_ @Int32 Range.linearBounded
, dbTypeTest "Int64" $ Gen.integral @_ @Int64 Range.linearBounded
2020-06-19 12:59:36 +03:00
, dbTypeTest "Lazy ByteString" $ Data.ByteString.Lazy.fromStrict <$> Gen.bytes (Range.linear 0 128)
, dbTypeTest "Lazy Text" $ Data.Text.Lazy.fromStrict <$> Gen.text (Range.linear 0 10) Gen.unicode
, dbTypeTest "LocalTime" genLocalTime
2021-02-28 20:54:50 +03:00
, dbTypeTest "Scientific" $ (/10) . fromIntegral @Int @Scientific <$> Gen.integral (Range.linear (-100) 100)
2020-06-14 14:47:08 +03:00
, dbTypeTest "String" $ Gen.list (Range.linear 0 10) Gen.unicode
2020-06-19 12:59:36 +03:00
, dbTypeTest "Text" $ Gen.text (Range.linear 0 10) Gen.unicode
, dbTypeTest "TimeOfDay" genTimeOfDay
, dbTypeTest "UTCTime" $ UTCTime <$> genDay <*> genDiffTime
, dbTypeTest "UUID" $ Data.UUID.fromWords <$> genWord32 <*> genWord32 <*> genWord32 <*> genWord32
, dbTypeTestEq ((==) `on` zonedTimeToUTC) "ZonedTime" $ ZonedTime <$> genLocalTime <*> genTimeZone
2020-06-14 14:47:08 +03:00
]
where
2021-02-28 20:45:19 +03:00
dbTypeTest :: (Eq a, Rel8.DBType a, Show a) => TestName -> Gen a -> TestTree
2020-06-14 14:57:14 +03:00
dbTypeTest name generator = testGroup name
2020-06-19 12:59:36 +03:00
[ databasePropertyTest name (t (==) generator) getTestDatabase
, databasePropertyTest ("Maybe " <> name) (t (==) (Gen.maybe generator)) getTestDatabase
]
dbTypeTestEq f name generator = testGroup name
[ databasePropertyTest name (t f generator) getTestDatabase
, databasePropertyTest ("Maybe " <> name) (t (maybeEq f) (Gen.maybe generator)) getTestDatabase
2020-06-14 14:57:14 +03:00
]
2020-06-19 12:59:36 +03:00
2021-02-28 20:54:50 +03:00
maybeEq :: (x -> y -> Bool) -> Maybe x -> Maybe y -> Bool
maybeEq _ Nothing Nothing = True
maybeEq _ Just{} Nothing = False
maybeEq _ Nothing Just{} = False
2020-06-19 12:59:36 +03:00
maybeEq f (Just x) (Just y) = f x y
2021-02-28 20:45:19 +03:00
t :: (Rel8.DBType a, Show a) => (a -> a -> Bool) -> Gen a -> ((Connection -> TestT IO ()) -> PropertyT IO b) -> PropertyT IO b
2020-06-19 14:47:45 +03:00
t eq generator transaction = do
2020-06-14 14:57:14 +03:00
x <- forAll generator
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[res] <- Rel8.select connection $ pure $ Rel8.lit x
diff res eq x
2020-06-19 12:59:36 +03:00
2021-02-28 20:54:50 +03:00
genDay :: Gen Day
2020-06-19 12:59:36 +03:00
genDay = do
year <- Gen.integral (Range.linear 1970 3000)
month <- Gen.integral (Range.linear 1 12)
day <- Gen.integral (Range.linear 1 31)
Gen.just $ pure $ fromGregorianValid year month day
2021-02-28 20:54:50 +03:00
genDiffTime :: Gen DiffTime
2020-06-19 12:59:36 +03:00
genDiffTime = secondsToDiffTime <$> Gen.integral (Range.linear 0 86401)
2021-02-28 20:54:50 +03:00
genTimeOfDay :: Gen TimeOfDay
2020-06-19 12:59:36 +03:00
genTimeOfDay = do
hour <- Gen.integral (Range.linear 0 23)
2021-02-28 20:54:50 +03:00
minute <- Gen.integral (Range.linear 0 59)
sec <- fromIntegral @Int <$> Gen.integral (Range.linear 0 59)
Gen.just $ pure $ makeTimeOfDayValid hour minute sec
2020-06-19 12:59:36 +03:00
genLocalTime = LocalTime <$> genDay <*> genTimeOfDay
2021-02-28 20:54:50 +03:00
genTimeZone :: Gen TimeZone
2020-06-19 12:59:36 +03:00
genTimeZone = hoursToTimeZone <$> Gen.integral (Range.linear (-6) 6)
2021-02-28 20:54:50 +03:00
genWord32 :: Gen Word32
2020-06-19 12:59:36 +03:00
genWord32 = Gen.integral Range.linearBounded
2020-06-14 14:57:14 +03:00
testDBEq :: IO TmpPostgres.DB -> TestTree
testDBEq getTestDatabase = testGroup "DBEq instances"
[ dbEqTest "Bool" Gen.bool
, dbEqTest "Int32" $ Gen.integral @_ @Int32 Range.linearBounded
, dbEqTest "Int64" $ Gen.integral @_ @Int64 Range.linearBounded
, dbEqTest "Text" $ Gen.text (Range.linear 0 10) Gen.unicode
, dbEqTest "String" $ Gen.list (Range.linear 0 10) Gen.unicode
]
where
2021-02-28 20:45:19 +03:00
dbEqTest :: (Eq a, Show a, Rel8.DBEq a) => TestName -> Gen a -> TestTree
2020-06-14 14:57:14 +03:00
dbEqTest name generator = testGroup name
[ databasePropertyTest name (t generator) getTestDatabase
, databasePropertyTest ("Maybe " <> name) (t (Gen.maybe generator)) getTestDatabase
]
2021-02-28 20:45:19 +03:00
t :: (Eq a, Show a, Rel8.DBEq a) => Gen a -> ((Connection -> TestT IO ()) -> PropertyT IO ()) -> PropertyT IO ()
2020-06-19 14:47:45 +03:00
t generator transaction = do
2020-06-14 14:57:14 +03:00
(x, y) <- forAll (liftA2 (,) generator generator)
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[res] <- Rel8.select connection $ pure $ Rel8.lit x Rel8.==. Rel8.lit y
res === (x == y)
cover 1 "Equal" $ x == y
cover 1 "Not Equal" $ x /= y
2020-06-14 14:47:08 +03:00
2020-06-18 16:07:38 +03:00
-- testTableEquality :: IO TmpPostgres.DB -> TestTree
-- testTableEquality = databasePropertyTest "TestTable equality" \transaction -> do
2020-06-18 16:07:38 +03:00
-- (x, y) <- forAll $ liftA2 (,) genTestTable genTestTable
2020-06-14 15:05:48 +03:00
-- transaction \connection -> do
-- [eq] <- Rel8.select connection do
-- pure $ Rel8.lit x Rel8.==. Rel8.lit y
2020-06-14 15:05:48 +03:00
-- eq === (x == y)
2020-06-14 15:05:48 +03:00
-- cover 1 "Equal" $ x == y
-- cover 1 "Not Equal" $ x /= y
2020-06-14 15:05:48 +03:00
2020-06-14 15:05:59 +03:00
testFromString :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testFromString = databasePropertyTest "FromString" \transaction -> do
2020-06-14 15:05:59 +03:00
str <- forAll $ Gen.list (Range.linear 0 10) Gen.unicode
2020-06-19 14:47:45 +03:00
transaction \connection -> do
[result] <- Rel8.select connection $ pure $ fromString str
result === str
2020-06-14 15:05:59 +03:00
2020-06-14 14:47:08 +03:00
2020-06-14 15:25:33 +03:00
testCatMaybeTable :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testCatMaybeTable = databasePropertyTest "catMaybeTable" \transaction -> do
2020-06-14 15:25:33 +03:00
rows <- forAll $ Gen.list (Range.linear 0 10) genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- Rel8.select connection do
testTable <- Rel8.values $ Rel8.lit <$> rows
2020-06-19 14:47:45 +03:00
Rel8.catMaybeTable $ Rel8.ifThenElse_ (testTableColumn2 testTable) (pure testTable) Rel8.noTable
2020-06-14 15:25:33 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort (filter testTableColumn2 rows)
2020-06-14 15:25:33 +03:00
2020-06-19 13:11:45 +03:00
testCatMaybe :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testCatMaybe = databasePropertyTest "catMaybe" \transaction -> evalM do
2020-06-19 13:11:45 +03:00
rows <- forAll $ Gen.list (Range.linear 0 10) $ Gen.maybe Gen.bool
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- evalM $ Rel8.select connection do
Rel8.catMaybe =<< Rel8.values (map Rel8.lit rows)
2020-06-19 13:11:45 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort (catMaybes rows)
2020-06-19 13:11:45 +03:00
2020-06-14 17:55:03 +03:00
testMaybeTable :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testMaybeTable = databasePropertyTest "maybeTable" \transaction -> evalM do
2020-06-14 17:55:03 +03:00
(rows, def) <- forAll $ liftA2 (,) (Gen.list (Range.linear 0 10) genTestTable) genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
2021-02-28 20:54:50 +03:00
void $ liftIO $ executeMany connection
2020-06-19 14:47:45 +03:00
[sql| INSERT INTO test_table (column1, column2) VALUES (?, ?) |]
[ ( testTableColumn1, testTableColumn2 ) | TestTable{..} <- rows ]
2020-06-14 17:55:03 +03:00
2020-06-19 14:47:45 +03:00
selected <- Rel8.select connection $
Rel8.maybeTable (Rel8.lit def) id <$> Rel8.optional (Rel8.each testTableSchema)
2020-06-14 17:55:03 +03:00
2020-06-19 14:47:45 +03:00
case rows of
[] -> selected === [def]
_ -> sort selected === sort rows
2020-06-14 17:55:03 +03:00
2020-06-14 18:00:00 +03:00
data TwoTestTables f =
TwoTestTables
{ testTable1 :: TestTable f
, testTable2 :: TestTable f
}
2021-02-28 20:54:50 +03:00
deriving stock Generic
deriving anyclass Rel8.HigherKindedTable
2020-06-14 18:00:00 +03:00
2021-02-28 20:54:50 +03:00
deriving stock instance Eq (TwoTestTables Identity)
deriving stock instance Ord (TwoTestTables Identity)
deriving stock instance Show (TwoTestTables Identity)
2020-06-14 18:00:00 +03:00
testNestedTables :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testNestedTables = databasePropertyTest "Nested TestTables" \transaction -> evalM do
2020-06-14 18:00:00 +03:00
rows <- forAll do
Gen.list (Range.linear 0 10) $
liftA2 TwoTestTables genTestTable genTestTable
2020-06-19 14:47:45 +03:00
transaction \connection -> do
selected <- Rel8.select connection do
Rel8.values (Rel8.lit <$> rows)
2020-06-14 18:00:00 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort rows
2020-06-14 18:00:00 +03:00
2020-06-15 11:49:39 +03:00
testMaybeTableApplicative :: IO TmpPostgres.DB -> TestTree
2020-06-19 14:47:45 +03:00
testMaybeTableApplicative = databasePropertyTest "MaybeTable (<*>)" \transaction -> evalM do
2020-06-15 11:49:39 +03:00
rows <- forAll do
2021-02-28 20:45:19 +03:00
-- TODO: We shouldn't need the @Identity type application, but without
-- it this fails to type check.
2021-02-28 20:45:19 +03:00
Gen.list (Range.linear 0 10) $ liftA2 (TestTable @Identity) (Gen.list (Range.linear 0 10) Gen.unicode) (pure True)
2020-06-15 11:49:39 +03:00
2020-06-19 14:47:45 +03:00
transaction \connection -> do
2021-02-28 20:54:50 +03:00
void $ liftIO $ executeMany connection
2020-06-19 14:47:45 +03:00
[sql| INSERT INTO test_table (column1, column2) VALUES (?, ?) |]
[ ( testTableColumn1, testTableColumn2 ) | TestTable{..} <- rows ]
2020-06-15 11:49:39 +03:00
2020-06-19 14:47:45 +03:00
selected <- Rel8.select connection do
fmap (pure id <*>) (Rel8.optional (Rel8.each testTableSchema))
2020-06-15 11:49:39 +03:00
2020-06-19 14:47:45 +03:00
let rowsExpected = case rows of
[] -> [Nothing]
xs -> map Just xs
2020-06-15 11:49:39 +03:00
2020-06-19 14:47:45 +03:00
sort selected === sort rowsExpected
2020-06-15 11:49:39 +03:00
2020-06-12 20:03:52 +03:00
rollingBack
:: (MonadBaseControl IO m, MonadIO m)
=> Connection -> m a -> m a
rollingBack connection m =
2020-06-14 14:14:24 +03:00
liftBaseOp_ (withTransaction connection) do
m `finally` liftIO (rollback connection)
2020-06-13 12:51:02 +03:00
2021-02-28 20:54:50 +03:00
genTestTable :: Gen (TestTable Identity)
2020-06-13 12:51:02 +03:00
genTestTable = do
2020-06-13 16:26:14 +03:00
testTableColumn1 <- Gen.list (Range.linear 0 5) Gen.alphaNum
2020-06-13 12:51:02 +03:00
testTableColumn2 <- Gen.bool
return TestTable{..}
2020-06-19 14:49:09 +03:00
testUpdate :: IO TmpPostgres.DB -> TestTree
testUpdate = databasePropertyTest "Can UPDATE TestTable" \transaction -> do
rows <- forAll $ Gen.map (Range.linear 0 5) $ liftA2 (,) genTestTable genTestTable
transaction \connection -> do
2021-02-28 20:54:50 +03:00
void $Rel8.insert connection
2020-06-19 14:49:09 +03:00
Rel8.Insert
{ into = testTableSchema
, rows = map Rel8.lit $ Map.keys rows
2020-06-19 14:49:09 +03:00
, onConflict = Rel8.DoNothing
, returning = Rel8.NumberOfRowsInserted
}
2021-02-28 20:54:50 +03:00
void $ Rel8.update connection
2020-06-19 14:49:09 +03:00
Rel8.Update
{ target = testTableSchema
, set = \r ->
let updates = map (bimap Rel8.lit Rel8.lit) $ Map.toList rows
2020-06-19 14:49:09 +03:00
in
foldl
( \e (x, y) ->
Rel8.ifThenElse_
( testTableColumn1 r Rel8.==. testTableColumn1 x Rel8.&&.
testTableColumn2 r Rel8.==. testTableColumn2 x
)
y
e
)
r
updates
, updateWhere = \_ -> Rel8.lit True
, returning = Rel8.NumberOfRowsInserted
}
selected <- Rel8.select connection do
Rel8.each testTableSchema
sort selected === sort (Map.elems rows)
cover 1 "Empty" $ null rows
cover 1 "Singleton" $ null $ drop 1 $ Map.keys rows
cover 1 ">1 row" $ not $ null $ drop 1 $ Map.keys rows
2020-06-19 14:55:40 +03:00
testDelete :: IO TmpPostgres.DB -> TestTree
testDelete = databasePropertyTest "Can DELETE TestTable" \transaction -> do
rows <- forAll $ Gen.list (Range.linear 0 5) genTestTable
transaction \connection -> do
2021-02-28 20:54:50 +03:00
void $ Rel8.insert connection
2020-06-19 14:55:40 +03:00
Rel8.Insert
{ into = testTableSchema
, rows = map Rel8.lit rows
2020-06-19 14:55:40 +03:00
, onConflict = Rel8.DoNothing
, returning = Rel8.NumberOfRowsInserted
}
deleted <-
Rel8.delete connection
Rel8.Delete
{ from = testTableSchema
, deleteWhere = testTableColumn2
, returning = Rel8.Projection id
}
selected <- Rel8.select connection do
Rel8.each testTableSchema
sort (deleted <> selected) === sort rows
2021-02-28 20:54:50 +03:00
newtype HKNestedPair f = HKNestedPair { pairOne :: (TestTable f, TestTable f) }
deriving stock Generic
deriving anyclass Rel8.HigherKindedTable
2021-02-28 18:29:18 +03:00
2021-02-28 20:54:50 +03:00
deriving stock instance Eq (HKNestedPair Identity)
deriving stock instance Ord (HKNestedPair Identity)
deriving stock instance Show (HKNestedPair Identity)
2021-02-28 18:29:18 +03:00
testSelectNestedPairs :: IO TmpPostgres.DB -> TestTree
testSelectNestedPairs = databasePropertyTest "Can SELECT nested pairs" \transaction -> do
rows <- forAll $ Gen.list (Range.linear 0 10) $ HKNestedPair <$> liftA2 (,) genTestTable genTestTable
transaction \connection -> do
selected <- Rel8.select connection do
Rel8.values $ map Rel8.lit rows
sort selected === sort rows
2021-03-02 20:58:38 +03:00
testSelectUnaggregatedArray :: IO TmpPostgres.DB -> TestTree
testSelectUnaggregatedArray = databasePropertyTest "Can SELECT Arrays (without aggregation)" \transaction -> do
rows <- forAll $ Gen.list (Range.linear 0 10) Gen.bool
transaction \connection -> do
selected <- Rel8.select connection do
Rel8.arrayAgg <$> Rel8.values (map Rel8.lit rows)
sort selected === sort (pure <$> rows)
testSelectArray :: IO TmpPostgres.DB -> TestTree
testSelectArray = databasePropertyTest "Can SELECT Arrays (with aggregation)" \transaction -> do
rows <- forAll $ Gen.list (Range.linear 0 10) Gen.bool
transaction \connection -> do
selected <- Rel8.select connection $ Rel8.aggregate do
Rel8.arrayAgg <$> Rel8.values (map Rel8.lit rows)
selected === [foldMap pure rows]
testAggregateArrayLit :: IO TmpPostgres.DB -> TestTree
testAggregateArrayLit = databasePropertyTest "Can use aggregate with a literal array" \transaction -> evalM do
rows <- forAll $ Gen.list (Range.linear 0 10) Gen.bool
annotate $ Rel8.showQuery $ Rel8.aggregate $ pure (Rel8.lit (foldMap pure rows) :: Rel8.Array (Rel8.Expr Bool))
transaction \connection -> do
selected <- Rel8.select connection $ Rel8.aggregate $ pure (Rel8.lit (foldMap pure rows) :: Rel8.Array (Rel8.Expr Bool))
selected === [foldMap pure rows]