1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309 |
- from datetime import timedelta
- from django.urls import reverse
- from django.utils import timezone
- from django.utils.encoding import smart_str
- from .. import test
- from ...acl.test import patch_user_acl
- from ...categories.models import Category
- from ...conf import settings
- from ...conf.test import override_dynamic_settings
- from ...readtracker import poststracker
- from ...users.test import AuthenticatedUserTestCase
- LISTS_URLS = ("", "my/", "new/", "unread/", "subscribed/")
- def patch_categories_acl(category_acl=None, base_acl=None):
- def patch_acl(_, user_acl):
- first_category = Category.objects.get(slug="first-category")
- first_category_acl = user_acl["categories"][first_category.id].copy()
- user_acl.update(
- {
- "categories": {},
- "visible_categories": [],
- "browseable_categories": [],
- "can_approve_content": [],
- }
- )
- # copy first category's acl to other categories to make base for overrides
- for category in Category.objects.all_categories():
- user_acl["categories"][category.id] = first_category_acl
- if base_acl:
- user_acl.update(base_acl)
- for category in Category.objects.all_categories():
- user_acl["visible_categories"].append(category.id)
- user_acl["browseable_categories"].append(category.id)
- user_acl["categories"][category.id].update(
- {
- "can_see": 1,
- "can_browse": 1,
- "can_see_all_threads": 1,
- "can_see_own_threads": 0,
- "can_hide_threads": 0,
- "can_approve_content": 0,
- }
- )
- if category_acl:
- user_acl["categories"][category.id].update(category_acl)
- if category_acl.get("can_approve_content"):
- user_acl["can_approve_content"].append(category.id)
- return patch_user_acl(patch_acl)
- class ThreadsListTestCase(AuthenticatedUserTestCase):
- def setUp(self):
- """
- Create categories tree for test cases:
- First category (created by migration)
- Category A
- + Category B
- + Subcategory C
- + Subcategory D
- Category E
- + Subcategory F
- """
- super().setUp()
- self.api_link = reverse("misago:api:thread-list")
- self.root = Category.objects.root_category()
- self.first_category = Category.objects.get(slug="first-category")
- Category(
- name="Category A", slug="category-a", css_class="showing-category-a"
- ).insert_at(self.root, position="last-child", save=True)
- Category(
- name="Category E", slug="category-e", css_class="showing-category-e"
- ).insert_at(self.root, position="last-child", save=True)
- self.root = Category.objects.root_category()
- self.category_a = Category.objects.get(slug="category-a")
- Category(
- name="Category B", slug="category-b", css_class="showing-category-b"
- ).insert_at(self.category_a, position="last-child", save=True)
- self.category_b = Category.objects.get(slug="category-b")
- Category(
- name="Category C", slug="category-c", css_class="showing-category-c"
- ).insert_at(self.category_b, position="last-child", save=True)
- Category(
- name="Category D", slug="category-d", css_class="showing-category-d"
- ).insert_at(self.category_b, position="last-child", save=True)
- self.category_c = Category.objects.get(slug="category-c")
- self.category_d = Category.objects.get(slug="category-d")
- self.category_e = Category.objects.get(slug="category-e")
- Category(
- name="Category F", slug="category-f", css_class="showing-category-f"
- ).insert_at(self.category_e, position="last-child", save=True)
- self.category_f = Category.objects.get(slug="category-f")
- Category.objects.partial_rebuild(self.root.tree_id)
- self.root = Category.objects.root_category()
- self.category_a = Category.objects.get(slug="category-a")
- self.category_b = Category.objects.get(slug="category-b")
- self.category_c = Category.objects.get(slug="category-c")
- self.category_d = Category.objects.get(slug="category-d")
- self.category_e = Category.objects.get(slug="category-e")
- self.category_f = Category.objects.get(slug="category-f")
- def assertContainsThread(self, response, thread):
- self.assertContains(response, ' href="%s"' % thread.get_absolute_url())
- def assertNotContainsThread(self, response, thread):
- self.assertNotContains(response, ' href="%s"' % thread.get_absolute_url())
- class ApiTests(ThreadsListTestCase):
- def test_root_category(self):
- """its possible to access threads endpoint with category=ROOT_ID"""
- response = self.client.get("%s?category=%s" % (self.api_link, self.root.pk))
- self.assertEqual(response.status_code, 200)
- def test_invalid_list_type(self):
- """api returns 404 for invalid list type"""
- response = self.client.get(
- "%s?category=%s&list=nope" % (self.api_link, self.root.pk)
- )
- self.assertEqual(response.status_code, 404)
- class AllThreadsListTests(ThreadsListTestCase):
- @patch_categories_acl()
- def test_list_renders_empty(self):
- """empty threads list renders"""
- for url in LISTS_URLS:
- response = self.client.get("/" + url)
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- if url:
- self.assertContains(response, "No threads matching specified criteria")
- else:
- self.assertContains(response, "There are no threads on this forum")
- response = self.client.get(self.category_b.get_absolute_url() + url)
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, self.category_b.name)
- self.assertContains(response, "empty-message")
- if url:
- self.assertContains(response, "No threads matching specified criteria")
- else:
- self.assertContains(response, "There are no threads in this category")
- response = self.client.get(
- "%s?list=%s" % (self.api_link, url.strip("/") or "all")
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- # empty lists render for anonymous user?
- self.logout_user()
- self.user = self.get_anonymous_user()
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- self.assertContains(response, "There are no threads on this forum")
- response = self.client.get(self.category_b.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, self.category_b.name)
- self.assertContains(response, "empty-message")
- self.assertContains(response, "There are no threads in this category")
- response = self.client.get("%s?list=all" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_authenticated_only_views(self):
- """authenticated only views return 403 for guests"""
- for url in LISTS_URLS:
- response = self.client.get("/" + url)
- self.assertEqual(response.status_code, 200)
- response = self.client.get(self.category_b.get_absolute_url() + url)
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, self.category_b.name)
- response = self.client.get(
- "%s?category=%s&list=%s"
- % (self.api_link, self.category_b.pk, url.strip("/") or "all")
- )
- self.assertEqual(response.status_code, 200)
- self.logout_user()
- self.user = self.get_anonymous_user()
- for url in LISTS_URLS[1:]:
- response = self.client.get("/" + url)
- self.assertEqual(response.status_code, 403)
- response = self.client.get(self.category_b.get_absolute_url() + url)
- self.assertEqual(response.status_code, 403)
- response = self.client.get(
- "%s?category=%s&list=%s"
- % (self.api_link, self.category_b.pk, url.strip("/") or "all")
- )
- self.assertEqual(response.status_code, 403)
- @patch_categories_acl()
- def test_list_renders_categories_picker(self):
- """categories picker renders valid categories"""
- Category(name="Hidden Category", slug="hidden-category").insert_at(
- self.root, position="last-child", save=True
- )
- test_category = Category.objects.get(slug="hidden-category")
- test.post_thread(category=self.category_b)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "subcategory-%s" % self.category_a.css_class)
- # readable categories, but non-accessible directly
- self.assertNotContains(response, "subcategory-%s" % self.category_b.css_class)
- self.assertNotContains(response, "subcategory-%s" % self.category_c.css_class)
- self.assertNotContains(response, "subcategory-%s" % self.category_d.css_class)
- self.assertNotContains(response, "subcategory-%s" % self.category_f.css_class)
- # hidden category
- self.assertNotContains(response, "subcategory-%s" % test_category.css_class)
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertIn(self.category_a.pk, response_json["subcategories"])
- self.assertNotIn(self.category_b.pk, response_json["subcategories"])
- # test category view
- response = self.client.get(self.category_a.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "subcategory-%s" % self.category_b.css_class)
- # readable categories, but non-accessible directly
- self.assertNotContains(response, "subcategory-%s" % self.category_c.css_class)
- self.assertNotContains(response, "subcategory-%s" % self.category_d.css_class)
- self.assertNotContains(response, "subcategory-%s" % self.category_f.css_class)
- response = self.client.get(
- "%s?category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["subcategories"][0], self.category_b.pk)
- def test_display_pinned_threads(self):
- """
- threads list displays globally pinned threads first
- and locally ones inbetween other
- """
- globally = test.post_thread(category=self.first_category, is_global=True)
- locally = test.post_thread(category=self.first_category, is_pinned=True)
- standard = test.post_thread(category=self.first_category)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- content = smart_str(response.content)
- positions = {
- "g": content.find(globally.get_absolute_url()),
- "l": content.find(locally.get_absolute_url()),
- "s": content.find(standard.get_absolute_url()),
- }
- # global announcement before others
- self.assertTrue(positions["g"] < positions["l"])
- self.assertTrue(positions["g"] < positions["s"])
- # standard in the middle
- self.assertTrue(positions["s"] < positions["l"])
- self.assertTrue(positions["s"] > positions["g"])
- # pinned last
- self.assertTrue(positions["l"] > positions["g"])
- self.assertTrue(positions["l"] > positions["s"])
- # API behaviour is identic
- response = self.client.get("/api/threads/")
- self.assertEqual(response.status_code, 200)
- content = smart_str(response.content)
- positions = {
- "g": content.find(globally.get_absolute_url()),
- "l": content.find(locally.get_absolute_url()),
- "s": content.find(standard.get_absolute_url()),
- }
- # global announcement before others
- self.assertTrue(positions["g"] < positions["l"])
- self.assertTrue(positions["g"] < positions["s"])
- # standard in the middle
- self.assertTrue(positions["s"] < positions["l"])
- self.assertTrue(positions["s"] > positions["g"])
- # pinned last
- self.assertTrue(positions["l"] > positions["g"])
- self.assertTrue(positions["l"] > positions["s"])
- @override_dynamic_settings(threads_per_page=5)
- def test_noscript_pagination(self):
- """threads list is paginated for users with js disabled"""
- threads_per_page = 5
- # post and discard thread to move last_post_id count by one
- test.post_thread(category=self.first_category).delete()
- # create test threads
- threads = []
- for _ in range(threads_per_page * 2):
- threads.append(test.post_thread(category=self.first_category))
- # threads starting with given one are on the list
- response = self.client.get("/?start=%s" % threads[-2].last_post_id)
- self.assertEqual(response.status_code, 200)
- # first thread is skipped by cursor pagination
- self.assertNotContainsThread(response, threads[-1])
- # starting thread is present
- self.assertContainsThread(response, threads[-2])
- # slice contains expected threads
- for visible_thread in threads[threads_per_page - 1 : -1]:
- self.assertContainsThread(response, visible_thread)
- # threads after slice are hidden
- for invisible_thread in threads[: threads_per_page - 1]:
- self.assertNotContainsThread(response, invisible_thread)
- # nonexisting start gives 404
- response = self.client.get("/?start=%s" % (threads[0].last_post_id - 1))
- self.assertEqual(response.status_code, 404)
- class CategoryThreadsListTests(ThreadsListTestCase):
- def test_access_hidden_category(self):
- """hidden category returns 404"""
- Category(name="Hidden Category", slug="hidden-category").insert_at(
- self.root, position="last-child", save=True
- )
- test_category = Category.objects.get(slug="hidden-category")
- for url in LISTS_URLS:
- response = self.client.get(test_category.get_absolute_url() + url)
- self.assertEqual(response.status_code, 404)
- response = self.client.get(
- "%s?category=%s" % (self.api_link, test_category.id)
- )
- self.assertEqual(response.status_code, 404)
- def test_access_protected_category(self):
- """protected category returns 403"""
- Category(name="Hidden Category", slug="hidden-category").insert_at(
- self.root, position="last-child", save=True
- )
- test_category = Category.objects.get(slug="hidden-category")
- for url in LISTS_URLS:
- with patch_user_acl(
- {
- "visible_categories": [test_category.id],
- "browseable_categories": [],
- "categories": {test_category.id: {"can_see": 1, "can_browse": 0}},
- }
- ):
- response = self.client.get(test_category.get_absolute_url() + url)
- self.assertEqual(response.status_code, 403)
- response = self.client.get(
- "%s?category=%s&list=%s"
- % (self.api_link, test_category.id, url.strip("/"))
- )
- self.assertEqual(response.status_code, 403)
- def test_display_pinned_threads(self):
- """
- category threads list displays globally pinned threads first
- then locally ones and unpinned last
- """
- globally = test.post_thread(category=self.first_category, is_global=True)
- locally = test.post_thread(category=self.first_category, is_pinned=True)
- standard = test.post_thread(category=self.first_category)
- response = self.client.get(self.first_category.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- content = smart_str(response.content)
- positions = {
- "g": content.find(globally.get_absolute_url()),
- "l": content.find(locally.get_absolute_url()),
- "s": content.find(standard.get_absolute_url()),
- }
- # global announcement before others
- self.assertTrue(positions["g"] < positions["l"])
- self.assertTrue(positions["g"] < positions["s"])
- # pinned in the middle
- self.assertTrue(positions["l"] < positions["s"])
- self.assertTrue(positions["l"] > positions["g"])
- # standard last
- self.assertTrue(positions["s"] > positions["g"])
- self.assertTrue(positions["s"] > positions["g"])
- # API behaviour is identic
- response = self.client.get("/api/threads/?category=%s" % self.first_category.id)
- self.assertEqual(response.status_code, 200)
- content = smart_str(response.content)
- positions = {
- "g": content.find(globally.get_absolute_url()),
- "l": content.find(locally.get_absolute_url()),
- "s": content.find(standard.get_absolute_url()),
- }
- # global announcement before others
- self.assertTrue(positions["g"] < positions["l"])
- self.assertTrue(positions["g"] < positions["s"])
- # pinned in the middle
- self.assertTrue(positions["l"] < positions["s"])
- self.assertTrue(positions["l"] > positions["g"])
- # standard last
- self.assertTrue(positions["s"] > positions["g"])
- self.assertTrue(positions["s"] > positions["g"])
- class ThreadsVisibilityTests(ThreadsListTestCase):
- @patch_categories_acl()
- def test_list_renders_test_thread(self):
- """list renders test thread with valid top category"""
- test_thread = test.post_thread(category=self.category_c)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- self.assertContains(response, "subcategory-%s" % self.category_a.css_class)
- self.assertContains(response, "subcategory-%s" % self.category_e.css_class)
- # api displays same data
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- self.assertEqual(len(response_json["subcategories"]), 3)
- self.assertIn(self.category_a.pk, response_json["subcategories"])
- # test category view
- response = self.client.get(self.category_b.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- # thread displays
- self.assertContainsThread(response, test_thread)
- # api displays same data
- response = self.client.get(
- "%s?category=%s" % (self.api_link, self.category_b.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- self.assertEqual(len(response_json["subcategories"]), 2)
- self.assertEqual(response_json["subcategories"][0], self.category_c.pk)
- def test_list_hides_hidden_thread(self):
- """list renders empty due to no permission to see thread"""
- Category(name="Hidden Category", slug="hidden-category").insert_at(
- self.root, position="last-child", save=True
- )
- test_category = Category.objects.get(slug="hidden-category")
- test_thread = test.post_thread(category=test_category)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- self.assertNotContainsThread(response, test_thread)
- def test_api_hides_hidden_thread(self):
- """api returns empty due to no permission to see thread"""
- Category(name="Hidden Category", slug="hidden-category").insert_at(
- self.root, position="last-child", save=True
- )
- test_category = Category.objects.get(slug="hidden-category")
- test.post_thread(category=test_category)
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_user_see_own_unapproved_thread(self):
- """list renders unapproved thread that belongs to viewer"""
- test_thread = test.post_thread(
- category=self.category_a, poster=self.user, is_unapproved=True
- )
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- @patch_categories_acl()
- def test_list_user_cant_see_unapproved_thread(self):
- """list hides unapproved thread that belongs to other user"""
- test_thread = test.post_thread(category=self.category_a, is_unapproved=True)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_user_cant_see_hidden_thread(self):
- """list hides hidden thread that belongs to other user"""
- test_thread = test.post_thread(category=self.category_a, is_hidden=True)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_user_cant_see_own_hidden_thread(self):
- """list hides hidden thread that belongs to viewer"""
- test_thread = test.post_thread(
- category=self.category_a, poster=self.user, is_hidden=True
- )
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl({"can_hide_threads": 1})
- def test_list_user_can_see_own_hidden_thread(self):
- """list shows hidden thread that belongs to viewer due to permission"""
- test_thread = test.post_thread(
- category=self.category_a, poster=self.user, is_hidden=True
- )
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- @patch_categories_acl({"can_hide_threads": 1})
- def test_list_user_can_see_hidden_thread(self):
- """list shows hidden thread that belongs to other user due to permission"""
- test_thread = test.post_thread(category=self.category_a, is_hidden=True)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- @patch_categories_acl({"can_approve_content": 1})
- def test_list_user_can_see_unapproved_thread(self):
- """list shows hidden thread that belongs to other user due to permission"""
- test_thread = test.post_thread(category=self.category_a, is_unapproved=True)
- response = self.client.get("/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get(self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- class MyThreadsListTests(ThreadsListTestCase):
- @patch_categories_acl()
- def test_list_renders_empty(self):
- """list renders empty"""
- response = self.client.get("/my/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- response = self.client.get(self.category_a.get_absolute_url() + "my/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- # test api
- response = self.client.get("%s?list=my" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=my&category=%s" % (self.api_link, self.category_a.pk)
- )
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_renders_test_thread(self):
- """list renders only threads posted by user"""
- test_thread = test.post_thread(category=self.category_a, poster=self.user)
- other_thread = test.post_thread(category=self.category_a)
- response = self.client.get("/my/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- self.assertNotContainsThread(response, other_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "my/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- self.assertNotContainsThread(response, other_thread)
- # test api
- response = self.client.get("%s?list=my" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- response = self.client.get(
- "%s?list=my&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- class NewThreadsListTests(ThreadsListTestCase):
- @patch_categories_acl()
- def test_list_renders_empty(self):
- """list renders empty"""
- response = self.client.get("/new/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- response = self.client.get(self.category_a.get_absolute_url() + "new/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- # test api
- response = self.client.get("%s?list=new" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
- )
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_renders_new_thread(self):
- """list renders new thread"""
- test_thread = test.post_thread(category=self.category_a)
- response = self.client.get("/new/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "new/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=new" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- response = self.client.get(
- "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- @patch_categories_acl()
- def test_list_renders_thread_bumped_after_user_cutoff(self):
- """list renders new thread bumped after user cutoff"""
- self.user.joined_on = timezone.now() - timedelta(days=10)
- self.user.save()
- test_thread = test.post_thread(
- category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
- )
- test.reply_thread(
- test_thread, posted_on=self.user.joined_on + timedelta(days=4)
- )
- response = self.client.get("/new/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "new/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=new" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- response = self.client.get(
- "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- @override_dynamic_settings(readtracker_cutoff=3)
- @patch_categories_acl()
- def test_list_hides_global_cutoff_thread(self):
- """list hides thread started before global cutoff"""
- self.user.joined_on = timezone.now() - timedelta(days=10)
- self.user.save()
- test_thread = test.post_thread(
- category=self.category_a, started_on=timezone.now() - timedelta(days=5)
- )
- response = self.client.get("/new/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "new/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=new" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_hides_user_cutoff_thread(self):
- """list hides thread started before users cutoff"""
- self.user.joined_on = timezone.now() - timedelta(days=5)
- self.user.save()
- test_thread = test.post_thread(
- category=self.category_a,
- started_on=self.user.joined_on - timedelta(minutes=1),
- )
- response = self.client.get("/new/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "new/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=new" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_hides_user_read_thread(self):
- """list hides thread already read by user"""
- self.user.joined_on = timezone.now() - timedelta(days=5)
- self.user.save()
- test_thread = test.post_thread(category=self.category_a)
- poststracker.save_read(self.user, test_thread.first_post)
- response = self.client.get("/new/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "new/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=new" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=new&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- class UnreadThreadsListTests(ThreadsListTestCase):
- @patch_categories_acl()
- def test_list_renders_empty(self):
- """list renders empty"""
- response = self.client.get("/unread/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- response = self.client.get(self.category_a.get_absolute_url() + "unread/")
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "empty-message")
- # test api
- response = self.client.get("%s?list=unread" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_renders_unread_thread(self):
- """list renders thread with unread posts"""
- self.user.joined_on = timezone.now() - timedelta(days=5)
- self.user.save()
- test_thread = test.post_thread(category=self.category_a)
- poststracker.save_read(self.user, test_thread.first_post)
- test.reply_thread(test_thread)
- response = self.client.get("/unread/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unread/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=unread" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- response = self.client.get(
- "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertEqual(response_json["results"][0]["id"], test_thread.pk)
- @patch_categories_acl()
- def test_list_hides_never_read_thread(self):
- """list hides never read thread"""
- self.user.joined_on = timezone.now() - timedelta(days=5)
- self.user.save()
- test_thread = test.post_thread(category=self.category_a)
- response = self.client.get("/unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=unread" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_hides_read_thread(self):
- """list hides read thread"""
- self.user.joined_on = timezone.now() - timedelta(days=5)
- self.user.save()
- test_thread = test.post_thread(category=self.category_a)
- poststracker.save_read(self.user, test_thread.first_post)
- response = self.client.get("/unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=unread" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @override_dynamic_settings(readtracker_cutoff=3)
- @patch_categories_acl()
- def test_list_hides_global_cutoff_thread(self):
- """list hides thread replied before global cutoff"""
- self.user.joined_on = timezone.now() - timedelta(days=10)
- self.user.save()
- test_thread = test.post_thread(
- category=self.category_a, started_on=timezone.now() - timedelta(days=5)
- )
- poststracker.save_read(self.user, test_thread.first_post)
- test.reply_thread(
- test_thread, posted_on=test_thread.started_on + timedelta(days=1)
- )
- response = self.client.get("/unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=unread" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- @patch_categories_acl()
- def test_list_hides_user_cutoff_thread(self):
- """list hides thread replied before user cutoff"""
- self.user.joined_on = timezone.now() - timedelta(days=10)
- self.user.save()
- test_thread = test.post_thread(
- category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
- )
- poststracker.save_read(self.user, test_thread.first_post)
- test.reply_thread(
- test_thread, posted_on=test_thread.started_on + timedelta(days=1)
- )
- response = self.client.get("/unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unread/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=unread" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- response = self.client.get(
- "%s?list=unread&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- class SubscribedThreadsListTests(ThreadsListTestCase):
- @patch_categories_acl()
- def test_list_shows_subscribed_thread(self):
- """list shows subscribed thread"""
- test_thread = test.post_thread(category=self.category_a)
- self.user.subscription_set.create(
- thread=test_thread,
- category=self.category_a,
- last_read_on=test_thread.last_post_on,
- )
- response = self.client.get("/subscribed/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "subscribed/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=subscribed" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertContains(response, test_thread.get_absolute_url())
- response = self.client.get(
- "%s?list=subscribed&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 1)
- self.assertContains(response, test_thread.get_absolute_url())
- @patch_categories_acl()
- def test_list_hides_unsubscribed_thread(self):
- """list shows subscribed thread"""
- test_thread = test.post_thread(category=self.category_a)
- response = self.client.get("/subscribed/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "subscribed/")
- self.assertEqual(response.status_code, 200)
- self.assertNotContainsThread(response, test_thread)
- # test api
- response = self.client.get("%s?list=subscribed" % self.api_link)
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- self.assertNotContainsThread(response, test_thread)
- response = self.client.get(
- "%s?list=subscribed&category=%s" % (self.api_link, self.category_a.pk)
- )
- self.assertEqual(response.status_code, 200)
- response_json = response.json()
- self.assertEqual(len(response_json["results"]), 0)
- self.assertNotContainsThread(response, test_thread)
- class UnapprovedListTests(ThreadsListTestCase):
- def test_list_errors_without_permission(self):
- """list errors if user has no permission to access it"""
- TEST_URLS = (
- "/unapproved/",
- self.category_a.get_absolute_url() + "unapproved/",
- "%s?list=unapproved" % self.api_link,
- )
- with patch_categories_acl():
- for test_url in TEST_URLS:
- response = self.client.get(test_url)
- self.assertEqual(response.status_code, 403)
- # approval perm has no influence on visibility
- with patch_categories_acl({"can_approve_content": True}):
- for test_url in TEST_URLS:
- response = self.client.get(test_url)
- self.assertEqual(response.status_code, 403)
- # approval perm has no influence on visibility
- with patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True}):
- for test_url in TEST_URLS:
- response = self.client.get(test_url)
- self.assertEqual(response.status_code, 200)
- @patch_categories_acl(
- {"can_approve_content": True}, {"can_see_unapproved_content_lists": True}
- )
- def test_list_shows_all_threads_for_approving_user(self):
- """list shows all threads with unapproved posts when user has perm"""
- visible_thread = test.post_thread(category=self.category_b, is_unapproved=True)
- hidden_thread = test.post_thread(category=self.category_b, is_unapproved=False)
- response = self.client.get("/unapproved/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, visible_thread)
- self.assertNotContainsThread(response, hidden_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unapproved/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, visible_thread)
- self.assertNotContainsThread(response, hidden_thread)
- # test api
- response = self.client.get("%s?list=unapproved" % self.api_link)
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, visible_thread.get_absolute_url())
- self.assertNotContains(response, hidden_thread.get_absolute_url())
- @patch_categories_acl(base_acl={"can_see_unapproved_content_lists": True})
- def test_list_shows_owned_threads_for_unapproving_user(self):
- """list shows owned threads with unapproved posts for user without perm"""
- visible_thread = test.post_thread(
- poster=self.user, category=self.category_b, is_unapproved=True
- )
- hidden_thread = test.post_thread(category=self.category_b, is_unapproved=True)
- response = self.client.get("/unapproved/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, visible_thread)
- self.assertNotContainsThread(response, hidden_thread)
- response = self.client.get(self.category_a.get_absolute_url() + "unapproved/")
- self.assertEqual(response.status_code, 200)
- self.assertContainsThread(response, visible_thread)
- self.assertNotContainsThread(response, hidden_thread)
- # test api
- response = self.client.get("%s?list=unapproved" % self.api_link)
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, visible_thread.get_absolute_url())
- self.assertNotContains(response, hidden_thread.get_absolute_url())
- def patch_category_see_all_threads_acl():
- def patch_acl(_, user_acl):
- category = Category.objects.get(slug="first-category")
- category_acl = user_acl["categories"][category.id].copy()
- category_acl.update({"can_see_all_threads": 0})
- user_acl["categories"][category.id] = category_acl
- return patch_user_acl(patch_acl)
- class OwnerOnlyThreadsVisibilityTests(AuthenticatedUserTestCase):
- def setUp(self):
- super().setUp()
- self.category = Category.objects.get(slug="first-category")
- def test_owned_threads_visibility(self):
- """only user-posted threads are visible in category"""
- visible_thread = test.post_thread(
- poster=self.user, category=self.category, is_unapproved=True
- )
- hidden_thread = test.post_thread(category=self.category, is_unapproved=True)
- with patch_category_see_all_threads_acl():
- response = self.client.get(self.category.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, visible_thread.get_absolute_url())
- self.assertNotContains(response, hidden_thread.get_absolute_url())
- def test_owned_threads_visibility_anonymous(self):
- """anons can't see any threads in limited visibility category"""
- self.logout_user()
- user_thread = test.post_thread(
- poster=self.user, category=self.category, is_unapproved=True
- )
- guest_thread = test.post_thread(category=self.category, is_unapproved=True)
- with patch_category_see_all_threads_acl():
- response = self.client.get(self.category.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- self.assertNotContains(response, user_thread.get_absolute_url())
- self.assertNotContains(response, guest_thread.get_absolute_url())
|