def test_merge_threads_kept_reads(self): """api keeps both threads readtrackers after merge""" self.override_acl({'can_merge_threads': 1}) self.override_other_acl({'can_merge_threads': 1}) other_thread = testutils.post_thread(self.category_b) poststracker.save_read(self.user, self.thread.first_post) poststracker.save_read(self.user, other_thread.first_post) response = self.client.post( self.api_link, { 'other_thread': other_thread.get_absolute_url(), }) self.assertContains(response, other_thread.get_absolute_url(), status_code=200) # posts reads are kept postreads = self.user.postread_set.filter( post__is_event=False).order_by('id') self.assertEqual( list(postreads.values_list('post_id', flat=True)), [self.thread.first_post_id, other_thread.first_post_id]) self.assertEqual(postreads.filter(thread=other_thread).count(), 2) self.assertEqual(postreads.filter(category=self.category_b).count(), 2)
def test_move_thread_reads(self): """api moves thread reads together with thread""" self.override_acl({'can_move_threads': True}) self.override_other_acl({'can_start_threads': 2}) poststracker.save_read(self.user, self.thread.first_post) self.assertEqual(self.user.postread_set.count(), 1) self.user.postread_set.get(category=self.category) response = self.patch(self.api_link, [ { 'op': 'replace', 'path': 'category', 'value': self.category_b.pk, }, { 'op': 'add', 'path': 'top-category', 'value': self.category_b.pk, }, { 'op': 'replace', 'path': 'flatten-categories', 'value': None, }, ]) self.assertEqual(response.status_code, 200) # thread read was moved to new category postreads = self.user.postread_set.filter( post__is_event=False).order_by('id') self.assertEqual(postreads.count(), 1) postreads.get(category=self.category_b)
def record_event(request, thread, event_type, context=None, commit=True): time_now = timezone.now() event = Post.objects.create( category=thread.category, thread=thread, poster=request.user, poster_name=request.user.username, original='-', parsed='-', posted_on=time_now, updated_on=time_now, is_event=True, event_type=event_type, event_context=context, ) thread.has_events = True thread.set_last_post(event) if commit: thread.save() if not (thread.is_hidden and thread.is_unapproved): thread.category.set_last_thread(thread) if commit: thread.category.save() poststracker.save_read(request.user, event) return event
def test_user_read_post(self): """tracked post is marked as read for authenticated users with read entry""" post = testutils.reply_thread(self.thread, posted_on=timezone.now()) poststracker.save_read(self.user, post) poststracker.make_read_aware(self.user, post) self.assertTrue(post.is_read) self.assertFalse(post.is_new)
def test_split_kitchensink(self): """api splits posts with kitchensink""" self.refresh_thread() self.assertEqual(self.thread.replies, 2) self.override_other_acl({ 'can_start_threads': 2, 'can_close_threads': True, 'can_hide_threads': True, 'can_pin_threads': 2, }) poststracker.save_read(self.user, self.thread.first_post) for post in self.posts: poststracker.save_read(self.user, Post.objects.select_related().get(pk=post)) response = self.client.post( self.api_link, json.dumps({ 'posts': self.posts, 'title': 'Split thread', 'category': self.category_b.id, 'weight': 2, 'is_closed': 1, 'is_hidden': 1, }), content_type="application/json", ) self.assertEqual(response.status_code, 200) # thread was created split_thread = self.category_b.thread_set.get(slug='split-thread') self.assertEqual(split_thread.replies, 1) self.assertEqual(split_thread.weight, 2) self.assertTrue(split_thread.is_closed) self.assertTrue(split_thread.is_hidden) # posts were removed from old thread self.refresh_thread() self.assertEqual(self.thread.replies, 0) # posts were moved to new thread self.assertEqual( split_thread.post_set.filter(pk__in=self.posts).count(), 2) # postreads were removed postreads = self.user.postread_set.filter( post__is_event=False).order_by('id') postreads_threads = list(postreads.values_list('thread_id', flat=True)) self.assertEqual(postreads_threads, [self.thread.pk]) postreads_categories = list( postreads.values_list('category_id', flat=True)) self.assertEqual(postreads_categories, [self.category.pk])
def test_user_read_post(self): """tracked thread with read post marked as read""" thread = testutils.post_thread(self.category, started_on=timezone.now()) poststracker.save_read(self.user, thread.first_post) categoriestracker.make_read_aware(self.user, self.category) self.assertTrue(self.category.is_read) self.assertFalse(self.category.is_new)
def test_user_first_unread_last_read_post(self): """tracked thread with unread first and last read post marked as unread""" thread = testutils.post_thread(self.category, started_on=timezone.now()) post = testutils.reply_thread(thread, posted_on=timezone.now()) poststracker.save_read(self.user, post) categoriestracker.make_read_aware(self.user, self.category) self.assertFalse(self.category.is_read) self.assertTrue(self.category.is_new)
def test_user_first_read_post_unread_event(self): """tracked thread with read first post and unread event""" thread = testutils.post_thread(self.category, started_on=timezone.now()) poststracker.save_read(self.user, thread.first_post) testutils.reply_thread(thread, posted_on=timezone.now(), is_event=True) categoriestracker.make_read_aware(self.user, self.category) self.assertFalse(self.category.is_read) self.assertTrue(self.category.is_new)
def test_goto_first_new_post(self): """first unread post redirect url in already read thread is valid""" save_read(self.user, self.thread.first_post) post = testutils.reply_thread(self.thread, posted_on=timezone.now()) for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1): testutils.reply_thread(self.thread, posted_on=timezone.now()) response = self.client.get(self.thread.get_new_post_url()) self.assertEqual(response.status_code, 302) self.assertEqual(response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk))
def test_delete_reads(self): """delete_reads util removes post's reads""" post = testutils.reply_thread(self.thread, posted_on=timezone.now()) other_post = testutils.reply_thread(self.thread, posted_on=timezone.now()) poststracker.save_read(self.user, post) poststracker.save_read(self.user, other_post) self.assertEqual(PostRead.objects.count(), 2) poststracker.delete_reads(post) self.assertEqual(PostRead.objects.count(), 1)
def test_goto_first_new_post_in_read_thread(self): """goto new in read thread points to last post""" save_read(self.user, self.thread.first_post) for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL): post = testutils.reply_thread(self.thread, posted_on=timezone.now()) save_read(self.user, post) response = self.client.get(self.thread.get_new_post_url()) self.assertEqual(response.status_code, 302) self.assertEqual( response['location'], GOTO_PAGE_URL % (self.thread.get_absolute_url(), 2, post.pk))
def post_read_endpoint(request, thread, post): poststracker.make_read_aware(request.user, post) if post.is_new: poststracker.save_read(request.user, post) if thread.subscription and thread.subscription.last_read_on < post.posted_on: thread.subscription.last_read_on = post.posted_on thread.subscription.save() threadstracker.make_read_aware(request.user, thread) # send signal if post read marked thread as read # used in some places, eg. syncing unread thread count if post.is_new and thread.is_read: thread_read.send(request.user, thread=thread) return Response({'thread_is_read': thread.is_read})
def test_merge_remove_reads(self): """two posts merge removes read tracker from post""" post_a = testutils.reply_thread(self.thread, poster=self.user, message="Battęry") post_b = testutils.reply_thread(self.thread, poster=self.user, message="Hórse") poststracker.save_read(self.user, post_a) poststracker.save_read(self.user, post_b) response = self.client.post( self.api_link, json.dumps({ 'posts': [post_a.pk, post_b.pk] }), content_type="application/json", ) self.assertEqual(response.status_code, 200) # both post's were removed from readtracker self.assertEqual(self.user.postread_set.count(), 0)
def test_move_posts_reads(self): """api moves posts reads together with posts""" self.override_other_acl({'can_reply_threads': 1}) other_thread = testutils.post_thread(self.category_b) posts = ( testutils.reply_thread(self.thread), testutils.reply_thread(self.thread), ) self.refresh_thread() self.assertEqual(self.thread.replies, 2) poststracker.save_read(self.user, self.thread.first_post) for post in posts: poststracker.save_read(self.user, post) response = self.client.post( self.api_link, json.dumps({ 'new_thread': other_thread.get_absolute_url(), 'posts': [p.pk for p in posts], }), content_type="application/json", ) self.assertEqual(response.status_code, 200) other_thread = Thread.objects.get(pk=other_thread.pk) # postreads were removed postreads = self.user.postread_set.order_by('id') postreads_threads = list(postreads.values_list('thread_id', flat=True)) self.assertEqual(postreads_threads, [self.thread.pk]) postreads_categories = list( postreads.values_list('category_id', flat=True)) self.assertEqual(postreads_categories, [self.category.pk])
def test_merge_kitchensink(self): """api performs merge""" posts_ids = [p.id for p in Post.objects.all()] self.override_acl({ 'can_merge_threads': True, 'can_close_threads': True, 'can_hide_threads': 1, 'can_pin_threads': 2, }) thread = testutils.post_thread(category=self.category) poststracker.save_read(self.user, self.thread.first_post) poststracker.save_read(self.user, thread.first_post) self.user.subscription_set.create( thread=self.thread, category=self.thread.category, last_read_on=self.thread.last_post_on, send_email=False, ) self.user.subscription_set.create( thread=thread, category=thread.category, last_read_on=thread.last_post_on, send_email=False, ) response = self.client.post( self.api_link, json.dumps({ 'threads': [self.thread.id, thread.id], 'title': 'Merged thread!', 'category': self.category.id, 'is_closed': 1, 'is_hidden': 1, 'weight': 2, }), content_type="application/json", ) self.assertEqual(response.status_code, 200) # is response json with new thread? response_json = response.json() new_thread = Thread.objects.get(pk=response_json['id']) new_thread.is_read = False new_thread.subscription = None self.assertEqual(new_thread.weight, 2) self.assertTrue(new_thread.is_closed) self.assertTrue(new_thread.is_hidden) add_acl(self.user, new_thread.category) add_acl(self.user, new_thread) self.assertEqual(response_json, ThreadsListSerializer(new_thread).data) # did posts move to new thread? for post in Post.objects.filter(id__in=posts_ids): self.assertEqual(post.thread_id, new_thread.id) # are old threads gone? self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk]) # posts reads are kept postreads = self.user.postread_set.filter( post__is_event=False).order_by('id') self.assertEqual(list(postreads.values_list('post_id', flat=True)), [self.thread.first_post_id, thread.first_post_id]) self.assertEqual(postreads.filter(thread=new_thread).count(), 2) self.assertEqual(postreads.filter(category=self.category).count(), 2) # subscriptions are kept self.assertEqual(self.user.subscription_set.count(), 1) self.user.subscription_set.get(thread=new_thread) self.user.subscription_set.get(category=self.category)