packages feed

polysemy-hasql-test-0.0.1.0: lib/Polysemy/Hasql/Test/Run.hs

module Polysemy.Hasql.Test.Run where

import Conc (interpretMaskFinal, interpretRace)
import Data.UUID (UUID)
import Exon (exon)
import Hasql.Session (QueryError)
import Hedgehog (TestT)
import Hedgehog.Internal.Property (Failure)
import Log (Severity (Error), interpretLogStdoutLevelConc)
import Polysemy.Db (interpretRandom)
import Polysemy.Db.Data.DbConfig (DbConfig (DbConfig))
import Polysemy.Db.Data.DbConnectionError (DbConnectionError)
import Polysemy.Db.Data.DbError (DbError)
import Polysemy.Db.Data.InitDbError (InitDbError)
import Polysemy.Db.Effect.Random (Random)
import qualified Polysemy.Test as Hedgehog
import Polysemy.Test (Hedgehog, Test, runTestAuto)
import Polysemy.Test.Data.TestError (TestError)
import Time (GhcTime, interpretTimeGhc)
import System.Environment (lookupEnv)

import Polysemy.Hasql.Test.Database (TestConnectionEffects, withTestConnection)

type DbErrors =
  [
    Stop DbConnectionError,
    Stop DbError,
    Stop QueryError,
    Stop Text,
    Error InitDbError,
    Error DbError
  ]

type TestEffects =
  DbErrors ++ [
    GhcTime,
    Random UUID,
    Log,
    Error Text,
    Mask,
    Race,
    Async,
    Test,
    Fail,
    Error TestError,
    Hedgehog IO,
    Error Failure,
    Embed IO,
    Resource,
    Final IO
  ]

dbConfig ::
  MonadIO m =>
  String ->
  Text ->
  m (Maybe DbConfig)
dbConfig envPrefix name = do
  traverse cons =<< (liftIO (lookupEnv [exon|#{envPrefix}_test_host|]))
  where
    cons host = do
      port <- parsePort =<< (fromMaybe "4321" <$> liftIO (lookupEnv [exon|#{envPrefix}_test_port|]))
      pure (DbConfig (fromString host) port (fromText name) (fromText name) (fromText name))
    parsePort p =
      case readMaybe p of
        Just a -> pure a
        Nothing -> error [exon|invalid port in env var $#{envPrefix}_test_port: #{p}|]

runIntegrationTestWith ::
  Members [Error Text, Embed IO] r =>
  HasCallStack =>
  String ->
  Text ->
  (DbConfig -> Sem (DbErrors ++ r) ()) ->
  Sem r ()
runIntegrationTestWith envPrefix name run =
  withFrozenCallStack do
    dbConfig envPrefix name >>= \case
      Just conf ->
        mapError @DbError @Text show $
        mapError @InitDbError @Text show $
        stopToError @Text $
        mapStop @QueryError @Text show $
        mapStop @DbError @Text show $
        mapStop @DbConnectionError @Text show $
        run conf
      Nothing ->
        unit

integrationTestWith ::
  HasCallStack =>
  String ->
  Text ->
  (DbConfig -> Sem TestEffects ()) ->
  TestT IO ()
integrationTestWith envPrefix name run =
  withFrozenCallStack $ runTestAuto do
    r <- asyncToIOFinal $
      interpretRace $
      interpretMaskFinal $
      runError @Text $
      interpretLogStdoutLevelConc (Just Error) $
      interpretRandom $
      interpretTimeGhc $
      runIntegrationTestWith envPrefix name run
    Hedgehog.evalEither r

integrationTest ::
  HasCallStack =>
  String ->
  Text ->
  Sem (TestConnectionEffects ++ TestEffects) () ->
  TestT IO ()
integrationTest envPrefix name thunk =
  withFrozenCallStack do
    integrationTestWith envPrefix name \ conf -> withTestConnection conf thunk