Mercurial > libervia-backend
comparison tests/unit/test_ap-gateway.py @ 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 | 04ecc8eeb81a |
children | fedbf7aade11 |
comparison
equal
deleted
inserted
replaced
3769:a75874df92b8 | 3770:f31113777881 |
---|---|
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, patch | 20 from unittest.mock import MagicMock, AsyncMock, patch |
21 from urllib import parse | 21 from urllib import parse |
22 from functools import partial | |
22 | 23 |
23 import pytest | 24 import pytest |
24 from pytest_twisted import ensureDeferred as ed | 25 from pytest_twisted import ensureDeferred as ed |
25 from twisted.words.protocols.jabber import jid | 26 from twisted.words.protocols.jabber import jid |
26 from twisted.web.server import Request | 27 from twisted.web.server import Request |
27 from wokkel import rsm, pubsub | 28 from wokkel import rsm, pubsub |
28 | 29 |
29 from sat.core import exceptions | 30 from sat.core import exceptions |
30 from sat.plugins import plugin_comp_ap_gateway | 31 from sat.plugins import plugin_comp_ap_gateway |
31 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer | 32 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer |
33 from sat.plugins.plugin_xep_0465 import NS_PPS | |
32 from sat.tools.utils import xmpp_date | 34 from sat.tools.utils import xmpp_date |
33 from sat.tools import xml_tools | 35 from sat.tools import xml_tools |
36 from sat.plugins.plugin_comp_ap_gateway import TYPE_ACTOR | |
34 | 37 |
35 | 38 |
36 TEST_BASE_URL = "https://example.org" | 39 TEST_BASE_URL = "https://example.org" |
37 TEST_USER = "test_user" | 40 TEST_USER = "test_user" |
38 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" | 41 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" |
42 PUBLIC_URL = "test.example" | |
39 | 43 |
40 AP_REQUESTS = { | 44 AP_REQUESTS = { |
41 f"{TEST_BASE_URL}/.well-known/webfinger?" | 45 f"{TEST_BASE_URL}/.well-known/webfinger?" |
42 f"resource=acct:{parse.quote(TEST_AP_ACCOUNT)}": { | 46 f"resource=acct:{parse.quote(TEST_AP_ACCOUNT)}": { |
43 "aliases": [ | 47 "aliases": [ |
69 "outbox": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", | 73 "outbox": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", |
70 "preferredUsername": f"{TEST_USER}", | 74 "preferredUsername": f"{TEST_USER}", |
71 "type": "Person", | 75 "type": "Person", |
72 "url": f"{TEST_BASE_URL}/@{TEST_USER}" | 76 "url": f"{TEST_BASE_URL}/@{TEST_USER}" |
73 }, | 77 }, |
74 | 78 f"{TEST_BASE_URL}/.well-known/webfinger?" |
79 f"resource=acct:{parse.quote('ext_user@example.org')}": { | |
80 "aliases": [ | |
81 f"{TEST_BASE_URL}/@ext_user", | |
82 f"{TEST_BASE_URL}/users/ext_user" | |
83 ], | |
84 "links": [ | |
85 { | |
86 "href": f"{TEST_BASE_URL}/users/ext_user", | |
87 "rel": "self", | |
88 "type": "application/activity+json" | |
89 }, | |
90 ], | |
91 "subject": f"acct:ext_user@example.org" | |
92 }, | |
93 f"{TEST_BASE_URL}/users/ext_user": { | |
94 "@context": [ | |
95 "https://www.w3.org/ns/activitystreams", | |
96 ], | |
97 "endpoints": { | |
98 "sharedInbox": f"{TEST_BASE_URL}/inbox" | |
99 }, | |
100 "followers": f"{TEST_BASE_URL}/users/ext_user/followers", | |
101 "following": f"{TEST_BASE_URL}/users/ext_user/following", | |
102 "id": f"{TEST_BASE_URL}/users/ext_user", | |
103 "inbox": f"{TEST_BASE_URL}/users/ext_user/inbox", | |
104 "name": "", | |
105 "outbox": f"{TEST_BASE_URL}/users/ext_user/outbox", | |
106 "preferredUsername": f"ext_user", | |
107 "type": "Person", | |
108 "url": f"{TEST_BASE_URL}/@ext_user" | |
109 }, | |
75 f"{TEST_BASE_URL}/users/{TEST_USER}/outbox": { | 110 f"{TEST_BASE_URL}/users/{TEST_USER}/outbox": { |
76 "@context": "https://www.w3.org/ns/activitystreams", | 111 "@context": "https://www.w3.org/ns/activitystreams", |
77 "first": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", | 112 "first": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", |
78 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", | 113 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", |
79 "last": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", | 114 "last": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", |
220 }, | 255 }, |
221 ], | 256 ], |
222 "partOf": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", | 257 "partOf": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", |
223 "prev": None, | 258 "prev": None, |
224 "type": "OrderedCollectionPage" | 259 "type": "OrderedCollectionPage" |
225 } | 260 }, |
261 f"{TEST_BASE_URL}/users/{TEST_USER}/following": { | |
262 "@context": "https://www.w3.org/ns/activitystreams", | |
263 "first": f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1", | |
264 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/following", | |
265 "totalItems": 2, | |
266 "type": "OrderedCollection" | |
267 }, | |
268 f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1": { | |
269 "@context": "https://www.w3.org/ns/activitystreams", | |
270 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1", | |
271 "orderedItems": [ | |
272 f"{TEST_BASE_URL}/users/ext_user", | |
273 f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}", | |
274 ], | |
275 "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/following", | |
276 "totalItems": 2, | |
277 "type": "OrderedCollectionPage" | |
278 }, | |
279 f"{TEST_BASE_URL}/users/{TEST_USER}/followers": { | |
280 "@context": "https://www.w3.org/ns/activitystreams", | |
281 "first": f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1", | |
282 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/followers", | |
283 "totalItems": 2, | |
284 "type": "OrderedCollection" | |
285 }, | |
286 f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1": { | |
287 "@context": "https://www.w3.org/ns/activitystreams", | |
288 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1", | |
289 "orderedItems": [ | |
290 f"{TEST_BASE_URL}/users/ext_user", | |
291 f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}", | |
292 ], | |
293 "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/followers", | |
294 "totalItems": 2, | |
295 "type": "OrderedCollectionPage" | |
296 }, | |
226 | 297 |
227 } | 298 } |
228 | 299 |
229 XMPP_ITEM_TPL = """ | 300 XMPP_ITEM_TPL = """ |
230 <item id='{id}' publisher='{publisher_jid}'> | 301 <item id='{id}' publisher='{publisher_jid}'> |
274 namespace=pubsub.NS_PUBSUB | 345 namespace=pubsub.NS_PUBSUB |
275 ) | 346 ) |
276 for i in range(1, 5) | 347 for i in range(1, 5) |
277 ] | 348 ] |
278 | 349 |
350 | |
279 async def mock_ap_get(url): | 351 async def mock_ap_get(url): |
280 return deepcopy(AP_REQUESTS[url]) | 352 return deepcopy(AP_REQUESTS[url]) |
281 | 353 |
282 | 354 |
283 async def mock_treq_json(data): | 355 async def mock_treq_json(data): |
284 return dict(data) | 356 return dict(data) |
285 | 357 |
286 | 358 |
287 async def mock_getItems(*args, **kwargs): | 359 async def mock_getItems(*args, **kwargs): |
360 ret_items = kwargs.pop("ret_items", XMPP_ITEMS) | |
288 rsm_resp = rsm.RSMResponse( | 361 rsm_resp = rsm.RSMResponse( |
289 first=XMPP_ITEMS[0]["id"], | 362 first=ret_items[0]["id"], |
290 last=XMPP_ITEMS[-1]["id"], | 363 last=ret_items[-1]["id"], |
291 index=0, | 364 index=0, |
292 count=len(XMPP_ITEMS) | 365 count=len(ret_items) |
293 ) | 366 ) |
294 return XMPP_ITEMS, {"rsm": rsm_resp.toDict(), "complete": True} | 367 return ret_items, {"rsm": rsm_resp.toDict(), "complete": True} |
295 | 368 |
296 | 369 |
297 @pytest.fixture(scope="session") | 370 @pytest.fixture(scope="session") |
298 def ap_gateway(host): | 371 def ap_gateway(host): |
299 gateway = plugin_comp_ap_gateway.APGateway(host) | 372 gateway = plugin_comp_ap_gateway.APGateway(host) |
300 gateway.initialised = True | 373 gateway.initialised = True |
374 gateway.isPubsub = AsyncMock() | |
375 gateway.isPubsub.return_value = False | |
301 client = MagicMock() | 376 client = MagicMock() |
302 client.jid = jid.JID("ap.test.example") | 377 client.jid = jid.JID("ap.test.example") |
303 client.host = "test.example" | 378 client.host = "test.example" |
304 gateway.client = client | 379 gateway.client = client |
305 gateway.local_only = True | 380 gateway.local_only = True |
306 gateway.public_url = "test.example" | 381 gateway.public_url = PUBLIC_URL |
307 gateway.ap_path = '_ap' | 382 gateway.ap_path = '_ap' |
308 gateway.base_ap_url = parse.urljoin( | 383 gateway.base_ap_url = parse.urljoin( |
309 f"https://{gateway.public_url}", | 384 f"https://{gateway.public_url}", |
310 f"{gateway.ap_path}/" | 385 f"{gateway.ap_path}/" |
311 ) | 386 ) |
405 """AP requests are converted to pubsub""" | 480 """AP requests are converted to pubsub""" |
406 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) | 481 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) |
407 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) | 482 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) |
408 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) | 483 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) |
409 | 484 |
410 actor_data = await ap_gateway.getAPActorDataFromId(TEST_AP_ACCOUNT) | 485 actor_data = await ap_gateway.getAPActorDataFromAccount(TEST_AP_ACCOUNT) |
411 outbox = await ap_gateway.apGetObject(actor_data, "outbox") | 486 outbox = await ap_gateway.apGetObject(actor_data, "outbox") |
412 items, rsm_resp = await ap_gateway.getAPItems(outbox, 2) | 487 items, rsm_resp = await ap_gateway.getAPItems(outbox, 2) |
413 | 488 |
414 assert rsm_resp.count == 4 | 489 assert rsm_resp.count == 4 |
415 assert rsm_resp.index == 0 | 490 assert rsm_resp.index == 0 |
546 "request": request, | 621 "request": request, |
547 "account_jid": test_jid, | 622 "account_jid": test_jid, |
548 "node": None, | 623 "node": None, |
549 "ap_account": test_jid.full(), | 624 "ap_account": test_jid.full(), |
550 "ap_url": ap_url, | 625 "ap_url": ap_url, |
626 "signing_actor": None | |
551 } | 627 } |
552 if type_ == "outbox" and query_data: | 628 if type_ == "outbox" and query_data: |
553 kwargs["query_data"] = query_data | 629 kwargs["query_data"] = query_data |
630 # signing_actor is not used for page requests | |
631 del kwargs["signing_actor"] | |
554 return kwargs | 632 return kwargs |
555 | 633 |
556 @ed | 634 @ed |
557 async def test_pubsub_to_ap_conversion(self, ap_gateway, monkeypatch): | 635 async def test_pubsub_to_ap_conversion(self, ap_gateway, monkeypatch): |
558 """Pubsub nodes are converted to AP collections""" | 636 """Pubsub nodes are converted to AP collections""" |
584 assert first_item_obj["id"] == first_item["id"] | 662 assert first_item_obj["id"] == first_item["id"] |
585 assert first_item_obj["type"] == "Note" | 663 assert first_item_obj["type"] == "Note" |
586 assert first_item_obj["published"] == "2022-01-28T16:02:19Z" | 664 assert first_item_obj["published"] == "2022-01-28T16:02:19Z" |
587 assert first_item_obj["attributedTo"] == first_item["actor"] | 665 assert first_item_obj["attributedTo"] == first_item["actor"] |
588 assert first_item_obj["content"] == "<div><p>XMPP item 4</p></div>" | 666 assert first_item_obj["content"] == "<div><p>XMPP item 4</p></div>" |
589 assert first_item_obj["to"] == "https://www.w3.org/ns/activitystreams#Public" | 667 assert first_item_obj["to"] == ["https://www.w3.org/ns/activitystreams#Public"] |
668 | |
669 @ed | |
670 async def test_following_to_pps(self, ap_gateway, monkeypatch): | |
671 """AP following items are converted to Public Pubsub Subscription subscriptions""" | |
672 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) | |
673 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) | |
674 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) | |
675 | |
676 items, __ = await ap_gateway.pubsub_service.items( | |
677 jid.JID("toto@example.org"), | |
678 ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT), | |
679 ap_gateway._pps.subscriptions_node, | |
680 None, | |
681 None, | |
682 None | |
683 ) | |
684 assert len(items) == 2 | |
685 for idx, entity in enumerate(( | |
686 "local_user@test.example", | |
687 "ext_user\\40example.org@ap.test.example" | |
688 )): | |
689 subscription_elt = next(items[idx].elements(NS_PPS, "subscription"), None) | |
690 assert subscription_elt is not None | |
691 assert subscription_elt["node"] == ap_gateway._m.namespace | |
692 assert subscription_elt["service"] == entity | |
693 | |
694 @ed | |
695 async def test_followers_to_pps(self, ap_gateway, monkeypatch): | |
696 """AP followers items are converted to Public Pubsub Subscription subscribers""" | |
697 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) | |
698 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) | |
699 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) | |
700 | |
701 items, __ = await ap_gateway.pubsub_service.items( | |
702 jid.JID("toto@example.org"), | |
703 ap_gateway.getLocalJIDFromAccount(TEST_AP_ACCOUNT), | |
704 ap_gateway._pps.getPublicSubscribersNode(ap_gateway._m.namespace), | |
705 None, | |
706 None, | |
707 None | |
708 ) | |
709 assert len(items) == 2 | |
710 for idx, entity in enumerate(( | |
711 "local_user@test.example", | |
712 "ext_user\\40example.org@ap.test.example" | |
713 )): | |
714 subscriber_elt = next(items[idx].elements(NS_PPS, "subscriber"), None) | |
715 assert subscriber_elt is not None | |
716 assert subscriber_elt["jid"] == entity | |
717 | |
718 @ed | |
719 async def test_pps_to_following(self, ap_gateway, monkeypatch): | |
720 """Public Pubsub Subscription subscriptions are converted to AP following items""" | |
721 subscriptions = [ | |
722 pubsub.Item( | |
723 id="subscription_1", | |
724 payload = ap_gateway._pps.buildSubscriptionElt( | |
725 ap_gateway._m.namespace, | |
726 jid.JID("local_user@test.example") | |
727 ) | |
728 ), | |
729 pubsub.Item( | |
730 id="subscription_2", | |
731 payload = ap_gateway._pps.buildSubscriptionElt( | |
732 ap_gateway._m.namespace, | |
733 jid.JID("ext_user\\40example.org@ap.test.example") | |
734 ) | |
735 ) | |
736 ] | |
737 monkeypatch.setattr(ap_gateway._p, "getItems", partial( | |
738 mock_getItems, | |
739 ret_items=subscriptions | |
740 )) | |
741 following = await ap_gateway.server.resource.APFollowingRequest( | |
742 **self.ap_request_params(ap_gateway, "following") | |
743 ) | |
744 assert following["@context"] == "https://www.w3.org/ns/activitystreams" | |
745 assert following["id"] == "https://test.example/_ap/following/some_user%40test.example" | |
746 assert following["totalItems"] == len(subscriptions) | |
747 assert following["type"] == "OrderedCollection" | |
748 assert following.get("first") | |
749 | |
750 first_page = following["first"] | |
751 assert first_page["type"] == "OrderedCollectionPage" | |
752 assert len(first_page["orderedItems"]) == len(subscriptions) | |
753 items = first_page["orderedItems"] | |
754 assert items == ['local_user@test.example', 'ext_user@example.org'] | |
755 | |
756 @ed | |
757 async def test_pps_to_followers(self, ap_gateway, monkeypatch): | |
758 """Public Pubsub Subscription subscribers are converted to AP followers""" | |
759 subscribers = [ | |
760 pubsub.Item( | |
761 id="subscriber_1", | |
762 payload = ap_gateway._pps.buildSubscriberElt( | |
763 jid.JID("local_user@test.example") | |
764 ) | |
765 ), | |
766 pubsub.Item( | |
767 id="subscriber_2", | |
768 payload = ap_gateway._pps.buildSubscriberElt( | |
769 jid.JID("ext_user\\40example.org@ap.test.example") | |
770 ) | |
771 ) | |
772 ] | |
773 monkeypatch.setattr(ap_gateway._p, "getItems", partial( | |
774 mock_getItems, | |
775 ret_items=subscribers | |
776 )) | |
777 followers = await ap_gateway.server.resource.APFollowersRequest( | |
778 **self.ap_request_params(ap_gateway, "followers") | |
779 ) | |
780 assert followers["@context"] == "https://www.w3.org/ns/activitystreams" | |
781 assert followers["id"] == "https://test.example/_ap/followers/some_user%40test.example" | |
782 assert followers["totalItems"] == len(subscribers) | |
783 assert followers["type"] == "OrderedCollection" | |
784 assert followers.get("first") | |
785 | |
786 first_page = followers["first"] | |
787 assert first_page["type"] == "OrderedCollectionPage" | |
788 assert len(first_page["orderedItems"]) == len(subscribers) | |
789 items = first_page["orderedItems"] | |
790 assert items == ['local_user@test.example', 'ext_user@example.org'] |