659 lines
20 KiB
Python
659 lines
20 KiB
Python
import textwrap
|
|
|
|
import pytest
|
|
|
|
import falcon
|
|
from falcon import testing
|
|
from falcon.routing import DefaultRouter
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
return testing.TestClient(falcon.API())
|
|
|
|
|
|
@pytest.fixture
|
|
def router():
|
|
router = DefaultRouter()
|
|
|
|
router.add_route(
|
|
'/repos', {}, ResourceWithId(1))
|
|
router.add_route(
|
|
'/repos/{org}', {}, ResourceWithId(2))
|
|
router.add_route(
|
|
'/repos/{org}/{repo}', {}, ResourceWithId(3))
|
|
router.add_route(
|
|
'/repos/{org}/{repo}/commits', {}, ResourceWithId(4))
|
|
router.add_route(
|
|
u'/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}',
|
|
{}, ResourceWithId(5))
|
|
|
|
router.add_route(
|
|
'/teams/{id}', {}, ResourceWithId(6))
|
|
router.add_route(
|
|
'/teams/{id}/members', {}, ResourceWithId(7))
|
|
|
|
router.add_route(
|
|
'/teams/default', {}, ResourceWithId(19))
|
|
router.add_route(
|
|
'/teams/default/members/thing', {}, ResourceWithId(19))
|
|
|
|
router.add_route(
|
|
'/user/memberships', {}, ResourceWithId(8))
|
|
router.add_route(
|
|
'/emojis', {}, ResourceWithId(9))
|
|
router.add_route(
|
|
'/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}/full',
|
|
{}, ResourceWithId(10))
|
|
router.add_route(
|
|
'/repos/{org}/{repo}/compare/all', {}, ResourceWithId(11))
|
|
|
|
# NOTE(kgriffs): The ordering of these calls is significant; we
|
|
# need to test that the {id} field does not match the other routes,
|
|
# regardless of the order they are added.
|
|
router.add_route(
|
|
'/emojis/signs/0', {}, ResourceWithId(12))
|
|
router.add_route(
|
|
'/emojis/signs/{id}', {}, ResourceWithId(13))
|
|
router.add_route(
|
|
'/emojis/signs/42', {}, ResourceWithId(14))
|
|
router.add_route(
|
|
'/emojis/signs/42/small.jpg', {}, ResourceWithId(23))
|
|
router.add_route(
|
|
'/emojis/signs/78/small.png', {}, ResourceWithId(24))
|
|
|
|
# Test some more special chars
|
|
router.add_route(
|
|
'/emojis/signs/78/small(png)', {}, ResourceWithId(25))
|
|
router.add_route(
|
|
'/emojis/signs/78/small_png', {}, ResourceWithId(26))
|
|
router.add_route('/images/{id}.gif', {}, ResourceWithId(27))
|
|
|
|
router.add_route(
|
|
'/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}/part',
|
|
{}, ResourceWithId(15))
|
|
router.add_route(
|
|
'/repos/{org}/{repo}/compare/{usr0}:{branch0}',
|
|
{}, ResourceWithId(16))
|
|
router.add_route(
|
|
'/repos/{org}/{repo}/compare/{usr0}:{branch0}/full',
|
|
{}, ResourceWithId(17))
|
|
|
|
router.add_route(
|
|
'/gists/{id}/{representation}', {}, ResourceWithId(21))
|
|
router.add_route(
|
|
'/gists/{id}/raw', {}, ResourceWithId(18))
|
|
router.add_route(
|
|
'/gists/first', {}, ResourceWithId(20))
|
|
|
|
router.add_route('/item/{q}', {}, ResourceWithId(28))
|
|
|
|
# ----------------------------------------------------------------
|
|
# Routes with field converters
|
|
# ----------------------------------------------------------------
|
|
|
|
router.add_route(
|
|
'/cvt/teams/{id:int(min=7)}', {}, ResourceWithId(29))
|
|
router.add_route(
|
|
'/cvt/teams/{id:int(min=7)}/members', {}, ResourceWithId(30))
|
|
router.add_route(
|
|
'/cvt/teams/default', {}, ResourceWithId(31))
|
|
router.add_route(
|
|
'/cvt/teams/default/members/{id:int}-{tenure:int}', {}, ResourceWithId(32))
|
|
|
|
router.add_route(
|
|
'/cvt/repos/{org}/{repo}/compare/{usr0}:{branch0:int}...{usr1}:{branch1:int}/part',
|
|
{}, ResourceWithId(33))
|
|
router.add_route(
|
|
'/cvt/repos/{org}/{repo}/compare/{usr0}:{branch0:int}',
|
|
{}, ResourceWithId(34))
|
|
router.add_route(
|
|
'/cvt/repos/{org}/{repo}/compare/{usr0}:{branch0:int}/full',
|
|
{}, ResourceWithId(35))
|
|
|
|
return router
|
|
|
|
|
|
class ResourceWithId(object):
|
|
def __init__(self, resource_id):
|
|
self.resource_id = resource_id
|
|
|
|
def __repr__(self):
|
|
return 'ResourceWithId({0})'.format(self.resource_id)
|
|
|
|
def on_get(self, req, resp):
|
|
resp.body = self.resource_id
|
|
|
|
|
|
class SpamConverter(object):
|
|
def __init__(self, times, eggs=False):
|
|
self._times = times
|
|
self._eggs = eggs
|
|
|
|
def convert(self, fragment):
|
|
item = fragment
|
|
if self._eggs:
|
|
item += '&eggs'
|
|
|
|
return ', '.join(item for i in range(self._times))
|
|
|
|
|
|
# =====================================================================
|
|
# Regression tests for use cases reported by users
|
|
# =====================================================================
|
|
|
|
|
|
def test_user_regression_versioned_url():
|
|
router = DefaultRouter()
|
|
router.add_route('/{version}/messages', {}, ResourceWithId(2))
|
|
|
|
resource, __, __, __ = router.find('/v2/messages')
|
|
assert resource.resource_id == 2
|
|
|
|
router.add_route('/v2', {}, ResourceWithId(1))
|
|
|
|
resource, __, __, __ = router.find('/v2')
|
|
assert resource.resource_id == 1
|
|
|
|
resource, __, __, __ = router.find('/v2/messages')
|
|
assert resource.resource_id == 2
|
|
|
|
resource, __, __, __ = router.find('/v1/messages')
|
|
assert resource.resource_id == 2
|
|
|
|
route = router.find('/v1')
|
|
assert route is None
|
|
|
|
|
|
def test_user_regression_recipes():
|
|
router = DefaultRouter()
|
|
router.add_route(
|
|
'/recipes/{activity}/{type_id}',
|
|
{},
|
|
ResourceWithId(1)
|
|
)
|
|
router.add_route(
|
|
'/recipes/baking',
|
|
{},
|
|
ResourceWithId(2)
|
|
)
|
|
|
|
resource, __, __, __ = router.find('/recipes/baking/4242')
|
|
assert resource.resource_id == 1
|
|
|
|
resource, __, __, __ = router.find('/recipes/baking')
|
|
assert resource.resource_id == 2
|
|
|
|
route = router.find('/recipes/grilling')
|
|
assert route is None
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template,path,expected_params', [
|
|
('/serviceRoot/People|{field}', '/serviceRoot/People|susie', {'field': 'susie'}),
|
|
('/serviceRoot/People[{field}]', "/serviceRoot/People['calvin']", {'field': "'calvin'"}),
|
|
('/serviceRoot/People({field})', "/serviceRoot/People('hobbes')", {'field': "'hobbes'"}),
|
|
('/serviceRoot/People({field})', "/serviceRoot/People('hob)bes')", {'field': "'hob)bes'"}),
|
|
('/serviceRoot/People({field})(z)', '/serviceRoot/People(hobbes)(z)', {'field': 'hobbes'}),
|
|
("/serviceRoot/People('{field}')", "/serviceRoot/People('rosalyn')", {'field': 'rosalyn'}),
|
|
('/^{field}', '/^42', {'field': '42'}),
|
|
('/+{field}', '/+42', {'field': '42'}),
|
|
(
|
|
'/foo/{first}_{second}/bar',
|
|
'/foo/abc_def_ghijk/bar',
|
|
|
|
# NOTE(kgriffs): The regex pattern is greedy, so this is
|
|
# expected. We can not change this behavior in a minor
|
|
# release, since it would be a breaking change. If there
|
|
# is enough demand for it, we could introduce an option
|
|
# to toggle this behavior.
|
|
{'first': 'abc_def', 'second': 'ghijk'},
|
|
),
|
|
|
|
# NOTE(kgriffs): Why someone would use a question mark like this
|
|
# I have no idea (esp. since it would have to be encoded to avoid
|
|
# being mistaken for the query string separator). Including it only
|
|
# for completeness.
|
|
('/items/{x}?{y}', '/items/1080?768', {'x': '1080', 'y': '768'}),
|
|
|
|
('/items/{x}|{y}', '/items/1080|768', {'x': '1080', 'y': '768'}),
|
|
('/items/{x},{y}', '/items/1080,768', {'x': '1080', 'y': '768'}),
|
|
('/items/{x}^^{y}', '/items/1080^^768', {'x': '1080', 'y': '768'}),
|
|
('/items/{x}*{y}*', '/items/1080*768*', {'x': '1080', 'y': '768'}),
|
|
('/thing-2/something+{field}+', '/thing-2/something+42+', {'field': '42'}),
|
|
('/thing-2/something*{field}/notes', '/thing-2/something*42/notes', {'field': '42'}),
|
|
(
|
|
'/thing-2/something+{field}|{q}/notes',
|
|
'/thing-2/something+else|z/notes',
|
|
{'field': 'else', 'q': 'z'},
|
|
),
|
|
(
|
|
"serviceRoot/$metadata#Airports('{field}')/Name",
|
|
"serviceRoot/$metadata#Airports('KSFO')/Name",
|
|
{'field': 'KSFO'},
|
|
),
|
|
])
|
|
def test_user_regression_special_chars(uri_template, path, expected_params):
|
|
router = DefaultRouter()
|
|
|
|
router.add_route(uri_template, {}, ResourceWithId(1))
|
|
|
|
route = router.find(path)
|
|
assert route is not None
|
|
|
|
resource, __, params, __ = route
|
|
assert resource.resource_id == 1
|
|
assert params == expected_params
|
|
|
|
|
|
# =====================================================================
|
|
# Other tests
|
|
# =====================================================================
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
{},
|
|
set(),
|
|
object()
|
|
])
|
|
def test_not_str(uri_template):
|
|
app = falcon.API()
|
|
with pytest.raises(TypeError):
|
|
app.add_route(uri_template, ResourceWithId(-1))
|
|
|
|
|
|
def test_root_path():
|
|
router = DefaultRouter()
|
|
router.add_route('/', {}, ResourceWithId(42))
|
|
|
|
resource, __, __, __ = router.find('/')
|
|
assert resource.resource_id == 42
|
|
|
|
expected_src = textwrap.dedent("""
|
|
def find(path, return_values, patterns, converters, params):
|
|
path_len = len(path)
|
|
if path_len > 0:
|
|
if path[0] == '':
|
|
if path_len == 1:
|
|
return return_values[0]
|
|
return None
|
|
return None
|
|
return None
|
|
""").strip()
|
|
|
|
assert router.finder_src == expected_src
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
'/{field}{field}',
|
|
'/{field}...{field}',
|
|
'/{field}/{another}/{field}',
|
|
'/{field}/something/something/{field}/something',
|
|
])
|
|
def test_duplicate_field_names(uri_template):
|
|
router = DefaultRouter()
|
|
with pytest.raises(ValueError):
|
|
router.add_route(uri_template, {}, ResourceWithId(1))
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template,path', [
|
|
('/items/thing', '/items/t'),
|
|
('/items/{x}|{y}|', '/items/1080|768'),
|
|
('/items/{x}*{y}foo', '/items/1080*768foobar'),
|
|
('/items/{x}*768*', '/items/1080*768***'),
|
|
])
|
|
def test_match_entire_path(uri_template, path):
|
|
router = DefaultRouter()
|
|
|
|
router.add_route(uri_template, {}, ResourceWithId(1))
|
|
|
|
route = router.find(path)
|
|
assert route is None
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
'/teams/{conflict}', # simple vs simple
|
|
'/emojis/signs/{id_too}', # another simple vs simple
|
|
'/repos/{org}/{repo}/compare/{complex}:{vs}...{complex2}:{conflict}',
|
|
'/teams/{id:int}/settings', # converted vs. non-converted
|
|
])
|
|
def test_conflict(router, uri_template):
|
|
with pytest.raises(ValueError):
|
|
router.add_route(uri_template, {}, ResourceWithId(-1))
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
'/repos/{org}/{repo}/compare/{simple_vs_complex}',
|
|
'/repos/{complex}.{vs}.{simple}',
|
|
'/repos/{org}/{repo}/compare/{complex}:{vs}...{complex2}/full',
|
|
])
|
|
def test_non_conflict(router, uri_template):
|
|
router.add_route(uri_template, {}, ResourceWithId(-1))
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
# Missing field name
|
|
'/{}',
|
|
'/repos/{org}/{repo}/compare/{}',
|
|
'/repos/{complex}.{}.{thing}',
|
|
|
|
# Field names must be valid Python identifiers
|
|
'/{9v}',
|
|
'/{524hello}/world',
|
|
'/hello/{1world}',
|
|
'/repos/{complex}.{9v}.{thing}/etc',
|
|
'/{*kgriffs}',
|
|
'/{@kgriffs}',
|
|
'/repos/{complex}.{v}.{@thing}/etc',
|
|
'/{-kgriffs}',
|
|
'/repos/{complex}.{-v}.{thing}/etc',
|
|
'/repos/{simple-thing}/etc',
|
|
|
|
# Neither fields nor literal segments may not contain whitespace
|
|
'/this and that',
|
|
'/this\tand\tthat'
|
|
'/this\nand\nthat'
|
|
'/{thing }/world',
|
|
'/{thing\t}/world',
|
|
'/{\nthing}/world',
|
|
'/{th\ving}/world',
|
|
'/{ thing}/world',
|
|
'/{ thing }/world',
|
|
'/{thing}/wo rld',
|
|
'/{thing} /world',
|
|
'/repos/{or g}/{repo}/compare/{thing}',
|
|
'/repos/{org}/{repo}/compare/{th\ting}',
|
|
])
|
|
def test_invalid_field_name(router, uri_template):
|
|
with pytest.raises(ValueError):
|
|
router.add_route(uri_template, {}, ResourceWithId(-1))
|
|
|
|
|
|
def test_print_src(router):
|
|
"""Diagnostic test that simply prints the router's find() source code.
|
|
|
|
Example:
|
|
|
|
$ tox -e py27_debug -- -k test_print_src -s
|
|
"""
|
|
print('\n\n' + router.finder_src + '\n')
|
|
|
|
|
|
def test_override(router):
|
|
router.add_route('/emojis/signs/0', {}, ResourceWithId(-1))
|
|
|
|
resource, __, __, __ = router.find('/emojis/signs/0')
|
|
assert resource.resource_id == -1
|
|
|
|
|
|
def test_literal_segment(router):
|
|
resource, __, __, __ = router.find('/emojis/signs/0')
|
|
assert resource.resource_id == 12
|
|
|
|
resource, __, __, __ = router.find('/emojis/signs/1')
|
|
assert resource.resource_id == 13
|
|
|
|
resource, __, __, __ = router.find('/emojis/signs/42')
|
|
assert resource.resource_id == 14
|
|
|
|
resource, __, __, __ = router.find('/emojis/signs/42/small.jpg')
|
|
assert resource.resource_id == 23
|
|
|
|
route = router.find('/emojis/signs/1/small')
|
|
assert route is None
|
|
|
|
|
|
@pytest.mark.parametrize('path', [
|
|
'/teams',
|
|
'/emojis/signs',
|
|
'/gists',
|
|
'/gists/42',
|
|
])
|
|
def test_dead_segment(router, path):
|
|
route = router.find(path)
|
|
assert route is None
|
|
|
|
|
|
@pytest.mark.parametrize('path', [
|
|
'/repos/racker/falcon/compare/foo',
|
|
'/repos/racker/falcon/compare/foo/full',
|
|
])
|
|
def test_malformed_pattern(router, path):
|
|
route = router.find(path)
|
|
assert route is None
|
|
|
|
|
|
def test_literal(router):
|
|
resource, __, __, __ = router.find('/user/memberships')
|
|
assert resource.resource_id == 8
|
|
|
|
|
|
@pytest.mark.parametrize('path,expected_params', [
|
|
('/cvt/teams/007', {'id': 7}),
|
|
('/cvt/teams/1234/members', {'id': 1234}),
|
|
('/cvt/teams/default/members/700-5', {'id': 700, 'tenure': 5}),
|
|
(
|
|
'/cvt/repos/org/repo/compare/xkcd:353',
|
|
{'org': 'org', 'repo': 'repo', 'usr0': 'xkcd', 'branch0': 353},
|
|
),
|
|
(
|
|
'/cvt/repos/org/repo/compare/gunmachan:1234...kumamon:5678/part',
|
|
{
|
|
'org': 'org',
|
|
'repo': 'repo',
|
|
'usr0': 'gunmachan',
|
|
'branch0': 1234,
|
|
'usr1': 'kumamon',
|
|
'branch1': 5678,
|
|
}
|
|
),
|
|
(
|
|
'/cvt/repos/xkcd/353/compare/susan:0001/full',
|
|
{'org': 'xkcd', 'repo': '353', 'usr0': 'susan', 'branch0': 1},
|
|
)
|
|
])
|
|
def test_converters(router, path, expected_params):
|
|
__, __, params, __ = router.find(path)
|
|
assert params == expected_params
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
'/foo/{bar:int(0)}',
|
|
'/foo/{bar:int(num_digits=0)}',
|
|
'/foo/{bar:int(-1)}/baz',
|
|
'/foo/{bar:int(num_digits=-1)}/baz',
|
|
])
|
|
def test_converters_with_invalid_options(router, uri_template):
|
|
# NOTE(kgriffs): Sanity-check that errors are properly bubbled up
|
|
# when calling add_route(). Additional checks can be found
|
|
# in test_uri_converters.py
|
|
with pytest.raises(ValueError):
|
|
router.add_route(uri_template, {}, ResourceWithId(1))
|
|
|
|
|
|
@pytest.mark.parametrize('uri_template', [
|
|
'/foo/{bar:}',
|
|
'/foo/{bar:unknown}/baz',
|
|
])
|
|
def test_converters_malformed_specification(router, uri_template):
|
|
with pytest.raises(ValueError):
|
|
router.add_route(uri_template, {}, ResourceWithId(1))
|
|
|
|
|
|
def test_variable(router):
|
|
resource, __, params, __ = router.find('/teams/42')
|
|
assert resource.resource_id == 6
|
|
assert params == {'id': '42'}
|
|
|
|
__, __, params, __ = router.find('/emojis/signs/stop')
|
|
assert params == {'id': 'stop'}
|
|
|
|
__, __, params, __ = router.find('/gists/42/raw')
|
|
assert params == {'id': '42'}
|
|
|
|
__, __, params, __ = router.find('/images/42.gif')
|
|
assert params == {'id': '42'}
|
|
|
|
|
|
def test_single_character_field_name(router):
|
|
__, __, params, __ = router.find('/item/1234')
|
|
assert params == {'q': '1234'}
|
|
|
|
|
|
@pytest.mark.parametrize('path,expected_id', [
|
|
('/teams/default', 19),
|
|
('/teams/default/members', 7),
|
|
('/cvt/teams/default', 31),
|
|
('/cvt/teams/default/members/1234-10', 32),
|
|
('/teams/1234', 6),
|
|
('/teams/1234/members', 7),
|
|
('/gists/first', 20),
|
|
('/gists/first/raw', 18),
|
|
('/gists/first/pdf', 21),
|
|
('/gists/1776/pdf', 21),
|
|
('/emojis/signs/78', 13),
|
|
('/emojis/signs/78/small.png', 24),
|
|
('/emojis/signs/78/small(png)', 25),
|
|
('/emojis/signs/78/small_png', 26),
|
|
])
|
|
def test_literal_vs_variable(router, path, expected_id):
|
|
resource, __, __, __ = router.find(path)
|
|
assert resource.resource_id == expected_id
|
|
|
|
|
|
@pytest.mark.parametrize('path', [
|
|
# Misc.
|
|
'/this/does/not/exist',
|
|
'/user/bogus',
|
|
'/repos/racker/falcon/compare/johndoe:master...janedoe:dev/bogus',
|
|
|
|
# Literal vs variable (teams)
|
|
'/teams',
|
|
'/teams/42/members/undefined',
|
|
'/teams/42/undefined',
|
|
'/teams/42/undefined/segments',
|
|
'/teams/default/members/undefined',
|
|
'/teams/default/members/thing/undefined',
|
|
'/teams/default/members/thing/undefined/segments',
|
|
'/teams/default/undefined',
|
|
'/teams/default/undefined/segments',
|
|
|
|
# Literal vs. variable (converters)
|
|
'/cvt/teams/default/members', # 'default' can't be converted to an int
|
|
'/cvt/teams/NaN',
|
|
'/cvt/teams/default/members/NaN',
|
|
|
|
# Literal vs variable (emojis)
|
|
'/emojis/signs',
|
|
'/emojis/signs/0/small',
|
|
'/emojis/signs/0/undefined',
|
|
'/emojis/signs/0/undefined/segments',
|
|
'/emojis/signs/20/small',
|
|
'/emojis/signs/20/undefined',
|
|
'/emojis/signs/42/undefined',
|
|
'/emojis/signs/78/undefined',
|
|
])
|
|
def test_not_found(router, path):
|
|
route = router.find(path)
|
|
assert route is None
|
|
|
|
|
|
def test_subsegment_not_found(router):
|
|
route = router.find('/emojis/signs/0/x')
|
|
assert route is None
|
|
|
|
|
|
def test_multivar(router):
|
|
resource, __, params, __ = router.find('/repos/racker/falcon/commits')
|
|
assert resource.resource_id == 4
|
|
assert params == {'org': 'racker', 'repo': 'falcon'}
|
|
|
|
resource, __, params, __ = router.find('/repos/racker/falcon/compare/all')
|
|
assert resource.resource_id == 11
|
|
assert params == {'org': 'racker', 'repo': 'falcon'}
|
|
|
|
|
|
@pytest.mark.parametrize('url_postfix,resource_id', [
|
|
('', 5),
|
|
('/full', 10),
|
|
('/part', 15),
|
|
])
|
|
def test_complex(router, url_postfix, resource_id):
|
|
uri = '/repos/racker/falcon/compare/johndoe:master...janedoe:dev'
|
|
resource, __, params, __ = router.find(uri + url_postfix)
|
|
|
|
assert resource.resource_id == resource_id
|
|
assert (params == {
|
|
'org': 'racker',
|
|
'repo': 'falcon',
|
|
'usr0': 'johndoe',
|
|
'branch0': 'master',
|
|
'usr1': 'janedoe',
|
|
'branch1': 'dev',
|
|
})
|
|
|
|
|
|
@pytest.mark.parametrize('url_postfix,resource_id,expected_template', [
|
|
('', 16, '/repos/{org}/{repo}/compare/{usr0}:{branch0}'),
|
|
('/full', 17, '/repos/{org}/{repo}/compare/{usr0}:{branch0}/full')
|
|
])
|
|
def test_complex_alt(router, url_postfix, resource_id, expected_template):
|
|
uri = '/repos/falconry/falcon/compare/johndoe:master' + url_postfix
|
|
resource, __, params, uri_template = router.find(uri)
|
|
|
|
assert resource.resource_id == resource_id
|
|
assert (params == {
|
|
'org': 'falconry',
|
|
'repo': 'falcon',
|
|
'usr0': 'johndoe',
|
|
'branch0': 'master',
|
|
})
|
|
assert uri_template == expected_template
|
|
|
|
|
|
def test_options_converters_set(router):
|
|
router.options.converters['spam'] = SpamConverter
|
|
|
|
router.add_route('/{food:spam(3, eggs=True)}', {}, ResourceWithId(1))
|
|
resource, __, params, __ = router.find('/spam')
|
|
|
|
assert params == {'food': 'spam&eggs, spam&eggs, spam&eggs'}
|
|
|
|
|
|
@pytest.mark.parametrize('converter_name', [
|
|
'spam',
|
|
'spam_2'
|
|
])
|
|
def test_options_converters_update(router, converter_name):
|
|
router.options.converters.update({
|
|
'spam': SpamConverter,
|
|
'spam_2': SpamConverter,
|
|
})
|
|
|
|
template = '/{food:' + converter_name + '(3, eggs=True)}'
|
|
router.add_route(template, {}, ResourceWithId(1))
|
|
resource, __, params, __ = router.find('/spam')
|
|
|
|
assert params == {'food': 'spam&eggs, spam&eggs, spam&eggs'}
|
|
|
|
|
|
@pytest.mark.parametrize('name', [
|
|
'has whitespace',
|
|
'whitespace ',
|
|
' whitespace ',
|
|
' whitespace',
|
|
'funky$character',
|
|
'42istheanswer',
|
|
'with-hyphen',
|
|
])
|
|
def test_options_converters_invalid_name(router, name):
|
|
with pytest.raises(ValueError):
|
|
router.options.converters[name] = object
|
|
|
|
|
|
def test_options_converters_invalid_name_on_update(router):
|
|
with pytest.raises(ValueError):
|
|
router.options.converters.update({
|
|
'valid_name': SpamConverter,
|
|
'7eleven': SpamConverter,
|
|
})
|