test_thread_pollcreate_api.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. from django.urls import reverse
  2. from misago.core.utils import serialize_datetime
  3. from misago.threads.models import Poll, Thread
  4. from misago.threads.serializers.poll import MAX_POLL_OPTIONS
  5. from .test_thread_poll_api import ThreadPollApiTestCase
  6. class ThreadPollCreateTests(ThreadPollApiTestCase):
  7. def test_anonymous(self):
  8. """api requires you to sign in to create poll"""
  9. self.logout_user()
  10. response = self.post(self.api_link)
  11. self.assertEqual(response.status_code, 403)
  12. self.assertEqual(response.json(), {
  13. 'detail': "This action is not available to guests.",
  14. })
  15. def test_invalid_thread_id(self):
  16. """api validates that thread id is integer"""
  17. api_link = reverse(
  18. 'misago:api:thread-poll-list', kwargs={
  19. 'thread_pk': 'kjha6dsa687sa',
  20. }
  21. )
  22. response = self.post(api_link)
  23. self.assertEqual(response.status_code, 404)
  24. self.assertEqual(response.json(), {'detail': 'NOT FOUND'})
  25. def test_nonexistant_thread_id(self):
  26. """api validates that thread exists"""
  27. api_link = reverse(
  28. 'misago:api:thread-poll-list', kwargs={
  29. 'thread_pk': self.thread.pk + 1,
  30. }
  31. )
  32. response = self.post(api_link)
  33. self.assertEqual(response.status_code, 404)
  34. self.assertEqual(response.json(), {'detail': 'NOT FOUND'})
  35. def test_no_permission(self):
  36. """api validates that user has permission to start poll in thread"""
  37. self.override_acl({'can_start_polls': 0})
  38. response = self.post(self.api_link)
  39. self.assertEqual(response.status_code, 403)
  40. self.assertEqual(response.json(), {
  41. 'detail': "You can't start polls.",
  42. })
  43. def test_no_permission_closed_thread(self):
  44. """api validates that user has permission to start poll in closed thread"""
  45. self.override_acl(category={'can_close_threads': 0})
  46. self.thread.is_closed = True
  47. self.thread.save()
  48. response = self.post(self.api_link)
  49. self.assertEqual(response.status_code, 403)
  50. self.assertEqual(response.json(), {
  51. 'detail': "This thread is closed. You can't start polls in it.",
  52. })
  53. self.override_acl(category={'can_close_threads': 1})
  54. response = self.post(self.api_link)
  55. self.assertEqual(response.status_code, 400)
  56. def test_no_permission_closed_category(self):
  57. """api validates that user has permission to start poll in closed category"""
  58. self.override_acl(category={'can_close_threads': 0})
  59. self.category.is_closed = True
  60. self.category.save()
  61. response = self.post(self.api_link)
  62. self.assertEqual(response.status_code, 403)
  63. self.assertEqual(response.json(), {
  64. 'detail': "This category is closed. You can't start polls in it.",
  65. })
  66. self.override_acl(category={'can_close_threads': 1})
  67. response = self.post(self.api_link)
  68. self.assertEqual(response.status_code, 400)
  69. def test_no_permission_other_user_thread(self):
  70. """api validates that user has permission to start poll in other user's thread"""
  71. self.override_acl({'can_start_polls': 1})
  72. self.thread.starter = None
  73. self.thread.save()
  74. response = self.post(self.api_link)
  75. self.assertEqual(response.status_code, 403)
  76. self.assertEqual(response.json(), {
  77. 'detail': "You can't start polls in other users threads.",
  78. })
  79. self.override_acl({'can_start_polls': 2})
  80. response = self.post(self.api_link)
  81. self.assertEqual(response.status_code, 400)
  82. def test_no_permission_poll_exists(self):
  83. """api validates that user can't start second poll in thread"""
  84. self.thread.poll = Poll.objects.create(
  85. thread=self.thread,
  86. category=self.category,
  87. poster_name='Test',
  88. poster_slug='test',
  89. poster_ip='127.0.0.1',
  90. length=30,
  91. question='Test',
  92. choices=[
  93. {
  94. 'hash': 't3st'
  95. },
  96. ],
  97. allowed_choices=1,
  98. )
  99. response = self.post(self.api_link)
  100. self.assertEqual(response.status_code, 403)
  101. self.assertEqual(response.json(), {
  102. 'detail': "There's already a poll in this thread.",
  103. })
  104. def test_empty_data(self):
  105. """api handles empty request data"""
  106. response = self.post(self.api_link)
  107. self.assertEqual(response.status_code, 400)
  108. self.assertEqual(response.json(), {
  109. 'question': ["This field is required."],
  110. 'choices': ["This field is required."],
  111. 'length': ["This field is required."],
  112. 'allowed_choices': ["This field is required."],
  113. })
  114. def test_length_validation(self):
  115. """api validates poll's length"""
  116. response = self.post(
  117. self.api_link, data={
  118. 'length': -1,
  119. }
  120. )
  121. self.assertEqual(response.status_code, 400)
  122. self.assertEqual(response.json(), {
  123. 'question': ["This field is required."],
  124. 'choices': ["This field is required."],
  125. 'length': ["Ensure this value is greater than or equal to 0."],
  126. 'allowed_choices': ["This field is required."],
  127. })
  128. response = self.post(
  129. self.api_link, data={
  130. 'length': 200,
  131. }
  132. )
  133. self.assertEqual(response.status_code, 400)
  134. self.assertEqual(response.json(), {
  135. 'question': ["This field is required."],
  136. 'choices': ["This field is required."],
  137. 'length': ["Ensure this value is less than or equal to 180."],
  138. 'allowed_choices': ["This field is required."],
  139. })
  140. def test_question_validation(self):
  141. """api validates question length"""
  142. response = self.post(
  143. self.api_link, data={
  144. 'question': 'abcd' * 255,
  145. }
  146. )
  147. self.assertEqual(response.status_code, 400)
  148. self.assertEqual(response.json(), {
  149. 'question': ["Ensure this field has no more than 255 characters."],
  150. 'choices': ["This field is required."],
  151. 'length': ["This field is required."],
  152. 'allowed_choices': ["This field is required."],
  153. })
  154. def test_validate_choice_length(self):
  155. """api validates single choice length"""
  156. response = self.post(
  157. self.api_link, data={
  158. 'choices': [
  159. {
  160. 'hash': 'qwertyuiopas',
  161. 'label': '',
  162. },
  163. ],
  164. }
  165. )
  166. self.assertEqual(response.status_code, 400)
  167. self.assertEqual(response.json(), {
  168. 'question': ["This field is required."],
  169. 'choices': ["One or more poll choices are invalid."],
  170. 'length': ["This field is required."],
  171. 'allowed_choices': ["This field is required."],
  172. })
  173. response = self.post(
  174. self.api_link,
  175. data={
  176. 'choices': [
  177. {
  178. 'hash': 'qwertyuiopas',
  179. 'label': 'abcd' * 255,
  180. },
  181. ],
  182. }
  183. )
  184. self.assertEqual(response.status_code, 400)
  185. self.assertEqual(response.json(), {
  186. 'question': ["This field is required."],
  187. 'choices': ["One or more poll choices are invalid."],
  188. 'length': ["This field is required."],
  189. 'allowed_choices': ["This field is required."],
  190. })
  191. def test_validate_two_choices(self):
  192. """api validates that there are at least two choices in poll"""
  193. response = self.post(self.api_link, data={'choices': [{'label': 'Choice'}]})
  194. self.assertEqual(response.status_code, 400)
  195. self.assertEqual(response.json(), {
  196. 'question': ["This field is required."],
  197. 'choices': ["You need to add at least two choices to a poll."],
  198. 'length': ["This field is required."],
  199. 'allowed_choices': ["This field is required."],
  200. })
  201. def test_validate_max_choices(self):
  202. """api validates that there are no more choices in poll than allowed number"""
  203. response = self.post(
  204. self.api_link, data={
  205. 'choices': [
  206. {
  207. 'label': 'Choice',
  208. },
  209. ] * (MAX_POLL_OPTIONS + 1),
  210. }
  211. )
  212. self.assertEqual(response.status_code, 400)
  213. error_formats = (MAX_POLL_OPTIONS, MAX_POLL_OPTIONS + 1)
  214. self.assertEqual(response.json(), {
  215. 'question': ["This field is required."],
  216. 'choices': [
  217. "You can't add more than %s options to a single poll (added %s)." % error_formats
  218. ],
  219. 'length': ["This field is required."],
  220. 'allowed_choices': ["This field is required."],
  221. })
  222. def test_allowed_choices_validation(self):
  223. """api validates allowed choices number"""
  224. response = self.post(self.api_link, data={'allowed_choices': 0})
  225. self.assertEqual(response.status_code, 400)
  226. self.assertEqual(response.json(), {
  227. 'question': ["This field is required."],
  228. 'choices': ["This field is required."],
  229. 'length': ["This field is required."],
  230. 'allowed_choices': ["Ensure this value is greater than or equal to 1."],
  231. })
  232. response = self.post(
  233. self.api_link,
  234. data={
  235. 'length': 0,
  236. 'question': "Lorem ipsum",
  237. 'allowed_choices': 3,
  238. 'choices': [
  239. {
  240. 'label': 'Choice',
  241. },
  242. {
  243. 'label': 'Choice',
  244. },
  245. ],
  246. }
  247. )
  248. self.assertEqual(response.status_code, 400)
  249. self.assertEqual(response.json(), {
  250. 'non_field_errors': [
  251. "Number of allowed choices can't be greater than number of all choices."
  252. ],
  253. })
  254. def test_poll_created(self):
  255. """api creates public poll if provided with valid data"""
  256. response = self.post(
  257. self.api_link,
  258. data={
  259. 'length': 40,
  260. 'question': "Select two best colors",
  261. 'allowed_choices': 2,
  262. 'allow_revotes': True,
  263. 'is_public': True,
  264. 'choices': [
  265. {
  266. 'label': '\nRed ',
  267. },
  268. {
  269. 'label': 'Green',
  270. },
  271. {
  272. 'label': 'Blue',
  273. },
  274. ],
  275. }
  276. )
  277. self.assertEqual(response.status_code, 200)
  278. self.maxDiff = None
  279. poll = Poll.objects.all()[0]
  280. expected_choices = []
  281. for choice in poll.choices:
  282. expected_choices.append(choice.copy())
  283. expected_choices[-1]['selected'] = False
  284. self.assertEqual(response.json(), {
  285. 'id': poll.id,
  286. 'poster': {
  287. 'id': self.user.id,
  288. 'username': self.user.username,
  289. 'slug': self.user.slug,
  290. },
  291. 'posted_on': serialize_datetime(poll.posted_on),
  292. 'length': 40,
  293. 'question': "Select two best colors",
  294. 'allowed_choices': 2,
  295. 'allow_revotes': True,
  296. 'votes': 0,
  297. 'is_public': True,
  298. 'choices': expected_choices,
  299. })
  300. self.assertEqual(len(poll.choices), 3)
  301. self.assertEqual(len(set([c['hash'] for c in poll.choices])), 3)
  302. self.assertEqual([c['label'] for c in poll.choices], ['Red', 'Green', 'Blue'])
  303. thread = Thread.objects.get(pk=self.thread.pk)
  304. self.assertTrue(thread.has_poll)
  305. self.assertEqual(thread.poll, poll)