mirror of
https://github.com/apidoorman/doorman.git
synced 2026-02-09 02:29:42 -06:00
test graphql client and envelope
This commit is contained in:
222
backend-services/tests/test_graphql_client_and_envelope.py
Normal file
222
backend-services/tests/test_graphql_client_and_envelope.py
Normal file
@@ -0,0 +1,222 @@
|
||||
import pytest
|
||||
|
||||
|
||||
async def _setup_graphql(client, name, ver, allowed_headers=None):
|
||||
payload = {
|
||||
'api_name': name,
|
||||
'api_version': ver,
|
||||
'api_description': f'{name} {ver}',
|
||||
'api_allowed_roles': ['admin'],
|
||||
'api_allowed_groups': ['ALL'],
|
||||
'api_servers': ['http://gql.up'],
|
||||
'api_type': 'REST',
|
||||
'api_allowed_retry_count': 0,
|
||||
}
|
||||
if allowed_headers is not None:
|
||||
payload['api_allowed_headers'] = allowed_headers
|
||||
r = await client.post('/platform/api', json=payload)
|
||||
assert r.status_code in (200, 201)
|
||||
r2 = await client.post('/platform/endpoint', json={
|
||||
'api_name': name,
|
||||
'api_version': ver,
|
||||
'endpoint_method': 'POST',
|
||||
'endpoint_uri': '/graphql',
|
||||
'endpoint_description': 'graphql',
|
||||
})
|
||||
assert r2.status_code in (200, 201)
|
||||
from conftest import subscribe_self
|
||||
await subscribe_self(client, name, ver)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_graphql_uses_gql_client_when_available(monkeypatch, authed_client):
|
||||
import services.gateway_service as gs
|
||||
name, ver = 'gqlclient', 'v1'
|
||||
await _setup_graphql(authed_client, name, ver)
|
||||
|
||||
calls = {}
|
||||
|
||||
class FakeSession:
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
async def execute(self, query, variable_values=None):
|
||||
calls['query'] = query
|
||||
calls['vars'] = variable_values
|
||||
return {'ok': True, 'from': 'client'}
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, transport=None, fetch_schema_from_transport=False):
|
||||
pass
|
||||
async def __aenter__(self):
|
||||
return FakeSession()
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(gs, 'Client', FakeClient)
|
||||
r = await authed_client.post(
|
||||
f'/api/graphql/{name}',
|
||||
headers={'X-API-Version': ver, 'Content-Type': 'application/json'},
|
||||
json={'query': '{ ping }', 'variables': {'a': 1}},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
# Loose envelope by default: raw response dict
|
||||
assert body.get('ok') is True and body.get('from') == 'client'
|
||||
assert calls.get('vars') == {'a': 1}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_graphql_fallback_to_httpx_when_client_unavailable(monkeypatch, authed_client):
|
||||
import services.gateway_service as gs
|
||||
name, ver = 'gqlhttpx', 'v1'
|
||||
await _setup_graphql(authed_client, name, ver)
|
||||
|
||||
# Make Client unusable for async context
|
||||
class Dummy:
|
||||
pass
|
||||
monkeypatch.setattr(gs, 'Client', Dummy)
|
||||
|
||||
class FakeHTTPResp:
|
||||
def __init__(self, payload):
|
||||
self._p = payload
|
||||
def json(self):
|
||||
return self._p
|
||||
|
||||
class FakeHTTPX:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
async def post(self, url, json=None, headers=None):
|
||||
return FakeHTTPResp({'ok': True, 'from': 'httpx', 'url': url})
|
||||
|
||||
monkeypatch.setattr(gs.httpx, 'AsyncClient', FakeHTTPX)
|
||||
r = await authed_client.post(
|
||||
f'/api/graphql/{name}',
|
||||
headers={'X-API-Version': ver, 'Content-Type': 'application/json'},
|
||||
json={'query': '{ pong }', 'variables': {}},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body.get('from') == 'httpx'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_graphql_errors_returned_in_errors_array(monkeypatch, authed_client):
|
||||
import services.gateway_service as gs
|
||||
name, ver = 'gqlerrors', 'v1'
|
||||
await _setup_graphql(authed_client, name, ver)
|
||||
|
||||
class FakeHTTPResp:
|
||||
def __init__(self, payload):
|
||||
self._p = payload
|
||||
def json(self):
|
||||
return self._p
|
||||
|
||||
class FakeHTTPX:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
async def post(self, url, json=None, headers=None):
|
||||
return FakeHTTPResp({'errors': [{'message': 'boom'}]})
|
||||
|
||||
monkeypatch.setattr(gs.httpx, 'AsyncClient', FakeHTTPX)
|
||||
# Force HTTPX path
|
||||
class Dummy:
|
||||
pass
|
||||
monkeypatch.setattr(gs, 'Client', Dummy)
|
||||
|
||||
r = await authed_client.post(
|
||||
f'/api/graphql/{name}',
|
||||
headers={'X-API-Version': ver, 'Content-Type': 'application/json'},
|
||||
json={'query': '{ err }', 'variables': {}},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert isinstance(body.get('errors'), list) and body['errors'][0]['message'] == 'boom'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_graphql_strict_envelope_wraps_response(monkeypatch, authed_client):
|
||||
import services.gateway_service as gs
|
||||
name, ver = 'gqlstrict', 'v1'
|
||||
await _setup_graphql(authed_client, name, ver)
|
||||
|
||||
class FakeHTTPResp:
|
||||
def __init__(self, payload):
|
||||
self._p = payload
|
||||
def json(self):
|
||||
return self._p
|
||||
|
||||
class FakeHTTPX:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
async def post(self, url, json=None, headers=None):
|
||||
return FakeHTTPResp({'data': {'ok': True}})
|
||||
|
||||
monkeypatch.setenv('STRICT_RESPONSE_ENVELOPE', 'true')
|
||||
monkeypatch.setattr(gs.httpx, 'AsyncClient', FakeHTTPX)
|
||||
# Use httpx path by disabling Client
|
||||
class Dummy:
|
||||
pass
|
||||
monkeypatch.setattr(gs, 'Client', Dummy)
|
||||
|
||||
r = await authed_client.post(
|
||||
f'/api/graphql/{name}',
|
||||
headers={'X-API-Version': ver, 'Content-Type': 'application/json'},
|
||||
json={'query': '{ strict }', 'variables': {}},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body.get('status_code') == 200 and isinstance(body.get('response'), dict)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_graphql_loose_envelope_returns_raw_response(monkeypatch, authed_client):
|
||||
import services.gateway_service as gs
|
||||
name, ver = 'gqlloose', 'v1'
|
||||
await _setup_graphql(authed_client, name, ver)
|
||||
|
||||
class FakeHTTPResp:
|
||||
def __init__(self, payload):
|
||||
self._p = payload
|
||||
def json(self):
|
||||
return self._p
|
||||
|
||||
class FakeHTTPX:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
async def post(self, url, json=None, headers=None):
|
||||
return FakeHTTPResp({'data': {'ok': True}})
|
||||
|
||||
monkeypatch.delenv('STRICT_RESPONSE_ENVELOPE', raising=False)
|
||||
monkeypatch.setattr(gs.httpx, 'AsyncClient', FakeHTTPX)
|
||||
# Use httpx path by disabling Client
|
||||
class Dummy:
|
||||
pass
|
||||
monkeypatch.setattr(gs, 'Client', Dummy)
|
||||
|
||||
r = await authed_client.post(
|
||||
f'/api/graphql/{name}',
|
||||
headers={'X-API-Version': ver, 'Content-Type': 'application/json'},
|
||||
json={'query': '{ loose }', 'variables': {}},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body.get('data', {}).get('ok') is True
|
||||
|
||||
Reference in New Issue
Block a user