test_utils.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. from django.core.exceptions import PermissionDenied
  2. from django.test import TestCase
  3. from django.test.client import RequestFactory
  4. from django.urls import reverse
  5. from django.utils import six
  6. from misago.core.utils import (
  7. clean_return_path, format_plaintext_for_html, is_referer_local, is_request_to_misago,
  8. parse_iso8601_string, slugify, get_exception_message, clean_ids_list, get_host_from_address)
  9. class IsRequestToMisagoTests(TestCase):
  10. def test_is_request_to_misago(self):
  11. """
  12. is_request_to_misago correctly detects requests directed at Misago
  13. """
  14. VALID_PATHS = ('/', '/threads/', )
  15. INVALID_PATHS = ('', 'somewhere/', )
  16. misago_prefix = reverse('misago:index')
  17. for path in VALID_PATHS:
  18. request = RequestFactory().get('/')
  19. request.path = path
  20. self.assertTrue(
  21. is_request_to_misago(request),
  22. '"%s" is not overlapped by "%s"' % (path, misago_prefix)
  23. )
  24. for path in INVALID_PATHS:
  25. request = RequestFactory().get('/')
  26. request.path = path
  27. self.assertFalse(
  28. is_request_to_misago(request),
  29. '"%s" is overlapped by "%s"' % (path, misago_prefix)
  30. )
  31. class SlugifyTests(TestCase):
  32. def test_valid_slugify_output(self):
  33. """Misago's slugify correctly slugifies string"""
  34. test_cases = [
  35. ('Bob', 'bob'),
  36. ('Eric The Fish', 'eric-the-fish'),
  37. ('John Snow', 'john-snow'),
  38. ('J0n', 'j0n'),
  39. ('An###ne', 'anne'),
  40. ('S**t', 'st'),
  41. ('Łók', 'lok'),
  42. ]
  43. for original, slug in test_cases:
  44. self.assertEqual(slugify(original), slug)
  45. class ParseIso8601StringTests(TestCase):
  46. def test_valid_input(self):
  47. """util parses iso 8601 strings"""
  48. INPUTS = [
  49. '2016-10-22T20:55:39.185085Z',
  50. '2016-10-22T20:55:39.185085-01:00',
  51. '2016-10-22T20:55:39-01:00',
  52. '2016-10-22T20:55:39.185085+01:00',
  53. ]
  54. for test_input in INPUTS:
  55. self.assertTrue(parse_iso8601_string(test_input))
  56. def test_invalid_input(self):
  57. """util throws ValueError on invalid input"""
  58. INPUTS = [
  59. '',
  60. '2016-10-22',
  61. '2016-10-22T30:55:39.185085+11:00',
  62. '2016-10-22T20:55:39.18SSSSS5085Z',
  63. ]
  64. for test_input in INPUTS:
  65. with self.assertRaises(ValueError):
  66. self.assertTrue(parse_iso8601_string(test_input))
  67. PLAINTEXT_FORMAT_CASES = [
  68. ('Lorem ipsum.', '<p>Lorem ipsum.</p>'),
  69. ('Lorem <b>ipsum</b>.', '<p>Lorem &lt;b&gt;ipsum&lt;/b&gt;.</p>'),
  70. ('Lorem "ipsum" dolor met.', '<p>Lorem &quot;ipsum&quot; dolor met.</p>'),
  71. ('Lorem ipsum.\nDolor met.', '<p>Lorem ipsum.<br />Dolor met.</p>'),
  72. ('Lorem ipsum.\n\nDolor met.', '<p>Lorem ipsum.</p>\n\n<p>Dolor met.</p>'),
  73. (
  74. 'http://misago-project.org/login/',
  75. '<p><a href="http://misago-project.org/login/">http://misago-project.org/login/</a></p>'
  76. ),
  77. ]
  78. class FormatPlaintextForHtmlTests(TestCase):
  79. def test_format_plaintext_for_html(self):
  80. """format_plaintext_for_html correctly formats plaintext for html"""
  81. for plaintext, html in PLAINTEXT_FORMAT_CASES:
  82. output = format_plaintext_for_html(plaintext)
  83. assertion_message = """
  84. format_plaintext_for_html failed to produce expected output:
  85. expected: %s
  86. return: %s
  87. """ % (html, output)
  88. self.assertEqual(output, html, assertion_message)
  89. class MockRequest(object):
  90. scheme = 'http'
  91. def __init__(self, method, meta=None, post=None):
  92. self.method = method
  93. self.META = meta or {}
  94. self.POST = post or {}
  95. class CleanReturnPathTests(TestCase):
  96. def test_get_request(self):
  97. """clean_return_path works for GET requests"""
  98. bad_request = MockRequest(
  99. 'GET', {
  100. 'HTTP_REFERER': 'http://cookies.com',
  101. 'HTTP_HOST': 'misago-project.org',
  102. }
  103. )
  104. self.assertIsNone(clean_return_path(bad_request))
  105. bad_request = MockRequest(
  106. 'GET', {
  107. 'HTTP_REFERER': 'https://misago-project.org/',
  108. 'HTTP_HOST': 'misago-project.org/',
  109. }
  110. )
  111. self.assertIsNone(clean_return_path(bad_request))
  112. bad_request = MockRequest(
  113. 'GET', {
  114. 'HTTP_REFERER': 'https://misago-project.org/',
  115. 'HTTP_HOST': 'misago-project.org/assadsa/',
  116. }
  117. )
  118. self.assertIsNone(clean_return_path(bad_request))
  119. ok_request = MockRequest(
  120. 'GET', {
  121. 'HTTP_REFERER': 'http://misago-project.org/',
  122. 'HTTP_HOST': 'misago-project.org/',
  123. }
  124. )
  125. self.assertEqual(clean_return_path(ok_request), '/')
  126. ok_request = MockRequest(
  127. 'GET', {
  128. 'HTTP_REFERER': 'http://misago-project.org/login/',
  129. 'HTTP_HOST': 'misago-project.org/',
  130. }
  131. )
  132. self.assertEqual(clean_return_path(ok_request), '/login/')
  133. def test_post_request(self):
  134. """clean_return_path works for POST requests"""
  135. bad_request = MockRequest(
  136. 'POST', {
  137. 'HTTP_REFERER': 'http://misago-project.org/',
  138. 'HTTP_HOST': 'misago-project.org/',
  139. }, {
  140. 'return_path': '/sdasdsa/',
  141. }
  142. )
  143. self.assertIsNone(clean_return_path(bad_request))
  144. ok_request = MockRequest(
  145. 'POST', {
  146. 'HTTP_REFERER': 'http://misago-project.org/',
  147. 'HTTP_HOST': 'misago-project.org/',
  148. }, {
  149. 'return_path': '/login/',
  150. }
  151. )
  152. self.assertEqual(clean_return_path(ok_request), '/login/')
  153. class IsRefererLocalTests(TestCase):
  154. def test_local_referers(self):
  155. """local referers return true"""
  156. ok_request = MockRequest(
  157. 'GET', {
  158. 'HTTP_REFERER': 'http://misago-project.org/',
  159. 'HTTP_HOST': 'misago-project.org/',
  160. }
  161. )
  162. self.assertTrue(is_referer_local(ok_request))
  163. ok_request = MockRequest(
  164. 'GET', {
  165. 'HTTP_REFERER': 'http://misago-project.org/',
  166. 'HTTP_HOST': 'misago-project.org/',
  167. }
  168. )
  169. self.assertTrue(is_referer_local(ok_request))
  170. ok_request = MockRequest(
  171. 'GET', {
  172. 'HTTP_REFERER': 'http://misago-project.org/login/',
  173. 'HTTP_HOST': 'misago-project.org/',
  174. }
  175. )
  176. self.assertTrue(is_referer_local(ok_request))
  177. def test_foreign_referers(self):
  178. """non-local referers return false"""
  179. bad_request = MockRequest(
  180. 'GET', {
  181. 'HTTP_REFERER': 'http://else-project.org/',
  182. 'HTTP_HOST': 'misago-project.org/',
  183. }
  184. )
  185. self.assertFalse(is_referer_local(bad_request))
  186. bad_request = MockRequest(
  187. 'GET', {
  188. 'HTTP_REFERER': 'https://misago-project.org/',
  189. 'HTTP_HOST': 'misago-project.org/',
  190. }
  191. )
  192. self.assertFalse(is_referer_local(bad_request))
  193. bad_request = MockRequest(
  194. 'GET', {
  195. 'HTTP_REFERER': 'http://misago-project.org/',
  196. 'HTTP_HOST': 'misago-project.org/assadsa/',
  197. }
  198. )
  199. self.assertFalse(is_referer_local(bad_request))
  200. class GetExceptionMessageTests(TestCase):
  201. def test_no_args(self):
  202. """both of helper args are optional"""
  203. message = get_exception_message()
  204. self.assertIsNone(message)
  205. def test_no_default_message(self):
  206. """helper's default message arg is optional"""
  207. message = get_exception_message(PermissionDenied('Lorem Ipsum'))
  208. self.assertEqual(message, 'Lorem Ipsum')
  209. message = get_exception_message(PermissionDenied())
  210. self.assertIsNone(message)
  211. def test_default_message(self):
  212. """helper's default message arg is used"""
  213. message = get_exception_message(PermissionDenied('Lorem Ipsum'), 'Default')
  214. self.assertEqual(message, 'Lorem Ipsum')
  215. message = get_exception_message(PermissionDenied(), 'Default')
  216. self.assertEqual(message, 'Default')
  217. message = get_exception_message(default_message='Lorem Ipsum')
  218. self.assertEqual(message, 'Lorem Ipsum')
  219. class CleanIdsListTests(TestCase):
  220. def test_valid_list(self):
  221. """list of valid ids is cleaned"""
  222. self.assertEqual(clean_ids_list(['1', 3, '42'], None), [1, 3, 42])
  223. def test_empty_list(self):
  224. """empty list passes validation"""
  225. self.assertEqual(clean_ids_list([], None), [])
  226. def test_string_list(self):
  227. """string list passes validation"""
  228. self.assertEqual(clean_ids_list('1234', None), [1, 2, 3, 4])
  229. def test_message(self):
  230. """utility uses passed message for exception"""
  231. with self.assertRaisesMessage(PermissionDenied, "Test error message!"):
  232. clean_ids_list(None, "Test error message!")
  233. def test_invalid_inputs(self):
  234. """utility raises exception for invalid inputs"""
  235. INVALID_INPUTS = (
  236. None,
  237. 'abc',
  238. [None],
  239. [1, 2, 'a', 4],
  240. [1, None, 3],
  241. {1: 2, 'a': 4},
  242. )
  243. for invalid_input in INVALID_INPUTS:
  244. with self.assertRaisesMessage(PermissionDenied, "Test error message!"):
  245. clean_ids_list(invalid_input, "Test error message!")
  246. class GetHostFromAddressTests(TestCase):
  247. def test_none(self):
  248. """get_host_from_address returns None for None"""
  249. result = get_host_from_address(None)
  250. self.assertIsNone(result)
  251. def test_empty_string(self):
  252. """get_host_from_address returns None for empty string"""
  253. result = get_host_from_address('')
  254. self.assertIsNone(result)
  255. def test_hostname(self):
  256. """get_host_from_address returns hostname unchanged"""
  257. result = get_host_from_address('hostname')
  258. self.assertEqual(result, 'hostname')
  259. def test_hostname_with_trailing_slash(self):
  260. """get_host_from_address returns hostname for hostname with trailing slash"""
  261. result = get_host_from_address('//hostname')
  262. self.assertEqual(result, 'hostname')
  263. def test_hostname_with_port(self):
  264. """get_host_from_address returns hostname for hostname with port"""
  265. result = get_host_from_address('hostname:8888')
  266. self.assertEqual(result, 'hostname')
  267. def test_hostname_with_port_path_and_protocol(self):
  268. """get_host_from_address returns hostname for hostname with port and path"""
  269. result = get_host_from_address('https://hostname:8888/somewhere/else/')
  270. self.assertEqual(result, 'hostname')