diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 0a90c9f4..0e07905b 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -273,9 +273,10 @@ def add_status_on_create(sender, instance, created, *args, **kwargs): created_date__lt=instance.created_date, ) for stream in streams.values(): - stream.remove_object_from_related_stores(boosted) + audience = stream.get_stores_for_object(instance) + stream.remove_object_from_related_stores(boosted, stores=audience) for status in old_versions: - stream.remove_object_from_related_stores(status) + stream.remove_object_from_related_stores(status, stores=audience) @receiver(signals.post_delete, sender=models.Boost) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index e96d7bc7..8a9bdfef 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -36,10 +36,11 @@ class RedisStore(ABC): # and go! return pipeline.execute() - def remove_object_from_related_stores(self, obj): + def remove_object_from_related_stores(self, obj, stores=None): """remove an object from all stores""" + stores = stores or self.get_stores_for_object(obj) pipeline = r.pipeline() - for store in self.get_stores_for_object(obj): + for store in stores: pipeline.zrem(store, -1, obj.id) pipeline.execute() diff --git a/bookwyrm/tests/test_activitystreams.py b/bookwyrm/tests/test_activitystreams.py index ac57d8b3..56ba844a 100644 --- a/bookwyrm/tests/test_activitystreams.py +++ b/bookwyrm/tests/test_activitystreams.py @@ -8,6 +8,7 @@ from bookwyrm import activitystreams, models @patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.BooksStream.add_book_statuses") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +# pylint: disable=too-many-public-methods class Activitystreams(TestCase): """using redis to build activity streams""" @@ -286,3 +287,76 @@ class Activitystreams(TestCase): # yes book, yes audience result = activitystreams.BooksStream().get_statuses_for_user(self.local_user) self.assertEqual(list(result), [status]) + + @patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores") + @patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores") + def test_boost_to_another_timeline(self, *_): + """add a boost and deduplicate the boosted status on the timeline""" + status = models.Status.objects.create(user=self.local_user, content="hi") + with patch( + "bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores" + ): + boost = models.Boost.objects.create( + boosted_status=status, + user=self.another_user, + ) + with patch( + "bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores" + ) as mock: + activitystreams.add_status_on_create(models.Boost, boost, True) + self.assertTrue(mock.called) + call_args = mock.call_args + self.assertEqual(call_args[0][0], status) + self.assertEqual( + call_args[1]["stores"], ["{:d}-home".format(self.another_user.id)] + ) + + @patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores") + @patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores") + def test_boost_to_following_timeline(self, *_): + """add a boost and deduplicate the boosted status on the timeline""" + self.local_user.following.add(self.another_user) + status = models.Status.objects.create(user=self.local_user, content="hi") + with patch( + "bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores" + ): + boost = models.Boost.objects.create( + boosted_status=status, + user=self.another_user, + ) + with patch( + "bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores" + ) as mock: + activitystreams.add_status_on_create(models.Boost, boost, True) + self.assertTrue(mock.called) + call_args = mock.call_args + self.assertEqual(call_args[0][0], status) + self.assertTrue( + "{:d}-home".format(self.another_user.id) in call_args[1]["stores"] + ) + self.assertTrue( + "{:d}-home".format(self.local_user.id) in call_args[1]["stores"] + ) + + @patch("bookwyrm.activitystreams.LocalStream.remove_object_from_related_stores") + @patch("bookwyrm.activitystreams.BooksStream.remove_object_from_related_stores") + def test_boost_to_same_timeline(self, *_): + """add a boost and deduplicate the boosted status on the timeline""" + status = models.Status.objects.create(user=self.local_user, content="hi") + with patch( + "bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores" + ): + boost = models.Boost.objects.create( + boosted_status=status, + user=self.local_user, + ) + with patch( + "bookwyrm.activitystreams.HomeStream.remove_object_from_related_stores" + ) as mock: + activitystreams.add_status_on_create(models.Boost, boost, True) + self.assertTrue(mock.called) + call_args = mock.call_args + self.assertEqual(call_args[0][0], status) + self.assertEqual( + call_args[1]["stores"], ["{:d}-home".format(self.local_user.id)] + )