diff --git a/.env.example b/.env.example index d2b43e171..91516422f 100644 --- a/.env.example +++ b/.env.example @@ -23,16 +23,10 @@ POSTGRES_USER=fedireads POSTGRES_DB=fedireads POSTGRES_HOST=db -# Redis as activitystreams manager -REDIS_ACTIVITY_HOST=redis_activity -REDIS_ACTIVITY_PORT=6379 -REDIS_ACTIVITY_PASSWORD=redispassword345 - -# Redis as celery broker -REDIS_BROKER_PORT=6379 -REDIS_BROKER_PASSWORD=redispassword123 -CELERY_BROKER=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 -CELERY_RESULT_BACKEND=redis://:${REDIS_BROKER_PASSWORD}@redis_broker:${REDIS_BROKER_PORT}/0 +REDIS_PORT=6379 +REDIS_PASSWORD=redispassword123 +CELERY_BROKER=redis://:${REDIS_PASSWORD}@redis:${REDIS_PORT}/0 +CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:${REDIS_PORT}/0 FLOWER_PORT=8888 FLOWER_USER=mouse diff --git a/README.md b/README.md index e798fedf5..e6a75375d 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ Web backend - [ActivityPub](http://activitypub.rocks/) federation - [Celery](http://celeryproject.org/) task queuing - [Redis](https://redis.io/) task backend -- [Redis (again)](https://redis.io/) activity stream manager Front end - Django templates @@ -237,11 +236,6 @@ When there are changes available in the production branch, you can install and g - `docker-compose exec web python manage.py collectstatic --no-input` loads any updated static files (such as the JavaScript and CSS) - `docker-compose restart` reloads the docker containers -### Re-building activity streams - -If something goes awry with user timelines, and you want to re-create them en mass, there's a management command for that: -`docker-compose run --rm web python manage.py rebuild_feeds` - ### Port Conflicts BookWyrm has multiple services that run on their default ports. diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py deleted file mode 100644 index 5968c5d14..000000000 --- a/bookwyrm/activitystreams.py +++ /dev/null @@ -1,281 +0,0 @@ -""" access the activity streams stored in redis """ -from abc import ABC -from django.dispatch import receiver -from django.db.models import signals, Q -import redis - -from bookwyrm import models, settings -from bookwyrm.views.helpers import privacy_filter - -r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, - port=settings.REDIS_ACTIVITY_PORT, - password=settings.REDIS_ACTIVITY_PASSWORD, - db=0, -) - - -class ActivityStream(ABC): - """ a category of activity stream (like home, local, federated) """ - - def stream_id(self, user): - """ the redis key for this user's instance of this stream """ - return "{}-{}".format(user.id, self.key) - - def unread_id(self, user): - """ the redis key for this user's unread count for this stream """ - return "{}-unread".format(self.stream_id(user)) - - def add_status(self, status): - """ add a status to users' feeds """ - # we want to do this as a bulk operation, hence "pipeline" - pipeline = r.pipeline() - for user in self.stream_users(status): - # add the status to the feed - pipeline.lpush(self.stream_id(user), status.id) - pipeline.ltrim(self.stream_id(user), 0, settings.MAX_STREAM_LENGTH) - - # add to the unread status count - pipeline.incr(self.unread_id(user)) - # and go! - pipeline.execute() - - def remove_status(self, status): - """ remove a status from all feeds """ - pipeline = r.pipeline() - for user in self.stream_users(status): - pipeline.lrem(self.stream_id(user), -1, status.id) - pipeline.execute() - - def add_user_statuses(self, viewer, user): - """ add a user's statuses to another user's feed """ - pipeline = r.pipeline() - for status in user.status_set.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lpush(self.stream_id(viewer), status.id) - pipeline.execute() - - def remove_user_statuses(self, viewer, user): - """ remove a user's status from another user's feed """ - pipeline = r.pipeline() - for status in user.status_set.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lrem(self.stream_id(viewer), -1, status.id) - pipeline.execute() - - def get_activity_stream(self, user): - """ load the ids for statuses to be displayed """ - # clear unreads for this feed - r.set(self.unread_id(user), 0) - - statuses = r.lrange(self.stream_id(user), 0, -1) - return ( - models.Status.objects.select_subclasses() - .filter(id__in=statuses) - .order_by("-published_date") - ) - - def get_unread_count(self, user): - """ get the unread status count for this user's feed """ - return int(r.get(self.unread_id(user))) - - def populate_stream(self, user): - """ go from zero to a timeline """ - pipeline = r.pipeline() - statuses = self.stream_statuses(user) - - stream_id = self.stream_id(user) - for status in statuses.all()[: settings.MAX_STREAM_LENGTH]: - pipeline.lpush(stream_id, status.id) - pipeline.execute() - - def stream_users(self, status): # pylint: disable=no-self-use - """ given a status, what users should see it """ - # direct messages don't appeard in feeds, direct comments/reviews/etc do - if status.privacy == "direct" and status.status_type == "Note": - return None - - # everybody who could plausibly see this status - audience = models.User.objects.filter( - is_active=True, - local=True, # we only create feeds for users of this instance - ).exclude( - Q(id__in=status.user.blocks.all()) | Q(blocks=status.user) # not blocked - ) - - # only visible to the poster and mentioned users - if status.privacy == "direct": - audience = audience.filter( - Q(id=status.user.id) # if the user is the post's author - | Q(id__in=status.mention_users.all()) # if the user is mentioned - ) - # only visible to the poster's followers and tagged users - elif status.privacy == "followers": - audience = audience.filter( - Q(id=status.user.id) # if the user is the post's author - | Q(following=status.user) # if the user is following the author - ) - return audience - - def stream_statuses(self, user): # pylint: disable=no-self-use - """ given a user, what statuses should they see on this stream """ - return privacy_filter( - user, - models.Status.objects.select_subclasses(), - privacy_levels=["public", "unlisted", "followers"], - ) - - -class HomeStream(ActivityStream): - """ users you follow """ - - key = "home" - - def stream_users(self, status): - audience = super().stream_users(status) - return audience.filter( - Q(id=status.user.id) # if the user is the post's author - | Q(following=status.user) # if the user is following the author - ) - - def stream_statuses(self, user): - return privacy_filter( - user, - models.Status.objects.select_subclasses(), - privacy_levels=["public", "unlisted", "followers"], - following_only=True, - ) - - -class LocalStream(ActivityStream): - """ users you follow """ - - key = "local" - - def stream_users(self, status): - # this stream wants no part in non-public statuses - if status.privacy != "public" or not status.user.local: - return None - return super().stream_users(status) - - def stream_statuses(self, user): - # all public statuses by a local user - return privacy_filter( - user, - models.Status.objects.select_subclasses().filter(user__local=True), - privacy_levels=["public"], - ) - - -class FederatedStream(ActivityStream): - """ users you follow """ - - key = "federated" - - def stream_users(self, status): - # this stream wants no part in non-public statuses - if status.privacy != "public": - return None - return super().stream_users(status) - - def stream_statuses(self, user): - return privacy_filter( - user, - models.Status.objects.select_subclasses(), - privacy_levels=["public"], - ) - - -streams = { - "home": HomeStream(), - "local": LocalStream(), - "federated": FederatedStream(), -} - - -@receiver(signals.post_save) -# pylint: disable=unused-argument -def add_status_on_create(sender, instance, created, *args, **kwargs): - """ add newly created statuses to activity feeds """ - # we're only interested in new statuses - if not issubclass(sender, models.Status): - return - - if instance.deleted: - for stream in streams.values(): - stream.remove_status(instance) - return - - if not created: - return - - # iterates through Home, Local, Federated - for stream in streams.values(): - stream.add_status(instance) - - -@receiver(signals.post_delete, sender=models.Boost) -# pylint: disable=unused-argument -def remove_boost_on_delete(sender, instance, *args, **kwargs): - """ boosts are deleted """ - # we're only interested in new statuses - for stream in streams.values(): - stream.remove_status(instance) - - -@receiver(signals.post_save, sender=models.UserFollows) -# pylint: disable=unused-argument -def add_statuses_on_follow(sender, instance, created, *args, **kwargs): - """ add a newly followed user's statuses to feeds """ - if not created or not instance.user_subject.local: - return - HomeStream().add_user_statuses(instance.user_subject, instance.user_object) - - -@receiver(signals.post_delete, sender=models.UserFollows) -# pylint: disable=unused-argument -def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): - """ remove statuses from a feed on unfollow """ - if not instance.user_subject.local: - return - HomeStream().remove_user_statuses(instance.user_subject, instance.user_object) - - -@receiver(signals.post_save, sender=models.UserBlocks) -# pylint: disable=unused-argument -def remove_statuses_on_block(sender, instance, *args, **kwargs): - """ remove statuses from all feeds on block """ - # blocks apply ot all feeds - if instance.user_subject.local: - for stream in streams.values(): - stream.remove_user_statuses(instance.user_subject, instance.user_object) - - # and in both directions - if instance.user_object.local: - for stream in streams.values(): - stream.remove_user_statuses(instance.user_object, instance.user_subject) - - -@receiver(signals.post_delete, sender=models.UserBlocks) -# pylint: disable=unused-argument -def add_statuses_on_unblock(sender, instance, *args, **kwargs): - """ remove statuses from all feeds on block """ - public_streams = [LocalStream(), FederatedStream()] - # add statuses back to streams with statuses from anyone - if instance.user_subject.local: - for stream in public_streams: - stream.add_user_statuses(instance.user_subject, instance.user_object) - - # add statuses back to streams with statuses from anyone - if instance.user_object.local: - for stream in public_streams: - stream.add_user_statuses(instance.user_object, instance.user_subject) - - -@receiver(signals.post_save, sender=models.User) -# pylint: disable=unused-argument -def populate_feed_on_account_create(sender, instance, created, *args, **kwargs): - """ build a user's feeds when they join """ - if not created or not instance.local: - return - - for stream in streams.values(): - stream.populate_stream(instance) diff --git a/bookwyrm/management/commands/rebuild_feeds.py b/bookwyrm/management/commands/rebuild_feeds.py deleted file mode 100644 index 70d4dd981..000000000 --- a/bookwyrm/management/commands/rebuild_feeds.py +++ /dev/null @@ -1,36 +0,0 @@ -""" Delete and re-create user feeds """ -from django.core.management.base import BaseCommand -import redis - -from bookwyrm import activitystreams, models, settings - -r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 -) - - -def erase_feeds(): - """ throw the whole redis away """ - r.flushall() - - -def create_feeds(): - """ build all the fields for all the users """ - users = models.User.objects.filter( - local=True, - is_active=True, - ) - for user in users: - for stream in activitystreams.streams.values(): - stream.populate_stream(user) - - -class Command(BaseCommand): - """ start all over with user feeds """ - - help = "Delete and re-create all the user feeds" - # pylint: disable=no-self-use,unused-argument - def handle(self, *args, **options): - """ run feed builder """ - erase_feeds() - create_feeds() diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index cb2fc851e..60e5da0ad 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -34,7 +34,7 @@ class BookWyrmModel(models.Model): @receiver(models.signals.post_save) # pylint: disable=unused-argument -def set_remote_id(sender, instance, created, *args, **kwargs): +def execute_after_save(sender, instance, created, *args, **kwargs): """ set the remote_id after save (when the id is available) """ if not created or not hasattr(instance, "get_remote_id"): return diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 998d7bed5..df99d2165 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -62,7 +62,7 @@ class UserFollows(ActivityMixin, UserRelationship): status = "follows" - def to_activity(self): # pylint: disable=arguments-differ + def to_activity(self): """ overrides default to manually set serializer """ return activitypub.Follow(**generate_activity(self)) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 77ac4b582..904ce461d 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -114,7 +114,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): return list(set(mentions)) @classmethod - def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements + def ignore_activity(cls, activity): """ keep notes if they are replies to existing statuses """ if activity.type == "Announce": try: diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 0f010da27..bcff58287 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -92,13 +92,6 @@ TEMPLATES = [ WSGI_APPLICATION = "bookwyrm.wsgi.application" -# redis/activity streams settings -REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost") -REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379) -REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD") - -MAX_STREAM_LENGTH = env("MAX_STREAM_LENGTH", 200) -STREAMS = ["home", "local", "federated"] # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index 1f26aba63..d390f482f 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -61,9 +61,9 @@ function polling(el, delay) { function updateCountElement(el, data) { const currentCount = el.innerText; - const count = data.count; + const count = data[el.getAttribute('data-poll')]; if (count != currentCount) { - addRemoveClass(el.closest('[data-poll-wrapper]'), 'hidden', count < 1); + addRemoveClass(el, 'hidden', count < 1); el.innerText = count; } } diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 379bf9272..76afea7e7 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -3,15 +3,7 @@ {% load bookwyrm_tags %} {% block panel %} -
{% trans "There aren't any activities right now! Try following a user to get started" %}
diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index d08b18202..e57f61520 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -139,8 +139,8 @@ {% trans "Notifications" %} - - {{ request.user | notification_count }} + + {{ request.user | notification_count }} diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 8ec0b703f..f3d3decde 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -19,7 +19,6 @@ from bookwyrm.activitypub import ActivitySerializerError from bookwyrm import models -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class BaseActivity(TestCase): """ the super class for model-linked activitypub dataclasses """ @@ -44,24 +43,24 @@ class BaseActivity(TestCase): image.save(output, format=image.format) self.image_data = output.getvalue() - def test_init(self, _): + def test_init(self): """ simple successfuly init """ instance = ActivityObject(id="a", type="b") self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "type")) - def test_init_missing(self, _): + def test_init_missing(self): """ init with missing required params """ with self.assertRaises(ActivitySerializerError): ActivityObject() - def test_init_extra_fields(self, _): + def test_init_extra_fields(self): """ init ignoring additional fields """ instance = ActivityObject(id="a", type="b", fish="c") self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "type")) - def test_init_default_field(self, _): + def test_init_default_field(self): """ replace an existing required field with a default field """ @dataclass(init=False) @@ -74,7 +73,7 @@ class BaseActivity(TestCase): self.assertEqual(instance.id, "a") self.assertEqual(instance.type, "TestObject") - def test_serialize(self, _): + def test_serialize(self): """ simple function for converting dataclass to dict """ instance = ActivityObject(id="a", type="b") serialized = instance.serialize() @@ -83,7 +82,7 @@ class BaseActivity(TestCase): self.assertEqual(serialized["type"], "b") @responses.activate - def test_resolve_remote_id(self, _): + def test_resolve_remote_id(self): """ look up or load remote data """ # existing item result = resolve_remote_id("http://example.com/a/b", model=models.User) @@ -105,14 +104,14 @@ class BaseActivity(TestCase): self.assertEqual(result.remote_id, "https://example.com/user/mouse") self.assertEqual(result.name, "MOUSE?? MOUSE!!") - def test_to_model_invalid_model(self, _): + def test_to_model_invalid_model(self): """ catch mismatch between activity type and model type """ instance = ActivityObject(id="a", type="b") with self.assertRaises(ActivitySerializerError): instance.to_model(model=models.User) @responses.activate - def test_to_model_image(self, _): + def test_to_model_image(self): """ update an image field """ activity = activitypub.Person( id=self.user.remote_id, @@ -145,7 +144,7 @@ class BaseActivity(TestCase): self.assertEqual(self.user.name, "New Name") self.assertEqual(self.user.key_pair.public_key, "hi") - def test_to_model_many_to_many(self, _): + def test_to_model_many_to_many(self): """ annoying that these all need special handling """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( @@ -176,7 +175,7 @@ class BaseActivity(TestCase): self.assertEqual(status.mention_books.first(), book) @responses.activate - def test_to_model_one_to_many(self, _): + def test_to_model_one_to_many(self): """these are reversed relationships, where the secondary object keys the primary object but not vice versa""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -215,7 +214,7 @@ class BaseActivity(TestCase): self.assertIsNone(status.attachments.first()) @responses.activate - def test_set_related_field(self, _): + def test_set_related_field(self): """ celery task to add back-references to created objects """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 0d1acd978..930f3a533 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -13,7 +13,6 @@ from bookwyrm.models.activitypub_mixin import ActivitypubMixin from bookwyrm.models.activitypub_mixin import ActivityMixin, ObjectMixin -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class ActivitypubMixins(TestCase): """ functionality shared across models """ @@ -45,7 +44,7 @@ class ActivitypubMixins(TestCase): } # ActivitypubMixin - def test_to_activity(self, _): + def test_to_activity(self): """ model to ActivityPub json """ @dataclass(init=False) @@ -66,7 +65,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(activity["id"], "https://www.example.com/test") self.assertEqual(activity["type"], "Test") - def test_find_existing_by_remote_id(self, _): + def test_find_existing_by_remote_id(self): """ attempt to match a remote id to an object in the db """ # uses a different remote id scheme # this isn't really part of this test directly but it's helpful to state @@ -99,7 +98,7 @@ class ActivitypubMixins(TestCase): # test subclass match result = models.Status.find_existing_by_remote_id("https://comment.net") - def test_find_existing(self, _): + def test_find_existing(self): """ match a blob of data to a model """ book = models.Edition.objects.create( title="Test edition", @@ -109,7 +108,7 @@ class ActivitypubMixins(TestCase): result = models.Edition.find_existing({"openlibraryKey": "OL1234"}) self.assertEqual(result, book) - def test_get_recipients_public_object(self, _): + def test_get_recipients_public_object(self): """ determines the recipients for an object's broadcast """ MockSelf = namedtuple("Self", ("privacy")) mock_self = MockSelf("public") @@ -117,7 +116,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_no_followers(self, _): + def test_get_recipients_public_user_object_no_followers(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -125,7 +124,7 @@ class ActivitypubMixins(TestCase): recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 0) - def test_get_recipients_public_user_object(self, _): + def test_get_recipients_public_user_object(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -135,7 +134,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_with_mention(self, _): + def test_get_recipients_public_user_object_with_mention(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -158,7 +157,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(recipients[0], another_remote_user.inbox) self.assertEqual(recipients[1], self.remote_user.inbox) - def test_get_recipients_direct(self, _): + def test_get_recipients_direct(self): """ determines the recipients for a user's object broadcast """ MockSelf = namedtuple("Self", ("privacy", "user")) mock_self = MockSelf("public", self.local_user) @@ -180,7 +179,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], another_remote_user.inbox) - def test_get_recipients_combine_inboxes(self, _): + def test_get_recipients_combine_inboxes(self): """ should combine users with the same shared_inbox """ self.remote_user.shared_inbox = "http://example.com/inbox" self.remote_user.save(broadcast=False) @@ -204,7 +203,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], "http://example.com/inbox") - def test_get_recipients_software(self, _): + def test_get_recipients_software(self): """ should differentiate between bookwyrm and other remote users """ with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( @@ -234,7 +233,7 @@ class ActivitypubMixins(TestCase): self.assertEqual(recipients[0], another_remote_user.inbox) # ObjectMixin - def test_object_save_create(self, _): + def test_object_save_create(self): """ should save uneventufully when broadcast is disabled """ class Success(Exception): @@ -265,7 +264,7 @@ class ActivitypubMixins(TestCase): ObjectModel(user=self.local_user).save(broadcast=False) ObjectModel(user=None).save() - def test_object_save_update(self, _): + def test_object_save_update(self): """ should save uneventufully when broadcast is disabled """ class Success(Exception): @@ -291,7 +290,7 @@ class ActivitypubMixins(TestCase): with self.assertRaises(Success): UpdateObjectModel(id=1, last_edited_by=self.local_user).save() - def test_object_save_delete(self, _): + def test_object_save_delete(self): """ should create delete activities when objects are deleted by flag """ class ActivitySuccess(Exception): @@ -313,7 +312,7 @@ class ActivitypubMixins(TestCase): with self.assertRaises(ActivitySuccess): DeletableObjectModel(id=1, user=self.local_user, deleted=True).save() - def test_to_delete_activity(self, _): + def test_to_delete_activity(self): """ wrapper for Delete activity """ MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( @@ -328,7 +327,7 @@ class ActivitypubMixins(TestCase): activity["cc"], ["https://www.w3.org/ns/activitystreams#Public"] ) - def test_to_update_activity(self, _): + def test_to_update_activity(self): """ ditto above but for Update """ MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( @@ -346,7 +345,7 @@ class ActivitypubMixins(TestCase): self.assertIsInstance(activity["object"], dict) # Activity mixin - def test_to_undo_activity(self, _): + def test_to_undo_activity(self): """ and again, for Undo """ MockSelf = namedtuple("Self", ("remote_id", "to_activity", "user")) mock_self = MockSelf( diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index 25a2e7ee6..4479d1560 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -27,18 +27,18 @@ class BaseModel(TestCase): expected = instance.get_remote_id() self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN) - def test_set_remote_id(self): + def test_execute_after_save(self): """ this function sets remote ids after creation """ # using Work because it BookWrymModel is abstract and this requires save # Work is a relatively not-fancy model. instance = models.Work.objects.create(title="work title") instance.remote_id = None - base_model.set_remote_id(None, instance, True) + base_model.execute_after_save(None, instance, True) self.assertEqual( instance.remote_id, "https://%s/book/%d" % (DOMAIN, instance.id) ) # shouldn't set remote_id if it's not created instance.remote_id = None - base_model.set_remote_id(None, instance, False) + base_model.execute_after_save(None, instance, False) self.assertIsNone(instance.remote_id) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 18bb028ff..28faf52c3 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -185,8 +185,7 @@ class ActivitypubFields(TestCase): self.assertEqual(model_instance.privacy_field, "unlisted") @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_privacy_field_set_activity_from_field(self, *_): + def test_privacy_field_set_activity_from_field(self, _): """ translate between to/cc fields and privacy """ user = User.objects.create_user( "rat", "rat@rat.rat", "ratword", local=True, localname="rat" diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 54fe7fee9..1dcf56339 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -15,7 +15,6 @@ from bookwyrm import activitypub, models, settings # pylint: disable=too-many-public-methods @patch("bookwyrm.models.Status.broadcast") -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class Status(TestCase): """ lotta types of statuses """ @@ -45,14 +44,14 @@ class Status(TestCase): image.save(output, format=image.format) self.book.cover.save("test.jpg", ContentFile(output.getvalue())) - def test_status_generated_fields(self, *_): + def test_status_generated_fields(self, _): """ setting remote id """ status = models.Status.objects.create(content="bleh", user=self.local_user) expected_id = "https://%s/user/mouse/status/%d" % (settings.DOMAIN, status.id) self.assertEqual(status.remote_id, expected_id) self.assertEqual(status.privacy, "public") - def test_replies(self, *_): + def test_replies(self, _): """ get a list of replies """ parent = models.Status.objects.create(content="hi", user=self.local_user) child = models.Status.objects.create( @@ -71,7 +70,7 @@ class Status(TestCase): # should select subclasses self.assertIsInstance(replies.last(), models.Review) - def test_status_type(self, *_): + def test_status_type(self, _): """ class name """ self.assertEqual(models.Status().status_type, "Note") self.assertEqual(models.Review().status_type, "Review") @@ -79,14 +78,14 @@ class Status(TestCase): self.assertEqual(models.Comment().status_type, "Comment") self.assertEqual(models.Boost().status_type, "Announce") - def test_boostable(self, *_): + def test_boostable(self, _): """ can a status be boosted, based on privacy """ self.assertTrue(models.Status(privacy="public").boostable) self.assertTrue(models.Status(privacy="unlisted").boostable) self.assertFalse(models.Status(privacy="followers").boostable) self.assertFalse(models.Status(privacy="direct").boostable) - def test_to_replies(self, *_): + def test_to_replies(self, _): """ activitypub replies collection """ parent = models.Status.objects.create(content="hi", user=self.local_user) child = models.Status.objects.create( @@ -103,7 +102,7 @@ class Status(TestCase): self.assertEqual(replies["id"], "%s/replies" % parent.remote_id) self.assertEqual(replies["totalItems"], 2) - def test_status_to_activity(self, *_): + def test_status_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Status.objects.create( content="test content", user=self.local_user @@ -114,21 +113,20 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["sensitive"], False) - def test_status_to_activity_tombstone(self, *_): + def test_status_to_activity_tombstone(self, _): """ subclass of the base model version with a "pure" serializer """ - with patch("bookwyrm.activitystreams.ActivityStream.remove_status"): - status = models.Status.objects.create( - content="test content", - user=self.local_user, - deleted=True, - deleted_date=timezone.now(), - ) + status = models.Status.objects.create( + content="test content", + user=self.local_user, + deleted=True, + deleted_date=timezone.now(), + ) activity = status.to_activity() self.assertEqual(activity["id"], status.remote_id) self.assertEqual(activity["type"], "Tombstone") self.assertFalse(hasattr(activity, "content")) - def test_status_to_pure_activity(self, *_): + def test_status_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Status.objects.create( content="test content", user=self.local_user @@ -140,7 +138,7 @@ class Status(TestCase): self.assertEqual(activity["sensitive"], False) self.assertEqual(activity["attachment"], []) - def test_generated_note_to_activity(self, *_): + def test_generated_note_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.GeneratedNote.objects.create( content="test content", user=self.local_user @@ -154,7 +152,7 @@ class Status(TestCase): self.assertEqual(activity["sensitive"], False) self.assertEqual(len(activity["tag"]), 2) - def test_generated_note_to_pure_activity(self, *_): + def test_generated_note_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.GeneratedNote.objects.create( content="test content", user=self.local_user @@ -178,7 +176,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_comment_to_activity(self, *_): + def test_comment_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Comment.objects.create( content="test content", user=self.local_user, book=self.book @@ -189,7 +187,7 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["inReplyToBook"], self.book.remote_id) - def test_comment_to_pure_activity(self, *_): + def test_comment_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Comment.objects.create( content="test content", user=self.local_user, book=self.book @@ -209,7 +207,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_quotation_to_activity(self, *_): + def test_quotation_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Quotation.objects.create( quote="a sickening sense", @@ -224,7 +222,7 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["inReplyToBook"], self.book.remote_id) - def test_quotation_to_pure_activity(self, *_): + def test_quotation_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Quotation.objects.create( quote="a sickening sense", @@ -247,7 +245,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_review_to_activity(self, *_): + def test_review_to_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Review.objects.create( name="Review name", @@ -264,7 +262,7 @@ class Status(TestCase): self.assertEqual(activity["content"], "test content") self.assertEqual(activity["inReplyToBook"], self.book.remote_id) - def test_review_to_pure_activity(self, *_): + def test_review_to_pure_activity(self, _): """ subclass of the base model version with a "pure" serializer """ status = models.Review.objects.create( name="Review name", @@ -287,7 +285,7 @@ class Status(TestCase): ) self.assertEqual(activity["attachment"][0].name, "Test Edition") - def test_favorite(self, *_): + def test_favorite(self, _): """ fav a status """ real_broadcast = models.Favorite.broadcast @@ -313,7 +311,7 @@ class Status(TestCase): self.assertEqual(activity["object"], status.remote_id) models.Favorite.broadcast = real_broadcast - def test_boost(self, *_): + def test_boost(self, _): """ boosting, this one's a bit fussy """ status = models.Status.objects.create( content="test content", user=self.local_user @@ -325,7 +323,7 @@ class Status(TestCase): self.assertEqual(activity["type"], "Announce") self.assertEqual(activity, boost.to_activity(pure=True)) - def test_notification(self, *_): + def test_notification(self, _): """ a simple model """ notification = models.Notification.objects.create( user=self.local_user, notification_type="FAVORITE" @@ -337,7 +335,7 @@ class Status(TestCase): user=self.local_user, notification_type="GLORB" ) - def test_create_broadcast(self, _, broadcast_mock): + def test_create_broadcast(self, broadcast_mock): """ should send out two verions of a status on create """ models.Comment.objects.create( content="hi", user=self.local_user, book=self.book @@ -357,7 +355,7 @@ class Status(TestCase): self.assertEqual(args["type"], "Create") self.assertEqual(args["object"]["type"], "Comment") - def test_recipients_with_mentions(self, *_): + def test_recipients_with_mentions(self, _): """ get recipients to broadcast a status """ status = models.GeneratedNote.objects.create( content="test content", user=self.local_user @@ -366,7 +364,7 @@ class Status(TestCase): self.assertEqual(status.recipients, [self.remote_user]) - def test_recipients_with_reply_parent(self, *_): + def test_recipients_with_reply_parent(self, _): """ get recipients to broadcast a status """ parent_status = models.GeneratedNote.objects.create( content="test content", user=self.remote_user @@ -377,7 +375,7 @@ class Status(TestCase): self.assertEqual(status.recipients, [self.remote_user]) - def test_recipients_with_reply_parent_and_mentions(self, *_): + def test_recipients_with_reply_parent_and_mentions(self, _): """ get recipients to broadcast a status """ parent_status = models.GeneratedNote.objects.create( content="test content", user=self.remote_user @@ -390,7 +388,7 @@ class Status(TestCase): self.assertEqual(status.recipients, [self.remote_user]) @responses.activate - def test_ignore_activity_boost(self, *_): + def test_ignore_activity_boost(self, _): """ don't bother with most remote statuses """ activity = activitypub.Announce( id="http://www.faraway.com/boost/12", diff --git a/bookwyrm/tests/test_activitystreams.py b/bookwyrm/tests/test_activitystreams.py deleted file mode 100644 index d7a3d4eb6..000000000 --- a/bookwyrm/tests/test_activitystreams.py +++ /dev/null @@ -1,204 +0,0 @@ -""" testing activitystreams """ -from unittest.mock import patch -from django.test import TestCase -from bookwyrm import activitystreams, models - - -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") -@patch("bookwyrm.activitystreams.ActivityStream.add_status") -class Activitystreams(TestCase): - """ using redis to build activity streams """ - - def setUp(self): - """ use a test csv """ - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" - ) - self.another_user = models.User.objects.create_user( - "nutria", "nutria@nutria.nutria", "password", local=True, localname="nutria" - ) - with patch("bookwyrm.models.user.set_remote_server.delay"): - self.remote_user = models.User.objects.create_user( - "rat", - "rat@rat.com", - "ratword", - local=False, - remote_id="https://example.com/users/rat", - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", - ) - self.book = models.Edition.objects.create(title="test book") - - class TestStream(activitystreams.ActivityStream): - """ test stream, don't have to do anything here """ - - key = "test" - - self.test_stream = TestStream() - - def test_activitystream_class_ids(self, *_): - """ the abstract base class for stream objects """ - self.assertEqual( - self.test_stream.stream_id(self.local_user), - "{}-test".format(self.local_user.id), - ) - self.assertEqual( - self.test_stream.unread_id(self.local_user), - "{}-test-unread".format(self.local_user.id), - ) - - def test_abstractstream_stream_users(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = self.test_stream.stream_users(status) - # remote users don't have feeds - self.assertFalse(self.remote_user in users) - self.assertTrue(self.local_user in users) - self.assertTrue(self.another_user in users) - - def test_abstractstream_stream_users_direct(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - ) - status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) - self.assertIsNone(users) - - status = models.Comment.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - book=self.book, - ) - status.mention_users.add(self.local_user) - users = self.test_stream.stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_abstractstream_stream_users_followers_remote_user(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, - content="hi", - privacy="followers", - ) - users = self.test_stream.stream_users(status) - self.assertFalse(users.exists()) - - def test_abstractstream_stream_users_followers_self(self, *_): - """ get a list of users that should see a status """ - status = models.Comment.objects.create( - user=self.local_user, - content="hi", - privacy="direct", - book=self.book, - ) - users = self.test_stream.stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_abstractstream_stream_users_followers_with_mention(self, *_): - """ get a list of users that should see a status """ - status = models.Comment.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - book=self.book, - ) - status.mention_users.add(self.local_user) - - users = self.test_stream.stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_abstractstream_stream_users_followers_with_relationship(self, *_): - """ get a list of users that should see a status """ - self.remote_user.followers.add(self.local_user) - status = models.Comment.objects.create( - user=self.remote_user, - content="hi", - privacy="direct", - book=self.book, - ) - users = self.test_stream.stream_users(status) - self.assertFalse(self.local_user in users) - self.assertFalse(self.another_user in users) - self.assertFalse(self.remote_user in users) - - def test_homestream_stream_users(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.HomeStream().stream_users(status) - self.assertFalse(users.exists()) - - def test_homestream_stream_users_with_mentions(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - status.mention_users.add(self.local_user) - users = activitystreams.HomeStream().stream_users(status) - self.assertFalse(self.local_user in users) - self.assertFalse(self.another_user in users) - - def test_homestream_stream_users_with_relationship(self, *_): - """ get a list of users that should see a status """ - self.remote_user.followers.add(self.local_user) - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.HomeStream().stream_users(status) - self.assertTrue(self.local_user in users) - self.assertFalse(self.another_user in users) - - def test_localstream_stream_users_remote_status(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.LocalStream().stream_users(status) - self.assertIsNone(users) - - def test_localstream_stream_users_local_status(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.local_user, content="hi", privacy="public" - ) - users = activitystreams.LocalStream().stream_users(status) - self.assertTrue(self.local_user in users) - self.assertTrue(self.another_user in users) - - def test_localstream_stream_users_unlisted(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.local_user, content="hi", privacy="unlisted" - ) - users = activitystreams.LocalStream().stream_users(status) - self.assertIsNone(users) - - def test_federatedstream_stream_users(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="public" - ) - users = activitystreams.FederatedStream().stream_users(status) - self.assertTrue(self.local_user in users) - self.assertTrue(self.another_user in users) - - def test_federatedstream_stream_users_unlisted(self, *_): - """ get a list of users that should see a status """ - status = models.Status.objects.create( - user=self.remote_user, content="hi", privacy="unlisted" - ) - users = activitystreams.FederatedStream().stream_users(status) - self.assertIsNone(users) diff --git a/bookwyrm/tests/test_goodreads_import.py b/bookwyrm/tests/test_goodreads_import.py index c06b49fc3..a62cfdd28 100644 --- a/bookwyrm/tests/test_goodreads_import.py +++ b/bookwyrm/tests/test_goodreads_import.py @@ -203,8 +203,7 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.finish_date.month, 10) self.assertEqual(readthrough.finish_date.day, 25) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_review(self, _): + def test_handle_imported_book_review(self): """ goodreads review import """ import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") diff --git a/bookwyrm/tests/test_librarything_import.py b/bookwyrm/tests/test_librarything_import.py index 54b8b4226..a8e4cfe4f 100644 --- a/bookwyrm/tests/test_librarything_import.py +++ b/bookwyrm/tests/test_librarything_import.py @@ -1,4 +1,5 @@ """ testing import """ +from collections import namedtuple import csv import pathlib from unittest.mock import patch @@ -15,8 +16,8 @@ class LibrarythingImport(TestCase): """ importing from librarything tsv """ def setUp(self): - """ use a test tsv """ self.importer = LibrarythingImporter() + """ use a test tsv """ datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") # Librarything generates latin encoded exports... @@ -199,8 +200,7 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.finish_date.month, 5) self.assertEqual(readthrough.finish_date.day, 8) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_review(self, _): + def test_handle_imported_book_review(self): """ librarything review import """ import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 61136c2eb..6bdbd04c0 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -10,7 +10,6 @@ from bookwyrm import models from bookwyrm.templatetags import bookwyrm_tags -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class TemplateTags(TestCase): """ lotta different things here """ @@ -33,34 +32,34 @@ class TemplateTags(TestCase): ) self.book = models.Edition.objects.create(title="Test Book") - def test_dict_key(self, _): + def test_dict_key(self): """ just getting a value out of a dict """ test_dict = {"a": 1, "b": 3} self.assertEqual(bookwyrm_tags.dict_key(test_dict, "a"), 1) self.assertEqual(bookwyrm_tags.dict_key(test_dict, "c"), 0) - def test_get_user_rating(self, _): + def test_get_user_rating(self): """ get a user's most recent rating of a book """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create(user=self.user, book=self.book, rating=3) self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3) - def test_get_user_rating_doesnt_exist(self, _): + def test_get_user_rating_doesnt_exist(self): """ there is no rating available """ self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0) - def test_get_user_identifer_local(self, _): + def test_get_user_identifer_local(self): """ fall back to the simplest uid available """ self.assertNotEqual(self.user.username, self.user.localname) self.assertEqual(bookwyrm_tags.get_user_identifier(self.user), "mouse") - def test_get_user_identifer_remote(self, _): + def test_get_user_identifer_remote(self): """ for a remote user, should be their full username """ self.assertEqual( bookwyrm_tags.get_user_identifier(self.remote_user), "rat@example.com" ) - def test_get_notification_count(self, _): + def test_get_notification_count(self): """ just countin' """ self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 0) @@ -73,7 +72,7 @@ class TemplateTags(TestCase): self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 2) - def test_get_replies(self, _): + def test_get_replies(self): """ direct replies to a status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Review.objects.create( @@ -85,13 +84,12 @@ class TemplateTags(TestCase): second_child = models.Status.objects.create( reply_parent=parent, user=self.user, content="hi" ) - with patch("bookwyrm.activitystreams.ActivityStream.remove_status"): - third_child = models.Status.objects.create( - reply_parent=parent, - user=self.user, - deleted=True, - deleted_date=timezone.now(), - ) + third_child = models.Status.objects.create( + reply_parent=parent, + user=self.user, + deleted=True, + deleted_date=timezone.now(), + ) replies = bookwyrm_tags.get_replies(parent) self.assertEqual(len(replies), 2) @@ -99,7 +97,7 @@ class TemplateTags(TestCase): self.assertTrue(second_child in replies) self.assertFalse(third_child in replies) - def test_get_parent(self, _): + def test_get_parent(self): """ get the reply parent of a status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Review.objects.create( @@ -113,7 +111,7 @@ class TemplateTags(TestCase): self.assertEqual(result, parent) self.assertIsInstance(result, models.Review) - def test_get_user_liked(self, _): + def test_get_user_liked(self): """ did a user like a status """ status = models.Review.objects.create(user=self.remote_user, book=self.book) @@ -122,7 +120,7 @@ class TemplateTags(TestCase): models.Favorite.objects.create(user=self.user, status=status) self.assertTrue(bookwyrm_tags.get_user_liked(self.user, status)) - def test_get_user_boosted(self, _): + def test_get_user_boosted(self): """ did a user boost a status """ status = models.Review.objects.create(user=self.remote_user, book=self.book) @@ -131,7 +129,7 @@ class TemplateTags(TestCase): models.Boost.objects.create(user=self.user, boosted_status=status) self.assertTrue(bookwyrm_tags.get_user_boosted(self.user, status)) - def test_follow_request_exists(self, _): + def test_follow_request_exists(self): """ does a user want to follow """ self.assertFalse( bookwyrm_tags.follow_request_exists(self.user, self.remote_user) @@ -149,7 +147,7 @@ class TemplateTags(TestCase): bookwyrm_tags.follow_request_exists(self.remote_user, self.user) ) - def test_get_boosted(self, _): + def test_get_boosted(self): """ load a boosted status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Review.objects.create(user=self.remote_user, book=self.book) @@ -158,7 +156,7 @@ class TemplateTags(TestCase): self.assertIsInstance(boosted, models.Review) self.assertEqual(boosted, status) - def test_get_book_description(self, _): + def test_get_book_description(self): """ grab it from the edition or the parent """ work = models.Work.objects.create(title="Test Work") self.book.parent_work = work @@ -174,12 +172,12 @@ class TemplateTags(TestCase): self.book.save() self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello") - def test_get_uuid(self, _): + def test_get_uuid(self): """ uuid functionality """ uuid = bookwyrm_tags.get_uuid("hi") self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid)) - def test_time_since(self, _): + def test_time_since(self): """ ultraconcise timestamps """ self.assertEqual(bookwyrm_tags.time_since("bleh"), "") @@ -209,7 +207,7 @@ class TemplateTags(TestCase): re.match(r"[A-Z][a-z]{2} \d?\d \d{4}", bookwyrm_tags.time_since(years_ago)) ) - def test_get_markdown(self, _): + def test_get_markdown(self): """ mardown format data """ result = bookwyrm_tags.get_markdown("_hi_") self.assertEqual(result, "hi
") @@ -217,13 +215,13 @@ class TemplateTags(TestCase): result = bookwyrm_tags.get_markdown("") self.assertEqual(result, "hi
") - def test_get_mentions(self, _): + def test_get_mentions(self): """ list of people mentioned """ status = models.Status.objects.create(content="hi", user=self.remote_user) result = bookwyrm_tags.get_mentions(status, self.user) self.assertEqual(result, "@rat@example.com ") - def test_get_status_preview_name(self, _): + def test_get_status_preview_name(self): """ status context string """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(content="hi", user=self.user) @@ -248,7 +246,7 @@ class TemplateTags(TestCase): result = bookwyrm_tags.get_status_preview_name(status) self.assertEqual(result, "quotation from Test Book") - def test_related_status(self, _): + def test_related_status(self): """ gets the subclass model for a notification status """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(content="hi", user=self.user) diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index 71583d708..60920e38b 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -7,7 +7,6 @@ from django.test.client import RequestFactory from bookwyrm import models, views -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class BlockViews(TestCase): """ view user and edit profile """ @@ -33,7 +32,7 @@ class BlockViews(TestCase): ) models.SiteSettings.objects.create() - def test_block_get(self, _): + def test_block_get(self): """ there are so many views, this just makes sure it LOADS """ view = views.Block.as_view() request = self.factory.get("") @@ -43,19 +42,20 @@ class BlockViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_block_post(self, _): + def test_block_post(self): """ create a "block" database entry from an activity """ view = views.Block.as_view() self.local_user.followers.add(self.remote_user) - models.UserFollowRequest.objects.create( - user_subject=self.local_user, user_object=self.remote_user - ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.UserFollowRequest.objects.create( + user_subject=self.local_user, user_object=self.remote_user + ) self.assertTrue(models.UserFollows.objects.exists()) self.assertTrue(models.UserFollowRequest.objects.exists()) request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.remove_user_statuses"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.remote_user.id) block = models.UserBlocks.objects.get() self.assertEqual(block.user_subject, self.local_user) @@ -64,13 +64,13 @@ class BlockViews(TestCase): self.assertFalse(models.UserFollows.objects.exists()) self.assertFalse(models.UserFollowRequest.objects.exists()) - def test_unblock(self, _): + def test_unblock(self): """ undo a block """ self.local_user.blocks.add(self.remote_user) request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_user_statuses"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.block.unblock(request, self.remote_user.id) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index 1ff995732..426684676 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -14,8 +14,6 @@ from bookwyrm import views from bookwyrm.activitypub import ActivitypubResponse -@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class FeedViews(TestCase): """ activity feed, statuses, dms """ @@ -36,7 +34,7 @@ class FeedViews(TestCase): ) models.SiteSettings.objects.create() - def test_feed(self, *_): + def test_feed(self): """ there are so many views, this just makes sure it LOADS """ view = views.Feed.as_view() request = self.factory.get("") @@ -46,7 +44,7 @@ class FeedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_status_page(self, *_): + def test_status_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.Status.as_view() with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -66,7 +64,7 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_status_page_with_image(self, *_): + def test_status_page_with_image(self): """ there are so many views, this just makes sure it LOADS """ view = views.Status.as_view() @@ -102,7 +100,7 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_replies_page(self, *_): + def test_replies_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.Replies.as_view() with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -122,7 +120,7 @@ class FeedViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_direct_messages_page(self, *_): + def test_direct_messages_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.DirectMessage.as_view() request = self.factory.get("") @@ -132,7 +130,7 @@ class FeedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_get_suggested_book(self, *_): + def test_get_suggested_book(self): """ gets books the ~*~ algorithm ~*~ thinks you want to post about """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( diff --git a/bookwyrm/tests/views/test_goal.py b/bookwyrm/tests/views/test_goal.py index cbe4fe015..990bb5c2b 100644 --- a/bookwyrm/tests/views/test_goal.py +++ b/bookwyrm/tests/views/test_goal.py @@ -102,8 +102,7 @@ class GoalViews(TestCase): result = view(request, self.local_user.localname, 2020) self.assertEqual(result.status_code, 404) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_create_goal(self, _): + def test_create_goal(self): """ create a new goal """ view = views.Goal.as_view() request = self.factory.post( diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 4fd7ce66d..bb4cf69c4 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -80,6 +80,113 @@ class ViewsHelpers(TestCase): request.headers = {"Accept": "Praise"} self.assertFalse(views.helpers.is_api_request(request)) + def test_get_activity_feed(self): + """ loads statuses """ + rat = models.User.objects.create_user( + "rat", "rat@rat.rat", "password", local=True + ) + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + public_status = models.Comment.objects.create( + content="public status", book=self.book, user=self.local_user + ) + direct_status = models.Status.objects.create( + content="direct", user=self.local_user, privacy="direct" + ) + + rat_public = models.Status.objects.create(content="blah blah", user=rat) + rat_unlisted = models.Status.objects.create( + content="blah blah", user=rat, privacy="unlisted" + ) + remote_status = models.Status.objects.create( + content="blah blah", user=self.remote_user + ) + followers_status = models.Status.objects.create( + content="blah", user=rat, privacy="followers" + ) + rat_mention = models.Status.objects.create( + content="blah blah blah", user=rat, privacy="followers" + ) + rat_mention.mention_users.set([self.local_user]) + + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "unlisted", "followers"], + following_only=True, + queryset=models.Comment.objects, + ) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], public_status) + + statuses = views.helpers.get_activity_feed( + self.local_user, privacy=["public", "followers"], local_only=True + ) + self.assertEqual(len(statuses), 2) + self.assertEqual(statuses[1], public_status) + self.assertEqual(statuses[0], rat_public) + + statuses = views.helpers.get_activity_feed(self.local_user, privacy=["direct"]) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], direct_status) + + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "followers"], + ) + self.assertEqual(len(statuses), 3) + self.assertEqual(statuses[2], public_status) + self.assertEqual(statuses[1], rat_public) + self.assertEqual(statuses[0], remote_status) + + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "unlisted", "followers"], + following_only=True, + ) + self.assertEqual(len(statuses), 2) + self.assertEqual(statuses[1], public_status) + self.assertEqual(statuses[0], rat_mention) + + rat.followers.add(self.local_user) + statuses = views.helpers.get_activity_feed( + self.local_user, + privacy=["public", "unlisted", "followers"], + following_only=True, + ) + self.assertEqual(len(statuses), 5) + self.assertEqual(statuses[4], public_status) + self.assertEqual(statuses[3], rat_public) + self.assertEqual(statuses[2], rat_unlisted) + self.assertEqual(statuses[1], followers_status) + self.assertEqual(statuses[0], rat_mention) + + def test_get_activity_feed_blocks(self): + """ feed generation with blocked users """ + rat = models.User.objects.create_user( + "rat", "rat@rat.rat", "password", local=True + ) + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + public_status = models.Comment.objects.create( + content="public status", book=self.book, user=self.local_user + ) + rat_public = models.Status.objects.create(content="blah blah", user=rat) + + statuses = views.helpers.get_activity_feed( + self.local_user, privacy=["public"] + ) + self.assertEqual(len(statuses), 2) + + # block relationship + rat.blocks.add(self.local_user) + statuses = views.helpers.get_activity_feed(self.local_user, privacy=["public"]) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], public_status) + + statuses = views.helpers.get_activity_feed(rat, privacy=["public"]) + self.assertEqual(len(statuses), 1) + self.assertEqual(statuses[0], rat_public) + def test_is_bookwyrm_request(self): """ checks if a request came from a bookwyrm instance """ request = self.factory.get("", {"q": "Test Book"}) @@ -134,8 +241,7 @@ class ViewsHelpers(TestCase): self.assertIsInstance(result, models.User) self.assertEqual(result.username, "mouse@example.com") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_to_read(self, _): + def test_handle_reading_status_to_read(self): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="to-read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -147,8 +253,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "wants to read") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_reading(self, _): + def test_handle_reading_status_reading(self): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="reading") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -160,8 +265,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "started reading") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_read(self, _): + def test_handle_reading_status_read(self): """ posts shelve activities """ shelf = self.local_user.shelf_set.get(identifier="read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -173,8 +277,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "finished reading") - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_reading_status_other(self, _): + def test_handle_reading_status_other(self): """ posts shelve activities """ with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( @@ -182,8 +285,7 @@ class ViewsHelpers(TestCase): ) self.assertFalse(models.GeneratedNote.objects.exists()) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_object_visible_to_user(self, _): + def test_object_visible_to_user(self): """ does a user have permission to view an object """ obj = models.Status.objects.create( content="hi", user=self.remote_user, privacy="public" @@ -211,8 +313,7 @@ class ViewsHelpers(TestCase): obj.mention_users.add(self.local_user) self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_object_visible_to_user_follower(self, _): + def test_object_visible_to_user_follower(self): """ what you can see if you follow a user """ self.remote_user.followers.add(self.local_user) obj = models.Status.objects.create( @@ -231,8 +332,7 @@ class ViewsHelpers(TestCase): obj.mention_users.add(self.local_user) self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_object_visible_to_user_blocked(self, _): + def test_object_visible_to_user_blocked(self): """ you can't see it if they block you """ self.remote_user.blocks.add(self.local_user) obj = models.Status.objects.create( diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 7d08a1e49..10f55f89b 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -38,12 +38,11 @@ class Inbox(TestCase): outbox="https://example.com/users/rat/outbox", ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - self.status = models.Status.objects.create( - user=self.local_user, - content="Test status", - remote_id="https://example.com/status/1", - ) + self.status = models.Status.objects.create( + user=self.local_user, + content="Test status", + remote_id="https://example.com/status/1", + ) self.create_json = { "id": "hi", @@ -140,9 +139,7 @@ class Inbox(TestCase): activity = self.create_json activity["object"] = status_data - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) status = models.Quotation.objects.get() self.assertEqual( @@ -169,9 +166,7 @@ class Inbox(TestCase): activity = self.create_json activity["object"] = status_data - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) status = models.Status.objects.last() self.assertEqual(status.content, "test content in note") self.assertEqual(status.mention_users.first(), self.local_user) @@ -192,9 +187,7 @@ class Inbox(TestCase): activity = self.create_json activity["object"] = status_data - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) status = models.Status.objects.last() self.assertEqual(status.content, "test content in note") self.assertEqual(status.reply_parent, self.status) @@ -225,7 +218,7 @@ class Inbox(TestCase): self.assertEqual(book_list.description, "summary text") self.assertEqual(book_list.remote_id, "https://example.com/list/22") - def test_handle_follow(self): + def test_handle_follow_x(self): """ remote user wants to follow local user """ activity = { "@context": "https://www.w3.org/ns/activitystreams", @@ -443,11 +436,7 @@ class Inbox(TestCase): "actor": self.remote_user.remote_id, "object": {"id": self.status.remote_id, "type": "Tombstone"}, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone status = models.Status.objects.get() self.assertTrue(status.deleted) @@ -476,11 +465,7 @@ class Inbox(TestCase): "actor": self.remote_user.remote_id, "object": {"id": self.status.remote_id, "type": "Tombstone"}, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone status = models.Status.objects.get() self.assertTrue(status.deleted) @@ -550,8 +535,7 @@ class Inbox(TestCase): views.inbox.activity_task(activity) self.assertEqual(models.Favorite.objects.count(), 0) - @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_boost(self, _): + def test_handle_boost(self): """ boost a status """ self.assertEqual(models.Notification.objects.count(), 0) activity = { @@ -576,8 +560,7 @@ class Inbox(TestCase): content="hi", user=self.remote_user, ) - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - status.save(broadcast=False) + status.save(broadcast=False) activity = { "type": "Announce", "id": "http://www.faraway.com/boost/12", @@ -592,10 +575,9 @@ class Inbox(TestCase): def test_handle_unboost(self): """ undo a boost """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - boost = models.Boost.objects.create( - boosted_status=self.status, user=self.remote_user - ) + boost = models.Boost.objects.create( + boosted_status=self.status, user=self.remote_user + ) activity = { "type": "Undo", "actor": "hi", @@ -609,11 +591,7 @@ class Inbox(TestCase): "object": self.status.remote_id, }, } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) def test_handle_unboost_unknown_boost(self): """ undo a boost """ @@ -885,11 +863,6 @@ class Inbox(TestCase): "object": "https://example.com/user/mouse", } - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_user_statuses" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) views.inbox.activity_task(activity) block = models.UserBlocks.objects.get() self.assertEqual(block.user_subject, self.remote_user) @@ -923,9 +896,5 @@ class Inbox(TestCase): "object": "https://example.com/user/mouse", }, } - with patch( - "bookwyrm.activitystreams.ActivityStream.add_user_statuses" - ) as redis_mock: - views.inbox.activity_task(activity) - self.assertTrue(redis_mock.called) + views.inbox.activity_task(activity) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index 40152c0fb..857f7061f 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -6,7 +6,6 @@ from django.test.client import RequestFactory from bookwyrm import models, views -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class InteractionViews(TestCase): """ viewing and creating statuses """ @@ -39,12 +38,12 @@ class InteractionViews(TestCase): parent_work=work, ) - def test_handle_favorite(self, _): + def test_handle_favorite(self): """ create and broadcast faving a status """ view = views.Favorite.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) @@ -57,12 +56,12 @@ class InteractionViews(TestCase): self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_user, self.remote_user) - def test_handle_unfavorite(self, _): + def test_handle_unfavorite(self): """ unfav a status """ view = views.Unfavorite.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") views.Favorite.as_view()(request, status.id) @@ -74,12 +73,12 @@ class InteractionViews(TestCase): self.assertEqual(models.Favorite.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) - def test_handle_boost(self, _): + def test_handle_boost(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.remote_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) @@ -95,12 +94,12 @@ class InteractionViews(TestCase): self.assertEqual(notification.related_user, self.remote_user) self.assertEqual(notification.related_status, status) - def test_handle_boost_unlisted(self, _): + def test_handle_boost_unlisted(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( user=self.local_user, content="hi", privacy="unlisted" ) @@ -110,12 +109,12 @@ class InteractionViews(TestCase): boost = models.Boost.objects.get() self.assertEqual(boost.privacy, "unlisted") - def test_handle_boost_private(self, _): + def test_handle_boost_private(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( user=self.local_user, content="hi", privacy="followers" ) @@ -123,35 +122,31 @@ class InteractionViews(TestCase): view(request, status.id) self.assertFalse(models.Boost.objects.exists()) - def test_handle_boost_twice(self, _): + def test_handle_boost_twice(self): """ boost a status """ view = views.Boost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) view(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) - def test_handle_unboost(self, broadcast_mock): + def test_handle_unboost(self): """ undo a boost """ view = views.Unboost.as_view() request = self.factory.post("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") views.Boost.as_view()(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1) - broadcast_mock.call_count = 0 - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) - self.assertEqual(broadcast_mock.call_count, 1) - self.assertTrue(redis_mock.called) + self.assertEqual(mock.call_count, 1) self.assertEqual(models.Boost.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) diff --git a/bookwyrm/tests/views/test_isbn.py b/bookwyrm/tests/views/test_isbn.py index 7f03a6109..c7ae1f39f 100644 --- a/bookwyrm/tests/views/test_isbn.py +++ b/bookwyrm/tests/views/test_isbn.py @@ -3,10 +3,12 @@ import json from unittest.mock import patch from django.http import JsonResponse +from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory from bookwyrm import models, views +from bookwyrm.connectors import abstract_connector from bookwyrm.settings import DOMAIN diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 2513fecc9..910e4a851 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -1,5 +1,4 @@ """ test for app action functionality """ -from unittest.mock import patch from django.contrib.auth.models import AnonymousUser from django.template.response import TemplateResponse from django.test import TestCase @@ -31,8 +30,7 @@ class LandingViews(TestCase): view = views.Home.as_view() request = self.factory.get("") request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream"): - result = view(request) + result = view(request) self.assertEqual(result.status_code, 200) result.render() diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index 0bcfde693..5934eb7c7 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -11,7 +11,6 @@ from bookwyrm.settings import USER_AGENT # pylint: disable=too-many-public-methods -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class OutboxView(TestCase): """ sends out activities """ @@ -33,19 +32,19 @@ class OutboxView(TestCase): parent_work=work, ) - def test_outbox(self, _): + def test_outbox(self): """ returns user's statuses """ request = self.factory.get("") result = views.Outbox.as_view()(request, "mouse") self.assertIsInstance(result, JsonResponse) - def test_outbox_bad_method(self, _): + def test_outbox_bad_method(self): """ can't POST to outbox """ request = self.factory.post("") result = views.Outbox.as_view()(request, "mouse") self.assertEqual(result.status_code, 405) - def test_outbox_unknown_user(self, _): + def test_outbox_unknown_user(self): """ should 404 for unknown and remote users """ request = self.factory.post("") result = views.Outbox.as_view()(request, "beepboop") @@ -53,9 +52,9 @@ class OutboxView(TestCase): result = views.Outbox.as_view()(request, "rat") self.assertEqual(result.status_code, 405) - def test_outbox_privacy(self, _): + def test_outbox_privacy(self): """ don't show dms et cetera in outbox """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Status.objects.create( content="PRIVATE!!", user=self.local_user, privacy="direct" ) @@ -76,9 +75,9 @@ class OutboxView(TestCase): self.assertEqual(data["type"], "OrderedCollection") self.assertEqual(data["totalItems"], 2) - def test_outbox_filter(self, _): + def test_outbox_filter(self): """ if we only care about reviews, only get reviews """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( content="look at this", name="hi", @@ -102,9 +101,9 @@ class OutboxView(TestCase): self.assertEqual(data["type"], "OrderedCollection") self.assertEqual(data["totalItems"], 1) - def test_outbox_bookwyrm_request_true(self, _): + def test_outbox_bookwyrm_request_true(self): """ should differentiate between bookwyrm and outside requests """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( name="hi", content="look at this", @@ -120,9 +119,9 @@ class OutboxView(TestCase): self.assertEqual(len(data["orderedItems"]), 1) self.assertEqual(data["orderedItems"][0]["type"], "Review") - def test_outbox_bookwyrm_request_false(self, _): + def test_outbox_bookwyrm_request_false(self): """ should differentiate between bookwyrm and outside requests """ - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( name="hi", content="look at this", diff --git a/bookwyrm/tests/views/test_reading.py b/bookwyrm/tests/views/test_reading.py index 4661e487e..96d1f1f4c 100644 --- a/bookwyrm/tests/views/test_reading.py +++ b/bookwyrm/tests/views/test_reading.py @@ -8,7 +8,6 @@ from django.utils import timezone from bookwyrm import models, views -@patch("bookwyrm.activitystreams.ActivityStream.add_status") class ReadingViews(TestCase): """ viewing and creating statuses """ @@ -40,7 +39,7 @@ class ReadingViews(TestCase): outbox="https://example.com/users/rat/outbox", ) - def test_start_reading(self, _): + def test_start_reading(self): """ begin a book """ shelf = self.local_user.shelf_set.get(identifier="reading") self.assertFalse(shelf.books.exists()) @@ -71,7 +70,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_start_reading_reshelf(self, _): + def test_start_reading_reshelf(self): """ begin a book """ to_read_shelf = self.local_user.shelf_set.get(identifier="to-read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -91,7 +90,7 @@ class ReadingViews(TestCase): self.assertFalse(to_read_shelf.books.exists()) self.assertEqual(shelf.books.get(), self.book) - def test_finish_reading(self, _): + def test_finish_reading(self): """ begin a book """ shelf = self.local_user.shelf_set.get(identifier="read") self.assertFalse(shelf.books.exists()) @@ -127,7 +126,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_edit_readthrough(self, _): + def test_edit_readthrough(self): """ adding dates to an ongoing readthrough """ start = timezone.make_aware(dateutil.parser.parse("2021-01-03")) readthrough = models.ReadThrough.objects.create( @@ -154,7 +153,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.finish_date.day, 7) self.assertEqual(readthrough.book, self.book) - def test_delete_readthrough(self, _): + def test_delete_readthrough(self): """ remove a readthrough """ readthrough = models.ReadThrough.objects.create( book=self.book, user=self.local_user @@ -171,7 +170,7 @@ class ReadingViews(TestCase): views.delete_readthrough(request) self.assertFalse(models.ReadThrough.objects.filter(id=readthrough.id).exists()) - def test_create_readthrough(self, _): + def test_create_readthrough(self): """ adding new read dates """ request = self.factory.post( "", diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index 0230b4a96..c7fef2a94 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -26,30 +26,28 @@ class RssFeedView(TestCase): ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): - self.review = models.Review.objects.create( - name="Review name", - content="test content", - rating=3, - user=self.user, - book=self.book, - ) + self.review = models.Review.objects.create( + name="Review name", + content="test content", + rating=3, + user=self.user, + book=self.book, + ) - self.quote = models.Quotation.objects.create( - quote="a sickening sense", - content="test content", - user=self.user, - book=self.book, - ) + self.quote = models.Quotation.objects.create( + quote="a sickening sense", + content="test content", + user=self.user, + book=self.book, + ) - self.generatednote = models.GeneratedNote.objects.create( - content="test content", user=self.user - ) + self.generatednote = models.GeneratedNote.objects.create( + content="test content", user=self.user + ) self.factory = RequestFactory() - @patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") - def test_rss_feed(self, _): + def test_rss_feed(self): """ load an rss feed """ view = rss_feed.RssFeed() request = self.factory.get("/user/rss_user/rss") diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 5dc0e49a7..32080a5a8 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -8,7 +8,6 @@ from bookwyrm import forms, models, views from bookwyrm.settings import DOMAIN -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class StatusViews(TestCase): """ viewing and creating statuses """ @@ -41,7 +40,7 @@ class StatusViews(TestCase): parent_work=work, ) - def test_handle_status(self, _): + def test_handle_status(self): """ create a status """ view = views.CreateStatus.as_view() form = forms.CommentForm( @@ -54,23 +53,20 @@ class StatusViews(TestCase): ) request = self.factory.post("", form.data) request.user = self.local_user - - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "comment") - self.assertTrue(redis_mock.called) - status = models.Comment.objects.get() self.assertEqual(status.content, "hi
") self.assertEqual(status.user, self.local_user) self.assertEqual(status.book, self.book) - def test_handle_status_reply(self, _): + def test_handle_status_reply(self): """ create a status in reply to an existing status """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( "rat", "rat@rat.com", "password", local=True ) - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Status.objects.create( content="parent status", user=self.local_user ) @@ -84,17 +80,14 @@ class StatusViews(TestCase): ) request = self.factory.post("", form.data) request.user = user - - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "reply") - self.assertTrue(redis_mock.called) - status = models.Status.objects.get(user=user) self.assertEqual(status.content, "hi
") self.assertEqual(status.user, user) self.assertEqual(models.Notification.objects.get().user, self.local_user) - def test_handle_status_mentions(self, _): + def test_handle_status_mentions(self): """ @mention a user in a post """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -111,10 +104,8 @@ class StatusViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "comment") - self.assertTrue(redis_mock.called) - status = models.Status.objects.get() self.assertEqual(list(status.mention_users.all()), [user]) self.assertEqual(models.Notification.objects.get().user, user) @@ -122,7 +113,7 @@ class StatusViews(TestCase): status.content, 'hi @rat
' % user.remote_id ) - def test_handle_status_reply_with_mentions(self, _): + def test_handle_status_reply_with_mentions(self): """ reply to a post with an @mention'ed user """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -139,9 +130,8 @@ class StatusViews(TestCase): request = self.factory.post("", form.data) request.user = self.local_user - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "comment") - self.assertTrue(redis_mock.called) status = models.Status.objects.get() form = forms.ReplyForm( @@ -154,10 +144,8 @@ class StatusViews(TestCase): ) request = self.factory.post("", form.data) request.user = user - - with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, "reply") - self.assertTrue(redis_mock.called) reply = models.Status.replies(status).first() self.assertEqual(reply.content, "right
") @@ -166,7 +154,7 @@ class StatusViews(TestCase): self.assertFalse(self.remote_user in reply.mention_users.all()) self.assertTrue(self.local_user in reply.mention_users.all()) - def test_find_mentions(self, _): + def test_find_mentions(self): """ detect and look up @ mentions of users """ user = models.User.objects.create_user( "nutria@%s" % DOMAIN, @@ -212,7 +200,7 @@ class StatusViews(TestCase): ("@nutria@%s" % DOMAIN, user), ) - def test_format_links(self, _): + def test_format_links(self): """ find and format urls into a tags """ url = "http://www.fish.com/" self.assertEqual( @@ -235,7 +223,7 @@ class StatusViews(TestCase): "?q=arkady+strugatsky&mode=everything" % url, ) - def test_to_markdown(self, _): + def test_to_markdown(self): """ this is mostly handled in other places, but nonetheless """ text = "_hi_ and http://fish.com is " result = views.status.to_markdown(text) @@ -244,36 +232,32 @@ class StatusViews(TestCase): 'hi and fish.com ' "is rad
", ) - def test_to_markdown_link(self, _): + def test_to_markdown_link(self): """ this is mostly handled in other places, but nonetheless """ text = "[hi](http://fish.com) is " result = views.status.to_markdown(text) self.assertEqual(result, 'hi ' "is rad
") - def test_handle_delete_status(self, mock): + def test_handle_delete_status(self): """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) request = self.factory.post("") request.user = self.local_user - - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) - self.assertTrue(redis_mock.called) - activity = json.loads(mock.call_args_list[1][0][1]) - self.assertEqual(activity["type"], "Delete") - self.assertEqual(activity["object"]["type"], "Tombstone") + activity = json.loads(mock.call_args_list[0][0][1]) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["object"]["type"], "Tombstone") status.refresh_from_db() self.assertTrue(status.deleted) - def test_handle_delete_status_permission_denied(self, _): + def test_handle_delete_status_permission_denied(self): """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) request = self.factory.post("") @@ -284,23 +268,20 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertFalse(status.deleted) - def test_handle_delete_status_moderator(self, mock): + def test_handle_delete_status_moderator(self): """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch("bookwyrm.activitystreams.ActivityStream.add_status"): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) request = self.factory.post("") request.user = self.remote_user request.user.is_superuser = True - with patch( - "bookwyrm.activitystreams.ActivityStream.remove_status" - ) as redis_mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) - self.assertTrue(redis_mock.called) - activity = json.loads(mock.call_args_list[1][0][1]) - self.assertEqual(activity["type"], "Delete") - self.assertEqual(activity["object"]["type"], "Tombstone") + activity = json.loads(mock.call_args_list[0][0][1]) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["object"]["type"], "Tombstone") status.refresh_from_db() self.assertTrue(status.deleted) diff --git a/bookwyrm/tests/views/test_updates.py b/bookwyrm/tests/views/test_updates.py index dff730e6d..b41b20148 100644 --- a/bookwyrm/tests/views/test_updates.py +++ b/bookwyrm/tests/views/test_updates.py @@ -1,7 +1,5 @@ """ test for app action functionality """ import json -from unittest.mock import patch - from django.http import JsonResponse from django.test import TestCase from django.test.client import RequestFactory @@ -24,33 +22,21 @@ class UpdateViews(TestCase): ) models.SiteSettings.objects.create() - def test_get_notification_count(self): + def test_get_updates(self): """ there are so many views, this just makes sure it LOADS """ + view = views.Updates.as_view() request = self.factory.get("") request.user = self.local_user - result = views.get_notification_count(request) + result = view(request) self.assertIsInstance(result, JsonResponse) data = json.loads(result.getvalue()) - self.assertEqual(data["count"], 0) + self.assertEqual(data["notifications"], 0) models.Notification.objects.create( notification_type="BOOST", user=self.local_user ) - result = views.get_notification_count(request) + result = view(request) self.assertIsInstance(result, JsonResponse) data = json.loads(result.getvalue()) - self.assertEqual(data["count"], 1) - - def test_get_unread_status_count(self): - """ there are so many views, this just makes sure it LOADS """ - request = self.factory.get("") - request.user = self.local_user - - with patch("bookwyrm.activitystreams.ActivityStream.get_unread_count") as mock: - mock.return_value = 3 - result = views.get_unread_status_count(request, "home") - - self.assertIsInstance(result, JsonResponse) - data = json.loads(result.getvalue()) - self.assertEqual(data["count"], 3) + self.assertEqual(data["notifications"], 1) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 44db68928..766227999 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -37,8 +37,7 @@ urlpatterns = [ re_path(r"^api/v1/instance/?$", views.instance_info), re_path(r"^api/v1/instance/peers/?$", views.peers), # polling updates - re_path("^api/updates/notifications/?$", views.get_notification_count), - re_path("^api/updates/stream/(?P