"""Application tests for URI templates using simulate_get(). These tests differ from those in test_default_router in that they are a collection of sanity-checks that exercise the full framework code path via simulate_get(), vs. probing the router directly. """ from datetime import datetime import uuid import pytest import six import falcon from falcon import testing _TEST_UUID = uuid.uuid4() _TEST_UUID_2 = uuid.uuid4() _TEST_UUID_STR = str(_TEST_UUID) _TEST_UUID_STR_2 = str(_TEST_UUID_2) _TEST_UUID_STR_SANS_HYPHENS = _TEST_UUID_STR.replace('-', '') class IDResource(object): def __init__(self): self.id = None self.name = None self.called = False def on_get(self, req, resp, id): self.id = id self.called = True self.req = req class NameResource(object): def __init__(self): self.id = None self.name = None self.called = False def on_get(self, req, resp, id, name): self.id = id self.name = name self.called = True class NameAndDigitResource(object): def __init__(self): self.id = None self.name51 = None self.called = False def on_get(self, req, resp, id, name51): self.id = id self.name51 = name51 self.called = True class FileResource(object): def __init__(self): self.file_id = None self.called = False def on_get(self, req, resp, file_id): self.file_id = file_id self.called = True class FileDetailsResource(object): def __init__(self): self.file_id = None self.ext = None self.called = False def on_get(self, req, resp, file_id, ext): self.file_id = file_id self.ext = ext self.called = True @pytest.fixture def resource(): return testing.SimpleTestResource() @pytest.fixture def client(): return testing.TestClient(falcon.API()) def test_root_path(client, resource): client.app.add_route('/', resource) client.simulate_get('/') assert resource.called def test_no_vars(client, resource): client.app.add_route('/hello/world', resource) client.simulate_get('/hello/world') assert resource.called @pytest.mark.skipif(six.PY3, reason='Test only applies to Python 2') def test_unicode_literal_routes(client, resource): client.app.add_route(u'/hello/world', resource) client.simulate_get('/hello/world') assert resource.called def test_special_chars(client, resource): client.app.add_route('/hello/world.json', resource) client.app.add_route('/hello(world)', resource) client.simulate_get('/hello/world_json') assert not resource.called client.simulate_get('/helloworld') assert not resource.called client.simulate_get('/hello/world.json') assert resource.called client.simulate_get('/hello(world)') assert resource.called @pytest.mark.parametrize('field_name', [ 'id', 'id123', 'widget_id', ]) def test_single(client, resource, field_name): template = '/widgets/{{{0}}}'.format(field_name) client.app.add_route(template, resource) client.simulate_get('/widgets/123') assert resource.called assert resource.captured_kwargs[field_name] == '123' @pytest.mark.parametrize('uri_template,', [ '/{id:int}', '/{id:int(3)}', '/{id:int(min=123)}', '/{id:int(min=123, max=123)}', ]) def test_int_converter(client, uri_template): resource1 = IDResource() client.app.add_route(uri_template, resource1) result = client.simulate_get('/123') assert result.status_code == 200 assert resource1.called assert resource1.id == 123 assert resource1.req.path == '/123' @pytest.mark.parametrize('uri_template,', [ '/{id:int(2)}', '/{id:int(min=124)}', '/{id:int(num_digits=3, max=100)}', ]) def test_int_converter_rejections(client, uri_template): resource1 = IDResource() client.app.add_route(uri_template, resource1) result = client.simulate_get('/123') assert result.status_code == 404 assert not resource1.called @pytest.mark.parametrize('uri_template, path, dt_expected', [ ( '/{start_year:int}-to-{timestamp:dt}', '/1961-to-1969-07-21T02:56:00Z', datetime(1969, 7, 21, 2, 56, 0) ), ( '/{start_year:int}-to-{timestamp:dt("%Y-%m-%d")}', '/1961-to-1969-07-21', datetime(1969, 7, 21) ), ( '/{start_year:int}/{timestamp:dt("%Y-%m-%d %H:%M")}', '/1961/1969-07-21 14:30', datetime(1969, 7, 21, 14, 30) ), ( '/{start_year:int}-to-{timestamp:dt("%Y-%m")}', '/1961-to-1969-07-21', None ), ]) def test_datetime_converter(client, resource, uri_template, path, dt_expected): client.app.add_route(uri_template, resource) result = client.simulate_get(path) if dt_expected is None: assert result.status_code == 404 assert not resource.called else: assert result.status_code == 200 assert resource.called assert resource.captured_kwargs['start_year'] == 1961 assert resource.captured_kwargs['timestamp'] == dt_expected @pytest.mark.parametrize('uri_template, path, expected', [ ( '/widgets/{widget_id:uuid}', '/widgets/' + _TEST_UUID_STR, {'widget_id': _TEST_UUID} ), ( '/widgets/{widget_id:uuid}/orders', '/widgets/' + _TEST_UUID_STR_SANS_HYPHENS + '/orders', {'widget_id': _TEST_UUID} ), ( '/versions/diff/{left:uuid()}...{right:uuid()}', '/versions/diff/{0}...{1}'.format(_TEST_UUID_STR, _TEST_UUID_STR_2), {'left': _TEST_UUID, 'right': _TEST_UUID_2, } ), ( '/versions/diff/{left:uuid}...{right:uuid()}', '/versions/diff/{0}...{1}'.format(_TEST_UUID_STR, _TEST_UUID_STR_2), {'left': _TEST_UUID, 'right': _TEST_UUID_2, } ), ( '/versions/diff/{left:uuid()}...{right:uuid}', '/versions/diff/{0}...{1}'.format(_TEST_UUID_STR, _TEST_UUID_STR_2), {'left': _TEST_UUID, 'right': _TEST_UUID_2, } ), ( '/widgets/{widget_id:uuid}/orders', '/widgets/' + _TEST_UUID_STR_SANS_HYPHENS[:-1] + '/orders', None ), ]) def test_uuid_converter(client, resource, uri_template, path, expected): client.app.add_route(uri_template, resource) result = client.simulate_get(path) if expected is None: assert result.status_code == 404 assert not resource.called else: assert result.status_code == 200 assert resource.called assert resource.captured_kwargs == expected def test_uuid_converter_complex_segment(client, resource): client.app.add_route('/pages/{first:uuid}...{last:uuid}', resource) first_uuid = uuid.uuid4() last_uuid = uuid.uuid4() result = client.simulate_get('/pages/{0}...{1}'.format( first_uuid, last_uuid )) assert result.status_code == 200 assert resource.called assert resource.captured_kwargs['first'] == first_uuid assert resource.captured_kwargs['last'] == last_uuid @pytest.mark.parametrize('uri_template, path, expected', [ ( '/{food:spam}', '/something', {'food': 'spam!'} ), ( '/{food:spam(")")}:{food_too:spam("()")}', '/bacon:eggs', {'food': 'spam!', 'food_too': 'spam!'} ), ( '/({food:spam()}){food_too:spam("()")}', '/(bacon)eggs', {'food': 'spam!', 'food_too': 'spam!'} ), ]) def test_converter_custom(client, resource, uri_template, path, expected): class SpamConverter(object): def __init__(self, useless_text=None): pass def convert(self, fragment): return 'spam!' client.app.router_options.converters['spam'] = SpamConverter client.app.add_route(uri_template, resource) result = client.simulate_get(path) assert result.status_code == 200 assert resource.called assert resource.captured_kwargs == expected def test_single_trailing_slash(client): resource1 = IDResource() client.app.add_route('/1/{id}/', resource1) result = client.simulate_get('/1/123') assert result.status == falcon.HTTP_200 assert resource1.called assert resource1.id == '123' assert resource1.req.path == '/1/123' resource2 = IDResource() client.app.add_route('/2/{id}/', resource2) result = client.simulate_get('/2/123/') assert result.status == falcon.HTTP_200 assert resource2.called assert resource2.id == '123' assert resource2.req.path == '/2/123' resource3 = IDResource() client.app.add_route('/3/{id}', resource3) result = client.simulate_get('/3/123/') assert result.status == falcon.HTTP_200 assert resource3.called assert resource3.id == '123' assert resource3.req.path == '/3/123' def test_multiple(client): resource = NameResource() client.app.add_route('/messages/{id}/names/{name}', resource) test_id = 'bfb54d43-219b-4336-a623-6172f920592e' test_name = '758e3922-dd6d-4007-a589-50fba0789365' path = '/messages/' + test_id + '/names/' + test_name client.simulate_get(path) assert resource.called assert resource.id == test_id assert resource.name == test_name @pytest.mark.parametrize('uri_template', [ '//', '//begin', '/end//', '/in//side', ]) def test_empty_path_component(client, resource, uri_template): with pytest.raises(ValueError): client.app.add_route(uri_template, resource) @pytest.mark.parametrize('uri_template', [ '', 'no', 'no/leading_slash', ]) def test_relative_path(client, resource, uri_template): with pytest.raises(ValueError): client.app.add_route(uri_template, resource) @pytest.mark.parametrize('reverse', [True, False]) def test_same_level_complex_var(client, reverse): file_resource = FileResource() details_resource = FileDetailsResource() routes = [ ('/files/{file_id}', file_resource), ('/files/{file_id}.{ext}', details_resource) ] if reverse: routes.reverse() for uri_template, resource in routes: client.app.add_route(uri_template, resource) file_id_1 = 'bc6b201d-b449-4290-a061-8eeb9f7b1450' file_id_2 = '33b7f34c-6ee6-40e6-89a3-742a69b59de0' ext = 'a4581b95-bc36-4c08-a3c2-23ba266abdf2' path_1 = '/files/' + file_id_1 path_2 = '/files/' + file_id_2 + '.' + ext client.simulate_get(path_1) assert file_resource.called assert file_resource.file_id == file_id_1 client.simulate_get(path_2) assert details_resource.called assert details_resource.file_id == file_id_2 assert details_resource.ext == ext