Skip to content

Commit fd60b67

Browse files
committed
Add new test suite using purescript-spec
1 parent 6a39636 commit fd60b67

2 files changed

Lines changed: 110 additions & 62 deletions

File tree

bower.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,14 @@
3131
"purescript-foreign": "^1.0.0",
3232
"purescript-foldable-traversable": "^1.0.0",
3333
"purescript-transformers": "^1.0.0",
34-
"purescript-aff": "~0.17.0",
34+
"purescript-aff": "^1.0.0",
3535
"purescript-integers": "^1.0.0",
3636
"purescript-datetime": "^1.0.0",
37-
"purescript-unsafe-coerce": "~1.0.0"
37+
"purescript-unsafe-coerce": "^1.0.0",
38+
"purescript-nullable": "^1.0.1"
39+
},
40+
"devDependencies": {
41+
"purescript-spec": "~0.8.0",
42+
"purescript-generics": "~1.0.0"
3843
}
3944
}

test/Main.purs

Lines changed: 103 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,43 @@
11
module Test.Main where
22

33
import Prelude
4-
import Control.Monad.Eff.Console as C
5-
import Control.Monad.Aff (Aff, apathize, attempt, runAff)
6-
import Control.Monad.Aff.Console (log, logShow)
4+
import Control.Monad.Aff (Aff, apathize, attempt)
75
import Control.Monad.Eff (Eff)
86
import Control.Monad.Eff.Class (liftEff)
97
import Control.Monad.Eff.Console (CONSOLE)
108
import Control.Monad.Eff.Exception (error)
119
import Control.Monad.Error.Class (throwError)
10+
import Data.Array (length)
11+
import Data.Date (Date, canonicalDate)
12+
import Data.DateTime (DateTime(..))
13+
import Data.Date.Component (Day(..), Month(..), Year(..))
14+
import Data.Enum (toEnum)
15+
import Data.Time (Time(..))
16+
import Data.Time.Component (Hour(..), Minute(..), Second(..))
1217
import Data.Either (either)
13-
import Data.Foldable (foldMap)
18+
import Data.Foreign (Foreign)
19+
import Data.JSDate (JSDate, toDateTime)
20+
import Data.Maybe (Maybe(Nothing, Just), maybe)
1421
import Data.Foreign.Class (class IsForeign, readProp)
15-
import Data.Maybe (Maybe)
16-
import Database.Postgres (DB, Query(Query), queryOne_, execute_, withConnection, query, withClient, end, query_, connect, queryValue_, disconnect, mkConnectionString)
22+
import Data.Generic (class Generic, gEq)
23+
import Database.Postgres (DB, Query(Query), queryOne_, execute, execute_, withConnection, query, withClient, end, query_, connect, queryValue_, mkConnectionString)
1724
import Database.Postgres.SqlValue (toSql)
1825
import Database.Postgres.Transaction (withTransaction)
26+
import Node.Process (PROCESS)
1927

20-
main :: forall eff. Eff ( console :: CONSOLE , db :: DB | eff ) Unit
21-
main = runAff C.logShow (const $ C.log "All ok") $ do
22-
logShow $ "connecting to " <> mkConnectionString connectionInfo <> "..."
23-
exampleUsingWithConnection
24-
exampleLowLevel
28+
import Unsafe.Coerce (unsafeCoerce)
2529

26-
res <- attempt exampleError
27-
either (const $ log "got an error, like we should") (const $ log "FAIL") res
28-
29-
exampleQueries
30-
31-
exampleTransaction
32-
33-
liftEff $ disconnect
30+
import Test.Spec (describe, it)
31+
import Test.Spec.Runner (run)
32+
import Test.Spec.Assertions (fail, shouldEqual)
33+
import Test.Spec.Reporter.Console (consoleReporter)
3434

3535
data Artist = Artist
3636
{ name :: String
3737
, year :: Int
3838
}
3939

40+
connectionInfo :: { host :: String, db :: String, port :: Int, user :: String, password :: String }
4041
connectionInfo =
4142
{ host: "localhost"
4243
, db: "test"
@@ -45,57 +46,99 @@ connectionInfo =
4546
, password: "test"
4647
}
4748

48-
exampleUsingWithConnection :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
49-
exampleUsingWithConnection = withConnection connectionInfo $ \c -> do
50-
execute_ (Query "delete from artist") c
51-
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
52-
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
53-
year <- queryValue_ (Query "insert into artist values ('Fairport Convention', 1967) returning year" :: Query Number) c
54-
logShow (show year)
55-
artists <- query_ (Query "select * from artist" :: Query Artist) c
56-
printRows artists
57-
58-
exampleLowLevel :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
59-
exampleLowLevel = do
60-
client <- connect connectionInfo
61-
artists <- query_ (Query "select * from artist order by name desc" :: Query Artist) client
62-
printRows artists
63-
liftEff $ end client
49+
main :: Eff (process :: PROCESS, console :: CONSOLE , db :: DB) Unit
50+
main = run [consoleReporter] do
51+
describe "connection string" do
52+
it "should build one from the connection record" do
53+
mkConnectionString connectionInfo `shouldEqual` "postgres://testuser:test@localhost:5432/test"
54+
55+
describe "withConnection" do
56+
it "Returns a connection" do
57+
withConnection connectionInfo $ \c -> do
58+
execute_ (Query "delete from artist") c
59+
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
60+
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
61+
let
62+
q :: Query Int
63+
q = Query "insert into artist values ('Fairport Convention', 1967) returning year"
64+
65+
year <- queryValue_ q c
66+
year `shouldEqual` (Just 1967)
67+
68+
artists <- query_ (Query "select * from artist" :: Query Artist) c
69+
length artists `shouldEqual` 3
70+
71+
describe "Low level API" do
72+
it "Can be used to manage connections manually" do
73+
client <- connect connectionInfo
74+
execute_ (Query "delete from artist") client
75+
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") client
76+
77+
artists <- query_ (Query "select * from artist order by name desc" :: Query Artist) client
78+
artists `shouldEqual` [Artist { name: "Led Zeppelin", year: 1968 }]
79+
80+
liftEff $ end client
81+
82+
describe "Error handling" do
83+
it "When query cannot be converted to the requested data type we get an error" do
84+
res <- attempt exampleError
85+
either (const $ pure unit) (const $ fail "FAIL") res
86+
87+
describe "Query params" do
88+
it "Select using a query param" do
89+
withClient connectionInfo $ \c -> do
90+
execute_ (Query "delete from artist") c
91+
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
92+
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
93+
execute_ (Query "insert into artist values ('Toto', 1977)") c
94+
artists <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "Toto"] c
95+
length artists `shouldEqual` 1
96+
97+
noRows <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "FAIL"] c
98+
length noRows `shouldEqual` 0
99+
100+
describe "data types" do
101+
it "datetimes can be inserted" do
102+
withConnection connectionInfo \c -> do
103+
execute_ (Query "delete from types") c
104+
let date = canonicalDate <$> toEnum 2016 <*> Just January <*> toEnum 25
105+
time = Time <$> toEnum 23 <*> toEnum 1 <*> toEnum 59 <*> toEnum 0
106+
dt = DateTime <$> date <*> time
107+
maybe (fail "Not a datetime") (\ts -> do
108+
execute (Query "insert into types(timestamp_no_tz) VALUES ($1)") [toSql ts] c
109+
ts' <- queryValue_ (Query "select timestamp_no_tz at time zone 'UTC' from types" :: Query Foreign) c
110+
let res = unsafeCoerce <$> ts' >>= toDateTime
111+
res `shouldEqual` (Just ts)
112+
) dt
113+
114+
115+
describe "transactions" do
116+
it "does not commit after an error inside a transation" do
117+
withConnection connectionInfo $ \c -> do
118+
execute_ (Query "delete from artist") c
119+
apathize $ tryInsert c
120+
one <- queryOne_ (Query "select * from artist" :: Query Artist) c
121+
122+
one `shouldEqual` Nothing
123+
where
124+
tryInsert = withTransaction $ \c -> do
125+
execute_ (Query "insert into artist values ('Not there', 1999)") c
126+
throwError $ error "fail"
64127

65128
exampleError :: forall eff. Aff (db :: DB | eff) (Maybe Artist)
66129
exampleError = withConnection connectionInfo $ \c -> do
67130
execute_ (Query "delete from artist") c
68131
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
69132
queryOne_ (Query "select year from artist") c
70133

71-
exampleQueries :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
72-
exampleQueries = withClient connectionInfo $ \c -> do
73-
log "Example queries with params:"
74-
execute_ (Query "delete from artist") c
75-
execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c
76-
execute_ (Query "insert into artist values ('Deep Purple', 1968)") c
77-
execute_ (Query "insert into artist values ('Toto', 1977)") c
78-
artists <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "Toto"] c
79-
printRows artists
80-
81-
exampleTransaction :: forall eff. Aff (console :: C.CONSOLE, db :: DB | eff) Unit
82-
exampleTransaction = withConnection connectionInfo $ \c -> do
83-
execute_ (Query "delete from artist") c
84-
apathize $ tryInsert c
85-
one <- queryOne_ (Query "select * from artist" :: Query Artist) c
86-
void $ logShow one
87-
where
88-
tryInsert = withTransaction $ \c -> do
89-
execute_ (Query "insert into artist values ('Not there', 1999)") c
90-
throwError $ error "fail"
91-
92-
printRows :: forall a eff. (Show a) => Array a -> Aff (console :: C.CONSOLE | eff) Unit
93-
printRows rows = void $ log $ "result:\n" <> foldMap stringify rows
94-
where stringify = show >>> flip (<>) "\n"
95-
96134
instance artistShow :: Show Artist where
97135
show (Artist p) = "Artist (" <> p.name <> ", " <> show p.year <> ")"
98136

137+
derive instance genericArtist :: Generic Artist
138+
139+
instance eqArtist :: Eq Artist where
140+
eq = gEq
141+
99142
instance artistIsForeign :: IsForeign Artist where
100143
read obj = do
101144
n <- readProp "name" obj

0 commit comments

Comments
 (0)