✅ Add basic create order test
🎨 Add MutationTest helper to help write mutation tests with strawberry ➕ add pytest-django ⬆ Bump some python deps
This commit is contained in:
1 parent
549b0cfdd6
commit
9d3afadbff
6 files changed
+325
-193
No files matched your search
+131
@@ -0,0 +1,131 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from graphql.pyutils import snake_to_camel
|
||||
import strawberry
|
||||
from strawberry.arguments import is_unset
|
||||
from strawberry.types import ExecutionResult
|
||||
|
||||
from main.api.schema import schema
|
||||
from typing import Any, Iterable, Type
|
||||
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
|
||||
def lowercase_first_letter(text: str):
|
||||
return text[0].lower() + text[1:]
|
||||
|
||||
|
||||
def is_valid_field_values(values: Iterable) -> bool:
|
||||
return isinstance(values, list) and all(isinstance(value, str) or isinstance(value, dict) for value in values)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MutationTest:
|
||||
"""Helper class to test graphql mutations"""
|
||||
|
||||
mutation: Type[strawberry.mutation]
|
||||
fields: Iterable[str]
|
||||
user: User = None
|
||||
_schema: strawberry.Schema = schema # FIXME: take as arg
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"MutationTest ({self.name})"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return snake_to_camel(self.mutation.name)
|
||||
|
||||
@property
|
||||
def arguments(self) -> Iterable[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"name": lowercase_first_letter(snake_to_camel(arg.python_name)),
|
||||
"type": self._schema.schema_converter.from_argument(arg).type,
|
||||
"is_required": is_unset(arg),
|
||||
}
|
||||
for arg in self.mutation.arguments
|
||||
]
|
||||
|
||||
@property
|
||||
def required_arguments(self) -> Iterable[dict[str, Any]]:
|
||||
return [arg for arg in self.arguments if arg["is_required"]]
|
||||
|
||||
@property
|
||||
def optional_arguments(self) -> Iterable[dict[str, Any]]:
|
||||
return [arg for arg in self.arguments if not arg["is_required"]]
|
||||
|
||||
@property
|
||||
def mutation_args(self) -> tuple[Iterable[str], Iterable[str]]:
|
||||
_definitions = []
|
||||
_inputs = []
|
||||
|
||||
for arg in self.arguments:
|
||||
name = arg["name"]
|
||||
|
||||
_definitions.append(f"${name}: {arg['type']}")
|
||||
_inputs.append(f"{name}: ${name}")
|
||||
|
||||
return _definitions, _inputs
|
||||
|
||||
@classmethod
|
||||
def expand_field(cls, field):
|
||||
if isinstance(field, str):
|
||||
return field
|
||||
if isinstance(field, dict):
|
||||
field_string = ""
|
||||
for key, values in field.items():
|
||||
if not is_valid_field_values(values):
|
||||
raise ValueError("Wrong format in MutationTest retrieval fields")
|
||||
|
||||
values_string = ", ".join(cls.expand_field(value) for value in values)
|
||||
field_string = f"""
|
||||
{field_string}
|
||||
{key} {{
|
||||
{values_string}
|
||||
}}
|
||||
"""
|
||||
return field_string
|
||||
|
||||
raise ValueError("MutationTest: Fields can only be strings or dicts")
|
||||
|
||||
@property
|
||||
def serialize(self) -> str:
|
||||
_definitions, _inputs = self.mutation_args
|
||||
definitions = ", ".join(_definitions)
|
||||
inputs = ", ".join(_inputs)
|
||||
operation_name = lowercase_first_letter(self.name)
|
||||
if not isinstance(self.fields, str):
|
||||
expanded_fields = [self.expand_field(field) for field in self.fields]
|
||||
return_fields = " ".join(expanded_fields)
|
||||
else:
|
||||
return_fields = self.fields
|
||||
|
||||
return f"""mutation {self.name}({definitions}) {{
|
||||
{operation_name}({inputs}) {{
|
||||
{return_fields}
|
||||
}}
|
||||
}}"""
|
||||
|
||||
@property
|
||||
def user_request(self):
|
||||
request_factory = RequestFactory()
|
||||
request = request_factory.get("/api/v2/graphql/")
|
||||
if self.user:
|
||||
request.user = self.user
|
||||
return request
|
||||
|
||||
# Perform the mutation
|
||||
# only keyword args please
|
||||
def mutate(self, **kwargs) -> ExecutionResult:
|
||||
variables = {lowercase_first_letter(snake_to_camel(name)): val for name, val in kwargs.items()}
|
||||
required_names = set(arg["name"] for arg in self.required_arguments)
|
||||
|
||||
missing_args = required_names - set(variables.keys())
|
||||
if missing_args:
|
||||
raise ValueError(f"Missing required arguments: {missing_args}")
|
||||
|
||||
# Execute mutation
|
||||
return self._schema.execute_sync(
|
||||
query=self.serialize, variable_values=variables, context_value=self.user_request
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
"""
|
||||
This file demonstrates writing tests using the unittest module. These will pass
|
||||
when you run "manage.py test".
|
||||
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
||||
@@ -0,0 +1,50 @@
|
||||
import pytest
|
||||
from django.test import Client
|
||||
from strawberry.django.test import GraphQLTestClient
|
||||
|
||||
from main.api.mutations import create_order
|
||||
from main.models import Product, Order
|
||||
from main.testing import MutationTest
|
||||
|
||||
|
||||
@pytest.fixture(name="gql_client")
|
||||
def gql_client_fixture(client: Client):
|
||||
yield GraphQLTestClient(client)
|
||||
|
||||
|
||||
@pytest.fixture(name='product')
|
||||
def product_fix(db):
|
||||
return Product.objects.create(name='borg', sale_price_ext=35, sale_price_int=30)
|
||||
|
||||
|
||||
@pytest.fixture(name='create_order_mutation')
|
||||
def create_order_mutation_fixture(product):
|
||||
fields = """
|
||||
... on CreateOrderSuccess {
|
||||
order {
|
||||
id
|
||||
orderSum
|
||||
orderlines {
|
||||
id
|
||||
product {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}"""
|
||||
# FIXME: maybe drop this helper and just use GraphQLTestClient
|
||||
return MutationTest(create_order, fields)
|
||||
|
||||
|
||||
def test_create_order(product, create_order_mutation):
|
||||
assert Order.objects.count() == 0
|
||||
res = create_order_mutation.mutate(customerId="external", isExternal=True, orderLines=[{"productId": product.id, "amount": 1}])
|
||||
assert res
|
||||
assert not res.errors
|
||||
assert res.data
|
||||
order_lines = res.data.get('createOrder', {}).get('order', {}).get('orderlines', [])
|
||||
assert order_lines
|
||||
assert order_lines[0]['product']['id'] == str(product.id)
|
||||
assert Order.objects.count() == 1
|
||||
o = Order.objects.first()
|
||||
assert o.orderlines.first().product.id == product.id
|
||||
Reference in new issue
Block a user