server/tests-py: Start helper services in the test harness.

This makes it possible for the test harness to start the test JWK server and the test remote schema server.

In order to do this, we still generate the TLS certificates in the test script (because we need to install the generated CA certificate in the OS certificate store), and then pass the certificate and key paths into the test runner.

Because we are still using _test-server.sh_ for now, we don't use the JWK server fixture in that case, as HGE needs the JWK server to be up and running when it starts. Instead, we keep running it outside (for now).

This is also the case for the GraphQL server fixture when we are running the server upgrade/downgrade tests.

I have also refactored _graphql_server.py_ so there isn't a global `HGE_URLS` value, but instead the value is passed through.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6303
GitOrigin-RevId: 06f05ff674372dc5d632e55d68e661f5c7a17c10
This commit is contained in:
Samir Talwar 2022-10-13 17:43:59 +02:00 committed by hasura-bot
parent 2e0cd9267b
commit 0d4d7e6b1e
11 changed files with 333 additions and 221 deletions

View File

@ -22,11 +22,9 @@ stop_services() {
[[ -n "${HGE_PIDS[*]}" ]] && kill -s INT "${HGE_PIDS[@]}" || true
[[ -n "$WH_PID" ]] && kill "$WH_PID" || true
[[ -n "$GQL_SERVER_PID" ]] && kill "$GQL_SERVER_PID" || true
[[ -n "${HGE_PIDS[*]}" ]] && wait "${HGE_PIDS[@]}" || true
[[ -n "$WH_PID" ]] && wait "$WH_PID" || true
[[ -n "$GQL_SERVER_PID" ]] && wait "$GQL_SERVER_PID" || true
}
time_elapsed() {
@ -229,7 +227,6 @@ PYTEST_PARALLEL_ARGS=(
HGE_PIDS=()
WH_PID=""
GQL_SERVER_PID=""
trap stop_services ERR
trap stop_services INT
@ -970,23 +967,18 @@ remote-schema-https)
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH SECURE REMOTE SCHEMA #########################>\n"
OLD_REMOTE_SCHEMAS_WEBHOOK_DOMAIN="${REMOTE_SCHEMAS_WEBHOOK_DOMAIN}"
export REMOTE_SCHEMAS_WEBHOOK_DOMAIN="https://localhost:5001"
export REMOTE_SCHEMAS_WEBHOOK_DOMAIN="https://localhost:5000"
init_ssl
run_hge_with_args serve
wait_for_port 8080
python3 graphql_server.py 5001 "$OUTPUT_FOLDER/ssl/webhook.pem" "$OUTPUT_FOLDER/ssl/webhook-key.pem" >"$OUTPUT_FOLDER/remote_gql_server.log" 2>&1 &
GQL_SERVER_PID=$!
wait_for_port 5001
pytest "${PYTEST_COMMON_ARGS[@]}" test_schema_stitching.py::TestRemoteSchemaBasic
pytest "${PYTEST_COMMON_ARGS[@]}" \
--tls-cert="$OUTPUT_FOLDER/ssl/webhook.pem" --tls-key="$OUTPUT_FOLDER/ssl/webhook-key.pem" \
test_schema_stitching.py::TestRemoteSchemaBasic
export REMOTE_SCHEMAS_WEBHOOK_DOMAIN="${OLD_REMOTE_SCHEMAS_WEBHOOK_DOMAIN}"
kill_hge_servers
kill "$GQL_SERVER_PID"
;;
@ -1165,79 +1157,77 @@ jwk-url)
export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM"
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH JWK URL ########> \n"
# start the JWK server
export JWK_SERVER_URL='http://localhost:5001'
# Start the JWK server.
# There is a fixture to do this, but when running in this fashion, we need to
# start the JWK server first so the HGE server can communicate with it.
python3 jwk_server.py >"$OUTPUT_FOLDER/jwk_server.log" 2>&1 &
JWKS_PID=$!
wait_for_port 5001
echo "Test: Cache-Control with max-age=3"
export HASURA_GRAPHQL_JWT_SECRET='{"jwk_url": "http://localhost:5001/jwk-cache-control?max-age=3"}'
export HASURA_GRAPHQL_JWT_SECRET="{\"jwk_url\": \"${JWK_SERVER_URL}/jwk-cache-control?max-age=3\"}"
run_hge_with_args serve
wait_for_port 8080
pytest "${PYTEST_COMMON_ARGS[@]}" \
--test-jwk-url \
-k 'test_cache_control_header_max_age'
-- 'test_jwk.py::test_cache_control_header_max_age'
kill_hge_servers
unset HASURA_GRAPHQL_JWT_SECRET
echo "Test: Cache-Control with must-revalidate, max-age=3"
export HASURA_GRAPHQL_JWT_SECRET='{"jwk_url": "http://localhost:5001/jwk-cache-control?must-revalidate=true&must-revalidate=true"}'
export HASURA_GRAPHQL_JWT_SECRET="{\"jwk_url\": \"${JWK_SERVER_URL}/jwk-cache-control?max-age=3&must-revalidate=true\"}"
run_hge_with_args serve
wait_for_port 8080
pytest "${PYTEST_COMMON_ARGS[@]}" \
--test-jwk-url \
-k 'test_cache_control_header_max_age'
-- 'test_jwk.py::test_cache_control_header_max_age_must_revalidate'
kill_hge_servers
unset HASURA_GRAPHQL_JWT_SECRET
echo "Test: Cache-Control with must-revalidate"
export HASURA_GRAPHQL_JWT_SECRET='{"jwk_url": "http://localhost:5001/jwk-cache-control?must-revalidate=true"}'
export HASURA_GRAPHQL_JWT_SECRET="{\"jwk_url\": \"${JWK_SERVER_URL}/jwk-cache-control?must-revalidate=true\"}"
run_hge_with_args serve
wait_for_port 8080
pytest "${PYTEST_COMMON_ARGS[@]}" \
--test-jwk-url \
-k 'test_cache_control_header_no_caching'
-- 'test_jwk.py::test_cache_control_header_must_revalidate'
kill_hge_servers
unset HASURA_GRAPHQL_JWT_SECRET
echo "Test: Cache-Control with no-cache, public"
export HASURA_GRAPHQL_JWT_SECRET='{"jwk_url": "http://localhost:5001/jwk-cache-control?no-cache=true&public=true"}'
export HASURA_GRAPHQL_JWT_SECRET="{\"jwk_url\": \"${JWK_SERVER_URL}/jwk-cache-control?no-cache=true&public=true\"}"
run_hge_with_args serve
wait_for_port 8080
pytest "${PYTEST_COMMON_ARGS[@]}" \
--test-jwk-url \
-k 'test_cache_control_header_no_caching'
-- 'test_jwk.py::test_cache_control_header_no_cache_public'
kill_hge_servers
unset HASURA_GRAPHQL_JWT_SECRET
echo "Test: Cache-Control with no-store, max-age=3"
export HASURA_GRAPHQL_JWT_SECRET='{"jwk_url": "http://localhost:5001/jwk-cache-control?no-store=true&max-age=3"}'
export HASURA_GRAPHQL_JWT_SECRET="{\"jwk_url\": \"${JWK_SERVER_URL}/jwk-cache-control?no-store=true&max-age=3\"}"
run_hge_with_args serve
wait_for_port 8080
pytest "${PYTEST_COMMON_ARGS[@]}" \
--test-jwk-url \
-k 'test_cache_control_header_no_caching'
-- 'test_jwk.py::test_cache_control_header_no_store_max_age'
kill_hge_servers
unset HASURA_GRAPHQL_JWT_SECRET
echo "Test: Expires with three second expiry"
export HASURA_GRAPHQL_JWT_SECRET='{"jwk_url": "http://localhost:5001/jwk-expires?seconds=3"}'
export HASURA_GRAPHQL_JWT_SECRET="{\"jwk_url\": \"${JWK_SERVER_URL}/jwk-expires?seconds=3\"}"
run_hge_with_args serve
wait_for_port 8080
pytest "${PYTEST_COMMON_ARGS[@]}" \
--test-jwk-url \
-k 'test_expires_header'
-- 'test_jwk.py::test_expires_header'
kill_hge_servers
unset HASURA_GRAPHQL_JWT_SECRET

View File

@ -2,11 +2,13 @@ import collections
import os
import pytest
import re
import socket
import sqlalchemy
import sys
import threading
import time
from typing import Optional
from typing import Optional, Tuple
import urllib.parse
import uuid
import auth_webhook_server
@ -35,6 +37,10 @@ def pytest_addoption(parser):
required=False,
nargs='+'
)
parser.addoption('--tls-cert', help='The TLS certificate used for helper services', required=False)
parser.addoption('--tls-key', help='The TLS key used for helper services', required=False)
parser.addoption(
"--hge-webhook", metavar="HGE_WEBHOOK", help="url for graphql-engine's access control webhook", required=False
)
@ -83,14 +89,6 @@ def pytest_addoption(parser):
help="Run testcases for startup database calls"
)
parser.addoption(
"--test-jwk-url",
action="store_true",
default=False,
required=False,
help="Run testcases for JWK url behaviour"
)
parser.addoption(
"--accept",
action="store_true",
@ -297,23 +295,33 @@ def pg_version(request) -> int:
def pg_url(request) -> str:
return request.config.workerinput["pg-url"]
@pytest.fixture(scope='session')
def tls_configuration(request: pytest.FixtureRequest) -> Optional[Tuple[str, str]]:
tls_cert: Optional[str] = request.config.getoption('--tls-cert') # type: ignore
tls_key: Optional[str] = request.config.getoption('--tls-key') # type: ignore
if tls_cert and tls_key:
return (tls_cert, tls_key)
else:
return None
# Any test caught by this would also be caught by `hge_skip_function`, but
# this is faster.
@pytest.fixture(scope='class', autouse=True)
def hge_skip_class(request: pytest.FixtureRequest, hge_server: Optional[str]):
hge_skip(request, hge_server)
def hge_skip_class(request: pytest.FixtureRequest, hge_server: Optional[str], hge_fixture_env: dict[str, str]):
hge_skip(request, hge_server, hge_fixture_env)
@pytest.fixture(scope='function', autouse=True)
def hge_skip_function(request: pytest.FixtureRequest, hge_server: Optional[str]):
hge_skip(request, hge_server)
def hge_skip_function(request: pytest.FixtureRequest, hge_server: Optional[str], hge_fixture_env: dict[str, str]):
hge_skip(request, hge_server, hge_fixture_env)
def hge_skip(request: pytest.FixtureRequest, hge_server: Optional[str]):
def hge_skip(request: pytest.FixtureRequest, hge_server: Optional[str], hge_fixture_env: dict[str, str]):
# Let `hge_server` manage this stuff.
if hge_server:
return
# Ensure that the correct environment variables have been set for the given test.
hge_marker_env: dict[str, str] = {marker.args[0]: marker.args[1] for marker in request.node.iter_markers('hge_env')}
incorrect_env = {name: value for name, value in hge_marker_env.items() if os.getenv(name) != value}
hge_env = {**hge_marker_env, **hge_fixture_env}
incorrect_env = {name: value for name, value in hge_env.items() if os.getenv(name) != value}
if len(incorrect_env) > 0:
pytest.skip(
'This test expects the following environment variables: '
@ -473,18 +481,39 @@ def scheduled_triggers_evts_webhook(hge_fixture_env: dict[str, str]):
webhook_httpd.server_close()
web_server.join()
@pytest.fixture(scope='class')
@pytest.fixture(scope='class', params=[False, True])
@pytest.mark.early
def gql_server(request, hge_fixture_env: dict[str, str]):
def gql_server(request: pytest.FixtureRequest, hge_bin: Optional[str], hge_url: str, hge_fixture_env: dict[str, str], tls_configuration: Optional[Tuple[str, str]]):
port = 5000
hge_urls: list[str] = request.config.getoption('--hge-urls')
graphql_server.set_hge_urls(hge_urls)
server = HGECtxGQLServer(port=port)
if socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1', port)) == 0:
# The server is already running. This might be the case if we're running the server upgrade/downgrade tests.
# In this case, skip starting it and rely on the external service.
url = f'http://127.0.0.1:{port}'
hge_fixture_env['REMOTE_SCHEMAS_WEBHOOK_DOMAIN'] = url
# Create an object with a field named "url", set to `url`.
return collections.namedtuple('ExternalGraphQLServer', ['url'])(url)
hge_urls: list[str] = request.config.getoption('--hge-urls') or [hge_url] # type: ignore
use_tls: bool = request.param # type: ignore
scheme = None
if not hge_bin:
scheme = str(urllib.parse.urlparse(os.getenv('REMOTE_SCHEMAS_WEBHOOK_DOMAIN')).scheme)
if use_tls:
if scheme is not None and scheme != 'https':
pytest.skip(f'Cannot run the remote schema server with TLS; HGE is configured to talk to it over "{scheme}".')
if not tls_configuration:
pytest.skip('Cannot run the remote schema server with TLS; no certificate and key provided.')
server = HGECtxGQLServer(port=port, tls_configuration=tls_configuration, hge_urls=hge_urls)
else:
if scheme is not None and scheme != 'http':
pytest.skip(f'Cannot run the remote schema server without TLS; HGE is configured to talk to it over "{scheme}".')
server = HGECtxGQLServer(port=port, hge_urls=hge_urls)
server.start_server()
hge_fixture_env['REMOTE_SCHEMAS_WEBHOOK_DOMAIN'] = server.url
request.addfinalizer(server.stop_server)
ports.wait_for_port(port)
yield server
server.stop_server()
return server
@pytest.fixture(scope='class')
def ws_client(request, hge_ctx):

View File

@ -19,7 +19,7 @@ import string
import subprocess
import threading
import time
from typing import Any, Optional
from typing import Any, Optional, Tuple
from urllib.parse import urlparse
import websocket
@ -769,14 +769,16 @@ class EvtsWebhookServer(ThreadedHTTPServer):
return f'http://{self.server_address[0]}:{self.server_address[1]}'
class HGECtxGQLServer:
def __init__(self, port: int):
def __init__(self, port: int, tls_configuration: Optional[Tuple[str, str]] = None, hge_urls: list[str] = []):
# start the graphql server
self.port = port
self.tls_configuration = tls_configuration
self.hge_urls = hge_urls
self.is_running: bool = False
def start_server(self):
if not self.is_running:
self.server = graphql_server.create_server('localhost', self.port)
self.server = graphql_server.create_server('localhost', self.port, self.tls_configuration, self.hge_urls)
self.thread = threading.Thread(target=self.server.serve_forever)
self.thread.start()
self.is_running = True
@ -789,7 +791,12 @@ class HGECtxGQLServer:
@property
def url(self):
return f'http://{self.server.server_address[0]}:{self.server.server_address[1]}'
scheme = 'https' if self.tls_configuration else 'http'
# We must use 'localhost' and not `self.server.server_address[0]`
# because when using TLS, we need a domain name, not an IP address.
host = 'localhost'
port = self.server.server_address[1]
return f'{scheme}://{host}:{port}'
class HGECtx:

View File

@ -8,12 +8,19 @@ from http import HTTPStatus
import time
import ssl
import sys
from typing import Callable, NamedTuple, Optional, Tuple, Union
from urllib.parse import urlparse
from graphql import GraphQLError
from webserver import MkHandlers, RequestHandler, Response
HGE_URLS=[]
DEFAULT_PORT = 5000
class Context(NamedTuple):
hge_urls: Optional[list[str]]
def mkJSONResp(graphql_result, extensions={}):
return Response(HTTPStatus.OK, {**graphql_result.to_dict(), **extensions},
@ -749,21 +756,42 @@ class EchoGraphQL(RequestHandler):
{'Content-Type': 'application/json'})
def header_test_expected_headers(hge_urls: Optional[list[str]]) -> dict[str, Union[list[str], Callable[[list[str]], bool]]]:
return {
'authorization': ['Bearer abcdef'],
'content-type': ['application/json'],
'x-hasura-role': ['user'],
'x-hasura-test': ['abcd'],
'x-hasura-user-id': ['abcd1234'],
'x-forwarded-host': lambda headers: all(header in hge_urls for header in headers) if hge_urls else True,
'x-forwarded-user-agent': lambda headers: all(header.startswith('python-requests/') for header in headers),
}
def header_test_expected_headers_str(hge_urls: Optional[list[str]]):
expected_headers = header_test_expected_headers(hge_urls)
return '\n'.join(f'{name}: {value}' for name, value in {
'authorization': repr(expected_headers['authorization']),
'content-type': repr(expected_headers['content-type']),
'x-hasura-role': repr(expected_headers['x-hasura-role']),
'x-hasura-test': repr(expected_headers['x-hasura-test']),
'x-hasura-user-id': repr(expected_headers['x-hasura-user-id']),
'x-forwarded-host': f'one of {hge_urls!r}' if hge_urls else 'ignored',
'x-forwarded-user-agent': 'starts with "python-requests"',
}.items())
class HeaderTest(graphene.ObjectType):
wassup = graphene.String(arg=graphene.String(default_value='world'))
def resolve_wassup(self, info, arg):
headers = info.context
hosts = list(map(lambda o: urlparse(o).netloc, HGE_URLS))
if not (headers.get_all('x-hasura-test') == ['abcd'] and
headers.get_all('x-hasura-role') == ['user'] and
headers.get_all('x-hasura-user-id') == ['abcd1234'] and
headers.get_all('content-type') == ['application/json'] and
headers.get_all('Authorization') == ['Bearer abcdef'] and
len(headers.get_all('x-forwarded-host')) == 1 and
all(host in headers.get_all('x-forwarded-host') for host in hosts) and
headers.get_all('x-forwarded-user-agent')[0].startswith('python-requests')):
raise Exception('headers dont match. Received: ' + str(headers))
hge_urls = info.context.context.hge_urls
actual_headers = info.context.headers
for header_name, expected_header_values in header_test_expected_headers(hge_urls).items():
actual_header_values: list[str] = actual_headers.get_all(header_name)
if callable(expected_header_values):
if not expected_header_values(actual_header_values):
raise Exception(f'Header {header_name} doesn\'t match.\n\nActual headers:\n{actual_headers}Expected headers:\n{header_test_expected_headers_str(hge_urls)}')
elif expected_header_values != actual_header_values:
raise Exception(f'Header {header_name} doesn\'t match: {expected_header_values} != {actual_header_values}\n\nActual headers:\n{actual_headers}Expected headers:\n{header_test_expected_headers_str(hge_urls)}')
return "Hello " + arg
@ -776,8 +804,7 @@ class HeaderTestGraphQL(RequestHandler):
def post(self, request):
if not request.json:
return Response(HTTPStatus.BAD_REQUEST)
res = header_test_schema.execute(request.json['query'],
context=request.headers)
res = header_test_schema.execute(request.json['query'], context=request)
return mkJSONResp(res)
@ -849,61 +876,74 @@ class JsonScalarGraphQL(RequestHandler):
res = json_schema.execute(request.json['query'])
return mkJSONResp(res)
handlers = MkHandlers({
'/hello': HelloWorldHandler,
'/hello-graphql': HelloGraphQL,
'/hello-echo-request-graphql': HelloGraphQLEchoRequest,
'/hello-graphql-extensions': HelloGraphQLExtensions,
'/user-graphql': UserGraphQL,
'/country-graphql': CountryGraphQL,
'/character-iface-graphql' : CharacterInterfaceGraphQL,
'/iface-graphql-err-empty-field-list' : InterfaceGraphQLErrEmptyFieldList,
'/iface-graphql-err-unknown-iface' : InterfaceGraphQLErrUnknownInterface,
'/iface-graphql-err-missing-field' : InterfaceGraphQLErrMissingField,
'/iface-graphql-err-wrong-field-type' : InterfaceGraphQLErrWrongFieldType,
'/iface-graphql-err-missing-arg' : InterfaceGraphQLErrMissingArg,
'/iface-graphql-err-wrong-arg-type' : InterfaceGraphQLErrWrongArgType,
'/iface-graphql-err-extra-non-null-arg' : InterfaceGraphQLErrExtraNonNullArg,
'/union-graphql' : UnionGraphQL,
'/union-graphql-err-unknown-types' : UnionGraphQLSchemaErrUnknownTypes,
'/union-graphql-err-subtype-iface' : UnionGraphQLSchemaErrSubTypeInterface,
'/union-graphql-err-no-member-types' : UnionGraphQLSchemaErrNoMemberTypes,
'/union-graphql-err-wrapped-type' : UnionGraphQLSchemaErrWrappedType,
'/default-value-echo-graphql' : EchoGraphQL,
'/person-graphql': PersonGraphQL,
'/header-graphql': HeaderTestGraphQL,
'/messages-graphql' : MessagesGraphQL,
'/auth-graphql': SampleAuthGraphQL,
'/json-scalar-graphql': JsonScalarGraphQL,
'/big': BigGraphQL
})
def handlers(context):
return MkHandlers({
'/hello': HelloWorldHandler,
'/hello-graphql': HelloGraphQL,
'/hello-echo-request-graphql': HelloGraphQLEchoRequest,
'/hello-graphql-extensions': HelloGraphQLExtensions,
'/user-graphql': UserGraphQL,
'/country-graphql': CountryGraphQL,
'/character-iface-graphql' : CharacterInterfaceGraphQL,
'/iface-graphql-err-empty-field-list' : InterfaceGraphQLErrEmptyFieldList,
'/iface-graphql-err-unknown-iface' : InterfaceGraphQLErrUnknownInterface,
'/iface-graphql-err-missing-field' : InterfaceGraphQLErrMissingField,
'/iface-graphql-err-wrong-field-type' : InterfaceGraphQLErrWrongFieldType,
'/iface-graphql-err-missing-arg' : InterfaceGraphQLErrMissingArg,
'/iface-graphql-err-wrong-arg-type' : InterfaceGraphQLErrWrongArgType,
'/iface-graphql-err-extra-non-null-arg' : InterfaceGraphQLErrExtraNonNullArg,
'/union-graphql' : UnionGraphQL,
'/union-graphql-err-unknown-types' : UnionGraphQLSchemaErrUnknownTypes,
'/union-graphql-err-subtype-iface' : UnionGraphQLSchemaErrSubTypeInterface,
'/union-graphql-err-no-member-types' : UnionGraphQLSchemaErrNoMemberTypes,
'/union-graphql-err-wrapped-type' : UnionGraphQLSchemaErrWrappedType,
'/default-value-echo-graphql' : EchoGraphQL,
'/person-graphql': PersonGraphQL,
'/header-graphql': HeaderTestGraphQL,
'/messages-graphql' : MessagesGraphQL,
'/auth-graphql': SampleAuthGraphQL,
'/json-scalar-graphql': JsonScalarGraphQL,
'/big': BigGraphQL
}, context)
def create_server(host='localhost', port=0):
return http.server.HTTPServer((host, port), handlers)
def create_server(
host: str = 'localhost',
port: int = 0,
tls_configuration: Optional[Tuple[str, str]] = None,
hge_urls: Optional[list[str]] = None,
):
context = Context([urlparse(url).netloc for url in hge_urls] if hge_urls else None)
server = http.server.HTTPServer((host, port), handlers(context))
if tls_configuration:
server.socket = ssl.wrap_socket(
server.socket,
certfile=tls_configuration[0],
keyfile=tls_configuration[1],
server_side=True,
ssl_version=ssl.PROTOCOL_SSLv23,
)
return server
def stop_server(server):
server.shutdown()
server.server_close()
def set_hge_urls(hge_urls = []):
global HGE_URLS
HGE_URLS=hge_urls
if __name__ == '__main__':
port = None
certfile = None
s = None
port = DEFAULT_PORT
if len(sys.argv) >= 2:
port = int(sys.argv[1])
server = create_server(port = port)
if len(sys.argv) == 4: # usage - python3 graphql-server.py <port> <certfile> <keyfile>
port_ = int(sys.argv[1])
certfile_ = sys.argv[2]
keyfile_ = sys.argv[3]
s = create_server(port = port_)
s.socket = ssl.wrap_socket( s.socket,
certfile=certfile_,
keyfile=keyfile_,
server_side=True,
ssl_version=ssl.PROTOCOL_SSLv23)
else:
s = create_server()
s.serve_forever()
certfile = sys.argv[2]
keyfile = sys.argv[3]
server.socket = ssl.wrap_socket(
server.socket,
certfile=certfile,
keyfile=keyfile,
server_side=True,
ssl_version=ssl.PROTOCOL_SSLv23,
)
server.serve_forever()

View File

@ -95,7 +95,7 @@ handlers = MkHandlers({
'/reset-state': ResetStateHandler
})
def create_server(host='127.0.0.1', port=5001):
def create_server(host, port):
return http.server.HTTPServer((host, port), handlers)
def stop_server(server):
@ -104,6 +104,6 @@ def stop_server(server):
# if you want to run this module to emulate a JWK server during development
if __name__ == '__main__':
s = create_server(port=5001)
s = create_server(host='localhost', port=5001)
s.serve_forever()
stop_server(s)

View File

@ -8,5 +8,6 @@ markers =
backend: The backends supported by the test case
admin_secret: Generate and use an admin secret
hge_env: Pass additional environment variables to the GraphQL Engine
jwk_path: When running a JWK server, the URL path that HGE should use
skip_server_upgrade_test: Tests with this marker should not be run as part of server upgrade test
allow_server_upgrade_test: Add tests with this marker to server upgrade test, as far as they don't have the skip_server_upgarde_test market

View File

@ -39,7 +39,7 @@ docker compose pull citus mssql postgres
echo
echo '*** Starting databases ***'
docker compose rm -svf citus mssql postgres # tear down databases beforehand
docker compose rm -svf citus mssql mssql-healthcheck postgres # tear down databases beforehand
docker compose up -d --wait citus mssql-healthcheck postgres
HASURA_GRAPHQL_CITUS_SOURCE_URL="postgresql://postgres:hasura@localhost:$(docker compose port citus 5432 | sed -E 's/.*://')/postgres"

View File

@ -69,6 +69,6 @@ for SERVER_TEST_TO_RUN in "${SERVER_TESTS_TO_RUN[@]}"; do
export SERVER_TEST_TO_RUN
echo
echo "*** Running test suite: ${SERVER_TEST_TO_RUN} ***"
docker compose rm -svf citus mssql postgres # tear down databases beforehand
docker compose rm -svf citus mssql mssql-healthcheck postgres # tear down databases beforehand
docker compose run --rm tests-py
done

View File

@ -1,43 +1,93 @@
from time import sleep, perf_counter
import requests
import os
import pytest
from context import PytestConf
import requests
import threading
from time import perf_counter, sleep
if not PytestConf.config.getoption("--test-jwk-url"):
pytest.skip("--test-jwk-url flag is missing, skipping tests", allow_module_level=True)
import jwk_server
import ports
# assumes the JWK server is running on 127.0.0.1:5001
pytestmark = [
pytest.mark.admin_secret
]
def wait_until_request_count_reaches(num_requests, state_key, max_wait_secs):
@pytest.fixture(scope='class')
@pytest.mark.early
def jwk_server_url(request: pytest.FixtureRequest, hge_fixture_env: dict[str, str]):
path_marker = request.node.get_closest_marker('jwk_path')
assert path_marker is not None, 'The test must set the `jwk_path` marker.'
path: str = path_marker.args[0]
# If the JWK server was started outside, just set the environment variable
# so that the test is skipped if the value is wrong.
env_var = os.getenv('JWK_SERVER_URL')
if env_var:
hge_fixture_env['HASURA_GRAPHQL_JWT_SECRET'] = '{"jwk_url": "' + env_var + path + '"}'
return env_var
port = 5001
server = jwk_server.create_server('localhost', port)
thread = threading.Thread(target=server.serve_forever)
thread.start()
request.addfinalizer(server.shutdown)
port = server.server_address[1]
ports.wait_for_port(port)
url = f'http://localhost:{port}'
hge_fixture_env['HASURA_GRAPHQL_JWT_SECRET'] = '{"jwk_url": "' + url + path + '"}'
return url
def wait_until_request_count_reaches(num_requests: int, state_key: str, timeout_secs: int, jwk_server_url: str) -> float:
start_time = perf_counter()
requests.post('http://localhost:5001/reset-state')
requests.post(jwk_server_url + '/reset-state')
request_count = 0
time_elapsed = 0
while request_count < num_requests:
time_elapsed = perf_counter() - start_time
if time_elapsed > max_wait_secs:
if time_elapsed > timeout_secs:
raise Exception(f'Waited {time_elapsed} seconds for {state_key} JWK requests to reach {num_requests}. Only received {request_count}.')
sleep(0.2)
state = requests.get('http://localhost:5001/state').json()
state = requests.get(jwk_server_url + '/state').json()
request_count = state[state_key]
return time_elapsed
def test_cache_control_header_max_age(hge_ctx):
# The test uses max-age=3, so we are expecting one request (timing out after 6 seconds)
time_elapsed = wait_until_request_count_reaches(1, 'cache-control', 6)
@pytest.mark.jwk_path('/jwk-cache-control?max-age=3')
def test_cache_control_header_max_age(jwk_server_url: str):
# The test uses max-age=3, so we are expecting one request
time_elapsed = wait_until_request_count_reaches(num_requests=1, state_key='cache-control', timeout_secs=6, jwk_server_url=jwk_server_url)
print(f"time_elapsed: {time_elapsed}")
def test_cache_control_header_no_caching(hge_ctx):
@pytest.mark.jwk_path('/jwk-cache-control?max-age=3&must-revalidate=true')
def test_cache_control_header_max_age_must_revalidate(jwk_server_url: str):
# The test uses max-age=3, so we are expecting one request
time_elapsed = wait_until_request_count_reaches(num_requests=1, state_key='cache-control', timeout_secs=6, jwk_server_url=jwk_server_url)
print(f"time_elapsed: {time_elapsed}")
@pytest.mark.jwk_path('/jwk-cache-control?must-revalidate=true')
def test_cache_control_header_must_revalidate(jwk_server_url: str):
# HGE should refresh the JWK once a second, so we are expecting three requests in at least two seconds
# (timing out after 10 seconds)
time_elapsed = wait_until_request_count_reaches(3, 'cache-control', 10)
time_elapsed = wait_until_request_count_reaches(num_requests=3, state_key='cache-control', timeout_secs=10, jwk_server_url=jwk_server_url)
print(f"time_elapsed: {time_elapsed}")
assert(time_elapsed >= 2)
def test_expires_header(hge_ctx):
# The test uses a three second jwk expiry so we are expecting one request (timing out after 6 seconds)
time_elapsed = wait_until_request_count_reaches(1, 'expires', 6)
@pytest.mark.jwk_path('/jwk-cache-control?no-cache=true&public=true')
def test_cache_control_header_no_cache_public(jwk_server_url: str):
# HGE should refresh the JWK once a second, so we are expecting three requests in at least two seconds
time_elapsed = wait_until_request_count_reaches(num_requests=3, state_key='cache-control', timeout_secs=10, jwk_server_url=jwk_server_url)
print(f"time_elapsed: {time_elapsed}")
assert(time_elapsed >= 2)
@pytest.mark.jwk_path('/jwk-cache-control?no-store=true&max-age=3')
def test_cache_control_header_no_store_max_age(jwk_server_url: str):
# HGE should refresh the JWK once a second, so we are expecting three requests in at least two seconds
time_elapsed = wait_until_request_count_reaches(num_requests=3, state_key='cache-control', timeout_secs=10, jwk_server_url=jwk_server_url)
print(f"time_elapsed: {time_elapsed}")
assert(time_elapsed >= 2)
@pytest.mark.jwk_path('/jwk-expires?seconds=3')
def test_expires_header(jwk_server_url: str):
# The test uses a three second jwk expiry so we are expecting one request
time_elapsed = wait_until_request_count_reaches(num_requests=1, state_key='expires', timeout_secs=6, jwk_server_url=jwk_server_url)
print(f"time_elapsed: {time_elapsed}")

View File

@ -1,20 +1,19 @@
#!/usr/bin/env python3
import graphql
import json
import requests
from ruamel.yaml import YAML
import pytest
import graphql
import requests
import time
import graphql_server
from validate import check_query_f, check_query
import pytest
pytestmark = [
pytest.mark.usefixtures('gql_server')
pytest.mark.usefixtures('gql_server'),
]
yaml = YAML(typ='safe', pure=True)
yaml=YAML(typ='safe', pure=True)
from validate import check_query_f, check_query
def mk_add_remote_q(name, url, headers=None, client_hdrs=False, timeout=None, customization=None):
return {
@ -76,12 +75,12 @@ class TestRemoteSchemaBasic:
dir = 'queries/remote_schemas'
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
# This is needed for supporting server upgrade tests
# Some marked tests in this class will be run as server upgrade tests
if not config.getoption('--skip-schema-setup'):
q = mk_add_remote_q('simple 1', 'http://localhost:5000/hello-graphql')
q = mk_add_remote_q('simple 1', f'{gql_server.url}/hello-graphql')
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -92,9 +91,9 @@ class TestRemoteSchemaBasic:
resp = hge_ctx.v1q(export_metadata_q)
assert resp['remote_schemas'][0]['name'] == "simple 1"
def test_update_schema_with_no_url_change(self, hge_ctx):
def test_update_schema_with_no_url_change(self, hge_ctx, gql_server):
""" call update_remote_schema API and check the details stored in metadata """
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, True, 120)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, True, 120)
hge_ctx.v1q(q)
resp = hge_ctx.v1q(export_metadata_q)
@ -103,35 +102,35 @@ class TestRemoteSchemaBasic:
assert resp['remote_schemas'][0]['definition']['forward_client_headers'] == True
""" revert to original config for remote schema """
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, False, 60)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, False, 60)
hge_ctx.v1q(q)
def test_update_schema_with_url_change(self, hge_ctx):
def test_update_schema_with_url_change(self, hge_ctx, gql_server):
""" call update_remote_schema API and check the details stored in metadata """
# This should succeed since there isn't any conflicting relations or permissions set up
q = mk_update_remote_q('simple 1', 'http://localhost:5000/user-graphql', None, True, 80)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/user-graphql', None, True, 80)
hge_ctx.v1q(q)
resp = hge_ctx.v1q(export_metadata_q)
assert resp['remote_schemas'][0]['name'] == "simple 1"
assert resp['remote_schemas'][0]['definition']['url'] == 'http://localhost:5000/user-graphql'
assert resp['remote_schemas'][0]['definition']['url'] == f'{gql_server.url}/user-graphql'
assert resp['remote_schemas'][0]['definition']['timeout_seconds'] == 80
assert resp['remote_schemas'][0]['definition']['forward_client_headers'] == True
""" revert to original config for remote schema """
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, False, 60)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, False, 60)
hge_ctx.v1q(q)
def test_update_schema_with_customization_change(self, hge_ctx):
def test_update_schema_with_customization_change(self, hge_ctx, gql_server):
""" call update_remote_schema API and check the details stored in metadata """
# This should succeed since there isn't any conflicting relations or permissions set up
customization = {'type_names': { 'prefix': 'Foo', 'mapping': {'String': 'MyString'}}, 'field_names': [{'parent_type': 'Hello', 'prefix': 'my_', 'mapping': {}}]}
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, False, 60, customization=customization)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, False, 60, customization=customization)
hge_ctx.v1q(q)
resp = hge_ctx.v1q(export_metadata_q)
assert resp['remote_schemas'][0]['name'] == "simple 1"
assert resp['remote_schemas'][0]['definition']['url'] == 'http://localhost:5000/hello-graphql'
assert resp['remote_schemas'][0]['definition']['url'] == f'{gql_server.url}/hello-graphql'
assert resp['remote_schemas'][0]['definition']['timeout_seconds'] == 60
assert resp['remote_schemas'][0]['definition']['customization'] == customization
@ -143,21 +142,21 @@ class TestRemoteSchemaBasic:
check_query_f(hge_ctx, self.dir + '/basic_query_customized.yaml')
""" revert to original config for remote schema """
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, False, 60)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, False, 60)
hge_ctx.v1q(q)
resp = hge_ctx.v1q(export_metadata_q)
assert 'customization' not in resp['remote_schemas'][0]['definition']
def test_update_schema_with_customization_change_invalid(self, hge_ctx):
def test_update_schema_with_customization_change_invalid(self, hge_ctx, gql_server):
""" call update_remote_schema API and check the details stored in metadata """
customization = {'type_names': { 'mapping': {'String': 'Foo', 'Hello': 'Foo'} } }
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, False, 60, customization=customization)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, False, 60, customization=customization)
resp = hge_ctx.v1q(q, expected_status_code = 400)
assert resp['error'] == 'Inconsistent object: Type name mappings are not distinct; the following types appear more than once: "Foo"'
""" revert to original config for remote schema """
q = mk_update_remote_q('simple 1', 'http://localhost:5000/hello-graphql', None, False, 60)
q = mk_update_remote_q('simple 1', f'{gql_server.url}/hello-graphql', None, False, 60)
hge_ctx.v1q(q)
@pytest.mark.allow_server_upgrade_test
@ -179,9 +178,9 @@ class TestRemoteSchemaBasic:
def test_remote_subscription(self, hge_ctx):
check_query_f(hge_ctx, self.dir + '/basic_subscription_not_supported.yaml')
def test_add_schema_conflicts(self, hge_ctx):
def test_add_schema_conflicts(self, hge_ctx, gql_server):
"""add 2 remote schemas with same node or types"""
q = mk_add_remote_q('simple 2', 'http://localhost:5000/hello-graphql')
q = mk_add_remote_q('simple 2', f'{gql_server.url}/hello-graphql')
resp = hge_ctx.v1q(q, expected_status_code = 400)
assert resp['code'] == 'unexpected'
@ -199,23 +198,23 @@ class TestRemoteSchemaBasic:
hge_ctx.v1q(q)
@pytest.mark.allow_server_upgrade_test
def test_add_second_remote_schema(self, hge_ctx):
def test_add_second_remote_schema(self, hge_ctx, gql_server):
"""add 2 remote schemas with different node and types"""
q = mk_add_remote_q('my remote', 'http://localhost:5000/user-graphql')
q = mk_add_remote_q('my remote', f'{gql_server.url}/user-graphql')
hge_ctx.v1q(q)
hge_ctx.v1q(mk_delete_remote_q('my remote'))
@pytest.mark.allow_server_upgrade_test
def test_json_scalar_dict(self, hge_ctx):
q = mk_add_remote_q('my remote', 'http://localhost:5000/json-scalar-graphql')
def test_json_scalar_dict(self, hge_ctx, gql_server):
q = mk_add_remote_q('my remote', f'{gql_server.url}/json-scalar-graphql')
hge_ctx.v1q(q)
check_query_f(hge_ctx, self.dir + '/json_scalar.yaml')
hge_ctx.v1q(mk_delete_remote_q('my remote'))
@pytest.mark.allow_server_upgrade_test
def test_add_remote_schema_with_interfaces(self, hge_ctx):
def test_add_remote_schema_with_interfaces(self, hge_ctx, gql_server):
"""add a remote schema with interfaces in it"""
q = mk_add_remote_q('my remote interface one', 'http://localhost:5000/character-iface-graphql')
q = mk_add_remote_q('my remote interface one', f'{gql_server.url}/character-iface-graphql')
hge_ctx.v1q(q)
check_query_f(hge_ctx, self.dir + '/character_interface_query.yaml')
hge_ctx.v1q(mk_delete_remote_q('my remote interface one'))
@ -256,9 +255,9 @@ class TestRemoteSchemaBasic:
check_query_f(hge_ctx, self.dir + '/add_remote_schema_with_iface_err_extra_non_null_arg.yaml')
@pytest.mark.allow_server_upgrade_test
def test_add_remote_schema_with_union(self, hge_ctx):
def test_add_remote_schema_with_union(self, hge_ctx, gql_server):
"""add a remote schema with union in it"""
q = mk_add_remote_q('my remote union one', 'http://localhost:5000/union-graphql')
q = mk_add_remote_q('my remote union one', f'{gql_server.url}/union-graphql')
hge_ctx.v1q(q)
check_query_f(hge_ctx, self.dir + '/search_union_type_query.yaml')
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "my remote union one"}})
@ -289,12 +288,12 @@ class TestRemoteSchemaBasicExtensions:
dir = 'queries/remote_schemas'
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
# This is needed for supporting server upgrade tests
# Some marked tests in this class will be run as server upgrade tests
if not config.getoption('--skip-schema-setup'):
q = mk_add_remote_q('simple 1', 'http://localhost:5000/hello-graphql-extensions')
q = mk_add_remote_q('simple 1', f'{gql_server.url}/hello-graphql-extensions')
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -321,16 +320,16 @@ class TestAddRemoteSchemaTbls:
resp = hge_ctx.v1q(export_metadata_q)
assert resp['remote_schemas'][0]['name'] == "simple2-graphql"
def test_add_schema_conflicts_with_tables(self, hge_ctx):
def test_add_schema_conflicts_with_tables(self, hge_ctx, gql_server):
"""add remote schema which conflicts with hasura tables"""
q = mk_add_remote_q('simple2', 'http://localhost:5000/hello-graphql')
q = mk_add_remote_q('simple2', f'{gql_server.url}/hello-graphql')
resp = hge_ctx.v1q(q, expected_status_code = 400)
assert resp['code'] == 'invalid-configuration'
@pytest.mark.allow_server_upgrade_test
def test_add_second_remote_schema(self, hge_ctx):
def test_add_second_remote_schema(self, hge_ctx, gql_server):
"""add 2 remote schemas with different node and types"""
q = mk_add_remote_q('my remote2', 'http://localhost:5000/country-graphql')
q = mk_add_remote_q('my remote2', f'{gql_server.url}/country-graphql')
hge_ctx.v1q(q)
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "my remote2"}})
@ -353,37 +352,35 @@ class TestAddRemoteSchemaTbls:
resp, _ = check_query(hge_ctx, query)
assert check_introspection_result(resp, ['User', 'hello'], ['user', 'hello'])
def test_add_schema_duplicate_name(self, hge_ctx):
q = mk_add_remote_q('simple2-graphql', 'http://localhost:5000/country-graphql')
def test_add_schema_duplicate_name(self, hge_ctx, gql_server):
q = mk_add_remote_q('simple2-graphql', f'{gql_server.url}/country-graphql')
resp = hge_ctx.v1q(q, expected_status_code = 400)
assert resp['code'] == 'already-exists'
@pytest.mark.allow_server_upgrade_test
def test_add_schema_same_type_containing_same_scalar(self, hge_ctx):
def test_add_schema_same_type_containing_same_scalar(self, hge_ctx, gql_server):
"""
test types get merged when remote schema has type with same name and
same structure + a same custom scalar
"""
hge_ctx.v1q_f(self.dir + '/person_table.yaml')
q = mk_add_remote_q('person-graphql', 'http://localhost:5000/person-graphql')
q = mk_add_remote_q('person-graphql', f'{gql_server.url}/person-graphql')
hge_ctx.v1q(q)
hge_ctx.v1q_f(self.dir + '/drop_person_table.yaml')
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "person-graphql"}})
@pytest.mark.allow_server_upgrade_test
def test_remote_schema_forward_headers(self, hge_ctx):
def test_remote_schema_forward_headers(self, hge_ctx, gql_server):
"""
test headers from client and conf and resolved info gets passed
correctly to remote schema, and no duplicates are sent. this test just
tests if the remote schema returns success or not. checking of header
duplicate logic is in the remote schema server
"""
graphql_server.set_hge_urls([])
conf_hdrs = [{'name': 'x-hasura-test', 'value': 'abcd'}]
add_remote = mk_add_remote_q('header-graphql',
'http://localhost:5000/header-graphql',
f'{gql_server.url}/header-graphql',
headers=conf_hdrs, client_hdrs=True)
hge_ctx.v1q(add_remote)
q = {'query': '{ wassup }'}
@ -402,8 +399,8 @@ class TestAddRemoteSchemaTbls:
print(resp.status_code, resp.json())
assert resp.status_code == 200
res = resp.json()
assert 'data' in res and res['data'], f'Response: {json.dumps(res)}'
assert res['data']['wassup'] == 'Hello world', f'Response: {json.dumps(res)}'
assert 'data' in res and res['data'] is not None, res
assert res['data']['wassup'] == 'Hello world'
hge_ctx.v1q({'type': 'remove_remote_schema', 'args': {'name': 'header-graphql'}})
@ -493,8 +490,8 @@ class TestRemoteSchemaResponseHeaders():
dir = 'queries/remote_schemas'
@pytest.fixture(autouse=True)
def transact(self, hge_ctx):
q = mk_add_remote_q('sample-auth', 'http://localhost:5000/auth-graphql')
def transact(self, hge_ctx, gql_server):
q = mk_add_remote_q('sample-auth', f'{gql_server.url}/auth-graphql')
hge_ctx.v1q(q)
yield
hge_ctx.v1q(self.teardown)
@ -516,23 +513,20 @@ class TestRemoteSchemaResponseHeaders():
class TestAddRemoteSchemaCompareRootQueryFields:
remote = 'http://localhost:5000/default-value-echo-graphql'
@pytest.fixture(autouse=True)
def transact(self, hge_ctx):
hge_ctx.v1q(mk_add_remote_q('default_value_test', self.remote))
def transact(self, hge_ctx, gql_server):
remote = f'{gql_server.url}/default-value-echo-graphql'
hge_ctx.v1q(mk_add_remote_q('default_value_test', remote))
yield
hge_ctx.v1q(mk_delete_remote_q('default_value_test'))
@pytest.mark.allow_server_upgrade_test
def test_schema_check_arg_default_values_and_field_and_arg_types(self, hge_ctx):
def test_schema_check_arg_default_values_and_field_and_arg_types(self, hge_ctx, gql_server):
remote = f'{gql_server.url}/default-value-echo-graphql'
with open('queries/graphql_introspection/introspection.yaml') as f:
query = yaml.load(f)
introspect_hasura, _ = check_query(hge_ctx, query)
resp = requests.post(
self.remote,
json=query['query']
)
resp = requests.post(remote, json=query['query'])
introspect_remote = resp.json()
assert resp.status_code == 200, introspect_remote
remote_root_ty_info = get_query_root_info(introspect_remote)
@ -672,7 +666,7 @@ class TestRemoteSchemaReload:
def test_inconsistent_remote_schema_reload_metadata(self, gql_server, hge_ctx):
# Add remote schema
hge_ctx.v1q(mk_add_remote_q('simple 1', 'http://localhost:5000/hello-graphql'))
hge_ctx.v1q(mk_add_remote_q('simple 1', f'{gql_server.url}/hello-graphql'))
# stop remote graphql server
gql_server.stop_server()
# Reload metadata with remote schemas
@ -714,12 +708,12 @@ class TestRemoteSchemaTypePrefix:
dir = 'queries/remote_schemas'
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
# This is needed for supporting server upgrade tests
# Some marked tests in this class will be run as server upgrade tests
if not config.getoption('--skip-schema-setup'):
q = mk_add_remote_q('simple 2', 'http://localhost:5000/user-graphql', customization=type_prefix_customization("Foo"))
q = mk_add_remote_q('simple 2', f'{gql_server.url}/user-graphql', customization=type_prefix_customization("Foo"))
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -744,10 +738,10 @@ class TestValidateRemoteSchemaTypePrefixQuery:
teardown = {"type": "clear_metadata", "args": {}}
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
if not config.getoption('--skip-schema-setup'):
q = mk_add_remote_q('character-foo', 'http://localhost:5000/character-iface-graphql', customization=type_prefix_customization("Foo"))
q = mk_add_remote_q('character-foo', f'{gql_server.url}/character-iface-graphql', customization=type_prefix_customization("Foo"))
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -765,11 +759,11 @@ class TestValidateRemoteSchemaFieldPrefixQuery:
teardown = {"type": "clear_metadata", "args": {}}
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
if not config.getoption('--skip-schema-setup'):
customization = { "field_names": [{"parent_type": "Character", "prefix": "foo_"},{"parent_type": "Human", "prefix": "foo_"},{"parent_type": "Droid", "prefix": "foo_"}] }
q = mk_add_remote_q('character-foo', 'http://localhost:5000/character-iface-graphql', customization=customization)
q = mk_add_remote_q('character-foo', f'{gql_server.url}/character-iface-graphql', customization=customization)
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -795,11 +789,11 @@ class TestValidateRemoteSchemaNamespaceQuery:
teardown = {"type": "clear_metadata", "args": {}}
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
if not config.getoption('--skip-schema-setup'):
customization = { "root_fields_namespace": "foo" }
q = mk_add_remote_q('character-foo', 'http://localhost:5000/character-iface-graphql', customization=customization)
q = mk_add_remote_q('character-foo', f'{gql_server.url}/character-iface-graphql', customization=customization)
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -820,7 +814,7 @@ class TestValidateRemoteSchemaCustomizeAllTheThings:
teardown = {"type": "clear_metadata", "args": {}}
@pytest.fixture(autouse=True)
def transact(self, request, hge_ctx):
def transact(self, request, hge_ctx, gql_server):
config = request.config
if not config.getoption('--skip-schema-setup'):
customization = {
@ -833,7 +827,7 @@ class TestValidateRemoteSchemaCustomizeAllTheThings:
{"parent_type": "CharacterIFaceQuery", "prefix": "super_" }
]
}
q = mk_add_remote_q('character-foo', 'http://localhost:5000/character-iface-graphql', customization=customization)
q = mk_add_remote_q('character-foo', f'{gql_server.url}/character-iface-graphql', customization=customization)
hge_ctx.v1q(q)
yield
if request.session.testsfailed > 0 or not config.getoption('--skip-schema-teardown'):
@ -851,8 +845,8 @@ class TestRemoteSchemaRequestPayload:
teardown = {"type": "clear_metadata", "args": {}}
@pytest.fixture(autouse=True)
def transact(self, hge_ctx):
q = mk_add_remote_q('echo request', 'http://localhost:5000/hello-echo-request-graphql')
def transact(self, hge_ctx, gql_server):
q = mk_add_remote_q('echo request', f'{gql_server.url}/hello-echo-request-graphql')
hge_ctx.v1q(q)
yield
hge_ctx.v1q(self.teardown)

View File

@ -33,12 +33,13 @@ class Response():
class Request():
""" Represents a HTTP `Request` object """
def __init__(self, path, qs=None, body=None, json=None, headers=None):
def __init__(self, path, qs=None, body=None, json=None, headers=None, context=None):
self.path = path
self.qs = qs
self.body = body
self.json = json
self.headers = headers
self.context = context
class RequestHandler(ABC):
@ -54,7 +55,7 @@ class RequestHandler(ABC):
pass
def MkHandlers(handlers):
def MkHandlers(handlers, context = None):
class HTTPHandler(http.BaseHTTPRequestHandler):
def not_found(self):
self.send_response(HTTPStatus.NOT_FOUND)
@ -79,7 +80,7 @@ def MkHandlers(handlers):
path = raw_path.path
handler = handlers[path]()
qs = parse_qs(raw_path.query)
req = Request(path, qs, None, None, self.headers)
req = Request(path, qs, None, None, self.headers, context)
resp = handler.get(req)
self.send_response(resp.status)
if resp.headers:
@ -100,7 +101,7 @@ def MkHandlers(handlers):
req_json = None
if self.headers.get('Content-Type') == 'application/json':
req_json = json.loads(req_body)
req = Request(self.path, qs, req_body, req_json, self.headers)
req = Request(self.path, qs, req_body, req_json, self.headers, context)
resp = handler.post(req)
self.send_response(resp.status)
if resp.headers: