Skip to content

Commit 08837a7

Browse files
committed
Take the new pyswagger fix to encode path parameters and test it
1 parent 2edb0b1 commit 08837a7

File tree

3 files changed

+30
-16
lines changed

3 files changed

+30
-16
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
setup(
1010
name='swagger-conformance',
1111
packages=find_packages(exclude=['examples', 'docs', 'tests']),
12-
install_requires=['hypothesis', 'pyswagger', 'requests'],
12+
install_requires=['hypothesis', 'pyswagger>=0.8.28', 'requests'],
1313
version=VERSION,
1414
description="Tool for testing whether your API conforms to its swagger "
1515
"specification",

swaggerconformance/template/valuetemplates.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,24 +225,24 @@ def __init__(self, max_length=None, min_length=None,
225225
pattern=None, enum=None):
226226
if min_length is None:
227227
min_length = 1
228+
if min_length < 1:
229+
raise ValueError("Path parameters must be at least 1 char long")
228230
super().__init__(max_length=max_length, min_length=min_length,
229231
pattern=pattern, enum=enum)
230232

231-
def hypothesize(self):
232-
return super().hypothesize().map(lambda x: urllib.parse.quote(x,
233-
safe=''))
234-
235233

236234
class HTTPHeaderStringTemplate(StringTemplate):
237235
"""Template for a string value which must be valid in a HTTP header."""
238236

239237
def __init__(self, max_length=None, min_length=None,
240238
pattern=None, enum=None):
239+
# Heaved values are strings but cannot contain newlines.
241240
super().__init__(max_length=max_length, min_length=min_length,
242241
pattern=pattern, enum=enum,
243242
blacklist_chars=['\r', '\n'])
244243

245244
def hypothesize(self):
245+
# Header values shouldn't have surrounding whitespace.
246246
return super().hypothesize().map(str.strip)
247247

248248
class DateTemplate(ValueTemplate):

tests/test_swaggerconformance.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import re
1414
import os.path as osp
1515
import json
16+
import urllib
1617

1718
import responses
1819
import hypothesis
@@ -56,6 +57,7 @@ def respond_to_delete(path, response_json=None, status=200):
5657

5758

5859
class APITemplateTestCase(unittest.TestCase):
60+
"""Simple tests working with the `APITemplate` class directly."""
5961

6062
def setUp(self):
6163
self.client = swaggerconformance.SwaggerClient(TEST_SCHEMA_PATH)
@@ -65,13 +67,15 @@ def tearDown(self):
6567
pass
6668

6769
def test_schema_parse(self):
70+
"""Test we can parse an API schema, and find all the endpoints."""
6871
api_template = swaggerconformance.APITemplate(self.client)
6972
expected_endpoints = {'/schema', '/apps', '/apps/{appid}'}
7073
self.assertSetEqual(set(api_template.endpoints.keys()),
7174
expected_endpoints)
7275

7376
@responses.activate
7477
def test_endpoint_manually(self):
78+
"""Test we can make a request against an endpoint manually."""
7579
api_template = swaggerconformance.APITemplate(self.client)
7680

7781
# Find the template GET operation on the /apps/{appid} endpoint.
@@ -96,9 +100,11 @@ def test_endpoint_manually(self):
96100

97101

98102
class ParameterTypesTestCase(unittest.TestCase):
103+
"""Tests to cover all the options/constraints on parameters."""
99104

100105
@responses.activate
101106
def test_full_put(self):
107+
"""A PUT request containing all different parameter types."""
102108
# Handle all the basic endpoints.
103109
respond_to_get('/schema')
104110
respond_to_get('/example')
@@ -110,6 +116,7 @@ def test_full_put(self):
110116

111117
@responses.activate
112118
def test_all_constraints(self):
119+
"""A PUT request containing all parameter constraint combinations."""
113120
# Handle all the basic endpoints.
114121
respond_to_get('/schema')
115122
respond_to_put(r'/example/-?\d+', status=204)
@@ -119,9 +126,11 @@ def test_all_constraints(self):
119126

120127

121128
class ExternalExamplesTestCase(unittest.TestCase):
129+
"""Tests of API specs from external sources to get coverage."""
122130

123131
@responses.activate
124132
def test_swaggerio_petstore(self):
133+
"""The petstore API spec from the swagger.io site is handled."""
125134
# Example responses matching the required models.
126135
pet = {"id": 0,
127136
"category": {"id": 0, "name": "string"},
@@ -182,6 +191,7 @@ def test_swaggerio_petstore(self):
182191

183192
@responses.activate
184193
def test_openapi_uber(self):
194+
"""An example Uber API spec from the OpenAPI examples is handled."""
185195
profile = {"first_name": "steve",
186196
"last_name": "stevenson",
187197
"email": "example@stevemail.com",
@@ -218,18 +228,21 @@ def test_openapi_uber(self):
218228

219229

220230
class CompareResponsesTestCase(unittest.TestCase):
231+
"""Tests that values sent on requests can be returned unchanged."""
221232

222233
@responses.activate
223234
def test_get_resp(self):
235+
"""Test that a GET URL parameter is the same when passed back in the
236+
GET response body - i.e. there's no mismatched encode/decode."""
224237
url_base = SCHEMA_URL_BASE + '/example/'
225-
def request_callback(request):
238+
def _request_callback(request):
226239
value = request.url[len(url_base):]
227-
value = value.replace("~", "%7E") # Hack to fix URL quoting
228-
# value = urllib.parse.unquote(value) # when pyswagger urlquotes
240+
# Special characters will be quoted in the URL - unquote them here.
241+
value = urllib.parse.unquote_plus(value)
229242
return (200, {}, json.dumps({'in_str': value}))
230243

231244
responses.add_callback(responses.GET, re.compile(url_base),
232-
callback=request_callback,
245+
callback=_request_callback,
233246
content_type=CONTENT_TYPE_JSON)
234247

235248
my_val_factory = swaggerconformance.ValueFactory()
@@ -238,9 +251,9 @@ def request_callback(request):
238251
operation = api_template.endpoints["/example/{in_str}"]["get"]
239252
strategy = operation.hypothesize_parameters(my_val_factory)
240253

241-
@hypothesis.settings(max_examples=50)
254+
@hypothesis.settings(max_examples=200)
242255
@hypothesis.given(strategy)
243-
def single_operation_test(client, operation, params):
256+
def _single_operation_test(client, operation, params):
244257
result = client.request(operation, params)
245258
assert result.status in operation.response_codes, \
246259
"{} not in {}".format(result.status,
@@ -249,31 +262,32 @@ def single_operation_test(client, operation, params):
249262
assert result.data.in_str == params["in_str"], \
250263
"{} != {}".format(result.data.in_str, params["in_str"])
251264

252-
single_operation_test(client, operation) # pylint: disable=I0011,E1120
265+
_single_operation_test(client, operation) # pylint: disable=I0011,E1120
253266

254267

255268
class MultiRequestTestCase(unittest.TestCase):
269+
"""Test that multiple requests can be handled as part of single test."""
256270

257271
@responses.activate
258272
def test_put_get_combined(self):
259273
"""Test just to show how tests using multiple requests work."""
260274
body = []
261275
single_app_url_base = SCHEMA_URL_BASE + '/apps/'
262-
def put_request_callback(request):
276+
def _put_request_callback(request):
263277
# Save off body to respond with.
264278
body.append(json.loads(request.body))
265279
return 204, {}, None
266-
def get_request_callback(request):
280+
def _get_request_callback(_):
267281
# Respond with the last saved body.
268282
data = body.pop()
269283
data["name"] = "example"
270284
return 200, {}, json.dumps(data)
271285

272286
responses.add_callback(responses.PUT, re.compile(single_app_url_base),
273-
callback=put_request_callback,
287+
callback=_put_request_callback,
274288
content_type=CONTENT_TYPE_JSON)
275289
responses.add_callback(responses.GET, re.compile(single_app_url_base),
276-
callback=get_request_callback,
290+
callback=_get_request_callback,
277291
content_type=CONTENT_TYPE_JSON)
278292

279293
my_val_factory = swaggerconformance.ValueFactory()

0 commit comments

Comments
 (0)