orville-postgresql-1.0.0.0: test/Test/TableDefinition.hs
module Test.TableDefinition
( tableDefinitionTests
)
where
import qualified Control.Exception as E
import qualified Control.Monad.IO.Class as MIO
import qualified Data.ByteString.Char8 as B8
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.Set as Set
import qualified Data.Text as T
import Hedgehog ((===))
import qualified Hedgehog as HH
import qualified Orville.PostgreSQL as Orville
import qualified Orville.PostgreSQL.Execution.ReturningOption as ReturningOption
import qualified Orville.PostgreSQL.Execution.Select as Select
import qualified Orville.PostgreSQL.Raw.Connection as Conn
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
import qualified Orville.PostgreSQL.Schema.ConstraintDefinition as ConstraintDefinition
import qualified Orville.PostgreSQL.Schema.TableDefinition as TableDefinition
import qualified Test.Entities.Bar as Bar
import qualified Test.Entities.Foo as Foo
import qualified Test.Property as Property
import qualified Test.TestTable as TestTable
tableDefinitionTests :: Orville.ConnectionPool -> Property.Group
tableDefinitionTests pool =
Property.group
"TableDefinition"
[ prop_roundTrip pool
, prop_readOnlyFields pool
, prop_primaryKey pool
, prop_uniqueConstraint pool
, prop_fieldConstraints
]
prop_roundTrip :: Property.NamedDBProperty
prop_roundTrip =
Property.namedDBProperty "Creates a table that can round trip an entity through it" $ \pool -> do
originalFoo <- HH.forAll Foo.generate
let
insertFoo =
TableDefinition.mkInsertExpr
ReturningOption.WithoutReturning
Foo.table
(originalFoo :| [])
selectFoos =
Select.selectTable Foo.table mempty
foosFromDB <-
MIO.liftIO . Orville.runOrville pool $ do
Orville.withConnection $ \connection -> do
MIO.liftIO $ TestTable.dropAndRecreateTableDef connection Foo.table
Orville.executeVoid Orville.InsertQuery insertFoo
Select.executeSelect selectFoos
foosFromDB === [originalFoo]
prop_readOnlyFields :: Property.NamedDBProperty
prop_readOnlyFields =
Property.namedDBProperty "Creates a table that can read from read only fields" $ \pool -> do
originalBar <- HH.forAll Bar.generate
let
insertBar =
TableDefinition.mkInsertExpr ReturningOption.WithoutReturning Bar.table (originalBar :| [])
selectBars =
Select.selectTable Bar.table mempty
barsFromDB <-
MIO.liftIO . Orville.runOrville pool $ do
Orville.withConnection $ \connection -> do
MIO.liftIO $ TestTable.dropAndRecreateTableDef connection Bar.table
Orville.executeVoid Orville.InsertQuery insertBar
Select.executeSelect selectBars
fmap Bar.barName barsFromDB === [Bar.barName originalBar]
prop_primaryKey :: Property.NamedDBProperty
prop_primaryKey =
Property.singletonNamedDBProperty "Creates a primary key that rejects duplicate records" $ \pool -> do
originalFoo <- HH.forAll Foo.generate
let
conflictingFoo =
originalFoo {Foo.fooName = T.reverse $ Foo.fooName originalFoo}
insertFoos =
TableDefinition.mkInsertExpr
ReturningOption.WithoutReturning
Foo.table
(originalFoo :| [conflictingFoo])
result <- MIO.liftIO . E.try . Conn.withPoolConnection pool $ \connection -> do
TestTable.dropAndRecreateTableDef connection Foo.table
RawSql.executeVoid connection insertFoos
case result of
Right () -> do
HH.footnote "Expected 'executeVoid' to return failure, but it did not"
HH.failure
Left err ->
Conn.sqlExecutionErrorSqlState err === Just (B8.pack "23505")
prop_uniqueConstraint :: Property.NamedDBProperty
prop_uniqueConstraint =
Property.singletonNamedDBProperty "Creates a unique constraint that rejects duplicate records" $ \pool -> do
originalFoo <- HH.forAll Foo.generate
let
fooTableWithUniqueNameConstraint =
Orville.addTableConstraints
[Orville.uniqueConstraint (Orville.fieldName Foo.fooNameField :| [])]
Foo.table
conflictingFoo =
originalFoo {Foo.fooId = 1 + Foo.fooId originalFoo}
insertFoos =
TableDefinition.mkInsertExpr
ReturningOption.WithoutReturning
Foo.table
(originalFoo :| [conflictingFoo])
result <- MIO.liftIO . E.try . Conn.withPoolConnection pool $ \connection -> do
TestTable.dropAndRecreateTableDef connection fooTableWithUniqueNameConstraint
RawSql.executeVoid connection insertFoos
case result of
Right () -> do
HH.footnote "Expected 'executeVoid' to return failure, but it did not"
HH.failure
Left err ->
Conn.sqlExecutionErrorSqlState err === Just (B8.pack "23505")
prop_fieldConstraints :: Property.NamedProperty
prop_fieldConstraints =
Property.singletonNamedProperty "Includes field constraints in table constraints" $ do
let
foreignTableId =
Orville.unqualifiedNameToTableId "foreign_table"
foreignFieldName =
Orville.stringToFieldName "foreign_field"
fieldWithoutConstraints =
Orville.integerField "foo"
fieldWithConstraints =
Orville.addForeignKeyConstraint foreignTableId foreignFieldName
. Orville.addUniqueConstraint
$ fieldWithoutConstraints
tableWithFieldConstraints =
Orville.mkTableDefinitionWithoutKey
"test_table"
(Orville.marshallField id fieldWithConstraints)
fieldName =
Orville.fieldName fieldWithoutConstraints
tableWithTableConstraints =
Orville.addTableConstraints
[ Orville.foreignKeyConstraint
foreignTableId
(Orville.foreignReference fieldName foreignFieldName :| [])
, Orville.uniqueConstraint (fieldName :| [])
]
$ Orville.mkTableDefinitionWithoutKey
"test_table"
(Orville.marshallField id fieldWithoutConstraints)
tableConstraintKeys ::
Orville.TableDefinition hasKey writeEntity readEntity ->
Set.Set Orville.ConstraintMigrationKey
tableConstraintKeys =
ConstraintDefinition.tableConstraintKeys . Orville.tableConstraints
tableConstraintKeys tableWithFieldConstraints === tableConstraintKeys tableWithTableConstraints