# HG changeset patch # User Goffi # Date 1658497533 -7200 # Node ID 8c01d8ab9447d4efdb26b65987532f91dd33dbf5 # Parent bd84d289fc944637dbfbf0706bad5790fa5d2d94 tests (unit/AP gateway): tests for item repeat/announce and noticed/like conversion: Add tests for - XMPP blog post repeat => AP Announce conversion and vice versa - XMPP pubsub `noticed` attachment => AP like conversion and vice versa rel 370 diff -r bd84d289fc94 -r 8c01d8ab9447 tests/unit/test_ap-gateway.py --- a/tests/unit/test_ap-gateway.py Fri Jul 22 15:42:59 2022 +0200 +++ b/tests/unit/test_ap-gateway.py Fri Jul 22 15:45:33 2022 +0200 @@ -17,35 +17,37 @@ # along with this program. If not, see . from copy import deepcopy -from unittest.mock import MagicMock, AsyncMock, patch, DEFAULT +from functools import partial +import io +import json +from typing import Any, Dict, Optional, Union +from unittest.mock import AsyncMock, DEFAULT, MagicMock, patch from urllib import parse -from functools import partial -from typing import Dict, Union import pytest from pytest_twisted import ensureDeferred as ed +from treq.response import _Response as TReqResponse from twisted.internet import defer +from twisted.web.server import Request from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber.error import StanzaError -from twisted.web.server import Request from twisted.words.xish import domish -from wokkel import rsm, pubsub -from treq.response import _Response as TReqResponse +from wokkel import pubsub, rsm from sat.core import exceptions from sat.core.constants import Const as C +from sat.memory.sqla_mapping import SubscriptionState from sat.plugins import plugin_comp_ap_gateway from sat.plugins.plugin_comp_ap_gateway import constants as ap_const +from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer from sat.plugins.plugin_xep_0277 import NS_ATOM from sat.plugins.plugin_xep_0422 import NS_FASTEN from sat.plugins.plugin_xep_0424 import NS_MESSAGE_RETRACT from sat.plugins.plugin_xep_0465 import NS_PPS -from sat.tools.utils import xmpp_date from sat.tools import xml_tools from sat.tools.common import uri as xmpp_uri -from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR -from sat.memory.sqla_mapping import SubscriptionState +from sat.tools.utils import xmpp_date TEST_BASE_URL = "https://example.org" @@ -362,6 +364,13 @@ for i in range(1, 5) ] +TEST_USER_DATA = AP_REQUESTS[f"{TEST_BASE_URL}/users/{TEST_USER}"] +OUTBOX_FIRST_PAGE = f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true" +TEST_AP_ITEMS = AP_REQUESTS[OUTBOX_FIRST_PAGE]["orderedItems"] +# request on an item ID must return the ID +for item in TEST_AP_ITEMS: + AP_REQUESTS[item["id"]] = item + async def mock_ap_get(url): return deepcopy(AP_REQUESTS[url]) @@ -649,11 +658,21 @@ "" ) - def ap_request_params(self, ap_gateway, type_=None, url=None, query_data=None): + def ap_request_params( + self, + ap_gateway, + type_: Optional[str] = None, + url: Optional[str] = None, + doc: Optional[Any] = None, + query_data: Optional[dict] = None, + signing_actor: Optional[str] = None + ) -> Dict[str, Any]: """Generate parameters for HTTPAPGServer's AP*Request @param type_: one of the AP query type (e.g. "outbox") @param url: URL to query (mutually exclusif with type_) + @param doc: if set, a document to embed in content of the request (will be + converted to JSON) @param query_data: query data as returned by parse.parse_qs @return dict with kw params to use """ @@ -676,6 +695,9 @@ request = Request(MagicMock()) request.path = path.encode() request.uri = uri.encode() + if doc is not None: + request.content = io.BytesIO(json.dumps(doc).encode()) + ap_url = parse.urljoin( f"https://{ap_gateway.public_url}", path @@ -686,7 +708,7 @@ "node": None, "ap_account": test_jid.full(), "ap_url": ap_url, - "signing_actor": None + "signing_actor": signing_actor } if type_ == "outbox" and query_data: kwargs["query_data"] = query_data @@ -1350,3 +1372,220 @@ "name": f"{TEST_AP_ACCOUNT}" } assert expected_mention in ap_object["tag"] + + @ed + async def test_xmpp_repeat_to_ap_announce(self, ap_gateway, monkeypatch): + """XEP-0272 post repeat is converted to AP Announce activity""" + 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) + + # JID repeated AP actor (also the recipient of the message) + recipient_jid = ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT) + # repeated item + ap_item = TEST_AP_ITEMS[0] + ap_item_url = xmpp_uri.buildXMPPUri( + "pubsub", + path=recipient_jid.full(), + node=ap_gateway._m.namespace, + item=ap_item["id"] + ) + item_elt = xml_tools.parse(f""" + + + test message 1 + + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>test message 1</p> + </div> + + + test_user + xmpp:{recipient_jid} + + 2022-07-21T14:38:53Z + 2022-07-21T14:38:53Z + {ap_item["id"]} + + + + """) + item_elt.uri = pubsub.NS_PUBSUB_EVENT + + with patch.object(ap_gateway, "signAndPost") as signAndPost: + signAndPost.return_value.code = 202 + await ap_gateway.convertAndPostItems( + ap_gateway.client, + TEST_AP_ACCOUNT, + TEST_JID, + ap_gateway._m.namespace, + [item_elt] + ) + + assert signAndPost.called + url, actor_id, doc = signAndPost.call_args.args + assert url == TEST_USER_DATA["inbox"] + assert actor_id == ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost()) + assert doc["type"] == "Announce" + assert ap_const.NS_AP_PUBLIC in doc["to"] + assert doc["object"] == ap_item["id"] + + @ed + async def test_ap_announce_to_xmpp_repeat(self, ap_gateway, monkeypatch): + """AP Announce activity is converted to XEP-0272 post repeat""" + 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) + + xmpp_actor_id = ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost()) + # announced item + xmpp_item = XMPP_ITEMS[0] + xmpp_item_url = ap_gateway.buildAPURL( + ap_const.TYPE_ITEM, TEST_JID.userhost(), xmpp_item["id"] + ) + announce = { + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Announce", + "actor": TEST_AP_ACTOR_ID, + "cc": [ + xmpp_actor_id, + TEST_USER_DATA["followers"] + ], + "id": "https://example.org/announce/123", + "object": xmpp_item_url, + "published": "2022-07-22T09:24:12Z", + "to": [ap_const.NS_AP_PUBLIC] + } + with patch.object(ap_gateway.host.memory.storage, "getItems") as getItems: + mock_pubsub_item = MagicMock + mock_pubsub_item.data = xmpp_item + getItems.return_value = ([mock_pubsub_item], {}) + with patch.object( + ap_gateway.host.memory.storage, + "cachePubsubItems") as cachePubsubItems: + await ap_gateway.server.resource.handleAnnounceActivity( + Request(MagicMock()), + announce, + None, + None, + None, + "", + TEST_AP_ACTOR_ID + ) + + assert cachePubsubItems.called + # the microblog data put in cache correspond to the item sent to subscribers + __, __, __, [mb_data] = cachePubsubItems.call_args.args + extra = mb_data["extra"] + assert "repeated" in extra + repeated = extra["repeated"] + assert repeated["by"] == ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT).full() + xmpp_item_xmpp_url = xmpp_uri.buildXMPPUri( + "pubsub", + path=TEST_JID.full(), + node=ap_gateway._m.namespace, + item=xmpp_item["id"] + ) + assert repeated["uri"] == xmpp_item_xmpp_url + + @ed + async def test_xmpp_attachment_noticed_to_ap_like(self, ap_gateway, monkeypatch): + """Pubsub-attachments ``noticed`` is converted to AP Like activity""" + 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) + + recipient_jid = ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT) + # noticed item + ap_item = TEST_AP_ITEMS[0] + ap_item_url = xmpp_uri.buildXMPPUri( + "pubsub", + path=recipient_jid.full(), + node=ap_gateway._m.namespace, + item=ap_item["id"] + ) + attachment_node = ap_gateway._pa.getAttachmentNodeName( + recipient_jid, + ap_gateway._m.namespace, + ap_item["id"] + ) + item_elt = xml_tools.parse(f""" + + + + + + """) + item_elt.uri = pubsub.NS_PUBSUB_EVENT + items_event = pubsub.ItemsEvent( + TEST_JID, + recipient_jid, + attachment_node, + [item_elt], + {} + ) + + with patch.object(ap_gateway, "signAndPost") as signAndPost: + signAndPost.return_value.code = 202 + await ap_gateway._itemsReceived( + ap_gateway.client, + items_event + ) + + assert signAndPost.called + url, actor_id, doc = signAndPost.call_args.args + assert url == TEST_USER_DATA["inbox"] + assert actor_id == ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost()) + assert doc["type"] == "Like" + assert ap_const.NS_AP_PUBLIC in doc["to"] + assert doc["object"] == ap_item["id"] + + @ed + async def test_ap_like_to_xmpp_noticed_attachment(self, ap_gateway, monkeypatch): + """AP Like activity is converted to ``noticed`` attachment""" + 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) + + xmpp_actor_id = ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost()) + # liked item + xmpp_item = XMPP_ITEMS[0] + xmpp_item_url = ap_gateway.buildAPURL( + ap_const.TYPE_ITEM, TEST_JID.userhost(), xmpp_item["id"] + ) + like = { + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Like", + "actor": TEST_AP_ACTOR_ID, + "cc": [ + xmpp_actor_id, + TEST_USER_DATA["followers"] + ], + "id": "https://example.org/like/123", + "object": xmpp_item_url, + "published": "2022-07-22T09:24:12Z", + "to": [ap_const.NS_AP_PUBLIC] + } + with patch.object(ap_gateway.host.memory.storage, "getItems") as getItems: + getItems.return_value = ([], {}) + with patch.object(ap_gateway._p, "sendItems") as sendItems: + await ap_gateway.server.resource.APInboxRequest( + **self.ap_request_params( + ap_gateway, + "inbox", + doc=like, + signing_actor=TEST_AP_ACTOR_ID + ) + ) + + assert sendItems.called + si_client, si_service, si_node, [si_item] = sendItems.call_args.args + assert si_client.jid == ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT) + assert si_service == TEST_JID + assert si_node == ap_gateway._pa.getAttachmentNodeName( + TEST_JID, ap_gateway._m.namespace, xmpp_item["id"] + ) + [parsed_item] = ap_gateway._pa.items2attachmentData(si_client, [si_item]) + assert parsed_item["from"] == si_client.jid.full() + assert "noticed" in parsed_item + assert parsed_item["noticed"]["noticed"] == True