test_thread_start_api.py 20 KB

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