Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions testbed/core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,18 @@ def test_liked_items_are_the_like_objects(basic_auth_context, mock_request):
{"@context": [ACTIVITY_STREAM_CONTEXT, PREVIOUSLY_TERM], **remote_data, "id": remote.object_url},
]
assert b"AUTHOR PRIVATE CONTENT" not in response.content


# LOLA §6.4: the owner's token reads the owner's whole liked collection, whatever each Like's visibility
@pytest.mark.django_db
def test_liked_lists_the_owners_non_public_likes():
owner = IsolatedActorFactory(prefix="liked_all")
likes = [
LikeActivityFactory(actor=owner, remote=True, visibility=visibility)
for visibility in ("public", "followers-only", "private")
]

response = lola_client(owner).get(reverse("liked-collection", kwargs={"pk": owner.id}))

listed = {item["id"] for item in response.data["orderedItems"]}
assert listed == {like.object_url for like in likes}
15 changes: 13 additions & 2 deletions testbed/core/tests/test_provisioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from django.urls import reverse
from rest_framework.test import APIClient

from testbed.core.models import Actor, Blocked, Following, Note
from testbed.core.models import Actor, Blocked, Following, LikeActivity, Note
from testbed.core.tests.helpers import lola_client, source_actor_for
from testbed.core.utils.provisioning import provision_actor_content
from testbed.core.utils.provisioning import LIKE_COUNT, VISIBILITIES, provision_actor_content


# production refuses `seed`, so what a signup provisions for itself is all its collections will ever hold
Expand Down Expand Up @@ -37,6 +37,17 @@ def test_provisioned_notes_span_months():
assert max(published) - min(published) > timedelta(days=90)


# Provisioned Likes span every visibility. A new account exercises the owner's full liked collection (LOLA §6.4)
@pytest.mark.django_db
def test_every_provisioned_like_reaches_its_owner():
actor = source_actor_for()
visibilities = set(LikeActivity.objects.filter(actor=actor).values_list("visibility", flat=True))
liked = lola_client(actor).get(reverse("liked-collection", kwargs={"pk": actor.pk})).data

assert visibilities == set(VISIBILITIES)
assert liked["totalItems"] == LIKE_COUNT


@pytest.mark.django_db
def test_scope_unlocks_more_of_the_outbox():
actor = source_actor_for()
Expand Down
2 changes: 1 addition & 1 deletion testbed/core/utils/provisioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def provision_actor_content(actor):
likes.append(LikeActivity(
actor=actor,
timestamp=liked_at,
visibility="public",
visibility=VISIBILITIES[(number - 1) % len(VISIBILITIES)],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A cool option for this idiom is to have the function or object own a couple cycles. From python 'itertools.cycle'

visibilities = cycle(VISIBILITIES)

for i in range(NOTE_COUNT):
...
visibility = next(visibility)
...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, that would be much cleaner. I'll do it in the follow up PR

object_url=f"{author_url}/notes/{number}",
object_data={
"type": "Note",
Expand Down
6 changes: 1 addition & 5 deletions testbed/core/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,9 @@ def liked_collection(request, pk, actor):
The objects of the actor's Likes (ActivityPub §5.5), each built by build_like_object_json_ld:
the same object the Like activity carries, so a non-public local Note is served as its id only.
"""
# Get all LikeActivity objects for this actor in reverse chronological order
# Every Like the actor made, whatever its visibility. The strict gate above admits only this actor's bound token
likes_qs = LikeActivity.objects.filter(actor=actor).order_by("-timestamp", "-id")

# Apply visibility filtering - only include likes of public objects for privacy
# TODO: This could be enhanced with trust controls
likes_qs = likes_qs.filter(visibility="public")

# Build standardized authentication context for JSON-LD building
auth_context = build_auth_context(request)

Expand Down
Loading