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.
## Install
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].
## Preprocessing static SQL statements
The purspgpp preprocessor has been replaced by [sqltopurs], which is a code
generator instead of a preprocessor, and easier to use.
[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-eff": "^3.1.0",
"purescript-exceptions": "^3.0.0",
"purescript-decimals": "^3.4.0"
"purescript-decimals": "^3.4.0",
"purescript-js-date": "^5.2.0"
},
"repository": {
"type": "git",
@ -25,6 +26,8 @@
},
"devDependencies": {
"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();
};
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) {
return x instanceof Buffer;
};

View File

@ -8,14 +8,15 @@ import Control.Monad.Except (runExcept)
import Data.Array as Array
import Data.Bifunctor (lmap)
import Data.ByteString (ByteString)
import Data.DateTime.Instant (Instant)
import Data.DateTime.Instant (Instant, instant)
import Data.Decimal (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.List (List)
import Data.List as List
import Data.Maybe (Maybe(..))
import Data.Time.Duration (Milliseconds(..))
import Data.Traversable (traverse)
-- | Convert things to SQL values.
@ -79,6 +80,11 @@ instance fromSQLValueByteString :: FromSQLValue ByteString where
instance toSQLValueInstant :: ToSQLValue Instant where
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
toSQLValue Nothing = null
toSQLValue (Just x) = toSQLValue x
@ -103,4 +109,5 @@ instance fromSQLValueDecimal :: FromSQLValue Decimal where
foreign import null :: 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

View File

@ -4,17 +4,53 @@ module Test.Main
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.Class (liftEff)
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.Maybe (Maybe(..))
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.Foldable (all)
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.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
pool <- newPool config
withConnection pool \conn -> do
@ -23,72 +59,87 @@ main = void $ launchAff do
name text NOT NULL,
delicious boolean NOT NULL,
price NUMERIC(4,2) NOT NULL,
added TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (name)
)
""") Row0
execute conn (Query """
INSERT INTO foods (name, delicious, price)
VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9)
""") (Row9
"pork" true (D.fromString "8.30")
"sauerkraut" false (D.fromString "3.30")
"rookworst" true (D.fromString "5.60"))
liftEff $ runTest $ do
suite "Postgresql client" $ do
let
testCount n = do
count <- scalar conn (Query """
SELECT count(*) = $1
FROM foods
""") (Row1 n)
liftEff <<< assert $ count == Just true
names <- query conn (Query """
SELECT name
FROM foods
WHERE delicious
ORDER BY name ASC
""") Row0
Test.Unit.test "transaction commit" $ do
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
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 """
SELECT name, price
FROM foods
WHERE NOT delicious
ORDER BY name ASC
""") Row0
liftEff <<< assert $ sour == [Row2 "sauerkraut" (D.fromString "3.30")]
let
insertFood =
execute conn (Query """
INSERT INTO foods (name, delicious, price)
VALUES ($1, $2, $3), ($4, $5, $6), ($7, $8, $9)
""") (Row9
"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
testTransactionRollback conn
test conn "select default instant value" $ do
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
where
testTransactionCommit conn = do
deleteAll conn
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
testCount conn 1
test conn "select decimal" $ do
insertFood
sauerkrautPrice <- query conn (Query """
SELECT price
FROM foods
WHERE NOT delicious
""") Row0
liftEff <<< assert $ sauerkrautPrice == [Row1 (D.fromString "3.30")]
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 =