]> git.immae.eu Git - github/fretlink/squeal-hspec.git/blame - src/Squeal/PostgreSQL/Hspec.hs
Generalize Migratory kind
[github/fretlink/squeal-hspec.git] / src / Squeal / PostgreSQL / Hspec.hs
CommitLineData
aa94c1a5
R
1{-|
2Helpers for creating database tests with hspec and squeal, inspired by Jonathan Fischoff's
3[hspec-pg-transact](http://hackage.haskell.org/package/hspec-pg-transact).
4
5This uses @tmp-postgres@ to automatically and connect to a temporary instance of postgres on a random port.
6
7Tests can be written with 'itDB' which is wrapper around 'it' that uses the passed in 'TestDB' to run a db transaction automatically for the test.
8
9The libary also provides a few other functions for more fine grained control over running transactions in tests.
10-}
11{-# LANGUAGE DataKinds #-}
12{-# LANGUAGE FlexibleContexts #-}
aa94c1a5
R
13{-# LANGUAGE MonoLocalBinds #-}
14{-# LANGUAGE RankNTypes #-}
15{-# LANGUAGE RecordWildCards #-}
dd35032c 16{-# LANGUAGE TupleSections #-}
aa94c1a5
R
17{-# LANGUAGE TypeInType #-}
18{-# LANGUAGE TypeOperators #-}
19module Squeal.PostgreSQL.Hspec
20where
21
22import Control.Exception
23import Control.Monad
d8e546eb 24import Control.Monad.Base (liftBase)
dd35032c 25import Data.ByteString (ByteString)
d8e546eb
R
26import qualified Data.ByteString.Char8 as BSC
27import qualified Database.Postgres.Temp as Temp
28import Generics.SOP (K)
aa94c1a5 29import Squeal.PostgreSQL
aa94c1a5 30import Squeal.PostgreSQL.Pool
dd35032c 31import System.Environment (lookupEnv)
aa94c1a5
R
32import Test.Hspec
33
34data TestDB a = TestDB
dd35032c 35 { tempDB :: Maybe Temp.DB
aa94c1a5 36 -- ^ Handle for temporary @postgres@ process
dd35032c 37 , pool :: Pool a
aa94c1a5 38 -- ^ Pool of 50 connections to the temporary @postgres@
dd35032c 39 , connectionString :: ByteString
aa94c1a5
R
40 }
41
aa94c1a5
R
42type Fixtures schema = (Pool (K Connection schema) -> IO ())
43type Actions schema a = PoolPQ schema IO a
d8e546eb 44type SquealContext schema = TestDB (K Connection schema)
aa94c1a5 45
dd35032c
R
46testDBEnv :: String
47testDBEnv = "TEST_DB_CONNECTION_STRING"
48
49getOrCreateConnectionString :: IO (ByteString, Maybe Temp.DB)
50getOrCreateConnectionString = do
51 hasConnectionString <- lookupEnv testDBEnv
52 maybe createTempDB (pure . (, Nothing) . BSC.pack) hasConnectionString
53
54createTempDB :: IO (ByteString, Maybe Temp.DB)
55createTempDB = do
56 tempDB <- either throwIO return =<< Temp.startAndLogToTmp []
57 let connectionString = BSC.pack (Temp.connectionString tempDB)
58 pure (connectionString, Just tempDB)
59
aa94c1a5
R
60-- | Start a temporary @postgres@ process and create a pool of connections to it
61setupDB
f4e2e1eb 62 :: Migratory p => AlignedList (Migration p) schema0 schema
aa94c1a5
R
63 -> Fixtures schema
64 -> IO (SquealContext schema)
65setupDB migration fixtures = do
dd35032c
R
66 (connectionString, tempDB) <- getOrCreateConnectionString
67 BSC.putStrLn connectionString
aa94c1a5
R
68 let singleStripe = 1
69 keepConnectionForOneHour = 3600
70 poolSizeOfFifty = 50
71 pool <- createConnectionPool
72 connectionString
73 singleStripe
74 keepConnectionForOneHour
75 poolSizeOfFifty
d8e546eb 76 withConnection connectionString (migrateUp migration)
aa94c1a5
R
77 fixtures pool
78 pure TestDB {..}
79
80-- | Drop all the connections and shutdown the @postgres@ process
dd35032c 81teardownDB
f4e2e1eb 82 :: Migratory p => AlignedList (Migration p) schema0 schema
dd35032c
R
83 -> TestDB a
84 -> IO ()
85teardownDB migration TestDB {..} = do
86 withConnection connectionString (migrateDown migration)
aa94c1a5 87 destroyAllResources pool
dd35032c 88 maybe (pure ()) (void . Temp.stop) tempDB
aa94c1a5
R
89
90-- | Run an 'IO' action with a connection from the pool
91withPool :: TestDB (K Connection schema) -> Actions schema a -> IO a
92withPool testDB = liftBase . flip runPoolPQ (pool testDB)
93
94-- | Run an 'DB' transaction, using 'transactionally_'
95withDB :: Actions schema a -> TestDB (K Connection schema) -> IO a
96withDB action testDB =
97 runPoolPQ (transactionally_ action) (pool testDB)
98
99-- | Flipped version of 'withDB'
100runDB :: TestDB (K Connection schema) -> Actions schema a -> IO a
101runDB = flip withDB
102
103-- | Helper for writing tests. Wrapper around 'it' that uses the passed
104-- in 'TestDB' to run a db transaction automatically for the test.
105itDB :: String -> Actions schema a -> SpecWith (TestDB (K Connection schema))
106itDB msg action = it msg $ void . withDB action
107
108-- | Wraps 'describe' with a
109--
110-- @
111-- 'beforeAll' ('setupDB' migrate)
112-- @
113--
114-- hook for creating a db and a
115--
116-- @
117-- 'afterAll' 'teardownDB'
118-- @
119--
120-- hook for stopping a db.
121describeDB
f4e2e1eb 122 :: Migratory p => AlignedList (Migration p) schema0 schema
aa94c1a5
R
123 -> Fixtures schema
124 -> String
125 -> SpecWith (SquealContext schema)
126 -> Spec
127describeDB migrate fixture str =
dd35032c 128 beforeAll (setupDB migrate fixture) . afterAll (teardownDB migrate) . describe str