Mercurial > libervia-backend
comparison tests/unit/test_ap-gateway.py @ 3809:04b57c0b2278
tests (unit/ap gateway): message/item retractation tests:
this patch adds 4 tests to check pubsub <=> AP item retractation and message <=> AP item
retractation.
rel 367
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 17 Jun 2022 15:50:34 +0200 |
parents | 39fc2e1b3793 |
children | 81c79b7cafa7 |
comparison
equal
deleted
inserted
replaced
3808:39fc2e1b3793 | 3809:04b57c0b2278 |
---|---|
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 | 20 from unittest.mock import MagicMock, AsyncMock, patch, DEFAULT |
21 from urllib import parse | 21 from urllib import parse |
22 from functools import partial | 22 from functools import partial |
23 | 23 |
24 import pytest | 24 import pytest |
25 from pytest_twisted import ensureDeferred as ed | 25 from pytest_twisted import ensureDeferred as ed |
26 from twisted.internet import defer | 26 from twisted.internet import defer |
27 from twisted.words.protocols.jabber import jid | 27 from twisted.words.protocols.jabber import jid |
28 from twisted.web.server import Request | 28 from twisted.web.server import Request |
29 from twisted.words.xish import domish | 29 from twisted.words.xish import domish |
30 from wokkel import rsm, pubsub | 30 from wokkel import rsm, pubsub |
31 from treq.response import _Response as TReqResponse | |
31 | 32 |
32 from sat.core import exceptions | 33 from sat.core import exceptions |
33 from sat.core.constants import Const as C | 34 from sat.core.constants import Const as C |
34 from sat.plugins import plugin_comp_ap_gateway | 35 from sat.plugins import plugin_comp_ap_gateway |
35 from sat.plugins.plugin_comp_ap_gateway import constants as ap_const | 36 from sat.plugins.plugin_comp_ap_gateway import constants as ap_const |
36 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer | 37 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer |
37 from sat.plugins.plugin_xep_0277 import NS_ATOM | 38 from sat.plugins.plugin_xep_0277 import NS_ATOM |
39 from sat.plugins.plugin_xep_0422 import NS_FASTEN | |
40 from sat.plugins.plugin_xep_0424 import NS_MESSAGE_RETRACT | |
38 from sat.plugins.plugin_xep_0465 import NS_PPS | 41 from sat.plugins.plugin_xep_0465 import NS_PPS |
39 from sat.tools.utils import xmpp_date | 42 from sat.tools.utils import xmpp_date |
40 from sat.tools import xml_tools | 43 from sat.tools import xml_tools |
41 from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR | 44 from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR |
45 from sat.memory.sqla_mapping import SubscriptionState | |
42 | 46 |
43 | 47 |
44 TEST_BASE_URL = "https://example.org" | 48 TEST_BASE_URL = "https://example.org" |
45 TEST_USER = "test_user" | 49 TEST_USER = "test_user" |
46 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" | 50 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" |
378 client = MagicMock() | 382 client = MagicMock() |
379 client.jid = jid | 383 client.jid = jid |
380 return client | 384 return client |
381 | 385 |
382 | 386 |
387 class FakeTReqPostResponse: | |
388 code = 202 | |
389 | |
390 | |
383 @pytest.fixture(scope="session") | 391 @pytest.fixture(scope="session") |
384 def ap_gateway(host): | 392 def ap_gateway(host): |
385 gateway = plugin_comp_ap_gateway.APGateway(host) | 393 gateway = plugin_comp_ap_gateway.APGateway(host) |
386 gateway.initialised = True | 394 gateway.initialised = True |
387 gateway.isPubsub = AsyncMock() | 395 gateway.isPubsub = AsyncMock() |
884 # sendMessage must be called for <message> stanza, and the "message" argument must | 892 # sendMessage must be called for <message> stanza, and the "message" argument must |
885 # be set to the content of the original AP message | 893 # be set to the content of the original AP message |
886 assert sendMessage.called | 894 assert sendMessage.called |
887 assert sendMessage.call_args.args[0] == TEST_JID | 895 assert sendMessage.call_args.args[0] == TEST_JID |
888 assert sendMessage.call_args.args[1] == {"": "test direct message"} | 896 assert sendMessage.call_args.args[1] == {"": "test direct message"} |
897 | |
898 @ed | |
899 async def test_pubsub_retract_to_ap_delete(self, ap_gateway, monkeypatch): | |
900 """Pubsub retract requests are converted to AP delete activity""" | |
901 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) | |
902 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) | |
903 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) | |
904 retract_id = "retract_123" | |
905 retract_elt = domish.Element((pubsub.NS_PUBSUB_EVENT, "retract")) | |
906 retract_elt["id"] = retract_id | |
907 items_event = pubsub.ItemsEvent( | |
908 sender=TEST_JID, | |
909 recipient=ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT), | |
910 nodeIdentifier=ap_gateway._m.namespace, | |
911 items=[retract_elt], | |
912 headers={} | |
913 ) | |
914 with patch.object(ap_gateway, "signAndPost") as signAndPost: | |
915 signAndPost.return_value = FakeTReqPostResponse() | |
916 # we simulate the reception of a retract event | |
917 await ap_gateway._itemsReceived(ap_gateway.client, items_event) | |
918 url, actor_id, doc = signAndPost.call_args[0] | |
919 jid_account = await ap_gateway.getAPAccountFromJidAndNode(TEST_JID, None) | |
920 jid_actor_id = ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, jid_account) | |
921 assert url == f"{TEST_BASE_URL}/users/{TEST_USER}/inbox" | |
922 assert actor_id == jid_actor_id | |
923 assert doc["type"] == "Delete" | |
924 assert doc["actor"] == jid_actor_id | |
925 obj = doc["object"] | |
926 assert obj["type"] == ap_const.TYPE_TOMBSTONE | |
927 url_item_id = ap_gateway.buildAPURL(ap_const.TYPE_ITEM, jid_account, retract_id) | |
928 assert obj["id"] == url_item_id | |
929 | |
930 @ed | |
931 async def test_ap_delete_to_pubsub_retract(self, ap_gateway): | |
932 """AP delete activity is converted to pubsub retract""" | |
933 client = ap_gateway.client.getVirtualClient( | |
934 ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT) | |
935 ) | |
936 | |
937 ap_item = { | |
938 "@context": "https://www.w3.org/ns/activitystreams", | |
939 "actor": TEST_AP_ACTOR_ID, | |
940 "id": "https://test.example/retract_123", | |
941 "type": "Delete", | |
942 "object": {"id": f"{TEST_AP_ACTOR_ID}/item/123", | |
943 "type": "Tombstone"}, | |
944 "to": ["https://www.w3.org/ns/activitystreams#Public"] | |
945 } | |
946 with patch.multiple( | |
947 ap_gateway.host.memory.storage, | |
948 get=DEFAULT, | |
949 getPubsubNode=DEFAULT, | |
950 deletePubsubItems=DEFAULT, | |
951 ) as mock_objs: | |
952 mock_objs["get"].return_value=None | |
953 cached_node = MagicMock() | |
954 mock_objs["getPubsubNode"].return_value=cached_node | |
955 subscription = MagicMock() | |
956 subscription.state = SubscriptionState.SUBSCRIBED | |
957 subscription.subscriber = TEST_JID | |
958 cached_node.subscriptions = [subscription] | |
959 with patch.object( | |
960 ap_gateway.pubsub_service, "notifyRetract" | |
961 ) as notifyRetract: | |
962 # we simulate a received Delete activity | |
963 await ap_gateway.newAPDeleteItem( | |
964 client=client, | |
965 destinee=None, | |
966 node=ap_gateway._m.namespace, | |
967 item=ap_item | |
968 ) | |
969 | |
970 # item is deleted from database | |
971 deletePubsubItems = mock_objs["deletePubsubItems"] | |
972 assert deletePubsubItems.call_count == 1 | |
973 assert deletePubsubItems.call_args.args[1] == [ap_item["id"]] | |
974 | |
975 # retraction notification is sent to subscribers | |
976 assert notifyRetract.call_count == 1 | |
977 assert notifyRetract.call_args.args[0] == client.jid | |
978 assert notifyRetract.call_args.args[1] == ap_gateway._m.namespace | |
979 notifications = notifyRetract.call_args.args[2] | |
980 assert len(notifications) == 1 | |
981 subscriber, __, item_elts = notifications[0] | |
982 assert subscriber == TEST_JID | |
983 assert len(item_elts) == 1 | |
984 item_elt = item_elts[0] | |
985 assert isinstance(item_elt, domish.Element) | |
986 assert item_elt.name == "item" | |
987 assert item_elt["id"] == ap_item["id"] | |
988 | |
989 @ed | |
990 async def test_message_retract_to_ap_delete(self, ap_gateway, monkeypatch): | |
991 """Message retract requests are converted to AP delete activity""" | |
992 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) | |
993 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) | |
994 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) | |
995 # origin ID is the ID of the message to retract | |
996 origin_id = "mess_retract_123" | |
997 | |
998 # we call retractByOriginId to get the message element of a retraction request | |
999 fake_client = MagicMock() | |
1000 fake_client.jid = TEST_JID | |
1001 dest_jid = ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT) | |
1002 ap_gateway._r.retractByOriginId(fake_client, dest_jid, origin_id) | |
1003 # message_retract_elt is the message which would be sent for a retraction | |
1004 message_retract_elt = fake_client.send.call_args.args[0] | |
1005 apply_to_elt = next(message_retract_elt.elements(NS_FASTEN, "apply-to")) | |
1006 retract_elt = apply_to_elt.retract | |
1007 | |
1008 with patch.object(ap_gateway, "signAndPost") as signAndPost: | |
1009 signAndPost.return_value = FakeTReqPostResponse() | |
1010 fake_fastened_elts = MagicMock() | |
1011 fake_fastened_elts.id = origin_id | |
1012 # we simulate the reception of a retract event using the message element that | |
1013 # we generated above | |
1014 await ap_gateway._onMessageRetract( | |
1015 ap_gateway.client, | |
1016 message_retract_elt, | |
1017 retract_elt, | |
1018 fake_fastened_elts | |
1019 ) | |
1020 url, actor_id, doc = signAndPost.call_args[0] | |
1021 | |
1022 # the AP delete activity must have been sent through signAndPost | |
1023 # we check its values | |
1024 jid_account = await ap_gateway.getAPAccountFromJidAndNode(TEST_JID, None) | |
1025 jid_actor_id = ap_gateway.buildAPURL(ap_const.TYPE_ACTOR, jid_account) | |
1026 assert url == f"{TEST_BASE_URL}/users/{TEST_USER}/inbox" | |
1027 assert actor_id == jid_actor_id | |
1028 assert doc["type"] == "Delete" | |
1029 assert doc["actor"] == jid_actor_id | |
1030 obj = doc["object"] | |
1031 assert obj["type"] == ap_const.TYPE_TOMBSTONE | |
1032 url_item_id = ap_gateway.buildAPURL(ap_const.TYPE_ITEM, jid_account, origin_id) | |
1033 assert obj["id"] == url_item_id | |
1034 | |
1035 @ed | |
1036 async def test_ap_delete_to_message_retract(self, ap_gateway, monkeypatch): | |
1037 """AP delete activity is converted to message retract""" | |
1038 # note: a message retract is used when suitable message is found in history, | |
1039 # otherwise it should be in pubsub cache and it's a pubsub retract (tested above | |
1040 # by ``test_ap_delete_to_pubsub_retract``) | |
1041 | |
1042 # we don't want actual queries in database | |
1043 retractDBHistory = AsyncMock() | |
1044 monkeypatch.setattr(ap_gateway._r, "retractDBHistory", retractDBHistory) | |
1045 | |
1046 client = ap_gateway.client.getVirtualClient( | |
1047 ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT) | |
1048 ) | |
1049 fake_send = MagicMock() | |
1050 monkeypatch.setattr(client, "send", fake_send) | |
1051 | |
1052 ap_item = { | |
1053 "@context": "https://www.w3.org/ns/activitystreams", | |
1054 "actor": TEST_AP_ACTOR_ID, | |
1055 "id": "https://test.example/retract_123", | |
1056 "type": "Delete", | |
1057 "object": {"id": f"{TEST_AP_ACTOR_ID}/item/123", | |
1058 "type": "Tombstone"}, | |
1059 "to": ["https://www.w3.org/ns/activitystreams#Public"] | |
1060 } | |
1061 with patch.object(ap_gateway.host.memory.storage, "get") as storage_get: | |
1062 fake_history = MagicMock() | |
1063 fake_history.source_jid = client.jid | |
1064 fake_history.dest_jid = TEST_JID | |
1065 fake_history.origin_id = ap_item["id"] | |
1066 storage_get.return_value = fake_history | |
1067 # we simulate a received Delete activity | |
1068 await ap_gateway.newAPDeleteItem( | |
1069 client=client, | |
1070 destinee=None, | |
1071 node=ap_gateway._m.namespace, | |
1072 item=ap_item | |
1073 ) | |
1074 | |
1075 # item is deleted from database | |
1076 assert retractDBHistory.call_count == 1 | |
1077 assert retractDBHistory.call_args.args[0] == client | |
1078 assert retractDBHistory.call_args.args[1] == fake_history | |
1079 | |
1080 # retraction notification is sent to destinee | |
1081 assert fake_send.call_count == 1 | |
1082 sent_elt = fake_send.call_args.args[0] | |
1083 assert sent_elt.name == "message" | |
1084 assert sent_elt["from"] == client.jid.full() | |
1085 assert sent_elt["to"] == TEST_JID.full() | |
1086 apply_to_elt = next(sent_elt.elements(NS_FASTEN, "apply-to")) | |
1087 assert apply_to_elt["id"] == ap_item["id"] | |
1088 retract_elt = apply_to_elt.retract | |
1089 assert retract_elt is not None | |
1090 assert retract_elt.uri == NS_MESSAGE_RETRACT |