"""Tests for connectors.graphql — GraphQLClient.""" import json import pytest from pytest_httpserver import HTTPServer from connectors.errors import ApiError from connectors.graphql import GraphQLClient, GraphQLError from infra.throttler import Throttler, TokenBucketStrategy SAMPLE_QUERY = 'query { hello }' @pytest.fixture() def server(httpserver: HTTPServer): return httpserver @pytest.fixture() def url(server: HTTPServer) -> str: return server.url_for('/graphql') class TestGraphQLClientQuery: def test_successful_query_returns_data(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json( {'data': {'hello': 'world'}} ) client = GraphQLClient(url=url) result = client.query(SAMPLE_QUERY) assert result == {'hello': 'world'} def test_passes_variables(self, server, url): def _handler(request): body = request.get_json() assert body['variables'] == {'id': 42} return json.dumps({'data': {'item': 'found'}}) server.expect_request('/graphql', method='POST').respond_with_handler(_handler) client = GraphQLClient(url=url) result = client.query(SAMPLE_QUERY, variables={'id': 42}) assert result == {'item': 'found'} def test_omits_variables_when_none(self, server, url): def _handler(request): body = request.get_json() assert 'variables' not in body return json.dumps({'data': {'ok': True}}) server.expect_request('/graphql', method='POST').respond_with_handler(_handler) client = GraphQLClient(url=url) client.query(SAMPLE_QUERY) def test_sends_custom_headers(self, server, url): def _handler(request): assert request.headers.get('Authorization') == 'Bearer abc' assert request.headers.get('Content-Type') == 'application/json' return json.dumps({'data': {}}) server.expect_request('/graphql', method='POST').respond_with_handler(_handler) client = GraphQLClient(url=url, headers={'Authorization': 'Bearer abc'}) client.query(SAMPLE_QUERY) class TestGraphQLClientErrors: def test_non_200_raises_api_error(self, server, url): server.expect_request('/graphql', method='POST').respond_with_data( 'Bad Gateway', status=502 ) client = GraphQLClient(url=url) with pytest.raises(ApiError, match='502'): client.query(SAMPLE_QUERY) def test_graphql_errors_in_body_raise_graphql_error(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json( {'errors': [{'message': 'Field not found'}]} ) client = GraphQLClient(url=url) with pytest.raises(GraphQLError, match='Field not found') as exc_info: client.query(SAMPLE_QUERY) assert len(exc_info.value.errors) == 1 def test_multiple_graphql_errors_joined(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json( {'errors': [{'message': 'Error A'}, {'message': 'Error B'}]} ) client = GraphQLClient(url=url) with pytest.raises(GraphQLError, match='Error A.*Error B'): client.query(SAMPLE_QUERY) def test_connection_error_raises_api_error(self): client = GraphQLClient(url='http://localhost:1', timeout=1) with pytest.raises(ApiError): client.query(SAMPLE_QUERY) def test_200_with_malformed_json_raises_api_error(self, server, url): server.expect_request('/graphql', method='POST').respond_with_data( 'not json at all', status=200 ) client = GraphQLClient(url=url) with pytest.raises(ApiError, match='Invalid JSON'): client.query(SAMPLE_QUERY) def test_empty_data_returns_empty_dict(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json({}) client = GraphQLClient(url=url) result = client.query(SAMPLE_QUERY) assert result == {} class TestGraphQLClientThrottler: def test_calls_throttler_acquire(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json( {'data': {'ok': True}} ) throttler = Throttler(TokenBucketStrategy(capacity=10, refill_rate=100.0)) client = GraphQLClient(url=url, throttler=throttler) # Should not raise — token available client.query(SAMPLE_QUERY) def test_works_without_throttler(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json( {'data': {'ok': True}} ) client = GraphQLClient(url=url, throttler=None) result = client.query(SAMPLE_QUERY) assert result == {'ok': True} class TestGraphQLClientSession: def test_reuses_session_across_calls(self, server, url): server.expect_request('/graphql', method='POST').respond_with_json( {'data': {'n': 1}} ) client = GraphQLClient(url=url) result1 = client.query(SAMPLE_QUERY) result2 = client.query(SAMPLE_QUERY) # Both calls succeed — session is reused (same underlying connection) assert result1 == {'n': 1} assert result2 == {'n': 1}