import ddt from falcon.routing import DefaultRouter import falcon.testing as testing 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 TestRegressionCases(testing.TestBase): """Test specific repros reported by users of the framework.""" def before(self): self.router = DefaultRouter() def test_versioned_url(self): self.router.add_route('/{version}/messages', {}, ResourceWithId(2)) resource, __, __, __ = self.router.find('/v2/messages') self.assertEqual(resource.resource_id, 2) self.router.add_route('/v2', {}, ResourceWithId(1)) resource, __, __, __ = self.router.find('/v2') self.assertEqual(resource.resource_id, 1) resource, __, __, __ = self.router.find('/v2/messages') self.assertEqual(resource.resource_id, 2) resource, __, __, __ = self.router.find('/v1/messages') self.assertEqual(resource.resource_id, 2) route = self.router.find('/v1') self.assertIs(route, None) def test_recipes(self): self.router.add_route( '/recipes/{activity}/{type_id}', {}, ResourceWithId(1)) self.router.add_route( '/recipes/baking', {}, ResourceWithId(2)) resource, __, __, __ = self.router.find('/recipes/baking/4242') self.assertEqual(resource.resource_id, 1) resource, __, __, __ = self.router.find('/recipes/baking') self.assertEqual(resource.resource_id, 2) route = self.router.find('/recipes/grilling') self.assertIs(route, None) @ddt.ddt class TestComplexRouting(testing.TestBase): def before(self): self.router = DefaultRouter() self.router.add_route( '/repos', {}, ResourceWithId(1)) self.router.add_route( '/repos/{org}', {}, ResourceWithId(2)) self.router.add_route( '/repos/{org}/{repo}', {}, ResourceWithId(3)) self.router.add_route( '/repos/{org}/{repo}/commits', {}, ResourceWithId(4)) self.router.add_route( '/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}', {}, ResourceWithId(5)) self.router.add_route( '/teams/{id}', {}, ResourceWithId(6)) self.router.add_route( '/teams/{id}/members', {}, ResourceWithId(7)) self.router.add_route( '/teams/default', {}, ResourceWithId(19)) self.router.add_route( '/teams/default/members/thing', {}, ResourceWithId(19)) self.router.add_route( '/user/memberships', {}, ResourceWithId(8)) self.router.add_route( '/emojis', {}, ResourceWithId(9)) self.router.add_route( '/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}/full', {}, ResourceWithId(10)) self.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. self.router.add_route( '/emojis/signs/0', {}, ResourceWithId(12)) self.router.add_route( '/emojis/signs/{id}', {}, ResourceWithId(13)) self.router.add_route( '/emojis/signs/42', {}, ResourceWithId(14)) self.router.add_route( '/emojis/signs/42/small', {}, ResourceWithId(14.1)) self.router.add_route( '/emojis/signs/78/small', {}, ResourceWithId(22)) self.router.add_route( '/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}/part', {}, ResourceWithId(15)) self.router.add_route( '/repos/{org}/{repo}/compare/{usr0}:{branch0}', {}, ResourceWithId(16)) self.router.add_route( '/repos/{org}/{repo}/compare/{usr0}:{branch0}/full', {}, ResourceWithId(17)) self.router.add_route( '/gists/{id}/{representation}', {}, ResourceWithId(21)) self.router.add_route( '/gists/{id}/raw', {}, ResourceWithId(18)) self.router.add_route( '/gists/first', {}, ResourceWithId(20)) self.router.add_route('/item/{q}', {}, ResourceWithId(22)) @ddt.data( '/teams/{collision}', # simple vs simple '/emojis/signs/{id_too}', # another simple vs simple '/repos/{org}/{repo}/compare/{complex}:{vs}...{complex2}:{collision}', ) def test_collision(self, template): self.assertRaises( ValueError, self.router.add_route, template, {}, ResourceWithId(-1) ) @ddt.data( '/repos/{org}/{repo}/compare/{simple_vs_complex}', '/repos/{complex}.{vs}.{simple}', '/repos/{org}/{repo}/compare/{complex}:{vs}...{complex2}/full', ) def test_non_collision(self, template): self.router.add_route(template, {}, ResourceWithId(-1)) @ddt.data( '/{}', '/{9v}', '/{@kgriffs}', '/repos/{simple-thing}/etc', '/repos/{or g}/{repo}/compare/{thing}', '/repos/{org}/{repo}/compare/{}', '/repos/{complex}.{}.{thing}', '/repos/{complex}.{9v}.{thing}/etc', ) def test_invalid_field_name(self, template): self.assertRaises( ValueError, self.router.add_route, template, {}, ResourceWithId(-1)) def test_dump(self): print(self.router._src) def test_override(self): self.router.add_route('/emojis/signs/0', {}, ResourceWithId(-1)) resource, __, __, __ = self.router.find('/emojis/signs/0') self.assertEqual(resource.resource_id, -1) def test_literal_segment(self): resource, __, __, __ = self.router.find('/emojis/signs/0') self.assertEqual(resource.resource_id, 12) resource, __, __, __ = self.router.find('/emojis/signs/1') self.assertEqual(resource.resource_id, 13) resource, __, __, __ = self.router.find('/emojis/signs/42') self.assertEqual(resource.resource_id, 14) resource, __, __, __ = self.router.find('/emojis/signs/42/small') self.assertEqual(resource.resource_id, 14.1) route = self.router.find('/emojis/signs/1/small') self.assertEqual(route, None) @ddt.data( '/teams', '/emojis/signs', '/gists', '/gists/42', ) def test_dead_segment(self, template): route = self.router.find(template) self.assertIs(route, None) def test_malformed_pattern(self): route = self.router.find( '/repos/racker/falcon/compare/foo') self.assertIs(route, None) route = self.router.find( '/repos/racker/falcon/compare/foo/full') self.assertIs(route, None) def test_literal(self): resource, __, __, __ = self.router.find('/user/memberships') self.assertEqual(resource.resource_id, 8) def test_variable(self): resource, __, params, __ = self.router.find('/teams/42') self.assertEqual(resource.resource_id, 6) self.assertEqual(params, {'id': '42'}) __, __, params, __ = self.router.find('/emojis/signs/stop') self.assertEqual(params, {'id': 'stop'}) __, __, params, __ = self.router.find('/gists/42/raw') self.assertEqual(params, {'id': '42'}) def test_single_character_field_name(self): __, __, params, __ = self.router.find('/item/1234') self.assertEqual(params, {'q': '1234'}) @ddt.data( ('/teams/default', 19), ('/teams/default/members', 7), ('/teams/foo', 6), ('/teams/foo/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', 22), ) @ddt.unpack def test_literal_vs_variable(self, path, expected_id): resource, __, __, __ = self.router.find(path) self.assertEqual(resource.resource_id, expected_id) @ddt.data( # 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 (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(self, path): route = self.router.find(path) self.assertIs(route, None) def test_subsegment_not_found(self): route = self.router.find('/emojis/signs/0/x') self.assertIs(route, None) def test_multivar(self): resource, __, params, __ = self.router.find( '/repos/racker/falcon/commits') self.assertEqual(resource.resource_id, 4) self.assertEqual(params, {'org': 'racker', 'repo': 'falcon'}) resource, __, params, __ = self.router.find( '/repos/racker/falcon/compare/all') self.assertEqual(resource.resource_id, 11) self.assertEqual(params, {'org': 'racker', 'repo': 'falcon'}) @ddt.data(('', 5), ('/full', 10), ('/part', 15)) @ddt.unpack def test_complex(self, url_postfix, resource_id): uri = '/repos/racker/falcon/compare/johndoe:master...janedoe:dev' resource, __, params, __ = self.router.find(uri + url_postfix) self.assertEqual(resource.resource_id, resource_id) self.assertEqual(params, { 'org': 'racker', 'repo': 'falcon', 'usr0': 'johndoe', 'branch0': 'master', 'usr1': 'janedoe', 'branch1': 'dev', }) @ddt.data( ('', 16, '/repos/{org}/{repo}/compare/{usr0}:{branch0}'), ('/full', 17, '/repos/{org}/{repo}/compare/{usr0}:{branch0}/full') ) @ddt.unpack def test_complex_alt(self, url_postfix, resource_id, expected_template): uri = '/repos/falconry/falcon/compare/johndoe:master' + url_postfix resource, __, params, uri_template = self.router.find(uri) self.assertEqual(resource.resource_id, resource_id) self.assertEqual(params, { 'org': 'falconry', 'repo': 'falcon', 'usr0': 'johndoe', 'branch0': 'master', }) self.assertEqual(uri_template, expected_template)