test_threadslists.py 58 KB

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