test_readtracker.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. from datetime import timedelta
  2. from django.contrib.auth import get_user_model
  3. from django.test import TestCase
  4. from django.utils import timezone
  5. from misago.acl import add_acl
  6. from misago.categories.models import Category
  7. from misago.threads import testutils
  8. from misago.users.models import AnonymousUser
  9. from misago.readtracker import categoriestracker, threadstracker
  10. UserModel = get_user_model()
  11. class ReadTrackerTests(TestCase):
  12. def setUp(self):
  13. self.categories = list(Category.objects.all_categories()[:1])
  14. self.category = self.categories[0]
  15. self.user = UserModel.objects.create_user(
  16. "Bob", "bob@test.com", "Pass.123")
  17. self.anon = AnonymousUser()
  18. def post_thread(self, datetime):
  19. return testutils.post_thread(
  20. category=self.category,
  21. started_on=datetime
  22. )
  23. class CategoriesTrackerTests(ReadTrackerTests):
  24. def test_anon_empty_category_read(self):
  25. """anon users content is always read"""
  26. categoriestracker.make_read_aware(self.anon, self.categories)
  27. self.assertIsNone(self.category.last_post_on)
  28. self.assertTrue(self.category.is_read)
  29. def test_anon_category_recent_reply_read(self):
  30. """anon users content is always read"""
  31. categoriestracker.make_read_aware(self.anon, self.categories)
  32. self.category.last_post_on = timezone.now()
  33. self.assertTrue(self.category.is_read)
  34. def test_empty_category_is_read(self):
  35. """empty category is read for signed in user"""
  36. categoriestracker.make_read_aware(self.user, self.categories)
  37. self.assertTrue(self.category.is_read)
  38. def test_make_read_aware_sets_read_flag_for_empty_category(self):
  39. """make_read_aware sets read flag on empty category"""
  40. categoriestracker.make_read_aware(self.anon, self.categories)
  41. self.assertTrue(self.category.is_read)
  42. categoriestracker.make_read_aware(self.user, self.categories)
  43. self.assertTrue(self.category.is_read)
  44. def test_make_read_aware_sets_read_flag_for_category_old_thread(self):
  45. """make_read_aware sets read flag on category with old thread"""
  46. self.category.last_post_on = self.user.joined_on - timedelta(days=1)
  47. categoriestracker.make_read_aware(self.user, self.categories)
  48. self.assertTrue(self.category.is_read)
  49. def test_make_read_aware_sets_unread_flag_for_category_new_thread(self):
  50. """make_read_aware sets unread flag on category with new thread"""
  51. self.category.last_post_on = self.user.joined_on + timedelta(days=1)
  52. categoriestracker.make_read_aware(self.user, self.categories)
  53. self.assertFalse(self.category.is_read)
  54. def test_sync_record_for_empty_category(self):
  55. """sync_record sets read flag on empty category"""
  56. add_acl(self.user, self.categories)
  57. categoriestracker.sync_record(self.user, self.category)
  58. self.user.categoryread_set.get(category=self.category)
  59. categoriestracker.make_read_aware(self.user, self.categories)
  60. self.assertTrue(self.category.is_read)
  61. def test_sync_record_for_category_old_thread_and_reply(self):
  62. """
  63. sync_record sets read flag on category with old thread,
  64. then changes flag to unread when new reply is posted
  65. """
  66. self.post_thread(self.user.joined_on - timedelta(days=1))
  67. add_acl(self.user, self.categories)
  68. categoriestracker.sync_record(self.user, self.category)
  69. self.user.categoryread_set.get(category=self.category)
  70. categoriestracker.make_read_aware(self.user, self.categories)
  71. self.assertTrue(self.category.is_read)
  72. thread = self.post_thread(self.user.joined_on + timedelta(days=1))
  73. categoriestracker.sync_record(self.user, self.category)
  74. categoriestracker.make_read_aware(self.user, self.categories)
  75. self.assertFalse(self.category.is_read)
  76. def test_sync_record_for_category_new_thread(self):
  77. """
  78. sync_record sets read flag on category with old thread,
  79. then keeps flag to unread when new reply is posted
  80. """
  81. self.post_thread(self.user.joined_on + timedelta(days=1))
  82. add_acl(self.user, self.categories)
  83. categoriestracker.sync_record(self.user, self.category)
  84. self.user.categoryread_set.get(category=self.category)
  85. categoriestracker.make_read_aware(self.user, self.categories)
  86. self.assertFalse(self.category.is_read)
  87. self.post_thread(self.user.joined_on + timedelta(days=1))
  88. categoriestracker.sync_record(self.user, self.category)
  89. categoriestracker.make_read_aware(self.user, self.categories)
  90. self.assertFalse(self.category.is_read)
  91. def test_sync_record_for_category_deleted_threads(self):
  92. """unread category reverts to read after its emptied"""
  93. self.post_thread(self.user.joined_on + timedelta(days=1))
  94. self.post_thread(self.user.joined_on + timedelta(days=1))
  95. self.post_thread(self.user.joined_on + timedelta(days=1))
  96. add_acl(self.user, self.categories)
  97. categoriestracker.sync_record(self.user, self.category)
  98. categoriestracker.make_read_aware(self.user, self.categories)
  99. self.assertFalse(self.category.is_read)
  100. self.category.thread_set.all().delete()
  101. self.category.synchronize()
  102. self.category.save()
  103. categoriestracker.make_read_aware(self.user, self.categories)
  104. self.assertTrue(self.category.is_read)
  105. def test_sync_record_for_category_many_threads(self):
  106. """sync_record sets unread flag on category with many threads"""
  107. self.post_thread(self.user.joined_on + timedelta(days=1))
  108. self.post_thread(self.user.joined_on - timedelta(days=1))
  109. self.post_thread(self.user.joined_on + timedelta(days=1))
  110. self.post_thread(self.user.joined_on - timedelta(days=1))
  111. add_acl(self.user, self.categories)
  112. categoriestracker.sync_record(self.user, self.category)
  113. self.user.categoryread_set.get(category=self.category)
  114. categoriestracker.make_read_aware(self.user, self.categories)
  115. self.assertFalse(self.category.is_read)
  116. self.post_thread(self.user.joined_on + timedelta(days=1))
  117. categoriestracker.sync_record(self.user, self.category)
  118. categoriestracker.make_read_aware(self.user, self.categories)
  119. self.assertFalse(self.category.is_read)
  120. def test_sync_record_for_category_threads_behind_cutoff(self):
  121. """
  122. sync_record sets read flag on category with only thread being behind cutoff
  123. """
  124. self.post_thread(timezone.now() - timedelta(days=180))
  125. read_thread = self.post_thread(timezone.now())
  126. threadstracker.make_read_aware(self.user, read_thread)
  127. threadstracker.read_thread(self.user, read_thread, read_thread.last_post)
  128. category = Category.objects.get(pk=self.category.pk)
  129. categoriestracker.make_read_aware(self.user, [category])
  130. self.assertTrue(category.is_read)
  131. def test_read_leaf_category(self):
  132. """read_category reads leaf category for user"""
  133. categoriestracker.read_category(self.user, self.category)
  134. self.assertTrue(self.user.categoryread_set.get(category=self.category))
  135. def test_read_root_category(self):
  136. """read_category reads its subcategories for user"""
  137. root_category = Category.objects.root_category()
  138. categoriestracker.read_category(self.user, root_category)
  139. child_read = self.user.categoryread_set.get(category=self.category)
  140. self.assertTrue(child_read.last_read_on > timezone.now() - timedelta(seconds=3))
  141. def test_read_category_prunes_threadreads(self):
  142. """read_category prunes threadreads in this category"""
  143. thread = self.post_thread(timezone.now())
  144. threadstracker.make_read_aware(self.user, thread)
  145. threadstracker.read_thread(self.user, thread, thread.last_post)
  146. self.assertTrue(self.user.threadread_set.exists())
  147. categoriestracker.read_category(self.user, self.category)
  148. self.assertTrue(self.user.categoryread_set.get(category=self.category))
  149. self.assertFalse(self.user.threadread_set.exists())
  150. class ThreadsTrackerTests(ReadTrackerTests):
  151. def setUp(self):
  152. super(ThreadsTrackerTests, self).setUp()
  153. self.thread = self.post_thread(timezone.now() - timedelta(days=10))
  154. def reply_thread(self, is_hidden=False, is_unapproved=False):
  155. self.post = testutils.reply_thread(
  156. thread=self.thread,
  157. is_hidden=is_hidden,
  158. is_unapproved=is_unapproved,
  159. posted_on=timezone.now()
  160. )
  161. return self.post
  162. def test_thread_read_for_guest(self):
  163. """threads are always read for guests"""
  164. threadstracker.make_read_aware(self.anon, self.thread)
  165. self.assertTrue(self.thread.is_read)
  166. self.reply_thread()
  167. threadstracker.make_read_aware(self.anon, [self.thread])
  168. self.assertTrue(self.thread.is_read)
  169. def test_thread_read_for_user(self):
  170. """thread is read for user"""
  171. threadstracker.make_read_aware(self.user, self.thread)
  172. self.assertTrue(self.thread.is_read)
  173. def test_thread_replied_unread_for_user(self):
  174. """replied thread is unread for user"""
  175. self.reply_thread()
  176. threadstracker.make_read_aware(self.user, self.thread)
  177. self.assertFalse(self.thread.is_read)
  178. def test_thread_read(self):
  179. """thread read flag is set for user, then its set as unread by reply"""
  180. self.reply_thread()
  181. add_acl(self.user, self.categories)
  182. threadstracker.make_read_aware(self.user, self.thread)
  183. self.assertFalse(self.thread.is_read)
  184. threadstracker.read_thread(self.user, self.thread, self.post)
  185. threadstracker.make_read_aware(self.user, self.thread)
  186. self.assertTrue(self.thread.is_read)
  187. categoriestracker.make_read_aware(self.user, self.categories)
  188. self.assertTrue(self.category.is_read)
  189. self.thread.last_post_on = timezone.now()
  190. self.thread.save()
  191. self.category.synchronize()
  192. self.category.save()
  193. self.reply_thread()
  194. threadstracker.make_read_aware(self.user, self.thread)
  195. self.assertFalse(self.thread.is_read)
  196. categoriestracker.make_read_aware(self.user, self.categories)
  197. self.assertFalse(self.category.is_read)
  198. posts = [post for post in self.thread.post_set.order_by('id')]
  199. threadstracker.make_posts_read_aware(self.user, self.thread, posts)
  200. for post in posts[:-1]:
  201. self.assertTrue(post.is_read)
  202. self.assertFalse(posts[-1].is_read)
  203. def test_thread_read_category_cutoff(self):
  204. """thread read is handled when category cutoff is present"""
  205. self.reply_thread()
  206. add_acl(self.user, self.categories)
  207. threadstracker.make_read_aware(self.user, self.thread)
  208. self.assertFalse(self.thread.is_read)
  209. categoriestracker.read_category(self.user, self.category)
  210. threadstracker.make_read_aware(self.user, self.thread)
  211. self.assertTrue(self.thread.is_read)
  212. categoriestracker.make_read_aware(self.user, self.categories)
  213. self.assertTrue(self.category.is_read)
  214. posts = list(self.thread.post_set.order_by('id'))
  215. threadstracker.make_posts_read_aware(self.user, self.thread, posts)
  216. for post in posts:
  217. self.assertTrue(post.is_read)
  218. # post reply
  219. self.reply_thread()
  220. # test if only last post is unread
  221. posts = list(self.thread.post_set.order_by('id'))
  222. threadstracker.make_read_aware(self.user, self.thread)
  223. threadstracker.make_posts_read_aware(self.user, self.thread, posts)
  224. for post in posts[:-1]:
  225. self.assertTrue(post.is_read)
  226. self.assertTrue(posts[-1].is_new)
  227. # last post read will change readstate of categories
  228. threadstracker.make_read_aware(self.user, self.thread)
  229. threadstracker.read_thread(self.user, self.thread, posts[-1])
  230. categoriestracker.make_read_aware(self.user, self.categories)
  231. self.assertTrue(self.category.is_read)