test_thread_start_api.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. from django.urls import reverse
  2. from misago.acl.testutils import override_acl
  3. from misago.categories.models import Category
  4. from misago.users.testutils import AuthenticatedUserTestCase
  5. class StartThreadTests(AuthenticatedUserTestCase):
  6. def setUp(self):
  7. super().setUp()
  8. self.category = Category.objects.get(slug='first-category')
  9. self.api_link = reverse('misago:api:thread-list')
  10. def override_acl(self, extra_acl=None):
  11. new_acl = self.user.acl_cache
  12. new_acl['categories'][self.category.pk].update({
  13. 'can_see': 1,
  14. 'can_browse': 1,
  15. 'can_start_threads': 1,
  16. 'can_pin_threads': 0,
  17. 'can_close_threads': 0,
  18. 'can_hide_threads': 0,
  19. 'can_hide_own_threads': 0,
  20. })
  21. if extra_acl:
  22. new_acl['categories'][self.category.pk].update(extra_acl)
  23. if 'can_see' in extra_acl and not extra_acl['can_see']:
  24. new_acl['visible_categories'].remove(self.category.pk)
  25. new_acl['browseable_categories'].remove(self.category.pk)
  26. if 'can_browse' in extra_acl and not extra_acl['can_browse']:
  27. new_acl['browseable_categories'].remove(self.category.pk)
  28. override_acl(self.user, new_acl)
  29. def test_cant_start_thread_as_guest(self):
  30. """user has to be authenticated to be able to post thread"""
  31. self.logout_user()
  32. response = self.client.post(self.api_link)
  33. self.assertEqual(response.status_code, 403)
  34. def test_cant_see(self):
  35. """has no permission to see selected category"""
  36. self.override_acl({'can_see': 0})
  37. response = self.client.post(self.api_link, {
  38. 'category': self.category.pk,
  39. })
  40. self.assertContains(response, "Selected category is invalid.", status_code=400)
  41. def test_cant_browse(self):
  42. """has no permission to browse selected category"""
  43. self.override_acl({'can_browse': 0})
  44. response = self.client.post(self.api_link, {
  45. 'category': self.category.pk,
  46. })
  47. self.assertContains(response, "Selected category is invalid.", status_code=400)
  48. def test_cant_start_thread(self):
  49. """permission to start thread in category is validated"""
  50. self.override_acl({'can_start_threads': 0})
  51. response = self.client.post(self.api_link, {
  52. 'category': self.category.pk,
  53. })
  54. self.assertContains(
  55. response, "You don't have permission to start new threads", status_code=400
  56. )
  57. def test_cant_start_thread_in_locked_category(self):
  58. """can't post in closed category"""
  59. self.category.is_closed = True
  60. self.category.save()
  61. self.override_acl({'can_close_threads': 0})
  62. response = self.client.post(self.api_link, {
  63. 'category': self.category.pk,
  64. })
  65. self.assertContains(response, "This category is closed.", status_code=400)
  66. def test_cant_start_thread_in_invalid_category(self):
  67. """can't post in invalid category"""
  68. self.category.is_closed = True
  69. self.category.save()
  70. self.override_acl({'can_close_threads': 0})
  71. response = self.client.post(self.api_link, {'category': self.category.pk * 100000})
  72. self.assertContains(response, "Selected category doesn't exist", status_code=400)
  73. def test_empty_data(self):
  74. """no data sent handling has no showstoppers"""
  75. self.override_acl()
  76. response = self.client.post(self.api_link, data={})
  77. self.assertEqual(response.status_code, 400)
  78. self.assertEqual(
  79. response.json(), {
  80. 'category': ["You have to select category to post thread in."],
  81. 'title': ["You have to enter thread title."],
  82. 'post': ["You have to enter a message."],
  83. }
  84. )
  85. def test_invalid_data(self):
  86. """api errors for invalid request data"""
  87. self.override_acl()
  88. response = self.client.post(
  89. self.api_link,
  90. 'false',
  91. content_type="application/json",
  92. )
  93. self.assertContains(response, "Invalid data.", status_code=400)
  94. def test_title_is_validated(self):
  95. """title is validated"""
  96. self.override_acl()
  97. response = self.client.post(
  98. self.api_link,
  99. data={
  100. 'category': self.category.pk,
  101. 'title': "------",
  102. 'post': "Lorem ipsum dolor met, sit amet elit!",
  103. }
  104. )
  105. self.assertEqual(response.status_code, 400)
  106. self.assertEqual(
  107. response.json(), {
  108. 'title': ["Thread title should contain alpha-numeric characters."],
  109. }
  110. )
  111. def test_post_is_validated(self):
  112. """post is validated"""
  113. self.override_acl()
  114. response = self.client.post(
  115. self.api_link,
  116. data={
  117. 'category': self.category.pk,
  118. 'title': "Lorem ipsum dolor met",
  119. 'post': "a",
  120. }
  121. )
  122. self.assertEqual(response.status_code, 400)
  123. self.assertEqual(
  124. response.json(), {
  125. 'post': ["Posted message should be at least 5 characters long (it has 1)."],
  126. }
  127. )
  128. def test_can_start_thread(self):
  129. """endpoint creates new thread"""
  130. self.override_acl()
  131. response = self.client.post(
  132. self.api_link,
  133. data={
  134. 'category': self.category.pk,
  135. 'title': "Hello, I am test thread!",
  136. 'post': "Lorem ipsum dolor met!",
  137. }
  138. )
  139. self.assertEqual(response.status_code, 200)
  140. thread = self.user.thread_set.all()[:1][0]
  141. response_json = response.json()
  142. self.assertEqual(response_json['url'], thread.get_absolute_url())
  143. self.override_acl()
  144. response = self.client.get(thread.get_absolute_url())
  145. self.assertContains(response, self.category.name)
  146. self.assertContains(response, thread.title)
  147. self.assertContains(response, "<p>Lorem ipsum dolor met!</p>")
  148. # api increased user's threads and posts counts
  149. self.reload_user()
  150. self.assertEqual(self.user.threads, 1)
  151. self.assertEqual(self.user.posts, 1)
  152. self.assertEqual(self.user.audittrail_set.count(), 1)
  153. self.assertEqual(thread.category_id, self.category.pk)
  154. self.assertEqual(thread.title, "Hello, I am test thread!")
  155. self.assertEqual(thread.starter_id, self.user.id)
  156. self.assertEqual(thread.starter_name, self.user.username)
  157. self.assertEqual(thread.starter_slug, self.user.slug)
  158. self.assertEqual(thread.last_poster_id, self.user.id)
  159. self.assertEqual(thread.last_poster_name, self.user.username)
  160. self.assertEqual(thread.last_poster_slug, self.user.slug)
  161. post = self.user.post_set.all()[:1][0]
  162. self.assertEqual(post.category_id, self.category.pk)
  163. self.assertEqual(post.original, 'Lorem ipsum dolor met!')
  164. self.assertEqual(post.poster_id, self.user.id)
  165. self.assertEqual(post.poster_name, self.user.username)
  166. category = Category.objects.get(pk=self.category.pk)
  167. self.assertEqual(category.threads, 1)
  168. self.assertEqual(category.posts, 1)
  169. self.assertEqual(category.last_thread_id, thread.id)
  170. self.assertEqual(category.last_thread_title, thread.title)
  171. self.assertEqual(category.last_thread_slug, thread.slug)
  172. self.assertEqual(category.last_poster_id, self.user.id)
  173. self.assertEqual(category.last_poster_name, self.user.username)
  174. self.assertEqual(category.last_poster_slug, self.user.slug)
  175. def test_start_closed_thread_no_permission(self):
  176. """permission is checked before thread is closed"""
  177. self.override_acl({'can_close_threads': 0})
  178. response = self.client.post(
  179. self.api_link,
  180. data={
  181. 'category': self.category.pk,
  182. 'title': "Hello, I am test thread!",
  183. 'post': "Lorem ipsum dolor met!",
  184. 'close': True,
  185. }
  186. )
  187. self.assertEqual(response.status_code, 200)
  188. thread = self.user.thread_set.all()[:1][0]
  189. self.assertFalse(thread.is_closed)
  190. def test_start_closed_thread(self):
  191. """can post closed thread"""
  192. self.override_acl({'can_close_threads': 1})
  193. response = self.client.post(
  194. self.api_link,
  195. data={
  196. 'category': self.category.pk,
  197. 'title': "Hello, I am test thread!",
  198. 'post': "Lorem ipsum dolor met!",
  199. 'close': True,
  200. }
  201. )
  202. self.assertEqual(response.status_code, 200)
  203. thread = self.user.thread_set.all()[:1][0]
  204. self.assertTrue(thread.is_closed)
  205. def test_start_unpinned_thread(self):
  206. """can post unpinned thread"""
  207. self.override_acl({'can_pin_threads': 1})
  208. response = self.client.post(
  209. self.api_link,
  210. data={
  211. 'category': self.category.pk,
  212. 'title': "Hello, I am test thread!",
  213. 'post': "Lorem ipsum dolor met!",
  214. 'pin': 0,
  215. }
  216. )
  217. self.assertEqual(response.status_code, 200)
  218. thread = self.user.thread_set.all()[:1][0]
  219. self.assertEqual(thread.weight, 0)
  220. def test_start_locally_pinned_thread(self):
  221. """can post locally pinned thread"""
  222. self.override_acl({'can_pin_threads': 1})
  223. response = self.client.post(
  224. self.api_link,
  225. data={
  226. 'category': self.category.pk,
  227. 'title': "Hello, I am test thread!",
  228. 'post': "Lorem ipsum dolor met!",
  229. 'pin': 1,
  230. }
  231. )
  232. self.assertEqual(response.status_code, 200)
  233. thread = self.user.thread_set.all()[:1][0]
  234. self.assertEqual(thread.weight, 1)
  235. def test_start_globally_pinned_thread(self):
  236. """can post globally pinned thread"""
  237. self.override_acl({'can_pin_threads': 2})
  238. response = self.client.post(
  239. self.api_link,
  240. data={
  241. 'category': self.category.pk,
  242. 'title': "Hello, I am test thread!",
  243. 'post': "Lorem ipsum dolor met!",
  244. 'pin': 2,
  245. }
  246. )
  247. self.assertEqual(response.status_code, 200)
  248. thread = self.user.thread_set.all()[:1][0]
  249. self.assertEqual(thread.weight, 2)
  250. def test_start_globally_pinned_thread_no_permission(self):
  251. """cant post globally pinned thread without permission"""
  252. self.override_acl({'can_pin_threads': 1})
  253. response = self.client.post(
  254. self.api_link,
  255. data={
  256. 'category': self.category.pk,
  257. 'title': "Hello, I am test thread!",
  258. 'post': "Lorem ipsum dolor met!",
  259. 'pin': 2,
  260. }
  261. )
  262. self.assertEqual(response.status_code, 200)
  263. thread = self.user.thread_set.all()[:1][0]
  264. self.assertEqual(thread.weight, 0)
  265. def test_start_locally_pinned_thread_no_permission(self):
  266. """cant post locally pinned thread without permission"""
  267. self.override_acl({'can_pin_threads': 0})
  268. response = self.client.post(
  269. self.api_link,
  270. data={
  271. 'category': self.category.pk,
  272. 'title': "Hello, I am test thread!",
  273. 'post': "Lorem ipsum dolor met!",
  274. 'pin': 1,
  275. }
  276. )
  277. self.assertEqual(response.status_code, 200)
  278. thread = self.user.thread_set.all()[:1][0]
  279. self.assertEqual(thread.weight, 0)
  280. def test_start_hidden_thread(self):
  281. """can post hidden thread"""
  282. self.override_acl({'can_hide_threads': 1})
  283. response = self.client.post(
  284. self.api_link,
  285. data={
  286. 'category': self.category.pk,
  287. 'title': "Hello, I am test thread!",
  288. 'post': "Lorem ipsum dolor met!",
  289. 'hide': 1,
  290. }
  291. )
  292. self.assertEqual(response.status_code, 200)
  293. thread = self.user.thread_set.all()[:1][0]
  294. self.assertTrue(thread.is_hidden)
  295. category = Category.objects.get(pk=self.category.pk)
  296. self.assertNotEqual(category.last_thread_id, thread.id)
  297. def test_start_hidden_thread_no_permission(self):
  298. """cant post hidden thread without permission"""
  299. self.override_acl({'can_hide_threads': 0})
  300. response = self.client.post(
  301. self.api_link,
  302. data={
  303. 'category': self.category.pk,
  304. 'title': "Hello, I am test thread!",
  305. 'post': "Lorem ipsum dolor met!",
  306. 'hide': 1,
  307. }
  308. )
  309. self.assertEqual(response.status_code, 200)
  310. thread = self.user.thread_set.all()[:1][0]
  311. self.assertFalse(thread.is_hidden)
  312. def test_post_unicode(self):
  313. """unicode characters can be posted"""
  314. self.override_acl()
  315. response = self.client.post(
  316. self.api_link,
  317. data={
  318. 'category': self.category.pk,
  319. 'title': "Brzęczyżczykiewicz",
  320. 'post': "Chrzążczyżewoszyce, powiat Łękółody.",
  321. }
  322. )
  323. self.assertEqual(response.status_code, 200)
  324. def test_category_moderation_queue(self):
  325. """start unapproved thread in category that requires approval"""
  326. self.category.require_threads_approval = True
  327. self.category.save()
  328. response = self.client.post(
  329. self.api_link,
  330. data={
  331. 'category': self.category.pk,
  332. 'title': "Hello, I am test thread!",
  333. 'post': "Lorem ipsum dolor met!",
  334. }
  335. )
  336. self.assertEqual(response.status_code, 200)
  337. thread = self.user.thread_set.all()[:1][0]
  338. self.assertTrue(thread.is_unapproved)
  339. self.assertTrue(thread.has_unapproved_posts)
  340. post = self.user.post_set.all()[:1][0]
  341. self.assertTrue(post.is_unapproved)
  342. category = Category.objects.get(slug='first-category')
  343. self.assertEqual(category.threads, self.category.threads)
  344. self.assertEqual(category.posts, self.category.posts)
  345. self.assertFalse(category.last_thread_id == thread.id)
  346. def test_category_moderation_queue_bypass(self):
  347. """bypass moderation queue due to user's acl"""
  348. override_acl(self.user, {'can_approve_content': 1})
  349. self.category.require_threads_approval = True
  350. self.category.save()
  351. response = self.client.post(
  352. self.api_link,
  353. data={
  354. 'category': self.category.pk,
  355. 'title': "Hello, I am test thread!",
  356. 'post': "Lorem ipsum dolor met!",
  357. }
  358. )
  359. self.assertEqual(response.status_code, 200)
  360. thread = self.user.thread_set.all()[:1][0]
  361. self.assertFalse(thread.is_unapproved)
  362. self.assertFalse(thread.has_unapproved_posts)
  363. post = self.user.post_set.all()[:1][0]
  364. self.assertFalse(post.is_unapproved)
  365. category = Category.objects.get(slug='first-category')
  366. self.assertEqual(category.threads, self.category.threads + 1)
  367. self.assertEqual(category.posts, self.category.posts + 1)
  368. self.assertEqual(category.last_thread_id, thread.id)
  369. def test_user_moderation_queue(self):
  370. """start unapproved thread in category that requires approval"""
  371. self.override_acl({'require_threads_approval': 1})
  372. response = self.client.post(
  373. self.api_link,
  374. data={
  375. 'category': self.category.pk,
  376. 'title': "Hello, I am test thread!",
  377. 'post': "Lorem ipsum dolor met!",
  378. }
  379. )
  380. self.assertEqual(response.status_code, 200)
  381. thread = self.user.thread_set.all()[:1][0]
  382. self.assertTrue(thread.is_unapproved)
  383. self.assertTrue(thread.has_unapproved_posts)
  384. post = self.user.post_set.all()[:1][0]
  385. self.assertTrue(post.is_unapproved)
  386. category = Category.objects.get(slug='first-category')
  387. self.assertEqual(category.threads, self.category.threads)
  388. self.assertEqual(category.posts, self.category.posts)
  389. self.assertFalse(category.last_thread_id == thread.id)
  390. def test_user_moderation_queue_bypass(self):
  391. """bypass moderation queue due to user's acl"""
  392. override_acl(self.user, {'can_approve_content': 1})
  393. self.override_acl({'require_threads_approval': 1})
  394. response = self.client.post(
  395. self.api_link,
  396. data={
  397. 'category': self.category.pk,
  398. 'title': "Hello, I am test thread!",
  399. 'post': "Lorem ipsum dolor met!",
  400. }
  401. )
  402. self.assertEqual(response.status_code, 200)
  403. thread = self.user.thread_set.all()[:1][0]
  404. self.assertFalse(thread.is_unapproved)
  405. self.assertFalse(thread.has_unapproved_posts)
  406. post = self.user.post_set.all()[:1][0]
  407. self.assertFalse(post.is_unapproved)
  408. category = Category.objects.get(slug='first-category')
  409. self.assertEqual(category.threads, self.category.threads + 1)
  410. self.assertEqual(category.posts, self.category.posts + 1)
  411. self.assertEqual(category.last_thread_id, thread.id)
  412. def test_omit_other_moderation_queues(self):
  413. """other queues are omitted"""
  414. self.category.require_replies_approval = True
  415. self.category.require_edits_approval = True
  416. self.category.save()
  417. self.override_acl({
  418. 'require_replies_approval': 1,
  419. 'require_edits_approval': 1,
  420. })
  421. response = self.client.post(
  422. self.api_link,
  423. data={
  424. 'category': self.category.pk,
  425. 'title': "Hello, I am test thread!",
  426. 'post': "Lorem ipsum dolor met!",
  427. }
  428. )
  429. self.assertEqual(response.status_code, 200)
  430. thread = self.user.thread_set.all()[:1][0]
  431. self.assertFalse(thread.is_unapproved)
  432. self.assertFalse(thread.has_unapproved_posts)
  433. post = self.user.post_set.all()[:1][0]
  434. self.assertFalse(post.is_unapproved)
  435. category = Category.objects.get(slug='first-category')
  436. self.assertEqual(category.threads, self.category.threads + 1)
  437. self.assertEqual(category.posts, self.category.posts + 1)
  438. self.assertEqual(category.last_thread_id, thread.id)