test_threadslists.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347
  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 misago.acl.test import patch_user_acl
  6. from misago.categories.models import Category
  7. from misago.conf import settings
  8. from misago.readtracker import poststracker
  9. from misago.threads import test
  10. from misago.users.models import AnonymousUser
  11. from misago.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(
  456. category=self.category_a, is_unapproved=True
  457. )
  458. response = self.client.get("/")
  459. self.assertEqual(response.status_code, 200)
  460. self.assertNotContainsThread(response, test_thread)
  461. # test api
  462. response = self.client.get(self.api_link)
  463. self.assertEqual(response.status_code, 200)
  464. response_json = response.json()
  465. self.assertEqual(len(response_json["results"]), 0)
  466. @patch_categories_acl()
  467. def test_list_user_cant_see_hidden_thread(self):
  468. """list hides hidden thread that belongs to other user"""
  469. test_thread = test.post_thread(category=self.category_a, is_hidden=True)
  470. response = self.client.get("/")
  471. self.assertEqual(response.status_code, 200)
  472. self.assertNotContainsThread(response, test_thread)
  473. # test api
  474. response = self.client.get(self.api_link)
  475. self.assertEqual(response.status_code, 200)
  476. response_json = response.json()
  477. self.assertEqual(len(response_json["results"]), 0)
  478. @patch_categories_acl()
  479. def test_list_user_cant_see_own_hidden_thread(self):
  480. """list hides hidden thread that belongs to viewer"""
  481. test_thread = test.post_thread(
  482. category=self.category_a, poster=self.user, is_hidden=True
  483. )
  484. response = self.client.get("/")
  485. self.assertEqual(response.status_code, 200)
  486. self.assertNotContainsThread(response, test_thread)
  487. # test api
  488. response = self.client.get(self.api_link)
  489. self.assertEqual(response.status_code, 200)
  490. response_json = response.json()
  491. self.assertEqual(len(response_json["results"]), 0)
  492. @patch_categories_acl({"can_hide_threads": 1})
  493. def test_list_user_can_see_own_hidden_thread(self):
  494. """list shows hidden thread that belongs to viewer due to permission"""
  495. test_thread = test.post_thread(
  496. category=self.category_a, poster=self.user, is_hidden=True
  497. )
  498. response = self.client.get("/")
  499. self.assertEqual(response.status_code, 200)
  500. self.assertContainsThread(response, test_thread)
  501. # test api
  502. response = self.client.get(self.api_link)
  503. self.assertEqual(response.status_code, 200)
  504. response_json = response.json()
  505. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  506. @patch_categories_acl({"can_hide_threads": 1})
  507. def test_list_user_can_see_hidden_thread(self):
  508. """list shows hidden thread that belongs to other user due to permission"""
  509. test_thread = test.post_thread(category=self.category_a, is_hidden=True)
  510. response = self.client.get("/")
  511. self.assertEqual(response.status_code, 200)
  512. self.assertContainsThread(response, test_thread)
  513. # test api
  514. response = self.client.get(self.api_link)
  515. self.assertEqual(response.status_code, 200)
  516. response_json = response.json()
  517. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  518. @patch_categories_acl({"can_approve_content": 1})
  519. def test_list_user_can_see_unapproved_thread(self):
  520. """list shows hidden thread that belongs to other user due to permission"""
  521. test_thread = test.post_thread(
  522. category=self.category_a, is_unapproved=True
  523. )
  524. response = self.client.get("/")
  525. self.assertEqual(response.status_code, 200)
  526. self.assertContainsThread(response, test_thread)
  527. # test api
  528. response = self.client.get(self.api_link)
  529. self.assertEqual(response.status_code, 200)
  530. response_json = response.json()
  531. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  532. class MyThreadsListTests(ThreadsListTestCase):
  533. @patch_categories_acl()
  534. def test_list_renders_empty(self):
  535. """list renders empty"""
  536. response = self.client.get("/my/")
  537. self.assertEqual(response.status_code, 200)
  538. self.assertContains(response, "empty-message")
  539. response = self.client.get(self.category_a.get_absolute_url() + "my/")
  540. self.assertEqual(response.status_code, 200)
  541. self.assertContains(response, "empty-message")
  542. # test api
  543. response = self.client.get("%s?list=my" % self.api_link)
  544. self.assertEqual(response.status_code, 200)
  545. response_json = response.json()
  546. self.assertEqual(len(response_json["results"]), 0)
  547. response = self.client.get(
  548. "%s?list=my&category=%s" % (self.api_link, self.category_a.pk)
  549. )
  550. response_json = response.json()
  551. self.assertEqual(len(response_json["results"]), 0)
  552. @patch_categories_acl()
  553. def test_list_renders_test_thread(self):
  554. """list renders only threads posted by user"""
  555. test_thread = test.post_thread(category=self.category_a, poster=self.user)
  556. other_thread = test.post_thread(category=self.category_a)
  557. response = self.client.get("/my/")
  558. self.assertEqual(response.status_code, 200)
  559. self.assertContainsThread(response, test_thread)
  560. self.assertNotContainsThread(response, other_thread)
  561. response = self.client.get(self.category_a.get_absolute_url() + "my/")
  562. self.assertEqual(response.status_code, 200)
  563. self.assertContainsThread(response, test_thread)
  564. self.assertNotContainsThread(response, other_thread)
  565. # test api
  566. response = self.client.get("%s?list=my" % self.api_link)
  567. self.assertEqual(response.status_code, 200)
  568. response_json = response.json()
  569. self.assertEqual(len(response_json["results"]), 1)
  570. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  571. response = self.client.get(
  572. "%s?list=my&category=%s" % (self.api_link, self.category_a.pk)
  573. )
  574. self.assertEqual(response.status_code, 200)
  575. response_json = response.json()
  576. self.assertEqual(len(response_json["results"]), 1)
  577. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  578. class NewThreadsListTests(ThreadsListTestCase):
  579. @patch_categories_acl()
  580. def test_list_renders_empty(self):
  581. """list renders empty"""
  582. response = self.client.get("/new/")
  583. self.assertEqual(response.status_code, 200)
  584. self.assertContains(response, "empty-message")
  585. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  586. self.assertEqual(response.status_code, 200)
  587. self.assertContains(response, "empty-message")
  588. # test api
  589. response = self.client.get("%s?list=new" % self.api_link)
  590. self.assertEqual(response.status_code, 200)
  591. response_json = response.json()
  592. self.assertEqual(len(response_json["results"]), 0)
  593. response = self.client.get(
  594. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  595. )
  596. response_json = response.json()
  597. self.assertEqual(len(response_json["results"]), 0)
  598. @patch_categories_acl()
  599. def test_list_renders_new_thread(self):
  600. """list renders new thread"""
  601. test_thread = test.post_thread(category=self.category_a)
  602. response = self.client.get("/new/")
  603. self.assertEqual(response.status_code, 200)
  604. self.assertContainsThread(response, test_thread)
  605. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  606. self.assertEqual(response.status_code, 200)
  607. self.assertContainsThread(response, test_thread)
  608. # test api
  609. response = self.client.get("%s?list=new" % self.api_link)
  610. self.assertEqual(response.status_code, 200)
  611. response_json = response.json()
  612. self.assertEqual(len(response_json["results"]), 1)
  613. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  614. response = self.client.get(
  615. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  616. )
  617. self.assertEqual(response.status_code, 200)
  618. response_json = response.json()
  619. self.assertEqual(len(response_json["results"]), 1)
  620. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  621. @patch_categories_acl()
  622. def test_list_renders_thread_bumped_after_user_cutoff(self):
  623. """list renders new thread bumped after user cutoff"""
  624. self.user.joined_on = timezone.now() - timedelta(days=10)
  625. self.user.save()
  626. test_thread = test.post_thread(
  627. category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
  628. )
  629. test.reply_thread(
  630. test_thread, posted_on=self.user.joined_on + timedelta(days=4)
  631. )
  632. response = self.client.get("/new/")
  633. self.assertEqual(response.status_code, 200)
  634. self.assertContainsThread(response, test_thread)
  635. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  636. self.assertEqual(response.status_code, 200)
  637. self.assertContainsThread(response, test_thread)
  638. # test api
  639. response = self.client.get("%s?list=new" % self.api_link)
  640. self.assertEqual(response.status_code, 200)
  641. response_json = response.json()
  642. self.assertEqual(len(response_json["results"]), 1)
  643. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  644. response = self.client.get(
  645. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  646. )
  647. self.assertEqual(response.status_code, 200)
  648. response_json = response.json()
  649. self.assertEqual(len(response_json["results"]), 1)
  650. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  651. @patch_categories_acl()
  652. def test_list_hides_global_cutoff_thread(self):
  653. """list hides thread started before global cutoff"""
  654. self.user.joined_on = timezone.now() - timedelta(days=10)
  655. self.user.save()
  656. test_thread = test.post_thread(
  657. category=self.category_a,
  658. started_on=timezone.now()
  659. - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 1),
  660. )
  661. response = self.client.get("/new/")
  662. self.assertEqual(response.status_code, 200)
  663. self.assertNotContainsThread(response, test_thread)
  664. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  665. self.assertEqual(response.status_code, 200)
  666. self.assertNotContainsThread(response, test_thread)
  667. # test api
  668. response = self.client.get("%s?list=new" % self.api_link)
  669. self.assertEqual(response.status_code, 200)
  670. response_json = response.json()
  671. self.assertEqual(len(response_json["results"]), 0)
  672. response = self.client.get(
  673. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  674. )
  675. self.assertEqual(response.status_code, 200)
  676. response_json = response.json()
  677. self.assertEqual(len(response_json["results"]), 0)
  678. @patch_categories_acl()
  679. def test_list_hides_user_cutoff_thread(self):
  680. """list hides thread started before users cutoff"""
  681. self.user.joined_on = timezone.now() - timedelta(days=5)
  682. self.user.save()
  683. test_thread = test.post_thread(
  684. category=self.category_a,
  685. started_on=self.user.joined_on - timedelta(minutes=1),
  686. )
  687. response = self.client.get("/new/")
  688. self.assertEqual(response.status_code, 200)
  689. self.assertNotContainsThread(response, test_thread)
  690. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  691. self.assertEqual(response.status_code, 200)
  692. self.assertNotContainsThread(response, test_thread)
  693. # test api
  694. response = self.client.get("%s?list=new" % self.api_link)
  695. self.assertEqual(response.status_code, 200)
  696. response_json = response.json()
  697. self.assertEqual(len(response_json["results"]), 0)
  698. response = self.client.get(
  699. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  700. )
  701. self.assertEqual(response.status_code, 200)
  702. response_json = response.json()
  703. self.assertEqual(len(response_json["results"]), 0)
  704. @patch_categories_acl()
  705. def test_list_hides_user_read_thread(self):
  706. """list hides thread already read by user"""
  707. self.user.joined_on = timezone.now() - timedelta(days=5)
  708. self.user.save()
  709. test_thread = test.post_thread(category=self.category_a)
  710. poststracker.save_read(self.user, test_thread.first_post)
  711. response = self.client.get("/new/")
  712. self.assertEqual(response.status_code, 200)
  713. self.assertNotContainsThread(response, test_thread)
  714. response = self.client.get(self.category_a.get_absolute_url() + "new/")
  715. self.assertEqual(response.status_code, 200)
  716. self.assertNotContainsThread(response, test_thread)
  717. # test api
  718. response = self.client.get("%s?list=new" % self.api_link)
  719. self.assertEqual(response.status_code, 200)
  720. response_json = response.json()
  721. self.assertEqual(len(response_json["results"]), 0)
  722. response = self.client.get(
  723. "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
  724. )
  725. self.assertEqual(response.status_code, 200)
  726. response_json = response.json()
  727. self.assertEqual(len(response_json["results"]), 0)
  728. class UnreadThreadsListTests(ThreadsListTestCase):
  729. @patch_categories_acl()
  730. def test_list_renders_empty(self):
  731. """list renders empty"""
  732. response = self.client.get("/unread/")
  733. self.assertEqual(response.status_code, 200)
  734. self.assertContains(response, "empty-message")
  735. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  736. self.assertEqual(response.status_code, 200)
  737. self.assertContains(response, "empty-message")
  738. # test api
  739. response = self.client.get("%s?list=unread" % self.api_link)
  740. self.assertEqual(response.status_code, 200)
  741. response_json = response.json()
  742. self.assertEqual(len(response_json["results"]), 0)
  743. response = self.client.get(
  744. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  745. )
  746. self.assertEqual(response.status_code, 200)
  747. response_json = response.json()
  748. self.assertEqual(len(response_json["results"]), 0)
  749. @patch_categories_acl()
  750. def test_list_renders_unread_thread(self):
  751. """list renders thread with unread posts"""
  752. self.user.joined_on = timezone.now() - timedelta(days=5)
  753. self.user.save()
  754. test_thread = test.post_thread(category=self.category_a)
  755. poststracker.save_read(self.user, test_thread.first_post)
  756. test.reply_thread(test_thread)
  757. response = self.client.get("/unread/")
  758. self.assertEqual(response.status_code, 200)
  759. self.assertContainsThread(response, test_thread)
  760. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  761. self.assertEqual(response.status_code, 200)
  762. self.assertContainsThread(response, test_thread)
  763. # test api
  764. response = self.client.get("%s?list=unread" % self.api_link)
  765. self.assertEqual(response.status_code, 200)
  766. response_json = response.json()
  767. self.assertEqual(len(response_json["results"]), 1)
  768. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  769. response = self.client.get(
  770. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  771. )
  772. self.assertEqual(response.status_code, 200)
  773. response_json = response.json()
  774. self.assertEqual(len(response_json["results"]), 1)
  775. self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
  776. @patch_categories_acl()
  777. def test_list_hides_never_read_thread(self):
  778. """list hides never read thread"""
  779. self.user.joined_on = timezone.now() - timedelta(days=5)
  780. self.user.save()
  781. test_thread = test.post_thread(category=self.category_a)
  782. response = self.client.get("/unread/")
  783. self.assertEqual(response.status_code, 200)
  784. self.assertNotContainsThread(response, test_thread)
  785. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  786. self.assertEqual(response.status_code, 200)
  787. self.assertNotContainsThread(response, test_thread)
  788. # test api
  789. response = self.client.get("%s?list=unread" % self.api_link)
  790. self.assertEqual(response.status_code, 200)
  791. response_json = response.json()
  792. self.assertEqual(len(response_json["results"]), 0)
  793. response = self.client.get(
  794. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  795. )
  796. self.assertEqual(response.status_code, 200)
  797. response_json = response.json()
  798. self.assertEqual(len(response_json["results"]), 0)
  799. @patch_categories_acl()
  800. def test_list_hides_read_thread(self):
  801. """list hides read thread"""
  802. self.user.joined_on = timezone.now() - timedelta(days=5)
  803. self.user.save()
  804. test_thread = test.post_thread(category=self.category_a)
  805. poststracker.save_read(self.user, test_thread.first_post)
  806. response = self.client.get("/unread/")
  807. self.assertEqual(response.status_code, 200)
  808. self.assertNotContainsThread(response, test_thread)
  809. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  810. self.assertEqual(response.status_code, 200)
  811. self.assertNotContainsThread(response, test_thread)
  812. # test api
  813. response = self.client.get("%s?list=unread" % self.api_link)
  814. self.assertEqual(response.status_code, 200)
  815. response_json = response.json()
  816. self.assertEqual(len(response_json["results"]), 0)
  817. response = self.client.get(
  818. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  819. )
  820. self.assertEqual(response.status_code, 200)
  821. response_json = response.json()
  822. self.assertEqual(len(response_json["results"]), 0)
  823. @patch_categories_acl()
  824. def test_list_hides_global_cutoff_thread(self):
  825. """list hides thread replied before global cutoff"""
  826. self.user.joined_on = timezone.now() - timedelta(days=10)
  827. self.user.save()
  828. test_thread = test.post_thread(
  829. category=self.category_a,
  830. started_on=timezone.now()
  831. - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 5),
  832. )
  833. poststracker.save_read(self.user, test_thread.first_post)
  834. test.reply_thread(
  835. test_thread, posted_on=test_thread.started_on + timedelta(days=1)
  836. )
  837. response = self.client.get("/unread/")
  838. self.assertEqual(response.status_code, 200)
  839. self.assertNotContainsThread(response, test_thread)
  840. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  841. self.assertEqual(response.status_code, 200)
  842. self.assertNotContainsThread(response, test_thread)
  843. # test api
  844. response = self.client.get("%s?list=unread" % self.api_link)
  845. self.assertEqual(response.status_code, 200)
  846. response_json = response.json()
  847. self.assertEqual(len(response_json["results"]), 0)
  848. response = self.client.get(
  849. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  850. )
  851. self.assertEqual(response.status_code, 200)
  852. response_json = response.json()
  853. self.assertEqual(len(response_json["results"]), 0)
  854. @patch_categories_acl()
  855. def test_list_hides_user_cutoff_thread(self):
  856. """list hides thread replied before user cutoff"""
  857. self.user.joined_on = timezone.now() - timedelta(days=10)
  858. self.user.save()
  859. test_thread = test.post_thread(
  860. category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
  861. )
  862. poststracker.save_read(self.user, test_thread.first_post)
  863. test.reply_thread(
  864. test_thread, posted_on=test_thread.started_on + timedelta(days=1)
  865. )
  866. response = self.client.get("/unread/")
  867. self.assertEqual(response.status_code, 200)
  868. self.assertNotContainsThread(response, test_thread)
  869. response = self.client.get(self.category_a.get_absolute_url() + "unread/")
  870. self.assertEqual(response.status_code, 200)
  871. self.assertNotContainsThread(response, test_thread)
  872. # test api
  873. response = self.client.get("%s?list=unread" % self.api_link)
  874. self.assertEqual(response.status_code, 200)
  875. response_json = response.json()
  876. self.assertEqual(len(response_json["results"]), 0)
  877. response = self.client.get(
  878. "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
  879. )
  880. self.assertEqual(response.status_code, 200)
  881. response_json = response.json()
  882. self.assertEqual(len(response_json["results"]), 0)
  883. class SubscribedThreadsListTests(ThreadsListTestCase):
  884. @patch_categories_acl()
  885. def test_list_shows_subscribed_thread(self):
  886. """list shows subscribed thread"""
  887. test_thread = test.post_thread(category=self.category_a)
  888. self.user.subscription_set.create(
  889. thread=test_thread,
  890. category=self.category_a,
  891. last_read_on=test_thread.last_post_on,
  892. )
  893. response = self.client.get("/subscribed/")
  894. self.assertEqual(response.status_code, 200)
  895. self.assertContainsThread(response, test_thread)
  896. response = self.client.get(self.category_a.get_absolute_url() + "subscribed/")
  897. self.assertEqual(response.status_code, 200)
  898. self.assertContainsThread(response, test_thread)
  899. # test api
  900. response = self.client.get("%s?list=subscribed" % self.api_link)
  901. self.assertEqual(response.status_code, 200)
  902. response_json = response.json()
  903. self.assertEqual(len(response_json["results"]), 1)
  904. self.assertContains(response, test_thread.get_absolute_url())
  905. response = self.client.get(
  906. "%s?list=subscribed&category=%s" % (self.api_link, self.category_a.pk)
  907. )
  908. self.assertEqual(response.status_code, 200)
  909. response_json = response.json()
  910. self.assertEqual(len(response_json["results"]), 1)
  911. self.assertContains(response, test_thread.get_absolute_url())
  912. @patch_categories_acl()
  913. def test_list_hides_unsubscribed_thread(self):
  914. """list shows subscribed thread"""
  915. test_thread = test.post_thread(category=self.category_a)
  916. response = self.client.get("/subscribed/")
  917. self.assertEqual(response.status_code, 200)
  918. self.assertNotContainsThread(response, test_thread)
  919. response = self.client.get(self.category_a.get_absolute_url() + "subscribed/")
  920. self.assertEqual(response.status_code, 200)
  921. self.assertNotContainsThread(response, test_thread)
  922. # test api
  923. response = self.client.get("%s?list=subscribed" % self.api_link)
  924. self.assertEqual(response.status_code, 200)
  925. response_json = response.json()
  926. self.assertEqual(len(response_json["results"]), 0)
  927. self.assertNotContainsThread(response, test_thread)
  928. response = self.client.get(
  929. "%s?list=subscribed&category=%s" % (self.api_link, self.category_a.pk)
  930. )
  931. self.assertEqual(response.status_code, 200)
  932. response_json = response.json()
  933. self.assertEqual(len(response_json["results"]), 0)
  934. self.assertNotContainsThread(response, test_thread)
  935. class UnapprovedListTests(ThreadsListTestCase):
  936. def test_list_errors_without_permission(self):
  937. """list errors if user has no permission to access it"""
  938. TEST_URLS = (
  939. "/unapproved/",
  940. self.category_a.get_absolute_url() + "unapproved/",
  941. "%s?list=unapproved" % self.api_link,
  942. )
  943. with patch_categories_acl():
  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({"can_approve_content": True}):
  949. for test_url in TEST_URLS:
  950. response = self.client.get(test_url)
  951. self.assertEqual(response.status_code, 403)
  952. # approval perm has no influence on visibility
  953. with patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True}):
  954. for test_url in TEST_URLS:
  955. response = self.client.get(test_url)
  956. self.assertEqual(response.status_code, 200)
  957. @patch_categories_acl(
  958. {"can_approve_content": True}, {"can_see_unapproved_content_lists": True}
  959. )
  960. def test_list_shows_all_threads_for_approving_user(self):
  961. """list shows all threads with unapproved posts when user has perm"""
  962. visible_thread = test.post_thread(
  963. category=self.category_b, is_unapproved=True
  964. )
  965. hidden_thread = test.post_thread(
  966. category=self.category_b, is_unapproved=False
  967. )
  968. response = self.client.get("/unapproved/")
  969. self.assertEqual(response.status_code, 200)
  970. self.assertContainsThread(response, visible_thread)
  971. self.assertNotContainsThread(response, hidden_thread)
  972. response = self.client.get(self.category_a.get_absolute_url() + "unapproved/")
  973. self.assertEqual(response.status_code, 200)
  974. self.assertContainsThread(response, visible_thread)
  975. self.assertNotContainsThread(response, hidden_thread)
  976. # test api
  977. response = self.client.get("%s?list=unapproved" % self.api_link)
  978. self.assertEqual(response.status_code, 200)
  979. self.assertContains(response, visible_thread.get_absolute_url())
  980. self.assertNotContains(response, hidden_thread.get_absolute_url())
  981. @patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True})
  982. def test_list_shows_owned_threads_for_unapproving_user(self):
  983. """list shows owned threads with unapproved posts for user without perm"""
  984. visible_thread = test.post_thread(
  985. poster=self.user, category=self.category_b, is_unapproved=True
  986. )
  987. hidden_thread = test.post_thread(
  988. category=self.category_b, is_unapproved=True
  989. )
  990. response = self.client.get("/unapproved/")
  991. self.assertEqual(response.status_code, 200)
  992. self.assertContainsThread(response, visible_thread)
  993. self.assertNotContainsThread(response, hidden_thread)
  994. response = self.client.get(self.category_a.get_absolute_url() + "unapproved/")
  995. self.assertEqual(response.status_code, 200)
  996. self.assertContainsThread(response, visible_thread)
  997. self.assertNotContainsThread(response, hidden_thread)
  998. # test api
  999. response = self.client.get("%s?list=unapproved" % self.api_link)
  1000. self.assertEqual(response.status_code, 200)
  1001. self.assertContains(response, visible_thread.get_absolute_url())
  1002. self.assertNotContains(response, hidden_thread.get_absolute_url())
  1003. def patch_category_see_all_threads_acl():
  1004. def patch_acl(_, user_acl):
  1005. category = Category.objects.get(slug="first-category")
  1006. category_acl = user_acl["categories"][category.id].copy()
  1007. category_acl.update({"can_see_all_threads": 0})
  1008. user_acl["categories"][category.id] = category_acl
  1009. return patch_user_acl(patch_acl)
  1010. class OwnerOnlyThreadsVisibilityTests(AuthenticatedUserTestCase):
  1011. def setUp(self):
  1012. super().setUp()
  1013. self.category = Category.objects.get(slug="first-category")
  1014. def test_owned_threads_visibility(self):
  1015. """only user-posted threads are visible in category"""
  1016. visible_thread = test.post_thread(
  1017. poster=self.user, category=self.category, is_unapproved=True
  1018. )
  1019. hidden_thread = test.post_thread(
  1020. category=self.category, is_unapproved=True
  1021. )
  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.assertContains(response, visible_thread.get_absolute_url())
  1026. self.assertNotContains(response, hidden_thread.get_absolute_url())
  1027. def test_owned_threads_visibility_anonymous(self):
  1028. """anons can't see any threads in limited visibility category"""
  1029. self.logout_user()
  1030. user_thread = test.post_thread(
  1031. poster=self.user, category=self.category, is_unapproved=True
  1032. )
  1033. guest_thread = test.post_thread(category=self.category, is_unapproved=True)
  1034. with patch_category_see_all_threads_acl():
  1035. response = self.client.get(self.category.get_absolute_url())
  1036. self.assertEqual(response.status_code, 200)
  1037. self.assertNotContains(response, user_thread.get_absolute_url())
  1038. self.assertNotContains(response, guest_thread.get_absolute_url())