Migrate tests to purescript-test-unit + Instant fetching + test

This commit is contained in:
Tomasz Rybarczyk 2018-04-22 16:47:07 +02:00
parent 45eb1acb91
commit bb493e5983
5 changed files with 146 additions and 65 deletions

View File

@ -2,10 +2,18 @@
purescript-postgresql-client is a PostgreSQL client library for PureScript. purescript-postgresql-client is a PostgreSQL client library for PureScript.
## Install
To use this library, you need to add `pg` and `decimal.js` as an npm dependency. You can also To use this library, you need to add `pg` and `decimal.js` as an npm dependency. You can also
find this npm library on [https://github.com/brianc/node-postgres][pg]. find this npm library on [https://github.com/brianc/node-postgres][pg].
## Preprocessing static SQL statements
The purspgpp preprocessor has been replaced by [sqltopurs], which is a code The purspgpp preprocessor has been replaced by [sqltopurs], which is a code
generator instead of a preprocessor, and easier to use. generator instead of a preprocessor, and easier to use.
[sqltopurs]: https://github.com/rightfold/sqltopurs [sqltopurs]: https://github.com/rightfold/sqltopurs
## Testing
To run test you have to prepare "purspg" database and use standard command: `pulp test`

View File

@ -17,7 +17,8 @@
"purescript-bifunctors": "^3.0.0", "purescript-bifunctors": "^3.0.0",
"purescript-eff": "^3.1.0", "purescript-eff": "^3.1.0",
"purescript-exceptions": "^3.0.0", "purescript-exceptions": "^3.0.0",
"purescript-decimals": "^3.4.0" "purescript-decimals": "^3.4.0",
"purescript-js-date": "^5.2.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -25,6 +26,8 @@
}, },
"devDependencies": { "devDependencies": {
"purescript-assert": "^3.0.0", "purescript-assert": "^3.0.0",
"purescript-eff": "^3.1.0" "purescript-eff": "^3.1.0",
"purescript-debug": "^3.0.0",
"purescript-test-unit": "^13.0.0"
} }
} }

View File

@ -6,6 +6,18 @@ exports.instantToString = function(i) {
return new Date(i).toUTCString(); return new Date(i).toUTCString();
}; };
exports.instantFromString = function(Left) {
return function(Right) {
return function(s) {
try {
return Right(Date.parse(s));
} catch(e) {
return Left("Date string parsing failed: \"" + s + "\", with: " + e);
}
};
};
};
exports.unsafeIsBuffer = function(x) { exports.unsafeIsBuffer = function(x) {
return x instanceof Buffer; return x instanceof Buffer;
}; };

View File

@ -8,14 +8,15 @@ import Control.Monad.Except (runExcept)
import Data.Array as Array import Data.Array as Array
import Data.Bifunctor (lmap) import Data.Bifunctor (lmap)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.DateTime.Instant (Instant) import Data.DateTime.Instant (Instant, instant)
import Data.Decimal (Decimal) import Data.Decimal (Decimal)
import Data.Decimal as Decimal import Data.Decimal as Decimal
import Data.Either (Either, note) import Data.Either (Either(..), note)
import Data.Foreign (Foreign, isNull, readArray, readBoolean, readChar, readInt, readNumber, readString, toForeign, unsafeFromForeign) import Data.Foreign (Foreign, isNull, readArray, readBoolean, readChar, readInt, readNumber, readString, toForeign, unsafeFromForeign)
import Data.List (List) import Data.List (List)
import Data.List as List import Data.List as List
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Time.Duration (Milliseconds(..))
import Data.Traversable (traverse) import Data.Traversable (traverse)
-- | Convert things to SQL values. -- | Convert things to SQL values.
@ -79,6 +80,11 @@ instance fromSQLValueByteString :: FromSQLValue ByteString where
instance toSQLValueInstant :: ToSQLValue Instant where instance toSQLValueInstant :: ToSQLValue Instant where
toSQLValue = instantToString toSQLValue = instantToString
instance fromSQLValueInstant :: FromSQLValue Instant where
fromSQLValue v = do
t instantFromString Left Right v
note ("Instant construction failed for given timestamp: " <> show t) $ instant (Milliseconds t)
instance toSQLValueMaybe :: (ToSQLValue a) => ToSQLValue (Maybe a) where instance toSQLValueMaybe :: (ToSQLValue a) => ToSQLValue (Maybe a) where
toSQLValue Nothing = null toSQLValue Nothing = null
toSQLValue (Just x) = toSQLValue x toSQLValue (Just x) = toSQLValue x
@ -103,4 +109,5 @@ instance fromSQLValueDecimal :: FromSQLValue Decimal where
foreign import null :: Foreign foreign import null :: Foreign
foreign import instantToString :: Instant -> Foreign foreign import instantToString :: Instant -> Foreign
foreign import instantFromString :: (String Either String Number) (Number Either String Number) Foreign Either String Number
foreign import unsafeIsBuffer :: a. a -> Boolean foreign import unsafeIsBuffer :: a. a -> Boolean

View File

@ -4,17 +4,53 @@ module Test.Main
import Prelude import Prelude
import Control.Monad.Aff (launchAff) import Control.Monad.Aff (Aff, launchAff)
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Aff.Console (CONSOLE)
import Control.Monad.Eff (Eff) import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class (liftEff) import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Exception (EXCEPTION, error) import Control.Monad.Eff.Exception (EXCEPTION, error)
import Control.Monad.Error.Class (throwError, try) import Control.Monad.Eff.Now (NOW)
import Control.Monad.Error.Class (catchError, throwError, try)
import Data.DateTime.Instant (Instant, unInstant)
import Data.Decimal as D import Data.Decimal as D
import Data.Maybe (Maybe(..)) import Data.Foldable (all)
import Database.PostgreSQL (POSTGRESQL, PoolConfiguration, Query(Query), Row0(Row0), Row1(Row1), Row2(Row2), Row3(Row3), Row9(Row9), execute, newPool, query, scalar, withConnection, withTransaction) import Data.JSDate (toInstant)
import Data.JSDate as JSDate
import Data.Maybe (Maybe(..), fromJust)
import Data.Newtype (unwrap)
import Database.PostgreSQL (Connection, POSTGRESQL, PoolConfiguration, Query(Query), Row0(Row0), Row1(Row1), Row2(Row2), Row3(Row3), Row9(Row9), execute, newPool, query, scalar, withConnection, withTransaction)
import Math ((%))
import Partial.Unsafe (unsafePartial)
import Test.Assert (ASSERT, assert) import Test.Assert (ASSERT, assert)
import Test.Unit (suite)
import Test.Unit as Test.Unit
import Test.Unit.Console (TESTOUTPUT)
import Test.Unit.Main (runTest)
main :: eff. Eff (assert :: ASSERT, exception :: EXCEPTION, postgreSQL :: POSTGRESQL | eff) Unit withRollback
:: eff
. Connection
-> Aff (postgreSQL :: POSTGRESQL | eff) Unit
-> Aff (postgreSQL :: POSTGRESQL | eff) Unit
withRollback conn action = do
execute conn (Query "BEGIN TRANSACTION") Row0
catchError (action >>= const rollback) (\e -> rollback >>= const (throwError e))
where
rollback = execute conn (Query "ROLLBACK") Row0
test
:: eff
. Connection
-> String
-> Aff ( postgreSQL :: POSTGRESQL | eff) Unit
-> Test.Unit.TestSuite (postgreSQL :: POSTGRESQL | eff)
test conn t a = Test.Unit.test t (withRollback conn a)
now :: eff. Eff (now :: NOW | eff) Instant
now = unsafePartial $ (fromJust <<< toInstant) <$> JSDate.now
main :: eff. Eff (assert :: ASSERT, avar :: AVAR, console :: CONSOLE, exception :: EXCEPTION, now :: NOW, postgreSQL :: POSTGRESQL, testOutput :: TESTOUTPUT | eff) Unit
main = void $ launchAff do main = void $ launchAff do
pool <- newPool config pool <- newPool config
withConnection pool \conn -> do withConnection pool \conn -> do
@ -23,72 +59,87 @@ main = void $ launchAff do
name text NOT NULL, name text NOT NULL,
delicious boolean NOT NULL, delicious boolean NOT NULL,
price NUMERIC(4,2) NOT NULL, price NUMERIC(4,2) NOT NULL,
added TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (name) PRIMARY KEY (name)
) )
""") Row0 """) Row0
execute conn (Query """ liftEff $ runTest $ do
INSERT INTO foods (name, delicious, price) suite "Postgresql client" $ do
VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9) let
""") (Row9 testCount n = do
"pork" true (D.fromString "8.30") count <- scalar conn (Query """
"sauerkraut" false (D.fromString "3.30") SELECT count(*) = $1
"rookworst" true (D.fromString "5.60")) FROM foods
""") (Row1 n)
liftEff <<< assert $ count == Just true
names <- query conn (Query """ Test.Unit.test "transaction commit" $ do
SELECT name withTransaction conn do
FROM foods execute conn (Query """
WHERE delicious INSERT INTO foods (name, delicious, price)
ORDER BY name ASC VALUES ($1, $2, $3)
""") Row0 """) (Row3 "pork" true (D.fromString "8.30"))
testCount 1
testCount 1
execute conn (Query """
DELETE FROM foods
""") Row0
liftEff <<< assert $ names == [Row1 "pork", Row1 "rookworst"] Test.Unit.test "transaction rollback" $ do
_ <- try $ withTransaction conn do
execute conn (Query """
INSERT INTO foods (name, delicious, price)
VALUES ($1, $2, $3)
""") (Row3 "pork" true (D.fromString "8.30"))
testCount 1
throwError $ error "fail"
testCount 0
sour <- query conn (Query """ let
SELECT name, price insertFood =
FROM foods execute conn (Query """
WHERE NOT delicious INSERT INTO foods (name, delicious, price)
ORDER BY name ASC VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9)
""") Row0 """) (Row9
liftEff <<< assert $ sour == [Row2 "sauerkraut" (D.fromString "3.30")] "pork" true (D.fromString "8.30")
"sauerkraut" false (D.fromString "3.30")
"rookworst" true (D.fromString "5.60"))
test conn "select column subset" $ do
insertFood
names <- query conn (Query """
SELECT name, delicious
FROM foods
WHERE delicious
ORDER BY name ASC
""") Row0
liftEff <<< assert $ names == [Row2 "pork" true, Row2 "rookworst" true]
testTransactionCommit conn test conn "select default instant value" $ do
testTransactionRollback conn before <- liftEff $ (unwrap <<< unInstant) <$> now
insertFood
added <- query conn (Query """
SELECT added
FROM foods
""") Row0
after <- liftEff $ (unwrap <<< unInstant) <$> now
-- | timestamps are fetched without milliseconds so we have to
-- | round before value down
liftEff <<< assert $ all
(\(Row1 t) ->
( unwrap $ unInstant t) >= (before - before % 1000.0)
&& after >= (unwrap $ unInstant t))
added
pure unit test conn "select decimal" $ do
where insertFood
testTransactionCommit conn = do sauerkrautPrice <- query conn (Query """
deleteAll conn SELECT price
withTransaction conn do FROM foods
execute conn (Query """ WHERE NOT delicious
INSERT INTO foods (name, delicious, price) """) Row0
VALUES ($1, $2, $3) liftEff <<< assert $ sauerkrautPrice == [Row1 (D.fromString "3.30")]
""") (Row3 "pork" true (D.fromString "8.30"))
testCount conn 1
testCount conn 1
testTransactionRollback conn = do
deleteAll conn
_ <- try $ withTransaction conn do
execute conn (Query """
INSERT INTO foods (name, delicious, price)
VALUES ($1, $2, $3)
""") (Row3 "pork" true (D.fromString "8.30"))
testCount conn 1
throwError $ error "fail"
testCount conn 0
deleteAll conn =
execute conn (Query """
DELETE FROM foods
""") Row0
testCount conn n = do
count <- scalar conn (Query """
SELECT count(*) = $1
FROM foods
""") (Row1 n)
liftEff <<< assert $ count == Just true
config :: PoolConfiguration config :: PoolConfiguration
config = config =