comparison tests/unit/test_ap-gateway.py @ 3871:8c01d8ab9447

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
author Goffi <goffi@goffi.org>
date Fri, 22 Jul 2022 15:45:33 +0200
parents 56720561f45f
children c129234a5d0b
comparison
equal deleted inserted replaced
3870:bd84d289fc94 3871:8c01d8ab9447
15 15
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18
19 from copy import deepcopy 19 from copy import deepcopy
20 from unittest.mock import MagicMock, AsyncMock, patch, DEFAULT 20 from functools import partial
21 import io
22 import json
23 from typing import Any, Dict, Optional, Union
24 from unittest.mock import AsyncMock, DEFAULT, MagicMock, patch
21 from urllib import parse 25 from urllib import parse
22 from functools import partial
23 from typing import Dict, Union
24 26
25 import pytest 27 import pytest
26 from pytest_twisted import ensureDeferred as ed 28 from pytest_twisted import ensureDeferred as ed
29 from treq.response import _Response as TReqResponse
27 from twisted.internet import defer 30 from twisted.internet import defer
31 from twisted.web.server import Request
28 from twisted.words.protocols.jabber import jid 32 from twisted.words.protocols.jabber import jid
29 from twisted.words.protocols.jabber.error import StanzaError 33 from twisted.words.protocols.jabber.error import StanzaError
30 from twisted.web.server import Request
31 from twisted.words.xish import domish 34 from twisted.words.xish import domish
32 from wokkel import rsm, pubsub 35 from wokkel import pubsub, rsm
33 from treq.response import _Response as TReqResponse
34 36
35 from sat.core import exceptions 37 from sat.core import exceptions
36 from sat.core.constants import Const as C 38 from sat.core.constants import Const as C
39 from sat.memory.sqla_mapping import SubscriptionState
37 from sat.plugins import plugin_comp_ap_gateway 40 from sat.plugins import plugin_comp_ap_gateway
38 from sat.plugins.plugin_comp_ap_gateway import constants as ap_const 41 from sat.plugins.plugin_comp_ap_gateway import constants as ap_const
42 from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR
39 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer 43 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer
40 from sat.plugins.plugin_xep_0277 import NS_ATOM 44 from sat.plugins.plugin_xep_0277 import NS_ATOM
41 from sat.plugins.plugin_xep_0422 import NS_FASTEN 45 from sat.plugins.plugin_xep_0422 import NS_FASTEN
42 from sat.plugins.plugin_xep_0424 import NS_MESSAGE_RETRACT 46 from sat.plugins.plugin_xep_0424 import NS_MESSAGE_RETRACT
43 from sat.plugins.plugin_xep_0465 import NS_PPS 47 from sat.plugins.plugin_xep_0465 import NS_PPS
44 from sat.tools.utils import xmpp_date
45 from sat.tools import xml_tools 48 from sat.tools import xml_tools
46 from sat.tools.common import uri as xmpp_uri 49 from sat.tools.common import uri as xmpp_uri
47 from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR 50 from sat.tools.utils import xmpp_date
48 from sat.memory.sqla_mapping import SubscriptionState
49 51
50 52
51 TEST_BASE_URL = "https://example.org" 53 TEST_BASE_URL = "https://example.org"
52 TEST_USER = "test_user" 54 TEST_USER = "test_user"
53 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" 55 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org"
360 namespace=pubsub.NS_PUBSUB 362 namespace=pubsub.NS_PUBSUB
361 ) 363 )
362 for i in range(1, 5) 364 for i in range(1, 5)
363 ] 365 ]
364 366
367 TEST_USER_DATA = AP_REQUESTS[f"{TEST_BASE_URL}/users/{TEST_USER}"]
368 OUTBOX_FIRST_PAGE = f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true"
369 TEST_AP_ITEMS = AP_REQUESTS[OUTBOX_FIRST_PAGE]["orderedItems"]
370 # request on an item ID must return the ID
371 for item in TEST_AP_ITEMS:
372 AP_REQUESTS[item["id"]] = item
373
365 374
366 async def mock_ap_get(url): 375 async def mock_ap_get(url):
367 return deepcopy(AP_REQUESTS[url]) 376 return deepcopy(AP_REQUESTS[url])
368 377
369 378
647 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" 656 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>"
648 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 1</p></div>" 657 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 1</p></div>"
649 "</title>" 658 "</title>"
650 ) 659 )
651 660
652 def ap_request_params(self, ap_gateway, type_=None, url=None, query_data=None): 661 def ap_request_params(
662 self,
663 ap_gateway,
664 type_: Optional[str] = None,
665 url: Optional[str] = None,
666 doc: Optional[Any] = None,
667 query_data: Optional[dict] = None,
668 signing_actor: Optional[str] = None
669 ) -> Dict[str, Any]:
653 """Generate parameters for HTTPAPGServer's AP*Request 670 """Generate parameters for HTTPAPGServer's AP*Request
654 671
655 @param type_: one of the AP query type (e.g. "outbox") 672 @param type_: one of the AP query type (e.g. "outbox")
656 @param url: URL to query (mutually exclusif with type_) 673 @param url: URL to query (mutually exclusif with type_)
674 @param doc: if set, a document to embed in content of the request (will be
675 converted to JSON)
657 @param query_data: query data as returned by parse.parse_qs 676 @param query_data: query data as returned by parse.parse_qs
658 @return dict with kw params to use 677 @return dict with kw params to use
659 """ 678 """
660 assert type_ and url is None or url and type_ is None 679 assert type_ and url is None or url and type_ is None
661 if type_ is not None: 680 if type_ is not None:
674 693
675 test_jid = jid.JID("some_user@test.example") 694 test_jid = jid.JID("some_user@test.example")
676 request = Request(MagicMock()) 695 request = Request(MagicMock())
677 request.path = path.encode() 696 request.path = path.encode()
678 request.uri = uri.encode() 697 request.uri = uri.encode()
698 if doc is not None:
699 request.content = io.BytesIO(json.dumps(doc).encode())
700
679 ap_url = parse.urljoin( 701 ap_url = parse.urljoin(
680 f"https://{ap_gateway.public_url}", 702 f"https://{ap_gateway.public_url}",
681 path 703 path
682 ) 704 )
683 kwargs = { 705 kwargs = {
684 "request": request, 706 "request": request,
685 "account_jid": test_jid, 707 "account_jid": test_jid,
686 "node": None, 708 "node": None,
687 "ap_account": test_jid.full(), 709 "ap_account": test_jid.full(),
688 "ap_url": ap_url, 710 "ap_url": ap_url,
689 "signing_actor": None 711 "signing_actor": signing_actor
690 } 712 }
691 if type_ == "outbox" and query_data: 713 if type_ == "outbox" and query_data:
692 kwargs["query_data"] = query_data 714 kwargs["query_data"] = query_data
693 # signing_actor is not used for page requests 715 # signing_actor is not used for page requests
694 del kwargs["signing_actor"] 716 del kwargs["signing_actor"]
1348 # we don't have a prefixing "@" here, because it's not needed in referencing 1370 # we don't have a prefixing "@" here, because it's not needed in referencing
1349 # item with XMPP 1371 # item with XMPP
1350 "name": f"{TEST_AP_ACCOUNT}" 1372 "name": f"{TEST_AP_ACCOUNT}"
1351 } 1373 }
1352 assert expected_mention in ap_object["tag"] 1374 assert expected_mention in ap_object["tag"]
1375
1376 @ed
1377 async def test_xmpp_repeat_to_ap_announce(self, ap_gateway, monkeypatch):
1378 """XEP-0272 post repeat is converted to AP Announce activity"""
1379 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get)
1380 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json)
1381 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get)
1382
1383 # JID repeated AP actor (also the recipient of the message)
1384 recipient_jid = ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT)
1385 # repeated item
1386 ap_item = TEST_AP_ITEMS[0]
1387 ap_item_url = xmpp_uri.buildXMPPUri(
1388 "pubsub",
1389 path=recipient_jid.full(),
1390 node=ap_gateway._m.namespace,
1391 item=ap_item["id"]
1392 )
1393 item_elt = xml_tools.parse(f"""
1394 <item id="123" publisher="{TEST_JID}/res.123">
1395 <entry xmlns="http://www.w3.org/2005/Atom">
1396 <title type="text">test message 1</title>
1397 <title type="xhtml">
1398 <div xmlns="http://www.w3.org/1999/xhtml">
1399 <p>test message 1</p>
1400 </div>
1401 </title>
1402 <author>
1403 <name>test_user</name>
1404 <uri>xmpp:{recipient_jid}</uri>
1405 </author>
1406 <updated>2022-07-21T14:38:53Z</updated>
1407 <published>2022-07-21T14:38:53Z</published>
1408 <id>{ap_item["id"]}</id>
1409 <link href="{ap_item_url}" rel="via"/>
1410 </entry>
1411 </item>
1412 """)
1413 item_elt.uri = pubsub.NS_PUBSUB_EVENT
1414
1415 with patch.object(ap_gateway, "signAndPost") as signAndPost:
1416 signAndPost.return_value.code = 202
1417 await ap_gateway.convertAndPostItems(
1418 ap_gateway.client,
1419 TEST_AP_ACCOUNT,
1420 TEST_JID,
1421 ap_gateway._m.namespace,
1422 [item_elt]
1423 )
1424
1425 assert signAndPost.called
1426 url, actor_id, doc = signAndPost.call_args.args
1427 assert url == TEST_USER_DATA["inbox"]
1428 assert actor_id == ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost())
1429 assert doc["type"] == "Announce"
1430 assert ap_const.NS_AP_PUBLIC in doc["to"]
1431 assert doc["object"] == ap_item["id"]
1432
1433 @ed
1434 async def test_ap_announce_to_xmpp_repeat(self, ap_gateway, monkeypatch):
1435 """AP Announce activity is converted to XEP-0272 post repeat"""
1436 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get)
1437 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json)
1438 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get)
1439
1440 xmpp_actor_id = ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost())
1441 # announced item
1442 xmpp_item = XMPP_ITEMS[0]
1443 xmpp_item_url = ap_gateway.buildAPURL(
1444 ap_const.TYPE_ITEM, TEST_JID.userhost(), xmpp_item["id"]
1445 )
1446 announce = {
1447 "@context": "https://www.w3.org/ns/activitystreams",
1448 "type": "Announce",
1449 "actor": TEST_AP_ACTOR_ID,
1450 "cc": [
1451 xmpp_actor_id,
1452 TEST_USER_DATA["followers"]
1453 ],
1454 "id": "https://example.org/announce/123",
1455 "object": xmpp_item_url,
1456 "published": "2022-07-22T09:24:12Z",
1457 "to": [ap_const.NS_AP_PUBLIC]
1458 }
1459 with patch.object(ap_gateway.host.memory.storage, "getItems") as getItems:
1460 mock_pubsub_item = MagicMock
1461 mock_pubsub_item.data = xmpp_item
1462 getItems.return_value = ([mock_pubsub_item], {})
1463 with patch.object(
1464 ap_gateway.host.memory.storage,
1465 "cachePubsubItems") as cachePubsubItems:
1466 await ap_gateway.server.resource.handleAnnounceActivity(
1467 Request(MagicMock()),
1468 announce,
1469 None,
1470 None,
1471 None,
1472 "",
1473 TEST_AP_ACTOR_ID
1474 )
1475
1476 assert cachePubsubItems.called
1477 # the microblog data put in cache correspond to the item sent to subscribers
1478 __, __, __, [mb_data] = cachePubsubItems.call_args.args
1479 extra = mb_data["extra"]
1480 assert "repeated" in extra
1481 repeated = extra["repeated"]
1482 assert repeated["by"] == ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT).full()
1483 xmpp_item_xmpp_url = xmpp_uri.buildXMPPUri(
1484 "pubsub",
1485 path=TEST_JID.full(),
1486 node=ap_gateway._m.namespace,
1487 item=xmpp_item["id"]
1488 )
1489 assert repeated["uri"] == xmpp_item_xmpp_url
1490
1491 @ed
1492 async def test_xmpp_attachment_noticed_to_ap_like(self, ap_gateway, monkeypatch):
1493 """Pubsub-attachments ``noticed`` is converted to AP Like activity"""
1494 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get)
1495 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json)
1496 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get)
1497
1498 recipient_jid = ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT)
1499 # noticed item
1500 ap_item = TEST_AP_ITEMS[0]
1501 ap_item_url = xmpp_uri.buildXMPPUri(
1502 "pubsub",
1503 path=recipient_jid.full(),
1504 node=ap_gateway._m.namespace,
1505 item=ap_item["id"]
1506 )
1507 attachment_node = ap_gateway._pa.getAttachmentNodeName(
1508 recipient_jid,
1509 ap_gateway._m.namespace,
1510 ap_item["id"]
1511 )
1512 item_elt = xml_tools.parse(f"""
1513 <item id="{TEST_JID.userhost()}" published="{TEST_JID.userhostJID()}">
1514 <attachments xmlns="urn:xmpp:pubsub-attachments:0">
1515 <noticed timestamp="2022-07-22T12:29:45Z"/>
1516 </attachments>
1517 </item>
1518 """)
1519 item_elt.uri = pubsub.NS_PUBSUB_EVENT
1520 items_event = pubsub.ItemsEvent(
1521 TEST_JID,
1522 recipient_jid,
1523 attachment_node,
1524 [item_elt],
1525 {}
1526 )
1527
1528 with patch.object(ap_gateway, "signAndPost") as signAndPost:
1529 signAndPost.return_value.code = 202
1530 await ap_gateway._itemsReceived(
1531 ap_gateway.client,
1532 items_event
1533 )
1534
1535 assert signAndPost.called
1536 url, actor_id, doc = signAndPost.call_args.args
1537 assert url == TEST_USER_DATA["inbox"]
1538 assert actor_id == ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost())
1539 assert doc["type"] == "Like"
1540 assert ap_const.NS_AP_PUBLIC in doc["to"]
1541 assert doc["object"] == ap_item["id"]
1542
1543 @ed
1544 async def test_ap_like_to_xmpp_noticed_attachment(self, ap_gateway, monkeypatch):
1545 """AP Like activity is converted to ``noticed`` attachment"""
1546 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get)
1547 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json)
1548 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get)
1549
1550 xmpp_actor_id = ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, TEST_JID.userhost())
1551 # liked item
1552 xmpp_item = XMPP_ITEMS[0]
1553 xmpp_item_url = ap_gateway.buildAPURL(
1554 ap_const.TYPE_ITEM, TEST_JID.userhost(), xmpp_item["id"]
1555 )
1556 like = {
1557 "@context": "https://www.w3.org/ns/activitystreams",
1558 "type": "Like",
1559 "actor": TEST_AP_ACTOR_ID,
1560 "cc": [
1561 xmpp_actor_id,
1562 TEST_USER_DATA["followers"]
1563 ],
1564 "id": "https://example.org/like/123",
1565 "object": xmpp_item_url,
1566 "published": "2022-07-22T09:24:12Z",
1567 "to": [ap_const.NS_AP_PUBLIC]
1568 }
1569 with patch.object(ap_gateway.host.memory.storage, "getItems") as getItems:
1570 getItems.return_value = ([], {})
1571 with patch.object(ap_gateway._p, "sendItems") as sendItems:
1572 await ap_gateway.server.resource.APInboxRequest(
1573 **self.ap_request_params(
1574 ap_gateway,
1575 "inbox",
1576 doc=like,
1577 signing_actor=TEST_AP_ACTOR_ID
1578 )
1579 )
1580
1581 assert sendItems.called
1582 si_client, si_service, si_node, [si_item] = sendItems.call_args.args
1583 assert si_client.jid == ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT)
1584 assert si_service == TEST_JID
1585 assert si_node == ap_gateway._pa.getAttachmentNodeName(
1586 TEST_JID, ap_gateway._m.namespace, xmpp_item["id"]
1587 )
1588 [parsed_item] = ap_gateway._pa.items2attachmentData(si_client, [si_item])
1589 assert parsed_item["from"] == si_client.jid.full()
1590 assert "noticed" in parsed_item
1591 assert parsed_item["noticed"]["noticed"] == True