From bb493e59832a1ce0594b278112ea49477e0a276c Mon Sep 17 00:00:00 2001 From: Tomasz Rybarczyk Date: Sun, 22 Apr 2018 16:47:07 +0200 Subject: [PATCH] Migrate tests to purescript-test-unit + Instant fetching + test --- README.md | 8 ++ bower.json | 7 +- src/Database/PostgreSQL/Value.js | 12 ++ src/Database/PostgreSQL/Value.purs | 11 +- test/Main.purs | 173 +++++++++++++++++++---------- 5 files changed, 146 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 44fba41..a36eaca 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/bower.json b/bower.json index 44a0875..524edf5 100644 --- a/bower.json +++ b/bower.json @@ -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" } } diff --git a/src/Database/PostgreSQL/Value.js b/src/Database/PostgreSQL/Value.js index 0cb6adc..a4b2dcb 100644 --- a/src/Database/PostgreSQL/Value.js +++ b/src/Database/PostgreSQL/Value.js @@ -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; }; diff --git a/src/Database/PostgreSQL/Value.purs b/src/Database/PostgreSQL/Value.purs index 9b6838c..54a467c 100644 --- a/src/Database/PostgreSQL/Value.purs +++ b/src/Database/PostgreSQL/Value.purs @@ -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 diff --git a/test/Main.purs b/test/Main.purs index 02fc0a0..991a0cf 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -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 =