test_utils.py 11 KB

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