"""Tests for GraphQL client module."""
from unittest.mock import AsyncMock, Mock, patch
import pytest
from open_targets_platform_mcp.client.graphql import execute_graphql_query, fetch_graphql_schema
from open_targets_platform_mcp.model.result import QueryResultStatus
# ============================================================================
# execute_graphql_query Tests - Unit Tests with Mocks
# ============================================================================
class TestExecuteGraphQLQuery:
"""Tests for execute_graphql_query function."""
@pytest.mark.asyncio
async def test_execute_query_success(self, sample_query_string, sample_graphql_response):
"""Test successful query execution."""
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=sample_graphql_response)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
result = await execute_graphql_query(sample_query_string)
assert result.status == QueryResultStatus.SUCCESS
assert result.result == sample_graphql_response
mock_client_instance.execute_async.assert_awaited_once()
@pytest.mark.asyncio
async def test_execute_query_with_variables(
self,
sample_query_string,
sample_variables,
sample_graphql_response,
):
"""Test query execution with variables."""
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=sample_graphql_response)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
result = await execute_graphql_query(sample_query_string, variables=sample_variables)
assert result.status == QueryResultStatus.SUCCESS
mock_client_instance.execute_async.assert_awaited_once_with("parsed_query", variable_values=sample_variables)
@pytest.mark.asyncio
async def test_execute_query_default_headers(self, sample_query_string):
"""Test that default headers are set when none provided."""
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value={})
with patch("open_targets_platform_mcp.client.graphql.AIOHTTPTransport") as mock_transport:
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
await execute_graphql_query(sample_query_string)
call_kwargs = mock_transport.call_args[1]
assert call_kwargs["headers"] == {"Content-Type": "application/json"}
@pytest.mark.asyncio
async def test_execute_query_invalid_query_string(self):
"""Test that invalid GraphQL query string errors bubble up."""
invalid_query = "this is not valid graphql"
with patch("open_targets_platform_mcp.client.graphql.gql", side_effect=Exception("Parse error")):
with pytest.raises(Exception, match="Parse error"):
await execute_graphql_query(invalid_query)
@pytest.mark.asyncio
async def test_execute_query_execution_error(self, sample_query_string):
"""Test that query execution errors bubble up."""
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(side_effect=Exception("Network error"))
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
with pytest.raises(Exception, match="Network error"):
await execute_graphql_query(sample_query_string)
mock_client_instance.execute_async.assert_awaited_once()
# ============================================================================
# JQ Filter Tests
# ============================================================================
class TestJQFiltering:
"""Tests for jq filter functionality."""
@pytest.mark.asyncio
async def test_execute_query_with_simple_jq_filter(self, sample_query_string):
"""Test query execution with simple jq filter."""
mock_response = {
"target": {"id": "ENSG00000141510", "approvedSymbol": "TP53", "approvedName": "tumor protein p53"},
}
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=mock_response)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
result = await execute_graphql_query(sample_query_string, jq_filter=".target.id")
# jq filter returns a list (even for single results)
assert result.status == QueryResultStatus.SUCCESS
assert isinstance(result.result, list)
assert result.result == ["ENSG00000141510"]
mock_client_instance.execute_async.assert_awaited_once()
@pytest.mark.asyncio
async def test_execute_query_with_complex_jq_filter(self, sample_query_string):
"""Test query execution with object-building jq filter."""
mock_response = {
"target": {"id": "ENSG00000141510", "approvedSymbol": "TP53", "approvedName": "tumor protein p53"},
}
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=mock_response)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
result = await execute_graphql_query(
sample_query_string,
jq_filter=".target | {id, symbol: .approvedSymbol}",
)
# jq filter returns a list (even for single results)
assert result.status == QueryResultStatus.SUCCESS
assert isinstance(result.result, list)
assert len(result.result) == 1
assert isinstance(result.result[0], dict)
assert "id" in result.result[0]
assert "symbol" in result.result[0]
assert result.result[0]["id"] == "ENSG00000141510"
assert result.result[0]["symbol"] == "TP53"
mock_client_instance.execute_async.assert_awaited_once()
@pytest.mark.asyncio
async def test_execute_query_with_array_jq_filter(self, sample_query_string):
"""Test query execution with jq filter that returns multiple results."""
mock_response = {
"targets": [
{"id": "ENSG00000141510", "approvedSymbol": "TP53"},
{"id": "ENSG00000012048", "approvedSymbol": "BRCA1"},
],
}
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=mock_response)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
result = await execute_graphql_query(sample_query_string, jq_filter=".targets[] | .approvedSymbol")
# Multiple results should be in result list
assert result.status == QueryResultStatus.SUCCESS
assert isinstance(result.result, list)
assert result.result == ["TP53", "BRCA1"]
mock_client_instance.execute_async.assert_awaited_once()
@pytest.mark.asyncio
async def test_execute_query_jq_filter_error_handling(self, sample_query_string):
"""Test that jq filter runtime errors are handled gracefully."""
mock_response = {"target": {"id": "ENSG00000141510"}}
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=mock_response)
# Mock jq filter to raise an error during execution (not compilation)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
with patch("open_targets_platform_mcp.client.graphql.jq.compile") as mock_jq_compile:
# Create a mock compiled filter that raises an error when used
mock_compiled_filter = AsyncMock()
mock_compiled_filter.input_value.return_value.all.side_effect = Exception("jq execution error")
mock_jq_compile.return_value = mock_compiled_filter
result = await execute_graphql_query(sample_query_string, jq_filter=".invalid_filter")
# Should return warning with original data
assert result.status == QueryResultStatus.WARNING
assert result.result == mock_response
assert "jq filter failed" in str(result.message)
assert "// empty" in str(result.message) # Should suggest null handling
mock_client_instance.execute_async.assert_awaited_once()
@pytest.mark.asyncio
async def test_execute_query_jq_compilation_error(self, sample_query_string):
"""Test that jq compilation errors bubble up."""
# Mock jq.compile to raise an error during compilation
with patch("open_targets_platform_mcp.client.graphql.jq.compile") as mock_jq:
mock_jq.side_effect = Exception("jq compilation error")
with pytest.raises(Exception, match="jq compilation error"):
await execute_graphql_query(sample_query_string, jq_filter=".invalid_filter")
@pytest.mark.asyncio
async def test_execute_query_no_jq_filter(self, sample_query_string, sample_graphql_response):
"""Test query execution without jq filter returns full response."""
mock_client_instance = AsyncMock()
mock_client_instance.execute_async = AsyncMock(return_value=sample_graphql_response)
with patch("open_targets_platform_mcp.client.graphql.gql", return_value="parsed_query"):
with patch("open_targets_platform_mcp.client.graphql.Client", return_value=mock_client_instance):
result = await execute_graphql_query(sample_query_string)
assert result.status == QueryResultStatus.SUCCESS
assert result.result == sample_graphql_response
mock_client_instance.execute_async.assert_awaited_once()
# ============================================================================
# Integration Tests
# ============================================================================
@pytest.mark.integration
class TestGraphQLIntegration:
"""Integration tests with real API calls."""
@pytest.mark.asyncio
async def test_real_query_execution(self):
"""Test real query execution against Open Targets Platform API."""
query = """
query {
target(ensemblId: "ENSG00000141510") {
id
approvedSymbol
}
}
"""
result = await execute_graphql_query(query)
assert result.status == QueryResultStatus.SUCCESS
assert "target" in result.result
assert result.result["target"]["id"] == "ENSG00000141510"
assert result.result["target"]["approvedSymbol"] == "TP53"
@pytest.mark.asyncio
async def test_real_query_with_variables(self):
"""Test real query with variables."""
query = """
query GetTarget($ensemblId: String!) {
target(ensemblId: $ensemblId) {
id
approvedSymbol
}
}
"""
variables = {"ensemblId": "ENSG00000012048"}
result = await execute_graphql_query(query, variables=variables)
assert result.status == QueryResultStatus.SUCCESS
assert result.result["target"]["id"] == "ENSG00000012048"
assert result.result["target"]["approvedSymbol"] == "BRCA1"
@pytest.mark.asyncio
async def test_real_query_with_jq_filter(self):
"""Test real query with jq filter."""
query = """
query {
target(ensemblId: "ENSG00000141510") {
id
approvedSymbol
approvedName
}
}
"""
result = await execute_graphql_query(query, jq_filter=".target.approvedSymbol")
assert result.status == QueryResultStatus.SUCCESS
# jq filter returns a list (even for single results)
assert isinstance(result.result, list)
assert result.result == ["TP53"]
@pytest.mark.asyncio
async def test_real_invalid_query(self):
"""Test that invalid query raises exception."""
from gql.transport.exceptions import TransportQueryError
invalid_query = """
query {
nonexistentField {
id
}
}
"""
with pytest.raises(TransportQueryError):
await execute_graphql_query(invalid_query)
# ============================================================================
# fetch_graphql_schema Tests
# ============================================================================
class TestFetchGraphQLSchema:
"""Tests for fetch_graphql_schema function."""
@pytest.mark.asyncio
async def test_fetch_graphql_schema_success(self):
"""Test successful schema fetching."""
mock_schema = Mock()
mock_client_instance = AsyncMock()
mock_client_instance.schema = mock_schema
mock_client_instance.__aenter__ = AsyncMock(return_value=mock_client_instance)
mock_client_instance.__aexit__ = AsyncMock(return_value=False)
with (
patch("open_targets_platform_mcp.client.graphql.AIOHTTPTransport"),
patch(
"open_targets_platform_mcp.client.graphql.Client",
return_value=mock_client_instance,
) as mock_client,
):
result = await fetch_graphql_schema()
assert result == mock_schema
assert mock_client.call_args[1]["fetch_schema_from_transport"] is True
mock_client_instance.__aenter__.assert_awaited_once()
@pytest.mark.asyncio
async def test_fetch_graphql_schema_no_schema(self):
"""Test that ValueError is raised when schema is not fetched."""
mock_client_instance = AsyncMock()
mock_client_instance.schema = None
mock_client_instance.__aenter__ = AsyncMock(return_value=mock_client_instance)
mock_client_instance.__aexit__ = AsyncMock(return_value=False)
with (
patch("open_targets_platform_mcp.client.graphql.AIOHTTPTransport"),
patch(
"open_targets_platform_mcp.client.graphql.Client",
return_value=mock_client_instance,
),
pytest.raises(ValueError, match="Failed to fetch schema"),
):
await fetch_graphql_schema()