Skip to content

Commit 523bb72

Browse files
authored
Improve test coverage (#61)
1 parent f779d32 commit 523bb72

File tree

8 files changed

+157
-50
lines changed

8 files changed

+157
-50
lines changed

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ python:
1111
- pypy3
1212
cache: pip
1313
install:
14-
- pip install -e .[test]
15-
- pip install flake8==3.7.9
14+
- pip install -e .[dev]
1615
script:
1716
- flake8 gql tests
18-
- pytest --cov=gql tests
17+
- pytest --cov-report=term-missing --cov=gql tests
1918
after_success:
2019
- coveralls
2120
deploy:

gql/dsl.py

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import decimal
21
from functools import partial
32

43
import six
54
from graphql.language import ast
65
from graphql.language.printer import print_ast
7-
from graphql.type import (GraphQLField, GraphQLList,
8-
GraphQLNonNull, GraphQLEnumType)
6+
from graphql.type import (GraphQLEnumType, GraphQLList, GraphQLNonNull)
7+
from graphql.utils.ast_from_value import ast_from_value
98

109
from .utils import to_camel_case
1110

@@ -31,7 +30,7 @@ def query(self, *args, **kwargs):
3130
return self.execute(query(*args, **kwargs))
3231

3332
def mutate(self, *args, **kwargs):
34-
return self.query(*args, operation='mutate', **kwargs)
33+
return self.query(*args, operation='mutation', **kwargs)
3534

3635
def execute(self, document):
3736
return self.client.execute(document)
@@ -54,26 +53,12 @@ def get_field(self, name):
5453
if camel_cased_name in self.type.fields:
5554
return camel_cased_name, self.type.fields[camel_cased_name]
5655

57-
raise KeyError('Field {} doesnt exist in type {}.'.format(name, self.type.name))
56+
raise KeyError('Field {} does not exist in type {}.'.format(name, self.type.name))
5857

5958

6059
def selections(*fields):
6160
for _field in fields:
62-
yield field(_field).ast
63-
64-
65-
def get_ast_value(value):
66-
if isinstance(value, ast.Node):
67-
return value
68-
if isinstance(value, six.string_types):
69-
return ast.StringValue(value=value)
70-
elif isinstance(value, bool):
71-
return ast.BooleanValue(value=value)
72-
elif isinstance(value, (float, decimal.Decimal)):
73-
return ast.FloatValue(value=value)
74-
elif isinstance(value, int):
75-
return ast.IntValue(value=value)
76-
return None
61+
yield selection_field(_field).ast
7762

7863

7964
class DSLField(object):
@@ -89,22 +74,22 @@ def select(self, *fields):
8974
self.ast_field.selection_set.selections.extend(selections(*fields))
9075
return self
9176

92-
def __call__(self, *args, **kwargs):
93-
return self.args(*args, **kwargs)
77+
def __call__(self, **kwargs):
78+
return self.args(**kwargs)
9479

9580
def alias(self, alias):
9681
self.ast_field.alias = ast.Name(value=alias)
9782
return self
9883

99-
def args(self, **args):
100-
for name, value in args.items():
84+
def args(self, **kwargs):
85+
for name, value in kwargs.items():
10186
arg = self.field.args.get(name)
10287
arg_type_serializer = get_arg_serializer(arg.type)
103-
value = arg_type_serializer(value)
88+
serialized_value = arg_type_serializer(value)
10489
self.ast_field.arguments.append(
10590
ast.Argument(
10691
name=ast.Name(value=name),
107-
value=get_ast_value(value)
92+
value=serialized_value
10893
)
10994
)
11095
return self
@@ -117,29 +102,29 @@ def __str__(self):
117102
return print_ast(self.ast_field)
118103

119104

120-
def field(field, **args):
121-
if isinstance(field, GraphQLField):
122-
return DSLField(field).args(**args)
123-
elif isinstance(field, DSLField):
105+
def selection_field(field):
106+
if isinstance(field, DSLField):
124107
return field
125108

126109
raise Exception('Received incompatible query field: "{}".'.format(field))
127110

128111

129-
def query(*fields):
112+
def query(*fields, **kwargs):
113+
if 'operation' not in kwargs:
114+
kwargs['operation'] = 'query'
130115
return ast.Document(
131116
definitions=[ast.OperationDefinition(
132-
operation='query',
117+
operation=kwargs['operation'],
133118
selection_set=ast.SelectionSet(
134119
selections=list(selections(*fields))
135120
)
136121
)]
137122
)
138123

139124

140-
def serialize_list(serializer, values):
141-
assert isinstance(values, Iterable), 'Expected iterable, received "{}"'.format(repr(values))
142-
return [serializer(v) for v in values]
125+
def serialize_list(serializer, list_values):
126+
assert isinstance(list_values, Iterable), 'Expected iterable, received "{}"'.format(repr(list_values))
127+
return ast.ListValue(values=[serializer(v) for v in list_values])
143128

144129

145130
def get_arg_serializer(arg_type):
@@ -150,8 +135,4 @@ def get_arg_serializer(arg_type):
150135
return partial(serialize_list, inner_serializer)
151136
if isinstance(arg_type, GraphQLEnumType):
152137
return lambda value: ast.EnumValue(value=arg_type.serialize(value))
153-
return arg_type.serialize
154-
155-
156-
def var(name):
157-
return ast.Variable(name=name)
138+
return lambda value: ast_from_value(arg_type.serialize(value))

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
]
99

1010
tests_require = [
11+
'coveralls==1.11.1',
1112
'pytest==4.6.9',
1213
'pytest-cov==2.8.1',
1314
'mock==3.0.5',

tests/starwars/fixtures.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from collections import namedtuple
22

3-
Human = namedtuple('Human', 'id name friends appearsIn homePlanet')
3+
Human = namedtuple('Human', 'id name friends appearsIn homePlanet isAlive')
44

55
luke = Human(
66
id='1000',
77
name='Luke Skywalker',
88
friends=['1002', '1003', '2000', '2001'],
99
appearsIn=[4, 5, 6],
1010
homePlanet='Tatooine',
11+
isAlive=True,
1112
)
1213

1314
vader = Human(
@@ -16,6 +17,7 @@
1617
friends=['1004'],
1718
appearsIn=[4, 5, 6],
1819
homePlanet='Tatooine',
20+
isAlive=False,
1921
)
2022

2123
han = Human(
@@ -24,6 +26,7 @@
2426
friends=['1000', '1003', '2001'],
2527
appearsIn=[4, 5, 6],
2628
homePlanet=None,
29+
isAlive=True,
2730
)
2831

2932
leia = Human(
@@ -32,6 +35,7 @@
3235
friends=['1000', '1002', '2000', '2001'],
3336
appearsIn=[4, 5, 6],
3437
homePlanet='Alderaan',
38+
isAlive=True,
3539
)
3640

3741
tarkin = Human(
@@ -40,6 +44,7 @@
4044
friends=['1001'],
4145
appearsIn=[4],
4246
homePlanet=None,
47+
isAlive=False,
4348
)
4449

4550
humanData = {
@@ -78,6 +83,10 @@ def getCharacter(id):
7883
return humanData.get(id) or droidData.get(id)
7984

8085

86+
def getCharacters(ids):
87+
return map(getCharacter, ids)
88+
89+
8190
def getFriends(character):
8291
return map(getCharacter, character.friends)
8392

@@ -94,3 +103,9 @@ def getHuman(id):
94103

95104
def getDroid(id):
96105
return droidData.get(id)
106+
107+
108+
def updateHumanAlive(id, status):
109+
human = humanData.get(id)
110+
human = human._replace(isAlive=status)
111+
return human

tests/starwars/schema.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
22
GraphQLField, GraphQLInterfaceType, GraphQLList,
33
GraphQLNonNull, GraphQLObjectType, GraphQLSchema,
4-
GraphQLString)
4+
GraphQLString, GraphQLBoolean)
55

6-
from .fixtures import getDroid, getFriends, getHero, getHuman
6+
from .fixtures import getCharacters, getDroid, getFriends, getHero, getHuman, updateHumanAlive
77

88
episodeEnum = GraphQLEnumType(
99
'Episode',
@@ -72,7 +72,11 @@
7272
'homePlanet': GraphQLField(
7373
GraphQLString,
7474
description='The home planet of the human, or null if unknown.',
75-
)
75+
),
76+
'isAlive': GraphQLField(
77+
GraphQLBoolean,
78+
description='The human is still alive.'
79+
),
7680
},
7781
interfaces=[characterInterface]
7882
)
@@ -140,7 +144,37 @@
140144
},
141145
resolver=lambda root, info, **args: getDroid(args['id']),
142146
),
147+
'characters': GraphQLField(
148+
GraphQLList(characterInterface),
149+
args={
150+
'ids': GraphQLArgument(
151+
description='list of character ids',
152+
type=GraphQLList(GraphQLString),
153+
)
154+
},
155+
resolver=lambda root, info, **args: getCharacters(args['ids']),
156+
),
157+
}
158+
)
159+
160+
mutationType = GraphQLObjectType(
161+
'Mutation',
162+
fields=lambda: {
163+
'updateHumanAliveStatus': GraphQLField(
164+
humanType,
165+
args={
166+
'id': GraphQLArgument(
167+
description='id of the human',
168+
type=GraphQLNonNull(GraphQLString),
169+
),
170+
'status': GraphQLArgument(
171+
description='set alive status',
172+
type=GraphQLNonNull(GraphQLBoolean),
173+
),
174+
},
175+
resolver=lambda root, info, **args: updateHumanAlive(args['id'], args['status']),
176+
),
143177
}
144178
)
145179

146-
StarWarsSchema = GraphQLSchema(query=queryType, types=[humanType, droidType])
180+
StarWarsSchema = GraphQLSchema(query=queryType, mutation=mutationType, types=[humanType, droidType])

tests/starwars/test_dsl.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_invalid_field_on_type_query(ds):
1818
ds.Query.extras.select(
1919
ds.Character.name
2020
)
21-
assert "Field extras doesnt exist in type Query." in str(excInfo.value)
21+
assert "Field extras does not exist in type Query." in str(excInfo.value)
2222

2323

2424
def test_incompatible_query_field(ds):
@@ -122,3 +122,55 @@ def test_hero_name_query_result(ds):
122122
}
123123
}
124124
assert result == expected
125+
126+
127+
def test_arg_serializer_list(ds):
128+
result = ds.query(
129+
ds.Query.characters.args(ids=[1000, 1001, 1003]).select(
130+
ds.Character.name,
131+
)
132+
)
133+
expected = {
134+
'characters': [
135+
{
136+
'name': 'Luke Skywalker'
137+
},
138+
{
139+
'name': 'Darth Vader'
140+
},
141+
{
142+
'name': 'Leia Organa'
143+
}
144+
]
145+
}
146+
assert result == expected
147+
148+
149+
def test_arg_serializer_enum(ds):
150+
result = ds.query(
151+
ds.Query.hero.args(episode=5).select(
152+
ds.Character.name
153+
)
154+
)
155+
expected = {
156+
'hero': {
157+
'name': 'Luke Skywalker'
158+
}
159+
}
160+
assert result == expected
161+
162+
163+
def test_human_alive_mutation_result(ds):
164+
result = ds.mutate(
165+
ds.Mutation.updateHumanAliveStatus.args(id=1004, status=True).select(
166+
ds.Human.name,
167+
ds.Human.isAlive
168+
)
169+
)
170+
expected = {
171+
'updateHumanAliveStatus': {
172+
'name': 'Wilhuff Tarkin',
173+
'isAlive': True
174+
}
175+
}
176+
assert result == expected

tests/test_client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,28 @@ def test_execute_result_error():
6464
with pytest.raises(Exception) as excInfo:
6565
client.execute(query)
6666
assert "Cannot query field \"id\" on type \"Continent\"." in str(excInfo.value)
67+
68+
69+
def test_http_transport_raise_for_status_error():
70+
client = Client(
71+
transport=RequestsHTTPTransport(
72+
url='https://countries.trevorblades.com/',
73+
use_json=False,
74+
headers={
75+
"Content-type": "application/json",
76+
}
77+
)
78+
)
79+
80+
query = gql('''
81+
query getContinents {
82+
continents {
83+
code
84+
name
85+
id
86+
}
87+
}
88+
''')
89+
with pytest.raises(Exception) as excInfo:
90+
client.execute(query)
91+
assert "400 Client Error: Bad Request for url" in str(excInfo.value)

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ deps = -e.[test]
1515
; Prevent installing issues: https://github.com/ContinuumIO/anaconda-issues/issues/542
1616
commands =
1717
pip install -U setuptools
18-
pytest --cov=gql tests {posargs}
18+
pytest --cov-report=term-missing --cov=gql tests {posargs}
1919

2020
[testenv:flake8]
2121
basepython = python3.8

0 commit comments

Comments
 (0)