test_threadslists.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334
  1. from datetime import timedelta
  2. from django.urls import reverse
  3. from django.utils import timezone
  4. from django.utils.encoding import smart_str
  5. from .. import test
  6. from ...acl.test import patch_user_acl
  7. from ...categories.models import Category
  8. from ...conf import settings
  9. from ...readtracker import poststracker
  10. from ...users.test import AuthenticatedUserTestCase
  11. LISTS_URLS = ("", "my/", "new/", "unread/", "subscribed/")
  12. def patch_categories_acl(category_acl=None, base_acl=None):
  13. def patch_acl(_, user_acl):
  14. first_category = Category.objects.get(slug="first-category")
  15. first_category_acl = user_acl["categories"][first_category.id].copy()
  16. user_acl.update(
  17. {
  18. "categories": {},
  19. "visible_categories": [],
  20. "browseable_categories": [],
  21. "can_approve_content": [],
  22. }
  23. )
  24. # copy first category's acl to other categories to make base for overrides
  25. for category in Category.objects.all_categories():
  26. user_acl["categories"][category.id] = first_category_acl
  27. if base_acl:
  28. user_acl.update(base_acl)
  29. for category in Category.objects.all_categories():
  30. user_acl["visible_categories"].append(category.id)
  31. user_acl["browseable_categories"].append(category.id)
  32. user_acl["categories"][category.id].update(
  33. {
  34. "can_see": 1,
  35. "can_browse": 1,
  36. "can_see_all_threads": 1,
  37. "can_see_own_threads": 0,
  38. "can_hide_threads": 0,
  39. "can_approve_content": 0,
  40. }
  41. )
  42. if category_acl:
  43. user_acl["categories"][category.id].update(category_acl)
  44. if category_acl.get("can_approve_content"):
  45. user_acl["can_approve_content"].append(category.id)
  46. return patch_user_acl(patch_acl)
  47. class ThreadsListTestCase(AuthenticatedUserTestCase):
  48. def setUp(self):
  49. """
  50. Create categories tree for test cases:
  51. First category (created by migration)
  52. Category A
  53. + Category B
  54. + Subcategory C
  55. + Subcategory D
  56. Category E
  57. + Subcategory F
  58. """
  59. super().setUp()
  60. self.api_link = reverse("misago:api:thread-list")
  61. self.root = Category.objects.root_category()
  62. self.first_category = Category.objects.get(slug="first-category")
  63. Category(
  64. name="Category A", slug="category-a", css_class="showing-category-a"
  65. ).insert_at(self.root, position="last-child", save=True)
  66. Category(
  67. name="Category E", slug="category-e", css_class="showing-category-e"
  68. ).insert_at(self.root, position="last-child", save=True)
  69. self.root = Category.objects.root_category()
  70. self.category_a = Category.objects.get(slug="category-a")
  71. Category(
  72. name="Category B", slug="category-b", css_class="showing-category-b"
  73. ).insert_at(self.category_a, position="last-child", save=True)
  74. self.category_b = Category.objects.get(slug="category-b")
  75. Category(
  76. name="Category C", slug="category-c", css_class="showing-category-c"
  77. ).insert_at(self.category_b, position="last-child", save=True)
  78. Category(
  79. name="Category D", slug="category-d", css_class="showing-category-d"
  80. ).insert_at(self.category_b, position="last-child", save=True)
  81. self.category_c = Category.objects.get(slug="category-c")
  82. self.category_d = Category.objects.get(slug="category-d")
  83. self.category_e = Category.objects.get(slug="category-e")
  84. Category(
  85. name="Category F", slug="category-f", css_class="showing-category-f"
  86. ).insert_at(self.category_e, position="last-child", save=True)
  87. self.category_f = Category.objects.get(slug="category-f")
  88. Category.objects.partial_rebuild(self.root.tree_id)
  89. self.root = Category.objects.root_category()
  90. self.category_a = Category.objects.get(slug="category-a")
  91. self.category_b = Category.objects.get(slug="category-b")
  92. self.category_c = Category.objects.get(slug="category-c")
  93. self.category_d = Category.objects.get(slug="category-d")
  94. self.category_e = Category.objects.get(slug="category-e")
  95. self.category_f = Category.objects.get(slug="category-f")
  96. def assertContainsThread(self, response, thread):
  97. self.assertContains(response, ' href="%s"' % thread.get_absolute_url())
  98. def assertNotContainsThread(self, response, thread):
  99. self.assertNotContains(response, ' href="%s"' % thread.get_absolute_url())
  100. class ApiTests(ThreadsListTestCase):
  101. def test_root_category(self):
  102. """its possible to access threads endpoint with category=ROOT_ID"""
  103. response = self.client.get("%s?category=%s" % (self.api_link, self.root.pk))
  104. self.assertEqual(response.status_code, 200)
  105. def test_explicit_first_page(self):
  106. """its possible to access threads endpoint with explicit first page"""
  107. response = self.client.get(
  108. "%s?category=%s&page=1" % (self.api_link, self.root.pk)
  109. )
  110. self.assertEqual(response.status_code, 200)
  111. def test_invalid_list_type(self):
  112. """api returns 404 for invalid list type"""
  113. response = self.client.get(
  114. "%s?category=%s&list=nope" % (self.api_link, self.root.pk)
  115. )
  116. self.assertEqual(response.status_code, 404)
  117. class AllThreadsListTests(ThreadsListTestCase):
  118. @patch_categories_acl()
  119. def test_list_renders_empty(self):
  120. """empty threads list renders"""
  121. for url in LISTS_URLS:
  122. response = self.client.get("/" + url)
  123. self.assertEqual(response.status_code, 200)
  124. self.assertContains(response, "empty-message")
  125. if url:
  126. self.assertContains(response, "No threads matching specified criteria")
  127. else:
  128. self.assertContains(response, "There are no threads on this forum")
  129. response = self.client.get(self.category_b.get_absolute_url() + url)
  130. self.assertEqual(response.status_code, 200)
  131. self.assertContains(response, self.category_b.name)
  132. self.assertContains(response, "empty-message")
  133. if url:
  134. self.assertContains(response, "No threads matching specified criteria")
  135. else:
  136. self.assertContains(response, "There are no threads in this category")
  137. response = self.client.get(
  138. "%s?list=%s" % (self.api_link, url.strip("/") or "all")
  139. )
  140. self.assertEqual(response.status_code, 200)
  141. response_json = response.json()
  142. self.assertEqual(len(response_json["results"]), 0)
  143. # empty lists render for anonymous user?
  144. self.logout_user()
  145. self.user = self.get_anonymous_user()
  146. response = self.client.get("/")
  147. self.assertEqual(response.status_code, 200)
  148. self.assertContains(response, "empty-message")
  149. self.assertContains(response, "There are no threads on this forum")
  150. response = self.client.get(self.category_b.get_absolute_url())
  151. self.assertEqual(response.status_code, 200)
  152. self.assertContains(response, self.category_b.name)
  153. self.assertContains(response, "empty-message")
  154. self.assertContains(response, "There are no threads in this category")
  155. response = self.client.get("%s?list=all" % self.api_link)
  156. self.assertEqual(response.status_code, 200)
  157. response_json = response.json()
  158. self.assertEqual(len(response_json["results"]), 0)
  159. @patch_categories_acl()
  160. def test_list_authenticated_only_views(self):
  161. """authenticated only views return 403 for guests"""
  162. for url in LISTS_URLS:
  163. response = self.client.get("/" + url)
  164. self.assertEqual(response.status_code, 200)
  165. response = self.client.get(self.category_b.get_absolute_url() + url)
  166. self.assertEqual(response.status_code, 200)
  167. self.assertContains(response, self.category_b.name)
  168. response = self.client.get(
  169. "%s?category=%s&list=%s"
  170. % (self.api_link, self.category_b.pk, url.strip("/") or "all")
  171. )
  172. self.assertEqual(response.status_code, 200)
  173. self.logout_user()
  174. self.user = self.get_anonymous_user()
  175. for url in LISTS_URLS[1:]:
  176. response = self.client.get("/" + url)
  177. self.assertEqual(response.status_code, 403)
  178. response = self.client.get(self.category_b.get_absolute_url() + url)
  179. self.assertEqual(response.status_code, 403)
  180. response = self.client.get(
  181. "%s?category=%s&list=%s"
  182. % (self.api_link, self.category_b.pk, url.strip("/") or "all")
  183. )
  184. self.assertEqual(response.status_code, 403)
  185. @patch_categories_acl()
  186. def test_list_renders_categories_picker(self):
  187. """categories picker renders valid categories"""
  188. Category(name="Hidden Category", slug="hidden-category").insert_at(
  189. self.root, position="last-child", save=True
  190. )
  191. test_category = Category.objects.get(slug="hidden-category")
  192. test.post_thread(category=self.category_b)
  193. response = self.client.get("/")
  194. self.assertEqual(response.status_code, 200)
  195. self.assertContains(response, "subcategory-%s" % self.category_a.css_class)
  196. # readable categories, but non-accessible directly
  197. self.assertNotContains(response, "subcategory-%s" % self.category_b.css_class)
  198. self.assertNotContains(response, "subcategory-%s" % self.category_c.css_class)
  199. self.assertNotContains(response, "subcategory-%s" % self.category_d.css_class)
  200. self.assertNotContains(response, "subcategory-%s" % self.category_f.css_class)
  201. # hidden category
  202. self.assertNotContains(response, "subcategory-%s" % test_category.css_class)
  203. response = self.client.get(self.api_link)
  204. self.assertEqual(response.status_code, 200)
  205. response_json = response.json()
  206. self.assertIn(self.category_a.pk, response_json["subcategories"])
  207. self.assertNotIn(self.category_b.pk, response_json["subcategories"])
  208. # test category view
  209. response = self.client.get(self.category_a.get_absolute_url())
  210. self.assertEqual(response.status_code, 200)
  211. self.assertContains(response, "subcategory-%s" % self.category_b.css_class)
  212. # readable categories, but non-accessible directly
  213. self.assertNotContains(response, "subcategory-%s" % self.category_c.css_class)
  214. self.assertNotContains(response, "subcategory-%s" % self.category_d.css_class)
  215. self.assertNotContains(response, "subcategory-%s" % self.category_f.css_class)
  216. response = self.client.get(
  217. "%s?category=%s" % (self.api_link, self.category_a.pk)
  218. )
  219. self.assertEqual(response.status_code, 200)
  220. response_json = response.json()
  221. self.assertEqual(response_json["subcategories"][0], self.category_b.pk)
  222. def test_display_pinned_threads(self):
  223. """
  224. threads list displays globally pinned threads first
  225. and locally ones inbetween other
  226. """
  227. globally = test.post_thread(category=self.first_category, is_global=True)
  228. locally = test.post_thread(category=self.first_category, is_pinned=True)
  229. standard = test.post_thread(category=self.first_category)
  230. response = self.client.get("/")
  231. self.assertEqual(response.status_code, 200)
  232. content = smart_str(response.content)
  233. positions = {
  234. "g": content.find(globally.get_absolute_url()),
  235. "l": content.find(locally.get_absolute_url()),
  236. "s": content.find(standard.get_absolute_url()),
  237. }
  238. # global announcement before others
  239. self.assertTrue(positions["g"] < positions["l"])
  240. self.assertTrue(positions["g"] < positions["s"])
  241. # standard in the middle
  242. self.assertTrue(positions["s"] < positions["l"])
  243. self.assertTrue(positions["s"] > positions["g"])
  244. # pinned last
  245. self.assertTrue(positions["l"] > positions["g"])
  246. self.assertTrue(positions["l"] > positions["s"])
  247. # API behaviour is identic
  248. response = self.client.get("/api/threads/")
  249. self.assertEqual(response.status_code, 200)
  250. content = smart_str(response.content)
  251. positions = {
  252. "g": content.find(globally.get_absolute_url()),
  253. "l": content.find(locally.get_absolute_url()),
  254. "s": content.find(standard.get_absolute_url()),
  255. }
  256. # global announcement before others
  257. self.assertTrue(positions["g"] < positions["l"])
  258. self.assertTrue(positions["g"] < positions["s"])
  259. # standard in the middle
  260. self.assertTrue(positions["s"] < positions["l"])
  261. self.assertTrue(positions["s"] > positions["g"])
  262. # pinned last
  263. self.assertTrue(positions["l"] > positions["g"])
  264. self.assertTrue(positions["l"] > positions["s"])
  265. def test_noscript_pagination(self):
  266. """threads list is paginated for users with js disabled"""
  267. threads_per_page = settings.MISAGO_THREADS_PER_PAGE
  268. threads = []
  269. for _ in range(settings.MISAGO_THREADS_PER_PAGE * 3):
  270. threads.append(test.post_thread(category=self.first_category))
  271. # secondary page renders
  272. response = self.client.get("/?page=2")
  273. self.assertEqual(response.status_code, 200)
  274. for thread in threads[:threads_per_page]:
  275. self.assertNotContainsThread(response, thread)
  276. for thread in threads[threads_per_page : threads_per_page * 2]:
  277. self.assertContainsThread(response, thread)
  278. for thread in threads[threads_per_page * 2 :]:
  279. self.assertNotContainsThread(response, thread)
  280. self.assertNotContains(response, "/?page=1")
  281. self.assertContains(response, "/?page=3")
  282. # third page renders
  283. response = self.client.get("/?page=3")
  284. self.assertEqual(response.status_code, 200)
  285. for thread in threads[threads_per_page:]:
  286. self.assertNotContainsThread(response, thread)
  287. for thread in threads[:threads_per_page]:
  288. self.assertContainsThread(response, thread)
  289. self.assertContains(response, "/?page=2")
  290. self.assertNotContains(response, "/?page=4")
  291. # excessive page gives 404
  292. response = self.client.get("/?page=4")
  293. self.assertEqual(response.status_code, 404)
  294. class CategoryThreadsListTests(ThreadsListTestCase):
  295. def test_access_hidden_category(self):
  296. """hidden category returns 404"""
  297. Category(name="Hidden Category", slug="hidden-category").insert_at(
  298. self.root, position="last-child", save=True
  299. )
  300. test_category = Category.objects.get(slug="hidden-category")
  301. for url in LISTS_URLS:
  302. response = self.client.get(test_category.get_absolute_url() + url)
  303. self.assertEqual(response.status_code, 404)
  304. response = self.client.get(
  305. "%s?category=%s" % (self.api_link, test_category.id)
  306. )
  307. self.assertEqual(response.status_code, 404)
  308. def test_access_protected_category(self):
  309. """protected category returns 403"""
  310. Category(name="Hidden Category", slug="hidden-category").insert_at(
  311. self.root, position="last-child", save=True
  312. )
  313. test_category = Category.objects.get(slug="hidden-category")
  314. for url in LISTS_URLS:
  315. with patch_user_acl(
  316. {
  317. "visible_categories": [test_category.id],
  318. "browseable_categories": [],
  319. "categories": {test_category.id: {"can_see": 1, "can_browse": 0}},
  320. }
  321. ):
  322. response = self.client.get(test_category.get_absolute_url() + url)
  323. self.assertEqual(response.status_code, 403)
  324. response = self.client.get(
  325. "%s?category=%s&list=%s"
  326. % (self.api_link, test_category.id, url.strip("/"))
  327. )
  328. self.assertEqual(response.status_code, 403)
  329. def test_display_pinned_threads(self):
  330. """
  331. category threads list displays globally pinned threads first
  332. then locally ones and unpinned last
  333. """
  334. globally = test.post_thread(category=self.first_category, is_global=True)
  335. locally = test.post_thread(category=self.first_category, is_pinned=True)
  336. standard = test.post_thread(category=self.first_category)
  337. response = self.client.get(self.first_category.get_absolute_url())
  338. self.assertEqual(response.status_code, 200)
  339. content = smart_str(response.content)
  340. positions = {
  341. "g": content.find(globally.get_absolute_url()),
  342. "l": content.find(locally.get_absolute_url()),
  343. "s": content.find(standard.get_absolute_url()),
  344. }
  345. # global announcement before others
  346. self.assertTrue(positions["g"] < positions["l"])
  347. self.assertTrue(positions["g"] < positions["s"])
  348. # pinned in the middle
  349. self.assertTrue(positions["l"] < positions["s"])
  350. self.assertTrue(positions["l"] > positions["g"])
  351. # standard last
  352. self.assertTrue(positions["s"] > positions["g"])
  353. self.assertTrue(positions["s"] > positions["g"])
  354. # API behaviour is identic
  355. response = self.client.get("/api/threads/?category=%s" % self.first_category.id)
  356. self.assertEqual(response.status_code, 200)
  357. content = smart_str(response.content)
  358. positions = {
  359. "g": content.find(globally.get_absolute_url()),
  360. "l": content.find(locally.get_absolute_url()),
  361. "s": content.find(standard.get_absolute_url()),
  362. }
  363. # global announcement before others
  364. self.assertTrue(positions["g"] < positions["l"])
  365. self.assertTrue(positions["g"] < positions["s"])
  366. # pinned in the middle
  367. self.assertTrue(positions["l"] < positions["s"])
  368. self.assertTrue(positions["l"] > positions["g"])
  369. # standard last
  370. self.assertTrue(positions["s"] > positions["g"])
  371. self.assertTrue(positions["s"] > positions["g"])
  372. class ThreadsVisibilityTests(ThreadsListTestCase):
  373. @patch_categories_acl()
  374. def test_list_renders_test_thread(self):
  375. """list renders test thread with valid top category"""
  376. test_thread = test.post_thread(category=self.category_c)
  377. response = self.client.get("/")
  378. self.assertEqual(response.status_code, 200)
  379. self.assertContainsThread(response, test_thread)
  380. self.assertContains(response, "subcategory-%s" % self.category_a.css_class)
  381. self.assertContains(response, "subcategory-%s" % self.category_e.css_class)
  382. self.assertNotContains(
  383. response, "thread-detail-category-%s" % self.category_a.css_class
  384. )
  385. self.assertContains(
  386. response, "thread-detail-category-%s" % self.category_c.css_class
  387. )
  388. # api displays same data
  389. response = self.client.get(self.api_link)
  390. self.assertEqual(response.status_code, 200)
  391. response_json = response.json()
  392. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  393. self.assertEqual(len(response_json["subcategories"]), 3)
  394. self.assertIn(self.category_a.pk, response_json["subcategories"])
  395. # test category view
  396. response = self.client.get(self.category_b.get_absolute_url())
  397. self.assertEqual(response.status_code, 200)
  398. # thread displays
  399. self.assertContainsThread(response, test_thread)
  400. self.assertNotContains(
  401. response, "thread-detail-category-%s" % self.category_b.css_class
  402. )
  403. self.assertContains(
  404. response, "thread-detail-category-%s" % self.category_c.css_class
  405. )
  406. # api displays same data
  407. response = self.client.get(
  408. "%s?category=%s" % (self.api_link, self.category_b.pk)
  409. )
  410. self.assertEqual(response.status_code, 200)
  411. response_json = response.json()
  412. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  413. self.assertEqual(len(response_json["subcategories"]), 2)
  414. self.assertEqual(response_json["subcategories"][0], self.category_c.pk)
  415. def test_list_hides_hidden_thread(self):
  416. """list renders empty due to no permission to see thread"""
  417. Category(name="Hidden Category", slug="hidden-category").insert_at(
  418. self.root, position="last-child", save=True
  419. )
  420. test_category = Category.objects.get(slug="hidden-category")
  421. test_thread = test.post_thread(category=test_category)
  422. response = self.client.get("/")
  423. self.assertEqual(response.status_code, 200)
  424. self.assertContains(response, "empty-message")
  425. self.assertNotContainsThread(response, test_thread)
  426. def test_api_hides_hidden_thread(self):
  427. """api returns empty due to no permission to see thread"""
  428. Category(name="Hidden Category", slug="hidden-category").insert_at(
  429. self.root, position="last-child", save=True
  430. )
  431. test_category = Category.objects.get(slug="hidden-category")
  432. test.post_thread(category=test_category)
  433. response = self.client.get(self.api_link)
  434. self.assertEqual(response.status_code, 200)
  435. response_json = response.json()
  436. self.assertEqual(len(response_json["results"]), 0)
  437. @patch_categories_acl()
  438. def test_list_user_see_own_unapproved_thread(self):
  439. """list renders unapproved thread that belongs to viewer"""
  440. test_thread = test.post_thread(
  441. category=self.category_a, poster=self.user, is_unapproved=True
  442. )
  443. response = self.client.get("/")
  444. self.assertEqual(response.status_code, 200)
  445. self.assertContainsThread(response, test_thread)
  446. # test api
  447. response = self.client.get(self.api_link)
  448. self.assertEqual(response.status_code, 200)
  449. response_json = response.json()
  450. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  451. @patch_categories_acl()
  452. def test_list_user_cant_see_unapproved_thread(self):
  453. """list hides unapproved thread that belongs to other user"""
  454. test_thread = test.post_thread(category=self.category_a, is_unapproved=True)
  455. response = self.client.get("/")
  456. self.assertEqual(response.status_code, 200)
  457. self.assertNotContainsThread(response, test_thread)
  458. # test api
  459. response = self.client.get(self.api_link)
  460. self.assertEqual(response.status_code, 200)
  461. response_json = response.json()
  462. self.assertEqual(len(response_json["results"]), 0)
  463. @patch_categories_acl()
  464. def test_list_user_cant_see_hidden_thread(self):
  465. """list hides hidden thread that belongs to other user"""
  466. test_thread = test.post_thread(category=self.category_a, is_hidden=True)
  467. response = self.client.get("/")
  468. self.assertEqual(response.status_code, 200)
  469. self.assertNotContainsThread(response, test_thread)
  470. # test api
  471. response = self.client.get(self.api_link)
  472. self.assertEqual(response.status_code, 200)
  473. response_json = response.json()
  474. self.assertEqual(len(response_json["results"]), 0)
  475. @patch_categories_acl()
  476. def test_list_user_cant_see_own_hidden_thread(self):
  477. """list hides hidden thread that belongs to viewer"""
  478. test_thread = test.post_thread(
  479. category=self.category_a, poster=self.user, is_hidden=True
  480. )
  481. response = self.client.get("/")
  482. self.assertEqual(response.status_code, 200)
  483. self.assertNotContainsThread(response, test_thread)
  484. # test api
  485. response = self.client.get(self.api_link)
  486. self.assertEqual(response.status_code, 200)
  487. response_json = response.json()
  488. self.assertEqual(len(response_json["results"]), 0)
  489. @patch_categories_acl({"can_hide_threads": 1})
  490. def test_list_user_can_see_own_hidden_thread(self):
  491. """list shows hidden thread that belongs to viewer due to permission"""
  492. test_thread = test.post_thread(
  493. category=self.category_a, poster=self.user, is_hidden=True
  494. )
  495. response = self.client.get("/")
  496. self.assertEqual(response.status_code, 200)
  497. self.assertContainsThread(response, test_thread)
  498. # test api
  499. response = self.client.get(self.api_link)
  500. self.assertEqual(response.status_code, 200)
  501. response_json = response.json()
  502. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  503. @patch_categories_acl({"can_hide_threads": 1})
  504. def test_list_user_can_see_hidden_thread(self):
  505. """list shows hidden thread that belongs to other user due to permission"""
  506. test_thread = test.post_thread(category=self.category_a, is_hidden=True)
  507. response = self.client.get("/")
  508. self.assertEqual(response.status_code, 200)
  509. self.assertContainsThread(response, test_thread)
  510. # test api
  511. response = self.client.get(self.api_link)
  512. self.assertEqual(response.status_code, 200)
  513. response_json = response.json()
  514. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  515. @patch_categories_acl({"can_approve_content": 1})
  516. def test_list_user_can_see_unapproved_thread(self):
  517. """list shows hidden thread that belongs to other user due to permission"""
  518. test_thread = test.post_thread(category=self.category_a, is_unapproved=True)
  519. response = self.client.get("/")
  520. self.assertEqual(response.status_code, 200)
  521. self.assertContainsThread(response, test_thread)
  522. # test api
  523. response = self.client.get(self.api_link)
  524. self.assertEqual(response.status_code, 200)
  525. response_json = response.json()
  526. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  527. class MyThreadsListTests(ThreadsListTestCase):
  528. @patch_categories_acl()
  529. def test_list_renders_empty(self):
  530. """list renders empty"""
  531. response = self.client.get("/my/")
  532. self.assertEqual(response.status_code, 200)
  533. self.assertContains(response, "empty-message")
  534. response = self.client.get(self.category_a.get_absolute_url() + "my/")
  535. self.assertEqual(response.status_code, 200)
  536. self.assertContains(response, "empty-message")
  537. # test api
  538. response = self.client.get("%s?list=my" % self.api_link)
  539. self.assertEqual(response.status_code, 200)
  540. response_json = response.json()
  541. self.assertEqual(len(response_json["results"]), 0)
  542. response = self.client.get(
  543. "%s?list=my&category=%s" % (self.api_link, self.category_a.pk)
  544. )
  545. response_json = response.json()
  546. self.assertEqual(len(response_json["results"]), 0)
  547. @patch_categories_acl()
  548. def test_list_renders_test_thread(self):
  549. """list renders only threads posted by user"""
  550. test_thread = test.post_thread(category=self.category_a, poster=self.user)
  551. other_thread = test.post_thread(category=self.category_a)
  552. response = self.client.get("/my/")
  553. self.assertEqual(response.status_code, 200)
  554. self.assertContainsThread(response, test_thread)
  555. self.assertNotContainsThread(response, other_thread)
  556. response = self.client.get(self.category_a.get_absolute_url() + "my/")
  557. self.assertEqual(response.status_code, 200)
  558. self.assertContainsThread(response, test_thread)
  559. self.assertNotContainsThread(response, other_thread)
  560. # test api
  561. response = self.client.get("%s?list=my" % self.api_link)
  562. self.assertEqual(response.status_code, 200)
  563. response_json = response.json()
  564. self.assertEqual(len(response_json["results"]), 1)
  565. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  566. response = self.client.get(
  567. "%s?list=my&category=%s" % (self.api_link, self.category_a.pk)
  568. )
  569. self.assertEqual(response.status_code, 200)
  570. response_json = response.json()
  571. self.assertEqual(len(response_json["results"]), 1)
  572. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  573. class NewThreadsListTests(ThreadsListTestCase):
  574. @patch_categories_acl()
  575. def test_list_renders_empty(self):
  576. """list renders empty"""
  577. response = self.client.get("/new/")
  578. self.assertEqual(response.status_code, 200)
  579. self.assertContains(response, "empty-message")
  580. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  581. self.assertEqual(response.status_code, 200)
  582. self.assertContains(response, "empty-message")
  583. # test api
  584. response = self.client.get("%s?list=new" % self.api_link)
  585. self.assertEqual(response.status_code, 200)
  586. response_json = response.json()
  587. self.assertEqual(len(response_json["results"]), 0)
  588. response = self.client.get(
  589. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  590. )
  591. response_json = response.json()
  592. self.assertEqual(len(response_json["results"]), 0)
  593. @patch_categories_acl()
  594. def test_list_renders_new_thread(self):
  595. """list renders new thread"""
  596. test_thread = test.post_thread(category=self.category_a)
  597. response = self.client.get("/new/")
  598. self.assertEqual(response.status_code, 200)
  599. self.assertContainsThread(response, test_thread)
  600. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  601. self.assertEqual(response.status_code, 200)
  602. self.assertContainsThread(response, test_thread)
  603. # test api
  604. response = self.client.get("%s?list=new" % self.api_link)
  605. self.assertEqual(response.status_code, 200)
  606. response_json = response.json()
  607. self.assertEqual(len(response_json["results"]), 1)
  608. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  609. response = self.client.get(
  610. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  611. )
  612. self.assertEqual(response.status_code, 200)
  613. response_json = response.json()
  614. self.assertEqual(len(response_json["results"]), 1)
  615. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  616. @patch_categories_acl()
  617. def test_list_renders_thread_bumped_after_user_cutoff(self):
  618. """list renders new thread bumped after user cutoff"""
  619. self.user.joined_on = timezone.now() - timedelta(days=10)
  620. self.user.save()
  621. test_thread = test.post_thread(
  622. category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
  623. )
  624. test.reply_thread(
  625. test_thread, posted_on=self.user.joined_on + timedelta(days=4)
  626. )
  627. response = self.client.get("/new/")
  628. self.assertEqual(response.status_code, 200)
  629. self.assertContainsThread(response, test_thread)
  630. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  631. self.assertEqual(response.status_code, 200)
  632. self.assertContainsThread(response, test_thread)
  633. # test api
  634. response = self.client.get("%s?list=new" % self.api_link)
  635. self.assertEqual(response.status_code, 200)
  636. response_json = response.json()
  637. self.assertEqual(len(response_json["results"]), 1)
  638. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  639. response = self.client.get(
  640. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  641. )
  642. self.assertEqual(response.status_code, 200)
  643. response_json = response.json()
  644. self.assertEqual(len(response_json["results"]), 1)
  645. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  646. @patch_categories_acl()
  647. def test_list_hides_global_cutoff_thread(self):
  648. """list hides thread started before global cutoff"""
  649. self.user.joined_on = timezone.now() - timedelta(days=10)
  650. self.user.save()
  651. test_thread = test.post_thread(
  652. category=self.category_a,
  653. started_on=timezone.now()
  654. - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 1),
  655. )
  656. response = self.client.get("/new/")
  657. self.assertEqual(response.status_code, 200)
  658. self.assertNotContainsThread(response, test_thread)
  659. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  660. self.assertEqual(response.status_code, 200)
  661. self.assertNotContainsThread(response, test_thread)
  662. # test api
  663. response = self.client.get("%s?list=new" % self.api_link)
  664. self.assertEqual(response.status_code, 200)
  665. response_json = response.json()
  666. self.assertEqual(len(response_json["results"]), 0)
  667. response = self.client.get(
  668. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  669. )
  670. self.assertEqual(response.status_code, 200)
  671. response_json = response.json()
  672. self.assertEqual(len(response_json["results"]), 0)
  673. @patch_categories_acl()
  674. def test_list_hides_user_cutoff_thread(self):
  675. """list hides thread started before users cutoff"""
  676. self.user.joined_on = timezone.now() - timedelta(days=5)
  677. self.user.save()
  678. test_thread = test.post_thread(
  679. category=self.category_a,
  680. started_on=self.user.joined_on - timedelta(minutes=1),
  681. )
  682. response = self.client.get("/new/")
  683. self.assertEqual(response.status_code, 200)
  684. self.assertNotContainsThread(response, test_thread)
  685. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  686. self.assertEqual(response.status_code, 200)
  687. self.assertNotContainsThread(response, test_thread)
  688. # test api
  689. response = self.client.get("%s?list=new" % self.api_link)
  690. self.assertEqual(response.status_code, 200)
  691. response_json = response.json()
  692. self.assertEqual(len(response_json["results"]), 0)
  693. response = self.client.get(
  694. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  695. )
  696. self.assertEqual(response.status_code, 200)
  697. response_json = response.json()
  698. self.assertEqual(len(response_json["results"]), 0)
  699. @patch_categories_acl()
  700. def test_list_hides_user_read_thread(self):
  701. """list hides thread already read by user"""
  702. self.user.joined_on = timezone.now() - timedelta(days=5)
  703. self.user.save()
  704. test_thread = test.post_thread(category=self.category_a)
  705. poststracker.save_read(self.user, test_thread.first_post)
  706. response = self.client.get("/new/")
  707. self.assertEqual(response.status_code, 200)
  708. self.assertNotContainsThread(response, test_thread)
  709. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  710. self.assertEqual(response.status_code, 200)
  711. self.assertNotContainsThread(response, test_thread)
  712. # test api
  713. response = self.client.get("%s?list=new" % self.api_link)
  714. self.assertEqual(response.status_code, 200)
  715. response_json = response.json()
  716. self.assertEqual(len(response_json["results"]), 0)
  717. response = self.client.get(
  718. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  719. )
  720. self.assertEqual(response.status_code, 200)
  721. response_json = response.json()
  722. self.assertEqual(len(response_json["results"]), 0)
  723. class UnreadThreadsListTests(ThreadsListTestCase):
  724. @patch_categories_acl()
  725. def test_list_renders_empty(self):
  726. """list renders empty"""
  727. response = self.client.get("/unread/")
  728. self.assertEqual(response.status_code, 200)
  729. self.assertContains(response, "empty-message")
  730. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  731. self.assertEqual(response.status_code, 200)
  732. self.assertContains(response, "empty-message")
  733. # test api
  734. response = self.client.get("%s?list=unread" % self.api_link)
  735. self.assertEqual(response.status_code, 200)
  736. response_json = response.json()
  737. self.assertEqual(len(response_json["results"]), 0)
  738. response = self.client.get(
  739. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  740. )
  741. self.assertEqual(response.status_code, 200)
  742. response_json = response.json()
  743. self.assertEqual(len(response_json["results"]), 0)
  744. @patch_categories_acl()
  745. def test_list_renders_unread_thread(self):
  746. """list renders thread with unread posts"""
  747. self.user.joined_on = timezone.now() - timedelta(days=5)
  748. self.user.save()
  749. test_thread = test.post_thread(category=self.category_a)
  750. poststracker.save_read(self.user, test_thread.first_post)
  751. test.reply_thread(test_thread)
  752. response = self.client.get("/unread/")
  753. self.assertEqual(response.status_code, 200)
  754. self.assertContainsThread(response, test_thread)
  755. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  756. self.assertEqual(response.status_code, 200)
  757. self.assertContainsThread(response, test_thread)
  758. # test api
  759. response = self.client.get("%s?list=unread" % self.api_link)
  760. self.assertEqual(response.status_code, 200)
  761. response_json = response.json()
  762. self.assertEqual(len(response_json["results"]), 1)
  763. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  764. response = self.client.get(
  765. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  766. )
  767. self.assertEqual(response.status_code, 200)
  768. response_json = response.json()
  769. self.assertEqual(len(response_json["results"]), 1)
  770. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  771. @patch_categories_acl()
  772. def test_list_hides_never_read_thread(self):
  773. """list hides never read thread"""
  774. self.user.joined_on = timezone.now() - timedelta(days=5)
  775. self.user.save()
  776. test_thread = test.post_thread(category=self.category_a)
  777. response = self.client.get("/unread/")
  778. self.assertEqual(response.status_code, 200)
  779. self.assertNotContainsThread(response, test_thread)
  780. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  781. self.assertEqual(response.status_code, 200)
  782. self.assertNotContainsThread(response, test_thread)
  783. # test api
  784. response = self.client.get("%s?list=unread" % self.api_link)
  785. self.assertEqual(response.status_code, 200)
  786. response_json = response.json()
  787. self.assertEqual(len(response_json["results"]), 0)
  788. response = self.client.get(
  789. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  790. )
  791. self.assertEqual(response.status_code, 200)
  792. response_json = response.json()
  793. self.assertEqual(len(response_json["results"]), 0)
  794. @patch_categories_acl()
  795. def test_list_hides_read_thread(self):
  796. """list hides read thread"""
  797. self.user.joined_on = timezone.now() - timedelta(days=5)
  798. self.user.save()
  799. test_thread = test.post_thread(category=self.category_a)
  800. poststracker.save_read(self.user, test_thread.first_post)
  801. response = self.client.get("/unread/")
  802. self.assertEqual(response.status_code, 200)
  803. self.assertNotContainsThread(response, test_thread)
  804. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  805. self.assertEqual(response.status_code, 200)
  806. self.assertNotContainsThread(response, test_thread)
  807. # test api
  808. response = self.client.get("%s?list=unread" % self.api_link)
  809. self.assertEqual(response.status_code, 200)
  810. response_json = response.json()
  811. self.assertEqual(len(response_json["results"]), 0)
  812. response = self.client.get(
  813. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  814. )
  815. self.assertEqual(response.status_code, 200)
  816. response_json = response.json()
  817. self.assertEqual(len(response_json["results"]), 0)
  818. @patch_categories_acl()
  819. def test_list_hides_global_cutoff_thread(self):
  820. """list hides thread replied before global cutoff"""
  821. self.user.joined_on = timezone.now() - timedelta(days=10)
  822. self.user.save()
  823. test_thread = test.post_thread(
  824. category=self.category_a,
  825. started_on=timezone.now()
  826. - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 5),
  827. )
  828. poststracker.save_read(self.user, test_thread.first_post)
  829. test.reply_thread(
  830. test_thread, posted_on=test_thread.started_on + timedelta(days=1)
  831. )
  832. response = self.client.get("/unread/")
  833. self.assertEqual(response.status_code, 200)
  834. self.assertNotContainsThread(response, test_thread)
  835. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  836. self.assertEqual(response.status_code, 200)
  837. self.assertNotContainsThread(response, test_thread)
  838. # test api
  839. response = self.client.get("%s?list=unread" % self.api_link)
  840. self.assertEqual(response.status_code, 200)
  841. response_json = response.json()
  842. self.assertEqual(len(response_json["results"]), 0)
  843. response = self.client.get(
  844. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  845. )
  846. self.assertEqual(response.status_code, 200)
  847. response_json = response.json()
  848. self.assertEqual(len(response_json["results"]), 0)
  849. @patch_categories_acl()
  850. def test_list_hides_user_cutoff_thread(self):
  851. """list hides thread replied before user cutoff"""
  852. self.user.joined_on = timezone.now() - timedelta(days=10)
  853. self.user.save()
  854. test_thread = test.post_thread(
  855. category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
  856. )
  857. poststracker.save_read(self.user, test_thread.first_post)
  858. test.reply_thread(
  859. test_thread, posted_on=test_thread.started_on + timedelta(days=1)
  860. )
  861. response = self.client.get("/unread/")
  862. self.assertEqual(response.status_code, 200)
  863. self.assertNotContainsThread(response, test_thread)
  864. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  865. self.assertEqual(response.status_code, 200)
  866. self.assertNotContainsThread(response, test_thread)
  867. # test api
  868. response = self.client.get("%s?list=unread" % self.api_link)
  869. self.assertEqual(response.status_code, 200)
  870. response_json = response.json()
  871. self.assertEqual(len(response_json["results"]), 0)
  872. response = self.client.get(
  873. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  874. )
  875. self.assertEqual(response.status_code, 200)
  876. response_json = response.json()
  877. self.assertEqual(len(response_json["results"]), 0)
  878. class SubscribedThreadsListTests(ThreadsListTestCase):
  879. @patch_categories_acl()
  880. def test_list_shows_subscribed_thread(self):
  881. """list shows subscribed thread"""
  882. test_thread = test.post_thread(category=self.category_a)
  883. self.user.subscription_set.create(
  884. thread=test_thread,
  885. category=self.category_a,
  886. last_read_on=test_thread.last_post_on,
  887. )
  888. response = self.client.get("/subscribed/")
  889. self.assertEqual(response.status_code, 200)
  890. self.assertContainsThread(response, test_thread)
  891. response = self.client.get(self.category_a.get_absolute_url() + "subscribed/")
  892. self.assertEqual(response.status_code, 200)
  893. self.assertContainsThread(response, test_thread)
  894. # test api
  895. response = self.client.get("%s?list=subscribed" % self.api_link)
  896. self.assertEqual(response.status_code, 200)
  897. response_json = response.json()
  898. self.assertEqual(len(response_json["results"]), 1)
  899. self.assertContains(response, test_thread.get_absolute_url())
  900. response = self.client.get(
  901. "%s?list=subscribed&category=%s" % (self.api_link, self.category_a.pk)
  902. )
  903. self.assertEqual(response.status_code, 200)
  904. response_json = response.json()
  905. self.assertEqual(len(response_json["results"]), 1)
  906. self.assertContains(response, test_thread.get_absolute_url())
  907. @patch_categories_acl()
  908. def test_list_hides_unsubscribed_thread(self):
  909. """list shows subscribed thread"""
  910. test_thread = test.post_thread(category=self.category_a)
  911. response = self.client.get("/subscribed/")
  912. self.assertEqual(response.status_code, 200)
  913. self.assertNotContainsThread(response, test_thread)
  914. response = self.client.get(self.category_a.get_absolute_url() + "subscribed/")
  915. self.assertEqual(response.status_code, 200)
  916. self.assertNotContainsThread(response, test_thread)
  917. # test api
  918. response = self.client.get("%s?list=subscribed" % self.api_link)
  919. self.assertEqual(response.status_code, 200)
  920. response_json = response.json()
  921. self.assertEqual(len(response_json["results"]), 0)
  922. self.assertNotContainsThread(response, test_thread)
  923. response = self.client.get(
  924. "%s?list=subscribed&category=%s" % (self.api_link, self.category_a.pk)
  925. )
  926. self.assertEqual(response.status_code, 200)
  927. response_json = response.json()
  928. self.assertEqual(len(response_json["results"]), 0)
  929. self.assertNotContainsThread(response, test_thread)
  930. class UnapprovedListTests(ThreadsListTestCase):
  931. def test_list_errors_without_permission(self):
  932. """list errors if user has no permission to access it"""
  933. TEST_URLS = (
  934. "/unapproved/",
  935. self.category_a.get_absolute_url() + "unapproved/",
  936. "%s?list=unapproved" % self.api_link,
  937. )
  938. with patch_categories_acl():
  939. for test_url in TEST_URLS:
  940. response = self.client.get(test_url)
  941. self.assertEqual(response.status_code, 403)
  942. # approval perm has no influence on visibility
  943. with patch_categories_acl({"can_approve_content": True}):
  944. for test_url in TEST_URLS:
  945. response = self.client.get(test_url)
  946. self.assertEqual(response.status_code, 403)
  947. # approval perm has no influence on visibility
  948. with patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True}):
  949. for test_url in TEST_URLS:
  950. response = self.client.get(test_url)
  951. self.assertEqual(response.status_code, 200)
  952. @patch_categories_acl(
  953. {"can_approve_content": True}, {"can_see_unapproved_content_lists": True}
  954. )
  955. def test_list_shows_all_threads_for_approving_user(self):
  956. """list shows all threads with unapproved posts when user has perm"""
  957. visible_thread = test.post_thread(category=self.category_b, is_unapproved=True)
  958. hidden_thread = test.post_thread(category=self.category_b, is_unapproved=False)
  959. response = self.client.get("/unapproved/")
  960. self.assertEqual(response.status_code, 200)
  961. self.assertContainsThread(response, visible_thread)
  962. self.assertNotContainsThread(response, hidden_thread)
  963. response = self.client.get(self.category_a.get_absolute_url() + "unapproved/")
  964. self.assertEqual(response.status_code, 200)
  965. self.assertContainsThread(response, visible_thread)
  966. self.assertNotContainsThread(response, hidden_thread)
  967. # test api
  968. response = self.client.get("%s?list=unapproved" % self.api_link)
  969. self.assertEqual(response.status_code, 200)
  970. self.assertContains(response, visible_thread.get_absolute_url())
  971. self.assertNotContains(response, hidden_thread.get_absolute_url())
  972. @patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True})
  973. def test_list_shows_owned_threads_for_unapproving_user(self):
  974. """list shows owned threads with unapproved posts for user without perm"""
  975. visible_thread = test.post_thread(
  976. poster=self.user, category=self.category_b, is_unapproved=True
  977. )
  978. hidden_thread = test.post_thread(category=self.category_b, is_unapproved=True)
  979. response = self.client.get("/unapproved/")
  980. self.assertEqual(response.status_code, 200)
  981. self.assertContainsThread(response, visible_thread)
  982. self.assertNotContainsThread(response, hidden_thread)
  983. response = self.client.get(self.category_a.get_absolute_url() + "unapproved/")
  984. self.assertEqual(response.status_code, 200)
  985. self.assertContainsThread(response, visible_thread)
  986. self.assertNotContainsThread(response, hidden_thread)
  987. # test api
  988. response = self.client.get("%s?list=unapproved" % self.api_link)
  989. self.assertEqual(response.status_code, 200)
  990. self.assertContains(response, visible_thread.get_absolute_url())
  991. self.assertNotContains(response, hidden_thread.get_absolute_url())
  992. def patch_category_see_all_threads_acl():
  993. def patch_acl(_, user_acl):
  994. category = Category.objects.get(slug="first-category")
  995. category_acl = user_acl["categories"][category.id].copy()
  996. category_acl.update({"can_see_all_threads": 0})
  997. user_acl["categories"][category.id] = category_acl
  998. return patch_user_acl(patch_acl)
  999. class OwnerOnlyThreadsVisibilityTests(AuthenticatedUserTestCase):
  1000. def setUp(self):
  1001. super().setUp()
  1002. self.category = Category.objects.get(slug="first-category")
  1003. def test_owned_threads_visibility(self):
  1004. """only user-posted threads are visible in category"""
  1005. visible_thread = test.post_thread(
  1006. poster=self.user, category=self.category, is_unapproved=True
  1007. )
  1008. hidden_thread = test.post_thread(category=self.category, is_unapproved=True)
  1009. with patch_category_see_all_threads_acl():
  1010. response = self.client.get(self.category.get_absolute_url())
  1011. self.assertEqual(response.status_code, 200)
  1012. self.assertContains(response, visible_thread.get_absolute_url())
  1013. self.assertNotContains(response, hidden_thread.get_absolute_url())
  1014. def test_owned_threads_visibility_anonymous(self):
  1015. """anons can't see any threads in limited visibility category"""
  1016. self.logout_user()
  1017. user_thread = test.post_thread(
  1018. poster=self.user, category=self.category, is_unapproved=True
  1019. )
  1020. guest_thread = test.post_thread(category=self.category, is_unapproved=True)
  1021. with patch_category_see_all_threads_acl():
  1022. response = self.client.get(self.category.get_absolute_url())
  1023. self.assertEqual(response.status_code, 200)
  1024. self.assertNotContains(response, user_thread.get_absolute_url())
  1025. self.assertNotContains(response, guest_thread.get_absolute_url())