From 6eb1a48cb2fc9a6a1fc89c58900a149ea232a39a Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 10 Oct 2015 14:53:46 -0700 Subject: [PATCH] Improved overall testing coverage --- graphene/contrib/django/fields.py | 4 ++ graphene/core/fields.py | 2 +- graphene/core/types.py | 11 ++++ graphene/relay/types.py | 6 +- tests/contrib_django/test_urls.py | 6 ++ tests/contrib_django/test_views.py | 8 +++ tests/core/test_fields.py | 68 +++++++++++++++++++++++ tests/core/test_scalars.py | 18 ++++++ tests/core/test_schema.py | 30 ++++++++++ tests/relay/test_relayfields.py | 43 ++++++++++++++ tests/starwars_django/data.py | 16 +++++- tests/starwars_django/models.py | 8 +++ tests/starwars_django/schema.py | 11 +++- tests/starwars_django/test_connections.py | 6 ++ 14 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 tests/core/test_scalars.py create mode 100644 tests/relay/test_relayfields.py diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index a0d24555..ba47047e 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -66,6 +66,10 @@ class DjangoModelField(Field): resolved = super(DjangoModelField, self).resolve(instance, args, info) schema = info.schema.graphene_schema _type = self.get_object_type(schema) + assert _type, ("Field %s cannot be retrieved as the " + "ObjectType is not registered by the schema" % ( + self.field_name + )) return _type(resolved) @memoize diff --git a/graphene/core/fields.py b/graphene/core/fields.py index 6318c25a..d4c9f4a8 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -121,7 +121,7 @@ class Field(object): """ Displays the module, class and name of the field. """ - path = '%s[%s]' % (self.__class__.__name__, str(self.field_type)) + path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) name = getattr(self, 'field_name', None) if name is not None: return '<%s: %s>' % (path, name) diff --git a/graphene/core/types.py b/graphene/core/types.py index fed216c4..e7b4e7fa 100644 --- a/graphene/core/types.py +++ b/graphene/core/types.py @@ -19,6 +19,9 @@ class ObjectTypeMeta(type): def is_interface(cls, parents): return Interface in parents + def is_mutation(cls, parents): + return Mutation in parents + def __new__(cls, name, bases, attrs): super_new = super(ObjectTypeMeta, cls).__new__ parents = [b for b in bases if isinstance(b, cls)] @@ -44,6 +47,10 @@ class ObjectTypeMeta(type): new_class.add_to_class('_meta', new_class.options_cls(meta)) new_class._meta.interface = new_class.is_interface(parents) + new_class._meta.mutation = new_class.is_mutation(parents) + + assert not (new_class._meta.interface and new_class._meta.mutation) + # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) @@ -155,5 +162,9 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): pass +class Mutation(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): + pass + + class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)): pass diff --git a/graphene/relay/types.py b/graphene/relay/types.py index 50e43e9c..fce02aee 100644 --- a/graphene/relay/types.py +++ b/graphene/relay/types.py @@ -15,9 +15,9 @@ def get_node_type(schema, obj): return obj.internal_type(schema) -def get_node(schema, globalId, *args): - resolvedGlobalId = from_global_id(globalId) - _type, _id = resolvedGlobalId.type, resolvedGlobalId.id +def get_node(schema, global_id, *args): + resolved_global_id = from_global_id(global_id) + _type, _id = resolved_global_id.type, resolved_global_id.id object_type = schema.get_type(_type) if not object_type or not issubclass(object_type, BaseNode): raise Exception("The type %s is not a Node" % _type) diff --git a/tests/contrib_django/test_urls.py b/tests/contrib_django/test_urls.py index 6d0bca57..6b95105c 100644 --- a/tests/contrib_django/test_urls.py +++ b/tests/contrib_django/test_urls.py @@ -2,6 +2,7 @@ from django.conf.urls import url from graphene.contrib.django.views import GraphQLView +import graphene from graphene import Schema from graphene.contrib.django.types import ( DjangoNode, @@ -20,9 +21,14 @@ class Character(DjangoNode): class Human(DjangoNode): + raises = graphene.StringField() + class Meta: model = Article + def resolve_raises(self, *args): + raise Exception("This field should raise exception") + def get_node(self, id): pass diff --git a/tests/contrib_django/test_views.py b/tests/contrib_django/test_views.py index be689062..435bcdf8 100644 --- a/tests/contrib_django/test_views.py +++ b/tests/contrib_django/test_views.py @@ -72,6 +72,14 @@ def test_client_get_good_query(settings, client): assert json_response == expected_json +def test_client_get_good_query_with_raise(settings, client): + settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' + response = client.get('/graphql', {'query': '{ raises }'}) + json_response = format_response(response) + assert json_response['errors'][0]['message'] == 'This field should raise exception' + assert json_response['data']['raises'] is None + + def test_client_post_good_query(settings, client): settings.ROOT_URLCONF = 'tests.contrib_django.test_urls' response = client.post('/graphql', json.dumps({'query': '{ headline }'}), 'application/json') diff --git a/tests/core/test_fields.py b/tests/core/test_fields.py index 68497843..bb644306 100644 --- a/tests/core/test_fields.py +++ b/tests/core/test_fields.py @@ -4,6 +4,7 @@ from pytest import raises from graphene.core.fields import ( Field, StringField, + NonNullField ) from graphene.core.options import Options @@ -27,6 +28,9 @@ class ObjectType(object): def can_resolve(self, *args): return True + def __str__(self): + return "ObjectType" + ot = ObjectType() ObjectType._meta.contribute_to_class(ObjectType, '_meta') @@ -69,6 +73,12 @@ def test_stringfield_type(): assert f.internal_type(schema) == GraphQLString +def test_nonnullfield_type(): + f = NonNullField(StringField()) + f.contribute_to_class(ot, 'field_name') + assert isinstance(f.internal_type(schema), GraphQLNonNull) + + def test_stringfield_type_required(): f = StringField(required=True) f.contribute_to_class(ot, 'field_name') @@ -108,3 +118,61 @@ def test_field_resolve_type_custom(): f.contribute_to_class(ot, 'field_name') field_type = f.get_object_type(s) assert field_type == ot + + +def test_field_orders(): + f1 = Field(None) + f2 = Field(None) + assert f1 < f2 + + +def test_field_orders_wrong_type(): + field = Field(None) + try: + assert not field < 1 + except TypeError: + # Fix exception raising in Python3+ + pass + + +def test_field_eq(): + f1 = Field(None) + f2 = Field(None) + assert f1 != f2 + + +def test_field_eq_wrong_type(): + field = Field(None) + assert field != 1 + + +def test_field_hash(): + f1 = Field(None) + f2 = Field(None) + assert hash(f1) != hash(f2) + + +def test_field_none_type_raises_error(): + s = Schema() + f = Field(None) + f.contribute_to_class(ot, 'field_name') + with raises(Exception) as excinfo: + f.internal_field(s) + assert str(excinfo.value) == "Internal type for field ObjectType.field_name is None" + + +def test_field_str(): + f = StringField() + f.contribute_to_class(ot, 'field_name') + assert str(f) == "ObjectType.field_name" + + +def test_field_repr(): + f = StringField() + assert repr(f) == "" + + +def test_field_repr_contributed(): + f = StringField() + f.contribute_to_class(ot, 'field_name') + assert repr(f) == "" diff --git a/tests/core/test_scalars.py b/tests/core/test_scalars.py new file mode 100644 index 00000000..654e99aa --- /dev/null +++ b/tests/core/test_scalars.py @@ -0,0 +1,18 @@ +from graphene.core.scalars import ( + GraphQLSkipField +) + + +def test_skipfield_serialize(): + f = GraphQLSkipField + assert f.serialize('a') is None + + +def test_skipfield_parse_value(): + f = GraphQLSkipField + assert f.parse_value('a') is None + + +def test_skipfield_parse_literal(): + f = GraphQLSkipField + assert f.parse_literal('a') is None diff --git a/tests/core/test_schema.py b/tests/core/test_schema.py index 4d89d725..f6e3d849 100644 --- a/tests/core/test_schema.py +++ b/tests/core/test_schema.py @@ -109,3 +109,33 @@ def test_schema_get_type_map(): schema.schema.get_type_map().keys(), ['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive', '__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean'] ) + + +def test_schema_no_query(): + schema = Schema(name='My own schema') + with raises(Exception) as excinfo: + schema.schema + assert 'define a base query type' in str(excinfo) + + +def test_schema_register(): + schema = Schema(name='My own schema') + + @schema.register + class MyType(ObjectType): + type = StringField(resolve=lambda *_: 'Dog') + + assert schema.get_type('MyType') == MyType + + +def test_schema_introspect(): + schema = Schema(name='My own schema') + + class MyType(ObjectType): + type = StringField(resolve=lambda *_: 'Dog') + + schema.query = MyType + + introspection = schema.introspect() + assert '__schema' in introspection + diff --git a/tests/relay/test_relayfields.py b/tests/relay/test_relayfields.py new file mode 100644 index 00000000..5285a219 --- /dev/null +++ b/tests/relay/test_relayfields.py @@ -0,0 +1,43 @@ +from pytest import raises + +import graphene +from graphene import relay + +schema = graphene.Schema() + + +class MyType(object): + name = 'my' + + +class MyNode(relay.Node): + name = graphene.StringField() + + @classmethod + def get_node(cls, id): + return MyNode(MyType()) + + +class Query(graphene.ObjectType): + my_node = relay.NodeField(MyNode) + + +schema.query = Query + + +def test_nodefield_query(): + query = ''' + query RebelsShipsQuery { + myNode(id:"TXlOb2RlOjE=") { + name + } + } + ''' + expected = { + 'myNode': { + 'name': 'my' + } + } + result = schema.execute(query) + assert not result.errors + assert result.data == expected diff --git a/tests/starwars_django/data.py b/tests/starwars_django/data.py index cb43c29b..bf88b7a9 100644 --- a/tests/starwars_django/data.py +++ b/tests/starwars_django/data.py @@ -1,18 +1,28 @@ -from collections import namedtuple - -from .models import Ship, Faction +from .models import Ship, Faction, Character def initialize(): + human = Character( + name='Human' + ) + human.save() + + droid = Character( + name='Droid' + ) + droid.save() + rebels = Faction( id='1', name='Alliance to Restore the Republic', + hero=human ) rebels.save() empire = Faction( id='2', name='Galactic Empire', + hero=droid ) empire.save() diff --git a/tests/starwars_django/models.py b/tests/starwars_django/models.py index 6afa152e..10050c33 100644 --- a/tests/starwars_django/models.py +++ b/tests/starwars_django/models.py @@ -2,8 +2,16 @@ from __future__ import absolute_import from django.db import models +class Character(models.Model): + name = models.CharField(max_length=50) + + def __str__(self): + return self.name + + class Faction(models.Model): name = models.CharField(max_length=50) + hero = models.ForeignKey(Character) def __str__(self): return self.name diff --git a/tests/starwars_django/schema.py b/tests/starwars_django/schema.py index 0bde6826..1949da6a 100644 --- a/tests/starwars_django/schema.py +++ b/tests/starwars_django/schema.py @@ -4,7 +4,8 @@ from graphene.contrib.django import ( DjangoObjectType, DjangoNode ) -from .models import Ship as ShipModel, Faction as FactionModel +from .models import ( + Ship as ShipModel, Faction as FactionModel, Character as CharacterModel) from .data import ( getFaction, getShip, @@ -17,7 +18,6 @@ schema = graphene.Schema(name='Starwars Django Relay Schema') class Ship(DjangoNode): - class Meta: model = ShipModel @@ -26,8 +26,13 @@ class Ship(DjangoNode): return Ship(getShip(id)) -class Faction(DjangoNode): +@schema.register +class CharacterModel(DjangoObjectType): + class Meta: + model = CharacterModel + +class Faction(DjangoNode): class Meta: model = FactionModel diff --git a/tests/starwars_django/test_connections.py b/tests/starwars_django/test_connections.py index e7181dc9..9992f0f3 100644 --- a/tests/starwars_django/test_connections.py +++ b/tests/starwars_django/test_connections.py @@ -14,6 +14,9 @@ def test_correct_fetch_first_ship_rebels(): query RebelsShipsQuery { rebels { name, + hero { + name + } ships(first: 1) { edges { node { @@ -27,6 +30,9 @@ def test_correct_fetch_first_ship_rebels(): expected = { 'rebels': { 'name': 'Alliance to Restore the Republic', + 'hero': { + 'name': 'Human' + }, 'ships': { 'edges': [ {