packages feed

postgresql-types-0.1: src/integration-tests/Main/Scopes.hs

module Main.Scopes where

import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import qualified Data.ByteString as ByteString
import Data.Function
import Data.Maybe
import Data.Proxy
import Data.String
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text.Encoding
import Data.Typeable
import Data.Word
import qualified Database.PostgreSQL.LibPQ as Pq
import Test.Hspec
import qualified TestcontainersPostgresql
import qualified TextBuilder
import Prelude

withContainer :: Text -> SpecWith (Text, Word16) -> Spec
withContainer tagName =
  describe (Text.unpack tagName) . aroundAll (TestcontainersPostgresql.run config)
  where
    config =
      TestcontainersPostgresql.Config
        { forwardLogs = False,
          auth = TestcontainersPostgresql.TrustAuth,
          tagName
        }

withConnection :: Maybe Int -> SpecWith Pq.Connection -> SpecWith (Text, Word16)
withConnection extraFloatDigits =
  withConnectionPool 100 extraFloatDigits . withTQueueElement clean
  where
    clean connection = pure connection

withConnectionPool :: Int -> Maybe Int -> SpecWith (TQueue Pq.Connection) -> SpecWith (Text, Word16)
withConnectionPool poolSize extraFloatDigits =
  describe name . mapSubject connectionStringByContainerInfo . withPool poolSize acquire release
  where
    name =
      mconcat
        [ "extraFloatDigits: ",
          case extraFloatDigits of
            Just digits -> show digits
            Nothing -> "default"
        ]

    connectionStringByContainerInfo (host, port) =
      ByteString.intercalate " " components
      where
        components =
          [ "host=" <> (Text.Encoding.encodeUtf8 host),
            "port=" <> (fromString . show) port,
            "user=" <> user,
            "password=" <> password,
            "dbname=" <> db
          ]
          where
            user = "postgres"
            password = "postgres"
            db = "postgres"

    acquire connectionString = do
      connection <- Pq.connectdb connectionString
      status <- Pq.status connection
      case status of
        Pq.ConnectionOk -> pure ()
        _ -> do
          message <- Pq.errorMessage connection
          fail ("Failed to connect to database: " <> show message)
      result <-
        Pq.exec
          connection
          ( [ Just "SET client_min_messages TO WARNING",
              Just "SET client_encoding = 'UTF8'",
              Just "SET intervalstyle = 'iso_8601'",
              fmap
                (\digits -> "SET extra_float_digits = " <> TextBuilder.decimal digits)
                extraFloatDigits
            ]
              & catMaybes
              & fmap (<> ";")
              & TextBuilder.intercalate "\n"
              & TextBuilder.toText
              & Text.Encoding.encodeUtf8
          )
      case result of
        Just _ -> pure ()
        Nothing -> do
          message <- Pq.errorMessage connection
          fail ("Failed to set connection parameters: " <> show message)
      pure connection

    release = Pq.finish

withPool ::
  -- | Pool size.
  Int ->
  -- | Acquire.
  (b -> IO a) ->
  -- | Release.
  (a -> IO ()) ->
  SpecWith (TQueue a) ->
  SpecWith b
withPool poolSize acquire release =
  describe ("poolSize: " <> show poolSize) . aroundAllWith \actionWithQueue b ->
    bracket
      ( do
          queue <- newTQueueIO
          replicateConcurrently_ poolSize do
            element <- acquire b
            atomically $ writeTQueue queue element
          pure queue
      )
      ( \queue -> do
          replicateConcurrently_ poolSize do
            element <- atomically $ readTQueue queue
            release element
      )
      actionWithQueue

withTQueueElement ::
  -- | Clean. Called upon returning to the pool.
  (a -> IO a) ->
  SpecWith a ->
  SpecWith (TQueue a)
withTQueueElement clean =
  aroundWith \actionWithElement queue ->
    bracket
      (atomically $ readTQueue queue)
      ( \element -> do
          element <- clean element
          atomically $ writeTQueue queue element
      )
      actionWithElement

withType :: forall a b. (Typeable a) => [Proxy a -> SpecWith b] -> SpecWith b
withType specs = do
  describe (show (typeOf (undefined :: a))) do
    mapM_ (\spec -> spec Proxy) specs