# HG changeset patch # User Goffi # Date 1652462957 -7200 # Node ID f31113777881ff06666cb95e9e98bede981101c9 # Parent a75874df92b8d6c4276772681aeab138affbdc5f tests (unit/ap-gateway): tests for following/followers <=> PPS: AP following/followers collections to XMPP Public Pubsub Subscriptions conversion is checked in both directions. rel 365 diff -r a75874df92b8 -r f31113777881 tests/unit/test_ap-gateway.py --- a/tests/unit/test_ap-gateway.py Fri May 13 19:27:21 2022 +0200 +++ b/tests/unit/test_ap-gateway.py Fri May 13 19:29:17 2022 +0200 @@ -17,8 +17,9 @@ # along with this program. If not, see . from copy import deepcopy -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, AsyncMock, patch from urllib import parse +from functools import partial import pytest from pytest_twisted import ensureDeferred as ed @@ -29,13 +30,16 @@ from sat.core import exceptions from sat.plugins import plugin_comp_ap_gateway from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer +from sat.plugins.plugin_xep_0465 import NS_PPS from sat.tools.utils import xmpp_date from sat.tools import xml_tools +from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR TEST_BASE_URL = "https://example.org" TEST_USER = "test_user" TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" +PUBLIC_URL = "test.example" AP_REQUESTS = { f"{TEST_BASE_URL}/.well-known/webfinger?" @@ -71,7 +75,38 @@ "type": "Person", "url": f"{TEST_BASE_URL}/@{TEST_USER}" }, - + f"{TEST_BASE_URL}/.well-known/webfinger?" + f"resource=acct:{parse.quote('ext_user@example.org')}": { + "aliases": [ + f"{TEST_BASE_URL}/@ext_user", + f"{TEST_BASE_URL}/users/ext_user" + ], + "links": [ + { + "href": f"{TEST_BASE_URL}/users/ext_user", + "rel": "self", + "type": "application/activity+json" + }, + ], + "subject": f"acct:ext_user@example.org" + }, + f"{TEST_BASE_URL}/users/ext_user": { + "@context": [ + "https://www.w3.org/ns/activitystreams", + ], + "endpoints": { + "sharedInbox": f"{TEST_BASE_URL}/inbox" + }, + "followers": f"{TEST_BASE_URL}/users/ext_user/followers", + "following": f"{TEST_BASE_URL}/users/ext_user/following", + "id": f"{TEST_BASE_URL}/users/ext_user", + "inbox": f"{TEST_BASE_URL}/users/ext_user/inbox", + "name": "", + "outbox": f"{TEST_BASE_URL}/users/ext_user/outbox", + "preferredUsername": f"ext_user", + "type": "Person", + "url": f"{TEST_BASE_URL}/@ext_user" + }, f"{TEST_BASE_URL}/users/{TEST_USER}/outbox": { "@context": "https://www.w3.org/ns/activitystreams", "first": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", @@ -222,7 +257,43 @@ "partOf": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", "prev": None, "type": "OrderedCollectionPage" - } + }, + f"{TEST_BASE_URL}/users/{TEST_USER}/following": { + "@context": "https://www.w3.org/ns/activitystreams", + "first": f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1", + "id": f"{TEST_BASE_URL}/users/{TEST_USER}/following", + "totalItems": 2, + "type": "OrderedCollection" + }, + f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1": { + "@context": "https://www.w3.org/ns/activitystreams", + "id": f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1", + "orderedItems": [ + f"{TEST_BASE_URL}/users/ext_user", + f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}", + ], + "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/following", + "totalItems": 2, + "type": "OrderedCollectionPage" + }, + f"{TEST_BASE_URL}/users/{TEST_USER}/followers": { + "@context": "https://www.w3.org/ns/activitystreams", + "first": f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1", + "id": f"{TEST_BASE_URL}/users/{TEST_USER}/followers", + "totalItems": 2, + "type": "OrderedCollection" + }, + f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1": { + "@context": "https://www.w3.org/ns/activitystreams", + "id": f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1", + "orderedItems": [ + f"{TEST_BASE_URL}/users/ext_user", + f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}", + ], + "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/followers", + "totalItems": 2, + "type": "OrderedCollectionPage" + }, } @@ -276,6 +347,7 @@ for i in range(1, 5) ] + async def mock_ap_get(url): return deepcopy(AP_REQUESTS[url]) @@ -285,25 +357,28 @@ async def mock_getItems(*args, **kwargs): + ret_items = kwargs.pop("ret_items", XMPP_ITEMS) rsm_resp = rsm.RSMResponse( - first=XMPP_ITEMS[0]["id"], - last=XMPP_ITEMS[-1]["id"], + first=ret_items[0]["id"], + last=ret_items[-1]["id"], index=0, - count=len(XMPP_ITEMS) + count=len(ret_items) ) - return XMPP_ITEMS, {"rsm": rsm_resp.toDict(), "complete": True} + return ret_items, {"rsm": rsm_resp.toDict(), "complete": True} @pytest.fixture(scope="session") def ap_gateway(host): gateway = plugin_comp_ap_gateway.APGateway(host) gateway.initialised = True + gateway.isPubsub = AsyncMock() + gateway.isPubsub.return_value = False client = MagicMock() client.jid = jid.JID("ap.test.example") client.host = "test.example" gateway.client = client gateway.local_only = True - gateway.public_url = "test.example" + gateway.public_url = PUBLIC_URL gateway.ap_path = '_ap' gateway.base_ap_url = parse.urljoin( f"https://{gateway.public_url}", @@ -407,7 +482,7 @@ monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) - actor_data = await ap_gateway.getAPActorDataFromId(TEST_AP_ACCOUNT) + actor_data = await ap_gateway.getAPActorDataFromAccount(TEST_AP_ACCOUNT) outbox = await ap_gateway.apGetObject(actor_data, "outbox") items, rsm_resp = await ap_gateway.getAPItems(outbox, 2) @@ -548,9 +623,12 @@ "node": None, "ap_account": test_jid.full(), "ap_url": ap_url, + "signing_actor": None } if type_ == "outbox" and query_data: kwargs["query_data"] = query_data + # signing_actor is not used for page requests + del kwargs["signing_actor"] return kwargs @ed @@ -586,4 +664,127 @@ assert first_item_obj["published"] == "2022-01-28T16:02:19Z" assert first_item_obj["attributedTo"] == first_item["actor"] assert first_item_obj["content"] == "

XMPP item 4

" - assert first_item_obj["to"] == "https://www.w3.org/ns/activitystreams#Public" + assert first_item_obj["to"] == ["https://www.w3.org/ns/activitystreams#Public"] + + @ed + async def test_following_to_pps(self, ap_gateway, monkeypatch): + """AP following items are converted to Public Pubsub Subscription subscriptions""" + monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) + monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) + monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) + + items, __ = await ap_gateway.pubsub_service.items( + jid.JID("toto@example.org"), + ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT), + ap_gateway._pps.subscriptions_node, + None, + None, + None + ) + assert len(items) == 2 + for idx, entity in enumerate(( + "local_user@test.example", + "ext_user\\40example.org@ap.test.example" + )): + subscription_elt = next(items[idx].elements(NS_PPS, "subscription"), None) + assert subscription_elt is not None + assert subscription_elt["node"] == ap_gateway._m.namespace + assert subscription_elt["service"] == entity + + @ed + async def test_followers_to_pps(self, ap_gateway, monkeypatch): + """AP followers items are converted to Public Pubsub Subscription subscribers""" + monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) + monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) + monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) + + items, __ = await ap_gateway.pubsub_service.items( + jid.JID("toto@example.org"), + ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT), + ap_gateway._pps.getPublicSubscribersNode(ap_gateway._m.namespace), + None, + None, + None + ) + assert len(items) == 2 + for idx, entity in enumerate(( + "local_user@test.example", + "ext_user\\40example.org@ap.test.example" + )): + subscriber_elt = next(items[idx].elements(NS_PPS, "subscriber"), None) + assert subscriber_elt is not None + assert subscriber_elt["jid"] == entity + + @ed + async def test_pps_to_following(self, ap_gateway, monkeypatch): + """Public Pubsub Subscription subscriptions are converted to AP following items""" + subscriptions = [ + pubsub.Item( + id="subscription_1", + payload = ap_gateway._pps.buildSubscriptionElt( + ap_gateway._m.namespace, + jid.JID("local_user@test.example") + ) + ), + pubsub.Item( + id="subscription_2", + payload = ap_gateway._pps.buildSubscriptionElt( + ap_gateway._m.namespace, + jid.JID("ext_user\\40example.org@ap.test.example") + ) + ) + ] + monkeypatch.setattr(ap_gateway._p, "getItems", partial( + mock_getItems, + ret_items=subscriptions + )) + following = await ap_gateway.server.resource.APFollowingRequest( + **self.ap_request_params(ap_gateway, "following") + ) + assert following["@context"] == "https://www.w3.org/ns/activitystreams" + assert following["id"] == "https://test.example/_ap/following/some_user%40test.example" + assert following["totalItems"] == len(subscriptions) + assert following["type"] == "OrderedCollection" + assert following.get("first") + + first_page = following["first"] + assert first_page["type"] == "OrderedCollectionPage" + assert len(first_page["orderedItems"]) == len(subscriptions) + items = first_page["orderedItems"] + assert items == ['local_user@test.example', 'ext_user@example.org'] + + @ed + async def test_pps_to_followers(self, ap_gateway, monkeypatch): + """Public Pubsub Subscription subscribers are converted to AP followers""" + subscribers = [ + pubsub.Item( + id="subscriber_1", + payload = ap_gateway._pps.buildSubscriberElt( + jid.JID("local_user@test.example") + ) + ), + pubsub.Item( + id="subscriber_2", + payload = ap_gateway._pps.buildSubscriberElt( + jid.JID("ext_user\\40example.org@ap.test.example") + ) + ) + ] + monkeypatch.setattr(ap_gateway._p, "getItems", partial( + mock_getItems, + ret_items=subscribers + )) + followers = await ap_gateway.server.resource.APFollowersRequest( + **self.ap_request_params(ap_gateway, "followers") + ) + assert followers["@context"] == "https://www.w3.org/ns/activitystreams" + assert followers["id"] == "https://test.example/_ap/followers/some_user%40test.example" + assert followers["totalItems"] == len(subscribers) + assert followers["type"] == "OrderedCollection" + assert followers.get("first") + + first_page = followers["first"] + assert first_page["type"] == "OrderedCollectionPage" + assert len(first_page["orderedItems"]) == len(subscribers) + items = first_page["orderedItems"] + assert items == ['local_user@test.example', 'ext_user@example.org']