peregrin-0.1.0: src-test/Database/PeregrinSpec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Database.PeregrinSpec
( mkMigrateSpec
) where
import Control.Exception (Exception, bracket)
import Control.Monad (forM_)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Reader (ReaderT, runReaderT, ask)
import Database.Peregrin
import Database.Peregrin.Metadata
import Data.Pool (Pool, withResource, destroyAllResources)
import Data.Text (Text)
import Database.PostgreSQL.Simple (Connection, Query, Only(..), FromRow)
import qualified Database.PostgreSQL.Simple as PS
import Test.Hspec (Spec, Selector, describe)
import qualified Test.Hspec as Hspec
-- | Create specs.
mkMigrateSpec :: IO (Pool Connection) -> Spec
mkMigrateSpec mkConnectionPool =
forM_ [DefaultSchema, NamedSchema "foobar"] $ \schema -> do
-- Show which case we're in
let extra = case schema of
DefaultSchema -> " (default schema)"
NamedSchema _ -> " (named schema)"
-- Specs:
describe ("migrate" ++ extra) $ do
it "can apply a single migration" $ do
-- Exercise:
migrate' schema [ (cid0, createXSql) ]
-- Verify:
assertCanSelectFromX
it "ignores migrations that have already been applied (single call)" $ do
-- Exercise:
migrate' schema [ (cid0, createXSql)
, (cid0, createXSql) -- Would fail if applied again
]
-- Verify:
assertCanSelectFromX
it "ignores migrations that have already been applied (multiple calls)" $ do
-- Exercise:
migrate' schema [ (cid0, createXSql) ]
migrate' schema [ (cid0, createXSql) ] -- Would fail if applied again
-- Verify:
assertCanSelectFromX
it "throws an error if SQL is changed for a given change set ID" $ do
-- Exercise:
migrate' schema [ (cid0, createXSql) ]
migrate' schema [ (cid0, createXSqlBad) ]
-- Verify: Should throw here
`shouldThrow` (== MigrationModifiedError cid0)
it "can apply multiple distinct migrations in a single call" $ do
-- Exercise
migrate' schema [ (cid0, createXSql)
, (cid1, createYSql)
]
-- Verify: Make sure both migrations have been applied
assertCanSelectFromXY
where
it msg action = Hspec.it msg $
bracket mkConnectionPool destroyAllResources $ runReaderT action
createXSql = "CREATE TABLE X (A INT)"
createYSql = "CREATE TABLE Y (B INT)"
createXSqlBad = "CREATE TABLE X (Y CHAR(1))"
assertCanSelectFromX =
assertCanQuery_ "SELECT * FROM X"
assertCanSelectFromXY =
assertCanQuery_ "SELECT * FROM X, Y where X.A = Y.B"
cid0 = "a328156d-9875-4471-8192-0c86959badb3"
cid1 = "00c6159c-c7f6-4cec-b63f-f70c1c4c7bb1"
assertCanQuery_ :: Query -> ReaderT (Pool Connection) IO ()
assertCanQuery_ q = do
_ :: [Only Int] <- query_ q
return ()
query_ :: FromRow a => Query -> ReaderT (Pool Connection) IO [a]
query_ sql = do
connectionPool <- ask
withResource connectionPool $ \connection ->
lift $ PS.query_ connection sql
shouldThrow :: Exception e => ReaderT (Pool Connection) IO a -> Selector e -> ReaderT (Pool Connection) IO ()
shouldThrow action selector = do
connectionPool <- ask
lift (Hspec.shouldThrow (runReaderT action connectionPool) selector)
migrate' :: Schema -> [(Text, Text)] -> ReaderT (Pool Connection) IO ()
migrate' schema migrations = do
connectionPool <- ask
lift $ withResource connectionPool $ \connection ->
migrate connection schema migrations