test_threadslists.py 51 KB

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