graphql-engine/server/tests-hspec/Harness/GraphqlEngine.hs
Chris Done 926da74819 Auto-launch graphql-engine (close hasura/graphql-engine#7801, hasura/graphql-engine#7827 )
Dupe of https://github.com/hasura/graphql-engine-mono/pull/2853 with branch renamed so it doesn't break a tool.

prev pr: https://github.com/hasura/graphql-engine-mono/pull/2911

next pr: https://github.com/hasura/graphql-engine-mono/pull/2922

This implements https://github.com/hasura/graphql-engine/issues/7801

Some points to keep in mind for review:

* How state is passed to the tests. Do we like how this works?
   * I quite like it, with the opaque type [`State`](68f33051ca/server/tests-hspec/Harness/State.hs (L17)), we can avoid churn when adding things.
   * The [setup/teardown](68f33051ca/server/tests-hspec/Spec.hs (L19-L31)) seems clean.
   * By using hspec's own means to pass and denote context, we avoid "getting new ideas" for how to structure the tests -- we use a standard. Hopefully, that means the tests' structure rarely change.
* The various flags passed in the [ServeOptions](68f33051ca/server/tests-hspec/Harness/Constants.hs (L123)) - if there are any causes for concern, raise them here. My thinking is that, there are lots. I've picked some "sane" defaults (mostly empty). The tests pass. I think as we add more complex tests, these flags will be scrutinised and updated as needed. I think it's valuable that all flags are explicitly listed here, though.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2921
GitOrigin-RevId: 2c2e70bf784ef571a48509a7e5006fd0f48773b5
2021-11-23 18:16:15 +00:00

135 lines
4.6 KiB
Haskell

{-# LANGUAGE ViewPatterns #-}
-- | Helpers for talking to graphql engine.
module Harness.GraphqlEngine
( post,
post_,
postGraphql,
postGraphqlYaml,
startServerThread,
stopServer,
serverUrl,
Server,
)
where
import Control.Concurrent
import Control.Exception
import Control.Monad.Trans.Managed (ManagedT (..), lowerManagedT)
import Data.Aeson
import Data.Environment qualified as Env
import Data.Functor
import Data.Text qualified as T
import Data.Time
import GHC.Stack
import Harness.Constants qualified as Constants
import Harness.Http qualified as Http
import Harness.State
import Hasura.App
import Hasura.Logging (Hasura)
import Hasura.Prelude hiding (State)
import Hasura.RQL.Types
import Hasura.Server.Init
import Hasura.Server.Metrics (ServerMetricsSpec, createServerMetrics)
import Network.Socket qualified as Socket
import Network.Wai.Handler.Warp qualified as Warp
import System.Metrics qualified as EKG
-- | Used only for 'runApp' below.
data TestMetricsSpec name metricType tags
= ServerSubset (ServerMetricsSpec name metricType tags)
-- | Post some JSON to graphql-engine, getting back more
-- JSON. Optimistically assumes success. Use another function if you
-- want to test for failure.
post :: HasCallStack => State -> String -> Value -> IO Value
post (getServer -> Server {urlPrefix, port}) path =
Http.postValue_ (urlPrefix ++ ":" ++ show port ++ path)
-- | Same as post, but ignores the value.
post_ :: HasCallStack => State -> String -> Value -> IO ()
post_ server path = void . post server path
-- | Same as post, but defaults to the graphql end-point.
postGraphqlYaml :: HasCallStack => State -> Value -> IO Value
postGraphqlYaml server = post server "/v1/graphql"
-- | Same as 'postGraphqlYaml', but adds the {query:..} wrapper.
postGraphql :: HasCallStack => State -> Value -> IO Value
postGraphql server value = postGraphqlYaml server (object ["query" .= value])
-- | Choose a random port and start a graphql-engine server on that
-- port accessible from localhost. It waits until the server is
-- available before returning.
--
-- The port availability is subject to races.
startServerThread :: Maybe (String, Int) -> IO Server
startServerThread murlPrefixport = do
(urlPrefix, port, threadId) <-
case murlPrefixport of
Just (urlPrefix, port) -> do
threadId <- forkIO (forever (threadDelay 1000000)) -- Just wait.
pure (urlPrefix, port, threadId)
Nothing -> do
port <- bracket (Warp.openFreePort) (Socket.close . snd) (pure . fst)
let urlPrefix = "http://127.0.0.1"
threadId <-
forkIO (runApp Constants.serveOptions {soPort = fromIntegral port})
pure (urlPrefix, port, threadId)
let server = Server {port = fromIntegral port, urlPrefix, threadId}
Http.healthCheck (serverUrl server)
pure server
-- | Stop the server.
stopServer :: Server -> IO ()
stopServer Server {threadId} = killThread threadId
-- | Get the server URL, for sanity checking.
serverUrl :: Server -> String
serverUrl Server {urlPrefix, port} = urlPrefix ++ ":" ++ show port
-- | Run the graphql-engine server.
runApp :: ServeOptions Hasura.Logging.Hasura -> IO ()
runApp serveOptions = do
let rci =
PostgresConnInfo
{ _pciDatabaseConn =
Just
( UrlFromParams
PGConnectionParams
{ _pgcpHost = T.pack Constants.postgresHost,
_pgcpUsername = T.pack Constants.postgresUser,
_pgcpPassword = Just (T.pack Constants.postgresPassword),
_pgcpPort = fromIntegral Constants.postgresPort,
_pgcpDatabase = T.pack Constants.postgresDb
}
),
_pciRetries = Nothing
}
metadataDbUrl = Just Constants.postgresqlConnectionString
env <- Env.getEnvironment
initTime <- liftIO getCurrentTime
globalCtx <- initGlobalCtx env metadataDbUrl rci
do
(ekgStore, serverMetrics) <-
liftIO $ do
store <- EKG.newStore @TestMetricsSpec
serverMetrics <-
liftIO $ createServerMetrics $ EKG.subset ServerSubset store
pure (EKG.subset EKG.emptyOf store, serverMetrics)
runManagedT (initialiseServeCtx env globalCtx serveOptions) $ \serveCtx ->
do
let Loggers _ _logger pgLogger = _scLoggers serveCtx
flip runPGMetadataStorageAppT (_scMetadataDbPool serveCtx, pgLogger)
. lowerManagedT
$ do
runHGEServer
(const $ pure ())
env
serveOptions
serveCtx
initTime
Nothing
serverMetrics
ekgStore