Mercurial > libervia-backend
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 |