2021-11-23 21:15:17 +03:00
|
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
|
2021-11-17 22:50:39 +03:00
|
|
|
-- | Helpers for talking to graphql engine.
|
|
|
|
module Harness.GraphqlEngine
|
2022-02-14 20:24:24 +03:00
|
|
|
( -- * HTTP Calls
|
|
|
|
|
|
|
|
-- ** POST
|
|
|
|
post,
|
2021-11-17 22:50:39 +03:00
|
|
|
post_,
|
2022-02-14 20:24:24 +03:00
|
|
|
postWithHeaders,
|
|
|
|
postWithHeaders_,
|
|
|
|
postMetadata_,
|
2021-11-17 22:50:39 +03:00
|
|
|
postGraphql,
|
|
|
|
postGraphqlYaml,
|
2022-02-14 20:24:24 +03:00
|
|
|
|
|
|
|
-- ** Misc.
|
2022-01-25 19:34:29 +03:00
|
|
|
setSource,
|
|
|
|
setSources,
|
2022-02-14 20:24:24 +03:00
|
|
|
|
|
|
|
-- * Misc. Helpers
|
|
|
|
graphqlEndpoint,
|
|
|
|
|
|
|
|
-- * Server Setup & Teardown
|
2021-11-23 21:15:17 +03:00
|
|
|
startServerThread,
|
|
|
|
stopServer,
|
2022-02-14 20:24:24 +03:00
|
|
|
|
|
|
|
-- * Re-exports
|
2021-11-23 21:15:17 +03:00
|
|
|
serverUrl,
|
|
|
|
Server,
|
2021-11-17 22:50:39 +03:00
|
|
|
)
|
|
|
|
where
|
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
import Control.Concurrent (forkIO, killThread, threadDelay)
|
|
|
|
import Control.Exception.Safe (bracket)
|
2021-11-23 21:15:17 +03:00
|
|
|
import Control.Monad.Trans.Managed (ManagedT (..), lowerManagedT)
|
2022-02-14 20:24:24 +03:00
|
|
|
import Data.Aeson (Value, object, (.=))
|
2021-11-23 21:15:17 +03:00
|
|
|
import Data.Environment qualified as Env
|
|
|
|
import Data.Text qualified as T
|
2022-02-14 20:24:24 +03:00
|
|
|
import Data.Time (getCurrentTime)
|
|
|
|
import GHC.Stack (HasCallStack)
|
2021-11-17 22:50:39 +03:00
|
|
|
import Harness.Constants qualified as Constants
|
|
|
|
import Harness.Http qualified as Http
|
2022-01-25 19:34:29 +03:00
|
|
|
import Harness.Quoter.Yaml (yaml)
|
2022-02-14 20:24:24 +03:00
|
|
|
import Harness.State (Server (..), State, getServer, serverUrl)
|
|
|
|
import Hasura.App (Loggers (..), ServeCtx (..))
|
|
|
|
import Hasura.App qualified as App
|
2021-11-23 21:15:17 +03:00
|
|
|
import Hasura.Logging (Hasura)
|
2022-02-14 20:24:24 +03:00
|
|
|
import Hasura.Prelude hiding (State, state)
|
|
|
|
import Hasura.RQL.Types (PGConnectionParams (..), UrlConf (..))
|
|
|
|
import Hasura.Server.Init (PostgresConnInfo (..), ServeOptions (..))
|
2021-11-23 21:15:17 +03:00
|
|
|
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
|
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-------------------------------------------------------------------------------
|
2021-11-17 22:50:39 +03:00
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-- HTTP Calls - POST
|
|
|
|
|
|
|
|
-- | Post some JSON to graphql-engine, getting back more JSON.
|
|
|
|
--
|
|
|
|
-- Optimistically assumes success; use another function if you want to test for
|
|
|
|
-- failure.
|
|
|
|
--
|
|
|
|
-- See 'postWithHeaders' to issue a request with 'Http.RequestHeaders'.
|
2021-11-23 21:15:17 +03:00
|
|
|
post :: HasCallStack => State -> String -> Value -> IO Value
|
2022-02-14 20:24:24 +03:00
|
|
|
post state path = postWithHeaders state path mempty
|
2021-11-17 22:50:39 +03:00
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-- | Same as 'post', but ignores the value.
|
|
|
|
--
|
|
|
|
-- See 'postWithHeaders_' to issue a request with 'Http.RequestHeaders'.
|
2021-11-23 21:15:17 +03:00
|
|
|
post_ :: HasCallStack => State -> String -> Value -> IO ()
|
2022-02-14 20:24:24 +03:00
|
|
|
post_ state path = void . postWithHeaders_ state path mempty
|
2021-11-17 22:50:39 +03:00
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-- | Post some JSON to graphql-engine, getting back more JSON.
|
|
|
|
--
|
|
|
|
-- Optimistically assumes success; use another function if you want to test for
|
|
|
|
-- failure.
|
|
|
|
postWithHeaders ::
|
|
|
|
HasCallStack => State -> String -> Http.RequestHeaders -> Value -> IO Value
|
|
|
|
postWithHeaders (getServer -> Server {urlPrefix, port}) path =
|
|
|
|
Http.postValue_ (urlPrefix ++ ":" ++ show port ++ path)
|
|
|
|
|
|
|
|
-- | Post some JSON to graphql-engine, getting back more JSON.
|
|
|
|
--
|
|
|
|
-- Optimistically assumes success; use another function if you want to test for
|
|
|
|
-- failure.
|
|
|
|
postWithHeaders_ ::
|
|
|
|
HasCallStack => State -> String -> Http.RequestHeaders -> Value -> IO ()
|
|
|
|
postWithHeaders_ state path headers =
|
|
|
|
void . postWithHeaders state path headers
|
|
|
|
|
|
|
|
-- | Same as 'postWithHeaders', but defaults to the graphql end-point.
|
2021-11-23 21:15:17 +03:00
|
|
|
postGraphqlYaml :: HasCallStack => State -> Value -> IO Value
|
2022-02-14 20:24:24 +03:00
|
|
|
postGraphqlYaml state = post state "/v1/graphql"
|
2021-11-17 22:50:39 +03:00
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-- | Same as 'postGraphqlYaml', but adds the @{query:..}@ wrapper.
|
2021-11-23 21:15:17 +03:00
|
|
|
postGraphql :: HasCallStack => State -> Value -> IO Value
|
2022-02-14 20:24:24 +03:00
|
|
|
postGraphql state value = postGraphqlYaml state (object ["query" .= value])
|
|
|
|
|
|
|
|
-- | Same as 'post_', but defaults to the @"v1/metadata"@ endpoint.
|
|
|
|
--
|
|
|
|
-- @headers@ are mostly irrelevant for the admin endpoint @v1/metadata@.
|
|
|
|
postMetadata_ :: HasCallStack => State -> Value -> IO ()
|
|
|
|
postMetadata_ state = post_ state "/v1/metadata"
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- HTTP Calls - Misc.
|
2021-11-23 21:15:17 +03:00
|
|
|
|
2022-01-25 19:34:29 +03:00
|
|
|
-- | Replace the engine's metadata to use the given source as the only source.
|
|
|
|
setSource :: State -> Value -> IO ()
|
2022-02-14 20:24:24 +03:00
|
|
|
setSource state sourceMetadata = setSources state [sourceMetadata]
|
2022-01-25 19:34:29 +03:00
|
|
|
|
|
|
|
-- | Replace the engine's metadata to use the given sources.
|
|
|
|
setSources :: State -> [Value] -> IO ()
|
2022-02-14 20:24:24 +03:00
|
|
|
setSources state sourcesMetadata =
|
|
|
|
postMetadata_
|
|
|
|
state
|
2022-01-25 19:34:29 +03:00
|
|
|
[yaml|
|
|
|
|
type: replace_metadata
|
|
|
|
args:
|
|
|
|
version: 3
|
|
|
|
sources: *sourcesMetadata
|
|
|
|
|]
|
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | Extracts the full GraphQL endpoint URL from a given 'Server'.
|
|
|
|
--
|
|
|
|
-- @
|
|
|
|
-- > graphqlEndpoint (Server 8080 "http://localhost" someThreadId)
|
|
|
|
-- "http://localhost:8080/graphql"
|
|
|
|
-- @
|
|
|
|
graphqlEndpoint :: Server -> String
|
|
|
|
graphqlEndpoint server = serverUrl server ++ "/graphql"
|
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
2021-11-23 21:15:17 +03:00
|
|
|
-- | 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
|
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-- | Forcibly stop a given 'Server'.
|
2021-11-23 21:15:17 +03:00
|
|
|
stopServer :: Server -> IO ()
|
|
|
|
stopServer Server {threadId} = killThread threadId
|
|
|
|
|
2022-02-14 20:24:24 +03:00
|
|
|
-------------------------------------------------------------------------------
|
2021-11-23 21:15:17 +03:00
|
|
|
|
|
|
|
-- | 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
|
2022-02-14 20:24:24 +03:00
|
|
|
globalCtx <- App.initGlobalCtx env metadataDbUrl rci
|
2021-11-23 21:15:17 +03:00
|
|
|
do
|
|
|
|
(ekgStore, serverMetrics) <-
|
|
|
|
liftIO $ do
|
|
|
|
store <- EKG.newStore @TestMetricsSpec
|
|
|
|
serverMetrics <-
|
|
|
|
liftIO $ createServerMetrics $ EKG.subset ServerSubset store
|
|
|
|
pure (EKG.subset EKG.emptyOf store, serverMetrics)
|
2022-02-14 20:24:24 +03:00
|
|
|
runManagedT (App.initialiseServeCtx env globalCtx serveOptions) $ \serveCtx ->
|
2021-11-23 21:15:17 +03:00
|
|
|
do
|
|
|
|
let Loggers _ _logger pgLogger = _scLoggers serveCtx
|
2022-02-14 20:24:24 +03:00
|
|
|
flip App.runPGMetadataStorageAppT (_scMetadataDbPool serveCtx, pgLogger)
|
2021-11-23 21:15:17 +03:00
|
|
|
. lowerManagedT
|
|
|
|
$ do
|
2022-02-14 20:24:24 +03:00
|
|
|
App.runHGEServer
|
2021-11-23 21:15:17 +03:00
|
|
|
(const $ pure ())
|
|
|
|
env
|
|
|
|
serveOptions
|
|
|
|
serveCtx
|
|
|
|
initTime
|
|
|
|
Nothing
|
|
|
|
serverMetrics
|
|
|
|
ekgStore
|
2022-02-14 20:24:24 +03:00
|
|
|
|
|
|
|
-- | Used only for 'runApp' above.
|
|
|
|
data TestMetricsSpec name metricType tags
|
|
|
|
= ServerSubset (ServerMetricsSpec name metricType tags)
|