packages feed

valiant-0.1.0.0: integration/TestSupport.hs

module TestSupport
  ( withTestConnection
  , withTestPool
  , requireDatabaseUrl
  , createSchema
  , dropSchema
  , withSchema
  , insertTestUsers
  , insertBulkUsers
  ) where

import Data.ByteString (ByteString)
import Data.ByteString.Char8 qualified as BS8
import Valiant
import System.Environment (lookupEnv)

-- Connection helpers --------------------------------------------------------

requireDatabaseUrl :: IO ByteString
requireDatabaseUrl = do
  mUrl <- lookupEnv "DATABASE_URL"
  case mUrl of
    Just url -> pure (BS8.pack url)
    Nothing -> error
      "DATABASE_URL is not set.\n\n\
      \  Run: eval $(scripts/pg-setup.sh)\n\
      \  Or:  export DATABASE_URL=postgres://user:pass@localhost:5432/mydb\n"

withTestConnection :: (Connection -> IO a) -> IO a
withTestConnection action = do
  url <- requireDatabaseUrl
  conn <- connectString url
  result <- action conn
  close conn
  pure result

withTestPool :: (Pool -> IO a) -> IO a
withTestPool action = do
  url <- requireDatabaseUrl
  pool <- newPool defaultPoolConfig
    { poolConnString = url
    , poolSize = 4
    , poolAcquireTimeout = 5
    }
  result <- action pool
  closePool pool
  pure result

-- Schema --------------------------------------------------------------------

createSchema :: Connection -> IO ()
createSchema conn = do
  _ <- simpleQuery conn
    "CREATE TABLE IF NOT EXISTS users (\
    \  id SERIAL PRIMARY KEY,\
    \  name TEXT NOT NULL,\
    \  email TEXT,\
    \  is_active BOOLEAN NOT NULL DEFAULT true,\
    \  created_at TIMESTAMPTZ NOT NULL DEFAULT now()\
    \)"
  _ <- simpleQuery conn
    "CREATE TABLE IF NOT EXISTS posts (\
    \  id SERIAL PRIMARY KEY,\
    \  author_id INTEGER NOT NULL REFERENCES users(id),\
    \  title TEXT NOT NULL,\
    \  body TEXT,\
    \  published_at TIMESTAMPTZ,\
    \  created_at TIMESTAMPTZ NOT NULL DEFAULT now()\
    \)"
  pure ()

dropSchema :: Connection -> IO ()
dropSchema conn = do
  _ <- simpleQuery conn "DROP TABLE IF EXISTS posts CASCADE"
  _ <- simpleQuery conn "DROP TABLE IF EXISTS users CASCADE"
  pure ()

withSchema :: Connection -> IO a -> IO a
withSchema conn action = do
  dropSchema conn
  createSchema conn
  result <- action
  dropSchema conn
  pure result

-- Fixtures ------------------------------------------------------------------

insertTestUsers :: Connection -> IO ()
insertTestUsers conn = do
  _ <- simpleQuery conn
    "INSERT INTO users (name, email) VALUES \
    \('Alice', 'alice@example.com'),\
    \('Bob', 'bob@example.com'),\
    \('Carol', NULL),\
    \('Dave', 'dave@example.com'),\
    \('Eve', 'eve@example.com')"
  pure ()

insertBulkUsers :: Connection -> Int -> IO ()
insertBulkUsers conn n = do
  let values = BS8.intercalate ","
        [ "('user_" <> BS8.pack (show i) <> "', 'user" <> BS8.pack (show i) <> "@test.com')"
        | i <- [1 .. n]
        ]
      sql = "INSERT INTO users (name, email) VALUES " <> values
  _ <- simpleQuery conn sql
  pure ()