changeset 3770:f31113777881

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
author Goffi <goffi@goffi.org>
date Fri, 13 May 2022 19:29:17 +0200
parents a75874df92b8
children e597dbfbc4c6
files tests/unit/test_ap-gateway.py
diffstat 1 files changed, 211 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- 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 <http://www.gnu.org/licenses/>.
 
 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"] == "<div><p>XMPP item 4</p></div>"
-        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']