Skip to content

Commit

Permalink
Merge pull request #59 from mirumee/56-variables-not-being-proxied
Browse files Browse the repository at this point in the history
Enhanced variable handling for object and list types
  • Loading branch information
rafalp authored Apr 11, 2024
2 parents d1b79e7 + e646d06 commit d6c3968
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 0.4.0 (UNRELEASED)

- Fixed handling of nested variables in objects and lists.


## 0.3.0 (2024-03-26)

- Added `CacheSerializer`, `NoopCacheSerializer` and `JSONCacheSerializer`. Changed `CacheBackend`, `InMemoryCache`, `CloudflareCacheBackend` and `DynamoDBCacheBackend` to accept `serializer` initialization option.
Expand Down
29 changes: 23 additions & 6 deletions ariadne_graphql_proxy/query_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
FragmentSpreadNode,
GraphQLSchema,
InlineFragmentNode,
ListValueNode,
NameNode,
ObjectValueNode,
OperationDefinitionNode,
SelectionNode,
SelectionSetNode,
Expand Down Expand Up @@ -153,12 +155,7 @@ def filter_field_node(
schema_obj: str,
context: QueryFilterContext,
) -> Optional[FieldNode]:
context.variables.update(
argument.value.name.value
for argument in field_node.arguments
if isinstance(argument.value, VariableNode)
)

self.update_context_variables(field_node, context)
if not field_node.selection_set:
return field_node

Expand Down Expand Up @@ -391,3 +388,23 @@ def get_type_fields_dependencies(
return self.dependencies[schema_id][type_name]

return None

def update_context_variables(
self, field_node: FieldNode, context: QueryFilterContext
):
for argument in field_node.arguments:
self.extract_variables(argument.value, context) # type: ignore

def extract_variables(
self,
value: VariableNode | ListValueNode | ObjectValueNode,
context: QueryFilterContext,
):
if isinstance(value, VariableNode):
context.variables.add(value.name.value)
elif isinstance(value, ObjectValueNode):
for field in value.fields:
self.extract_variables(field.value, context) # type: ignore
elif isinstance(value, ListValueNode):
for item in value.values:
self.extract_variables(item, context) # type: ignore
49 changes: 49 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,52 @@ def search_root_value():
@pytest.fixture
def gql():
return lambda x: x


@pytest.fixture
def car_schema():
return make_executable_schema(
"""
type Query {
carsByIds(ids: [ID!]!): [Car!]!
carsByCriteria(input: SearchInput!): [Car!]!
}
type Car {
id: ID!
make: String!
model: String!
year: Int!
}
input SearchInput {
search: SearchCriteria
}
input SearchCriteria {
make: String
model: String
year: Int
}
"""
)


@pytest.fixture
def car_schema_json(car_schema):
schema_data = graphql_sync(car_schema, get_introspection_query()).data
return {"data": schema_data}


@pytest.fixture
def car_root_value():
return {
"carsByIds": [
{"id": "car1", "make": "Toyota", "model": "Corolla", "year": 2020},
{"id": "car2", "make": "Honda", "model": "Civic", "year": 2019},
],
"carsByCriteria": [
{"id": "car3", "make": "Ford", "model": "Mustang", "year": 2018},
{"id": "car4", "make": "Chevrolet", "model": "Camaro", "year": 2017},
],
}
112 changes: 112 additions & 0 deletions tests/test_proxy_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,118 @@ async def test_proxy_schema_splits_variables_between_schemas(
}


@pytest.mark.asyncio
async def test_proxy_schema_handles_object_variables_correctly(
httpx_mock,
car_schema_json,
car_root_value,
):
httpx_mock.add_response(
url="http://graphql.example.com/cars/", json=car_schema_json
)
httpx_mock.add_response(
url="http://graphql.example.com/cars/",
json={"data": car_root_value["carsByCriteria"]},
)

proxy_schema = ProxySchema()
proxy_schema.add_remote_schema("http://graphql.example.com/cars/")
proxy_schema.get_final_schema()

await proxy_schema.root_resolver(
{},
"CarsByCriteriaQuery",
{"criteria": {"make": "Toyota", "model": "Corolla", "year": 2020}},
parse(
"""
query CarsByCriteriaQuery($criteria: SearchCriteria!) {
carsByCriteria(input: { criteria: $criteria }) {
id
make
model
year
}
}
"""
),
)

cars_request = httpx_mock.get_requests(url="http://graphql.example.com/cars/")[-1]

assert json.loads(cars_request.content) == {
"operationName": "CarsByCriteriaQuery",
"variables": {"criteria": {"make": "Toyota", "model": "Corolla", "year": 2020}},
"query": dedent(
"""
query CarsByCriteriaQuery($criteria: SearchCriteria!) {
carsByCriteria(input: {criteria: $criteria}) {
id
make
model
year
}
}
"""
).strip(),
}


@pytest.mark.asyncio
async def test_proxy_schema_handles_list_variables_correctly(
httpx_mock,
car_schema_json,
car_root_value,
):
httpx_mock.add_response(
url="http://graphql.example.com/cars/", json=car_schema_json
)
httpx_mock.add_response(
url="http://graphql.example.com/cars/",
json={"data": car_root_value["carsByIds"]},
)

proxy_schema = ProxySchema()
proxy_schema.add_remote_schema("http://graphql.example.com/cars/")
proxy_schema.get_final_schema()

await proxy_schema.root_resolver(
{},
"CarsQuery",
{"id": "car2"},
parse(
"""
query CarsQuery($id: ID!) {
carsByIds(ids: [$id]) {
id
make
model
year
}
}
"""
),
)

cars_request = httpx_mock.get_requests(url="http://graphql.example.com/cars/")[-1]

assert json.loads(cars_request.content) == {
"operationName": "CarsQuery",
"variables": {"id": "car2"},
"query": dedent(
"""
query CarsQuery($id: ID!) {
carsByIds(ids: [$id]) {
id
make
model
year
}
}
"""
).strip(),
}


@pytest.mark.asyncio
async def test_proxy_schema_splits_variables_from_fragments_between_schemas(
httpx_mock,
Expand Down

0 comments on commit d6c3968

Please sign in to comment.