fix: test client bindings

This commit is contained in:
orion 2024-03-27 15:26:40 -05:00
parent d7916683d7
commit d1f84bcc72
Signed by: orion
GPG Key ID: 6D4165AE4C928719
9 changed files with 281 additions and 30 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@
.log .log
.purs-repl .purs-repl
.env .env
pg

9
docker-compose.yml Normal file
View File

@ -0,0 +1,9 @@
version: '3.8'
services:
db:
image: 'postgres:16-bookworm'
volumes:
- './pg:/var/run/postgresql'
environment:
POSTGRES_USER: 'postgres'
POSTGRES_PASSWORD: 'password'

View File

@ -3,6 +3,7 @@ workspace:
pg: pg:
path: ./ path: ./
dependencies: dependencies:
- aff
- aff-promise - aff-promise
- bifunctors - bifunctors
- control - control
@ -11,6 +12,7 @@ workspace:
- exceptions - exceptions
- foldable-traversable - foldable-traversable
- foreign - foreign
- integers
- lists - lists
- maybe - maybe
- mmorph - mmorph
@ -20,11 +22,17 @@ workspace:
- nullable - nullable
- precise-datetime - precise-datetime
- prelude - prelude
- record
- simple-json - simple-json
- transformers - transformers
- tuples
- typelevel-prelude
- unsafe-coerce - unsafe-coerce
test_dependencies: test_dependencies:
- filterable
- foreign-object - foreign-object
- node-child-process
- node-process
- quickcheck - quickcheck
- spec - spec
- spec-quickcheck - spec-quickcheck
@ -49,6 +57,7 @@ workspace:
- enums - enums
- exceptions - exceptions
- exists - exists
- filterable
- fixed-points - fixed-points
- foldable-traversable - foldable-traversable
- foreign - foreign
@ -70,7 +79,13 @@ workspace:
- mmorph - mmorph
- newtype - newtype
- node-buffer - node-buffer
- node-child-process
- node-event-emitter - node-event-emitter
- node-fs
- node-os
- node-path
- node-process
- node-streams
- nonempty - nonempty
- now - now
- nullable - nullable
@ -81,6 +96,7 @@ workspace:
- parsing - parsing
- partial - partial
- pipes - pipes
- posix-types
- precise-datetime - precise-datetime
- prelude - prelude
- profunctor - profunctor
@ -313,6 +329,17 @@ packages:
integrity: sha256-A0JQHpTfo1dNOj9U5/Fd3xndlRSE0g2IQWOGor2yXn8= integrity: sha256-A0JQHpTfo1dNOj9U5/Fd3xndlRSE0g2IQWOGor2yXn8=
dependencies: dependencies:
- unsafe-coerce - unsafe-coerce
filterable:
type: registry
version: 5.0.0
integrity: sha256-cCojJHRnTmpY1j1kegI4CFwghdQ2Fm/8dzM8IlC+lng=
dependencies:
- arrays
- either
- foldable-traversable
- identity
- lists
- ordered-collections
fixed-points: fixed-points:
type: registry type: registry
version: 7.0.0 version: 7.0.0
@ -552,6 +579,22 @@ packages:
- nullable - nullable
- st - st
- unsafe-coerce - unsafe-coerce
node-child-process:
type: registry
version: 11.1.0
integrity: sha256-vioMNgk8p+CGwlb6T3I3TIir27el85Yg4satLE/I89w=
dependencies:
- exceptions
- foreign
- foreign-object
- functions
- node-event-emitter
- node-fs
- node-os
- node-streams
- nullable
- posix-types
- unsafe-coerce
node-event-emitter: node-event-emitter:
type: registry type: registry
version: 3.0.0 version: 3.0.0
@ -564,6 +607,85 @@ packages:
- nullable - nullable
- prelude - prelude
- unsafe-coerce - unsafe-coerce
node-fs:
type: registry
version: 9.1.0
integrity: sha256-TzhvGdrwcM0bazDvrWSqh+M/H8GKYf1Na6aGm2Qg4+c=
dependencies:
- datetime
- effect
- either
- enums
- exceptions
- functions
- integers
- js-date
- maybe
- node-buffer
- node-path
- node-streams
- nullable
- partial
- prelude
- strings
- unsafe-coerce
node-os:
type: registry
version: 5.1.0
integrity: sha256-K3gcu9AXanN1+qtk1900+Fi+CuO0s3/H/RMNRNgIzso=
dependencies:
- arrays
- bifunctors
- console
- control
- datetime
- effect
- either
- exceptions
- foldable-traversable
- foreign
- foreign-object
- functions
- maybe
- node-buffer
- nullable
- partial
- posix-types
- prelude
- unsafe-coerce
node-path:
type: registry
version: 5.0.0
integrity: sha256-pd82nQ+2l5UThzaxPdKttgDt7xlsgIDLpPG0yxDEdyE=
dependencies:
- effect
node-process:
type: registry
version: 11.2.0
integrity: sha256-+2MQDYChjGbVbapCyJtuWYwD41jk+BntF/kcOTKBMVs=
dependencies:
- effect
- foreign
- foreign-object
- maybe
- node-event-emitter
- node-streams
- posix-types
- prelude
- unsafe-coerce
node-streams:
type: registry
version: 9.0.0
integrity: sha256-2n6dq7YWleTDmD1Kur/ul7Cn08IvWrScgPf+0PgX2TQ=
dependencies:
- aff
- effect
- either
- exceptions
- node-buffer
- node-event-emitter
- nullable
- prelude
nonempty: nonempty:
type: registry type: registry
version: 7.0.0 version: 7.0.0
@ -683,6 +805,13 @@ packages:
- tailrec - tailrec
- transformers - transformers
- tuples - tuples
posix-types:
type: registry
version: 6.0.0
integrity: sha256-ZfFz8RR1lee/o/Prccyeut3Q+9tYd08mlR72sIh6GzA=
dependencies:
- maybe
- prelude
precise-datetime: precise-datetime:
type: registry type: registry
version: 7.0.0 version: 7.0.0

View File

@ -4,6 +4,7 @@ package:
strict: true strict: true
pedantic_packages: true pedantic_packages: true
dependencies: dependencies:
- aff
- aff-promise - aff-promise
- bifunctors - bifunctors
- control - control
@ -12,6 +13,7 @@ package:
- exceptions - exceptions
- foldable-traversable - foldable-traversable
- foreign - foreign
- integers
- lists - lists
- maybe - maybe
- mmorph - mmorph
@ -21,13 +23,19 @@ package:
- nullable - nullable
- precise-datetime - precise-datetime
- prelude - prelude
- record
- simple-json - simple-json
- transformers - transformers
- tuples
- typelevel-prelude
- unsafe-coerce - unsafe-coerce
test: test:
main: Test.Main main: Test.Main
dependencies: dependencies:
- filterable
- foreign-object - foreign-object
- node-child-process
- node-process
- quickcheck - quickcheck
- spec - spec
- spec-quickcheck - spec-quickcheck

View File

@ -1,5 +1,5 @@
import * as Pg from 'pg' import Pg from 'pg'
import * as Range from 'postgres-range' import Range from 'postgres-range'
export const null_ = null export const null_ = null

View File

@ -1,9 +1,12 @@
import {Client} from 'pg' import Pg from 'pg'
/** @typedef {{statementTimeout: unknown, queryTimeout: unknown, idleInTransactionTimeout: unknown, connectionTimeout: unknown, applicationName: string}} ClientConfigExtra */ /** @typedef {{statementTimeout: unknown, queryTimeout: unknown, idleInTransactionTimeout: unknown, connectionTimeout: unknown, applicationName: string}} ClientConfigExtra */
/** @type {(_: {unwrapMillis: (_m: unknown) => number}) => (cfg: import('pg').ClientConfig & ClientConfigExtra) => () => Client} */ /** @type {(_: {unwrapMillis: (_m: unknown) => number}) => (cfg: Pg.ClientConfig & ClientConfigExtra) => () => Pg.Client} */
export const makeImpl = ({unwrapMillis}) => cfg => () => { export const makeImpl =
({ unwrapMillis }) =>
cfg =>
() => {
if ('statementTimeout' in cfg) { if ('statementTimeout' in cfg) {
cfg.statement_timeout = unwrapMillis(cfg.statementTimeout) cfg.statement_timeout = unwrapMillis(cfg.statementTimeout)
} }
@ -11,7 +14,9 @@ export const makeImpl = ({unwrapMillis}) => cfg => () => {
cfg.query_timeout = unwrapMillis(cfg.queryTimeout) cfg.query_timeout = unwrapMillis(cfg.queryTimeout)
} }
if ('idleInTransactionTimeout' in cfg) { if ('idleInTransactionTimeout' in cfg) {
cfg.idle_in_transaction_session_timeout = unwrapMillis(cfg.idleInTransactionTimeout) cfg.idle_in_transaction_session_timeout = unwrapMillis(
cfg.idleInTransactionTimeout,
)
} }
if ('connectionTimeout' in cfg) { if ('connectionTimeout' in cfg) {
cfg.connectionTimeoutMillis = unwrapMillis(cfg.connectionTimeout) cfg.connectionTimeoutMillis = unwrapMillis(cfg.connectionTimeout)
@ -19,5 +24,5 @@ export const makeImpl = ({unwrapMillis}) => cfg => () => {
if ('applicationName' in cfg) { if ('applicationName' in cfg) {
cfg.application_name = cfg.applicationName cfg.application_name = cfg.applicationName
} }
return new Client(cfg) return new Pg.Client(cfg)
} }

View File

@ -1,7 +1,3 @@
module Effect.Postgres where module Effect.Postgres where
import Prelude
import Data.Time.Duration (Milliseconds)
foreign import data Pool :: Type foreign import data Pool :: Type

View File

@ -0,0 +1,52 @@
module Test.Effect.Postgres.Client where
import Prelude
import Control.Monad.Error.Class (try)
import Data.Array as Array
import Data.Either (isLeft)
import Data.Postgres (deserialize, smash)
import Data.Traversable (traverse)
import Effect.Aff (Aff)
import Effect.Aff.Postgres.Client as PG.Aff.Client
import Effect.Class (liftEffect)
import Effect.Console (log)
import Effect.Postgres.Client as PG
import Effect.Postgres.Client as PG.Client
import Effect.Postgres.Result as Result
import Node.Path as Path
import Node.Process (cwd)
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldEqual)
client :: Aff PG.Client
client = do
cwd' <- liftEffect cwd
host <- liftEffect $ Path.resolve [ cwd' ] "./pg"
liftEffect $ PG.Client.make { host, user: "postgres", password: "password", database: "postgres" }
spec :: Spec Unit
spec = do
describe "Client" do
describe "make" do
it "does not throw" $ void $ client
describe "connect" do
it "does not throw" $ PG.Aff.Client.connect =<< client
describe "end" do
it "does not throw" $ do
c <- client
PG.Aff.Client.connect c
PG.Aff.Client.end c
describe "query" do
it "ok if connected" $ do
c <- client
PG.Aff.Client.connect c
res <- Result.rows <$> PG.Aff.Client.query "select unnest(array[1, 2, 3])" c
ints :: Array Int <- liftEffect $ smash $ traverse deserialize $ Array.catMaybes $ map Array.head res
ints `shouldEqual` [ 1, 2, 3 ]
it "throws if ended" $ do
c <- client
PG.Aff.Client.connect c
PG.Aff.Client.end c
res <- try $ PG.Aff.Client.query "select 1" c
isLeft res `shouldEqual` true

View File

@ -2,12 +2,63 @@ module Test.Main where
import Prelude import Prelude
import Effect (Effect) import Control.Alternative (guard)
import Effect.Aff (launchAff_) import Control.Monad.Rec.Class (untilJust)
import Data.Either (Either(..), hush)
import Data.Filterable (filter)
import Data.Maybe (Maybe(..), isNothing, maybe)
import Data.Newtype (wrap)
import Data.String as String
import Effect (Effect, untilE)
import Effect.Aff (Aff, bracket, delay, launchAff_, makeAff)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Node.Buffer as Buffer
import Node.ChildProcess (ChildProcess)
import Node.ChildProcess as ChildProcess
import Node.ChildProcess.Aff as ChildProcess.Aff
import Node.ChildProcess.Types (Exit(..), stringSignal)
import Node.Encoding (Encoding(..))
import Node.EventEmitter as Event
import Test.Data.Postgres as Test.Data.Postgres import Test.Data.Postgres as Test.Data.Postgres
import Test.Spec.Reporter (consoleReporter) import Test.Effect.Postgres.Client as Test.Effect.Postgres.Client
import Test.Spec.Reporter (specReporter)
import Test.Spec.Runner (runSpec) import Test.Spec.Runner (runSpec)
spawnDb :: Aff ChildProcess
spawnDb =
let
isReady = do
{ exitStatus, error } <- liftEffect (ChildProcess.spawnSync "docker" [ "compose", "exec", "db", "pg_isready" ])
let
exitOk (Normally 0) = true
exitOk _ = false
pure $ isNothing error && exitOk exitStatus
waitReady =
void $ untilJust do
delay $ wrap $ 100.0
isReady' <- isReady
pure $ filter (const isReady') (Just unit)
in
do
liftEffect $ log $ "[db] starting..."
db <- liftEffect $ ChildProcess.spawn "docker" [ "compose", "up" ]
waitReady
liftEffect $ log $ "[db] started!"
pure db
killDb :: ChildProcess -> Aff Unit
killDb db = do
let
onexit eff = Event.on ChildProcess.exitH eff db
exit = makeAff \res -> mempty <$ onexit (const $ res $ Right unit)
_ <- liftEffect $ ChildProcess.kill' (stringSignal "SIGTERM") db
exit
main :: Effect Unit main :: Effect Unit
main = launchAff_ $ runSpec [ consoleReporter ] do main = launchAff_ do
bracket spawnDb killDb
$ const
$ runSpec [ specReporter ] do
Test.Data.Postgres.spec Test.Data.Postgres.spec
Test.Effect.Postgres.Client.spec