tests.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import uuid
  2. from django.core.exceptions import ImproperlyConfigured
  3. from django.test import SimpleTestCase
  4. from django.test.utils import override_settings
  5. from django.urls import Resolver404, path, resolve, reverse
  6. from .converters import DynamicConverter
  7. from .views import empty_view
  8. @override_settings(ROOT_URLCONF='urlpatterns.path_urls')
  9. class SimplifiedURLTests(SimpleTestCase):
  10. def test_path_lookup_without_parameters(self):
  11. match = resolve('/articles/2003/')
  12. self.assertEqual(match.url_name, 'articles-2003')
  13. self.assertEqual(match.args, ())
  14. self.assertEqual(match.kwargs, {})
  15. def test_path_lookup_with_typed_parameters(self):
  16. match = resolve('/articles/2015/')
  17. self.assertEqual(match.url_name, 'articles-year')
  18. self.assertEqual(match.args, ())
  19. self.assertEqual(match.kwargs, {'year': 2015})
  20. def test_path_lookup_with_multiple_paramaters(self):
  21. match = resolve('/articles/2015/04/12/')
  22. self.assertEqual(match.url_name, 'articles-year-month-day')
  23. self.assertEqual(match.args, ())
  24. self.assertEqual(match.kwargs, {'year': 2015, 'month': 4, 'day': 12})
  25. def test_two_variable_at_start_of_path_pattern(self):
  26. match = resolve('/en/foo/')
  27. self.assertEqual(match.url_name, 'lang-and-path')
  28. self.assertEqual(match.kwargs, {'lang': 'en', 'url': 'foo'})
  29. def test_path_reverse_without_parameter(self):
  30. url = reverse('articles-2003')
  31. self.assertEqual(url, '/articles/2003/')
  32. def test_path_reverse_with_parameter(self):
  33. url = reverse('articles-year-month-day', kwargs={'year': 2015, 'month': 4, 'day': 12})
  34. self.assertEqual(url, '/articles/2015/4/12/')
  35. @override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
  36. def test_non_identical_converter_resolve(self):
  37. match = resolve('/base64/aGVsbG8=/') # base64 of 'hello'
  38. self.assertEqual(match.url_name, 'base64')
  39. self.assertEqual(match.kwargs, {'value': b'hello'})
  40. @override_settings(ROOT_URLCONF='urlpatterns.path_base64_urls')
  41. def test_non_identical_converter_reverse(self):
  42. url = reverse('base64', kwargs={'value': b'hello'})
  43. self.assertEqual(url, '/base64/aGVsbG8=/')
  44. def test_path_inclusion_is_matchable(self):
  45. match = resolve('/included_urls/extra/something/')
  46. self.assertEqual(match.url_name, 'inner-extra')
  47. self.assertEqual(match.kwargs, {'extra': 'something'})
  48. def test_path_inclusion_is_reversable(self):
  49. url = reverse('inner-extra', kwargs={'extra': 'something'})
  50. self.assertEqual(url, '/included_urls/extra/something/')
  51. def test_invalid_converter(self):
  52. msg = "URL route 'foo/<nonexistent:var>/' uses invalid converter 'nonexistent'."
  53. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  54. path('foo/<nonexistent:var>/', empty_view)
  55. @override_settings(ROOT_URLCONF='urlpatterns.converter_urls')
  56. class ConverterTests(SimpleTestCase):
  57. def test_matching_urls(self):
  58. def no_converter(x):
  59. return x
  60. test_data = (
  61. ('int', {'0', '1', '01', 1234567890}, int),
  62. ('str', {'abcxyz'}, no_converter),
  63. ('path', {'allows.ANY*characters'}, no_converter),
  64. ('slug', {'abcxyz-ABCXYZ_01234567890'}, no_converter),
  65. ('uuid', {'39da9369-838e-4750-91a5-f7805cd82839'}, uuid.UUID),
  66. )
  67. for url_name, url_suffixes, converter in test_data:
  68. for url_suffix in url_suffixes:
  69. url = '/%s/%s/' % (url_name, url_suffix)
  70. with self.subTest(url=url):
  71. match = resolve(url)
  72. self.assertEqual(match.url_name, url_name)
  73. self.assertEqual(match.kwargs, {url_name: converter(url_suffix)})
  74. # reverse() works with string parameters.
  75. string_kwargs = {url_name: url_suffix}
  76. self.assertEqual(reverse(url_name, kwargs=string_kwargs), url)
  77. # reverse() also works with native types (int, UUID, etc.).
  78. if converter is not no_converter:
  79. # The converted value might be different for int (a
  80. # leading zero is lost in the conversion).
  81. converted_value = match.kwargs[url_name]
  82. converted_url = '/%s/%s/' % (url_name, converted_value)
  83. self.assertEqual(reverse(url_name, kwargs={url_name: converted_value}), converted_url)
  84. def test_nonmatching_urls(self):
  85. test_data = (
  86. ('int', {'-1', 'letters'}),
  87. ('str', {'', '/'}),
  88. ('path', {''}),
  89. ('slug', {'', 'stars*notallowed'}),
  90. ('uuid', {
  91. '',
  92. '9da9369-838e-4750-91a5-f7805cd82839',
  93. '39da9369-838-4750-91a5-f7805cd82839',
  94. '39da9369-838e-475-91a5-f7805cd82839',
  95. '39da9369-838e-4750-91a-f7805cd82839',
  96. '39da9369-838e-4750-91a5-f7805cd8283',
  97. }),
  98. )
  99. for url_name, url_suffixes in test_data:
  100. for url_suffix in url_suffixes:
  101. url = '/%s/%s/' % (url_name, url_suffix)
  102. with self.subTest(url=url), self.assertRaises(Resolver404):
  103. resolve(url)
  104. class ParameterRestrictionTests(SimpleTestCase):
  105. def test_non_identifier_parameter_name_causes_exception(self):
  106. msg = (
  107. "URL route 'hello/<int:1>/' uses parameter name '1' which isn't "
  108. "a valid Python identifier."
  109. )
  110. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  111. path(r'hello/<int:1>/', lambda r: None)
  112. def test_allows_non_ascii_but_valid_identifiers(self):
  113. # \u0394 is "GREEK CAPITAL LETTER DELTA", a valid identifier.
  114. p = path('hello/<str:\u0394>/', lambda r: None)
  115. match = p.resolve('hello/1/')
  116. self.assertEqual(match.kwargs, {'\u0394': '1'})
  117. @override_settings(ROOT_URLCONF='urlpatterns.path_dynamic_urls')
  118. class ConversionExceptionTests(SimpleTestCase):
  119. """How are errors in Converter.to_python() and to_url() handled?"""
  120. def test_resolve_value_error_means_no_match(self):
  121. @DynamicConverter.register_to_python
  122. def raises_value_error(value):
  123. raise ValueError()
  124. with self.assertRaises(Resolver404):
  125. resolve('/dynamic/abc/')
  126. def test_resolve_type_error_propogates(self):
  127. @DynamicConverter.register_to_python
  128. def raises_type_error(value):
  129. raise TypeError('This type error propagates.')
  130. with self.assertRaisesMessage(TypeError, 'This type error propagates.'):
  131. resolve('/dynamic/abc/')
  132. def test_reverse_value_error_propagates(self):
  133. @DynamicConverter.register_to_url
  134. def raises_value_error(value):
  135. raise ValueError('This value error propagates.')
  136. with self.assertRaisesMessage(ValueError, 'This value error propagates.'):
  137. reverse('dynamic', kwargs={'value': object()})