3733
|
1 #!/usr/bin/env python3 |
|
2 |
|
3 # Libervia: an XMPP client |
|
4 # Copyright (C) 2009-2022 Jérôme Poisson (goffi@goffi.org) |
|
5 |
|
6 # This program is free software: you can redistribute it and/or modify |
|
7 # it under the terms of the GNU Affero General Public License as published by |
|
8 # the Free Software Foundation, either version 3 of the License, or |
|
9 # (at your option) any later version. |
|
10 |
|
11 # This program is distributed in the hope that it will be useful, |
|
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 # GNU Affero General Public License for more details. |
|
15 |
|
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/>. |
|
18 |
|
19 from copy import deepcopy |
|
20 from unittest.mock import MagicMock, patch |
|
21 from urllib import parse |
|
22 |
|
23 import pytest |
|
24 from pytest_twisted import ensureDeferred as ed |
|
25 from twisted.words.protocols.jabber import jid |
|
26 from twisted.web.server import Request |
|
27 from wokkel import rsm, pubsub |
|
28 |
|
29 from sat.core import exceptions |
|
30 from sat.plugins import plugin_comp_ap_gateway |
|
31 from sat.plugins.plugin_comp_ap_gateway.http_server import HTTPServer |
|
32 from sat.tools.utils import xmpp_date |
|
33 from sat.tools import xml_tools |
|
34 |
|
35 |
|
36 TEST_BASE_URL = "https://example.org" |
|
37 TEST_USER = "test_user" |
|
38 TEST_AP_ACCOUNT = f"{TEST_USER}@example.org" |
|
39 |
|
40 AP_REQUESTS = { |
|
41 f"{TEST_BASE_URL}/.well-known/webfinger?" |
|
42 f"resource=acct:{parse.quote(TEST_AP_ACCOUNT)}": { |
|
43 "aliases": [ |
|
44 f"{TEST_BASE_URL}/@{TEST_USER}", |
|
45 f"{TEST_BASE_URL}/users/{TEST_USER}" |
|
46 ], |
|
47 "links": [ |
|
48 { |
|
49 "href": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
50 "rel": "self", |
|
51 "type": "application/activity+json" |
|
52 }, |
|
53 ], |
|
54 "subject": f"acct:{TEST_AP_ACCOUNT}" |
|
55 }, |
|
56 |
|
57 f"{TEST_BASE_URL}/users/{TEST_USER}": { |
|
58 "@context": [ |
|
59 "https://www.w3.org/ns/activitystreams", |
|
60 ], |
|
61 "endpoints": { |
|
62 "sharedInbox": f"{TEST_BASE_URL}/inbox" |
|
63 }, |
|
64 "followers": f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
65 "following": f"{TEST_BASE_URL}/users/{TEST_USER}/following", |
|
66 "id": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
67 "inbox": f"{TEST_BASE_URL}/users/{TEST_USER}/inbox", |
|
68 "name": "", |
|
69 "outbox": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", |
|
70 "preferredUsername": f"{TEST_USER}", |
|
71 "type": "Person", |
|
72 "url": f"{TEST_BASE_URL}/@{TEST_USER}" |
|
73 }, |
|
74 |
|
75 f"{TEST_BASE_URL}/users/{TEST_USER}/outbox": { |
|
76 "@context": "https://www.w3.org/ns/activitystreams", |
|
77 "first": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", |
|
78 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", |
|
79 "last": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", |
|
80 "totalItems": 4, |
|
81 "type": "OrderedCollection" |
|
82 }, |
|
83 f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true": { |
|
84 "@context": [ |
|
85 "https://www.w3.org/ns/activitystreams", |
|
86 ], |
|
87 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox?page=true", |
|
88 "orderedItems": [ |
|
89 { |
|
90 "actor": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
91 "cc": [ |
|
92 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
93 ], |
|
94 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/1/activity", |
|
95 "object": { |
|
96 "attributedTo": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
97 "cc": [ |
|
98 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
99 ], |
|
100 "content": "<p>test message 1</p>", |
|
101 "contentMap": { |
|
102 "en": "<p>test message 1</p>" |
|
103 }, |
|
104 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/1", |
|
105 "inReplyTo": None, |
|
106 "published": "2021-12-16T17:28:03Z", |
|
107 "sensitive": False, |
|
108 "summary": None, |
|
109 "tag": [], |
|
110 "to": [ |
|
111 "https://www.w3.org/ns/activitystreams#Public" |
|
112 ], |
|
113 "type": "Note", |
|
114 "url": f"{TEST_BASE_URL}/@{TEST_USER}/1" |
|
115 }, |
|
116 "published": "2021-12-16T17:28:03Z", |
|
117 "to": [ |
|
118 "https://www.w3.org/ns/activitystreams#Public" |
|
119 ], |
|
120 "type": "Create" |
|
121 }, |
|
122 { |
|
123 "actor": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
124 "cc": [ |
|
125 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
126 ], |
|
127 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/2/activity", |
|
128 "object": { |
|
129 "attributedTo": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
130 "cc": [ |
|
131 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
132 ], |
|
133 "content": "<p>test message 2</p>", |
|
134 "contentMap": { |
|
135 "en": "<p>test message 2</p>" |
|
136 }, |
|
137 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/2", |
|
138 "inReplyTo": None, |
|
139 "published": "2021-12-16T17:27:03Z", |
|
140 "sensitive": False, |
|
141 "summary": None, |
|
142 "tag": [], |
|
143 "to": [ |
|
144 "https://www.w3.org/ns/activitystreams#Public" |
|
145 ], |
|
146 "type": "Note", |
|
147 "url": f"{TEST_BASE_URL}/@{TEST_USER}/2" |
|
148 }, |
|
149 "published": "2021-12-16T17:27:03Z", |
|
150 "to": [ |
|
151 "https://www.w3.org/ns/activitystreams#Public" |
|
152 ], |
|
153 "type": "Create" |
|
154 }, |
|
155 { |
|
156 "actor": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
157 "cc": [ |
|
158 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
159 ], |
|
160 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/3/activity", |
|
161 "object": { |
|
162 "attributedTo": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
163 "cc": [ |
|
164 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
165 ], |
|
166 "content": "<p>test message 3</p>", |
|
167 "contentMap": { |
|
168 "en": "<p>test message 3</p>" |
|
169 }, |
|
170 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/3", |
|
171 "inReplyTo": None, |
|
172 "published": "2021-12-16T17:26:03Z", |
|
173 "sensitive": False, |
|
174 "summary": None, |
|
175 "tag": [], |
|
176 "to": [ |
|
177 "https://www.w3.org/ns/activitystreams#Public" |
|
178 ], |
|
179 "type": "Note", |
|
180 "url": f"{TEST_BASE_URL}/@{TEST_USER}/3" |
|
181 }, |
|
182 "published": "2021-12-16T17:26:03Z", |
|
183 "to": [ |
|
184 "https://www.w3.org/ns/activitystreams#Public" |
|
185 ], |
|
186 "type": "Create" |
|
187 }, |
|
188 { |
|
189 "actor": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
190 "cc": [ |
|
191 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
192 ], |
|
193 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/4/activity", |
|
194 "object": { |
|
195 "attributedTo": f"{TEST_BASE_URL}/users/{TEST_USER}", |
|
196 "cc": [ |
|
197 f"{TEST_BASE_URL}/users/{TEST_USER}/followers", |
|
198 ], |
|
199 "content": "<p>test message 4</p>", |
|
200 "contentMap": { |
|
201 "en": "<p>test message 4</p>" |
|
202 }, |
|
203 "id": f"{TEST_BASE_URL}/users/{TEST_USER}/statuses/4", |
|
204 "inReplyTo": None, |
|
205 "published": "2021-12-16T17:25:03Z", |
|
206 "sensitive": False, |
|
207 "summary": None, |
|
208 "tag": [], |
|
209 "to": [ |
|
210 "https://www.w3.org/ns/activitystreams#Public" |
|
211 ], |
|
212 "type": "Note", |
|
213 "url": f"{TEST_BASE_URL}/@{TEST_USER}/4" |
|
214 }, |
|
215 "published": "2021-12-16T17:25:03Z", |
|
216 "to": [ |
|
217 "https://www.w3.org/ns/activitystreams#Public" |
|
218 ], |
|
219 "type": "Create" |
|
220 }, |
|
221 ], |
|
222 "partOf": f"{TEST_BASE_URL}/users/{TEST_USER}/outbox", |
|
223 "prev": None, |
|
224 "type": "OrderedCollectionPage" |
|
225 } |
|
226 |
|
227 } |
|
228 |
|
229 XMPP_ITEM_TPL = """ |
|
230 <item id='{id}' publisher='{publisher_jid}'> |
|
231 <entry xmlns='http://www.w3.org/2005/Atom' xml:lang='en'> |
|
232 <title type='xhtml'> |
|
233 <div xmlns='http://www.w3.org/1999/xhtml'> |
|
234 <p> |
|
235 XMPP item {id} |
|
236 </p> |
|
237 </div> |
|
238 </title> |
|
239 <title type='text'> |
|
240 XMPP item {id} |
|
241 </title> |
|
242 <author> |
|
243 <name> |
|
244 test_user |
|
245 </name> |
|
246 <uri> |
|
247 xmpp:{publisher_jid} |
|
248 </uri> |
|
249 </author> |
|
250 <updated> |
|
251 {updated} |
|
252 </updated> |
|
253 <published> |
|
254 {published} |
|
255 </published> |
|
256 <id> |
|
257 xmpp:{publisher_jid}?;node=urn%3Axmpp%3Amicroblog%3A0;item={id} |
|
258 </id> |
|
259 </entry> |
|
260 </item> |
|
261 """ |
|
262 |
|
263 ITEM_BASE_TS = 1643385499 |
|
264 XMPP_ITEMS = [ |
|
265 xml_tools.parse( |
|
266 "".join( |
|
267 l.strip() for l in XMPP_ITEM_TPL.format( |
|
268 id=i, |
|
269 publisher_jid="some_user@test.example", |
|
270 updated=xmpp_date(ITEM_BASE_TS + i * 60), |
|
271 published=xmpp_date(ITEM_BASE_TS + i * 60), |
|
272 ).split("\n") |
|
273 ), |
|
274 namespace=pubsub.NS_PUBSUB |
|
275 ) |
|
276 for i in range(1, 5) |
|
277 ] |
|
278 |
|
279 async def mock_ap_get(url): |
|
280 return deepcopy(AP_REQUESTS[url]) |
|
281 |
|
282 |
|
283 async def mock_treq_json(data): |
|
284 return dict(data) |
|
285 |
|
286 |
|
287 async def mock_getItems(*args, **kwargs): |
|
288 rsm_resp = rsm.RSMResponse( |
|
289 first=XMPP_ITEMS[0]["id"], |
|
290 last=XMPP_ITEMS[-1]["id"], |
|
291 index=0, |
|
292 count=len(XMPP_ITEMS) |
|
293 ) |
|
294 return XMPP_ITEMS, {"rsm": rsm_resp.toDict(), "complete": True} |
|
295 |
|
296 |
|
297 @pytest.fixture(scope="session") |
|
298 def ap_gateway(host): |
|
299 gateway = plugin_comp_ap_gateway.APGateway(host) |
|
300 gateway.initialised = True |
|
301 client = MagicMock() |
|
302 client.jid = jid.JID("ap.test.example") |
|
303 client.host = "test.example" |
|
304 gateway.client = client |
|
305 gateway.local_only = True |
|
306 gateway.public_url = "test.example" |
|
307 gateway.ap_path = '_ap' |
|
308 gateway.base_ap_url = parse.urljoin( |
|
309 f"https://{gateway.public_url}", |
|
310 f"{gateway.ap_path}/" |
|
311 ) |
|
312 gateway.server = HTTPServer(gateway) |
|
313 return gateway |
|
314 |
|
315 |
|
316 class TestActivityPubGateway: |
|
317 |
|
318 @ed |
|
319 async def test_jid_and_node_convert_to_ap_handle(self, ap_gateway): |
|
320 """JID and pubsub node are converted correctly to an AP actor handle""" |
|
321 get_account = ap_gateway.getAPAccountFromJidAndNode |
|
322 |
|
323 # local jid |
|
324 assert await get_account( |
|
325 jid_ = jid.JID("simple@test.example"), |
|
326 node = None |
|
327 ) == "simple@test.example" |
|
328 |
|
329 # non local jid |
|
330 assert await get_account( |
|
331 jid_ = jid.JID("simple@example.org"), |
|
332 node = None |
|
333 ) == "___simple.40example.2eorg@ap.test.example" |
|
334 |
|
335 # local jid with non microblog node |
|
336 assert await get_account( |
|
337 jid_ = jid.JID("simple@test.example"), |
|
338 node = "some_other_node" |
|
339 ) == "some_other_node---simple@test.example" |
|
340 |
|
341 # local pubsub node |
|
342 with patch.object(ap_gateway, "isPubsub") as isPubsub: |
|
343 isPubsub.return_value = True |
|
344 assert await get_account( |
|
345 jid_ = jid.JID("pubsub.test.example"), |
|
346 node = "some_node" |
|
347 ) == "some_node@pubsub.test.example" |
|
348 |
|
349 # non local pubsub node |
|
350 with patch.object(ap_gateway, "isPubsub") as isPubsub: |
|
351 isPubsub.return_value = True |
|
352 assert await get_account( |
|
353 jid_ = jid.JID("pubsub.example.org"), |
|
354 node = "some_node" |
|
355 ) == "___some_node.40pubsub.2eexample.2eorg@ap.test.example" |
|
356 |
|
357 @ed |
|
358 async def test_ap_handle_convert_to_jid_and_node(self, ap_gateway, monkeypatch): |
|
359 """AP actor handle convert correctly to JID and pubsub node""" |
|
360 get_jid_node = ap_gateway.getJIDAndNode |
|
361 |
|
362 # for following assertion, host is not a pubsub service |
|
363 with patch.object(ap_gateway, "isPubsub") as isPubsub: |
|
364 isPubsub.return_value = False |
|
365 |
|
366 # simple local jid |
|
367 assert await get_jid_node( |
|
368 "toto@test.example" |
|
369 ) == (jid.JID("toto@test.example"), None) |
|
370 |
|
371 # simple external jid |
|
372 |
|
373 ## with "local_only" set, it should raise an exception |
|
374 with pytest.raises(exceptions.PermissionError): |
|
375 await get_jid_node("toto@example.org") |
|
376 |
|
377 ## with "local_only" unset, it should work |
|
378 with monkeypatch.context() as m: |
|
379 m.setattr(ap_gateway, "local_only", False, raising=True) |
|
380 assert await get_jid_node( |
|
381 "toto@example.org" |
|
382 ) == (jid.JID("toto@example.org"), None) |
|
383 |
|
384 # explicit node |
|
385 assert await get_jid_node( |
|
386 "tata---toto@test.example" |
|
387 ) == (jid.JID("toto@test.example"), "tata") |
|
388 |
|
389 # for following assertion, host is a pubsub service |
|
390 with patch.object(ap_gateway, "isPubsub") as isPubsub: |
|
391 isPubsub.return_value = True |
|
392 |
|
393 # simple local node |
|
394 assert await get_jid_node( |
|
395 "toto@pubsub.test.example" |
|
396 ) == (jid.JID("pubsub.test.example"), "toto") |
|
397 |
|
398 # encoded local node |
|
399 assert await get_jid_node( |
|
400 "___urn.3axmpp.3amicroblog.3a0@pubsub.test.example" |
|
401 ) == (jid.JID("pubsub.test.example"), "urn:xmpp:microblog:0") |
|
402 |
|
403 @ed |
|
404 async def test_ap_to_pubsub_conversion(self, ap_gateway, monkeypatch): |
|
405 """AP requests are converted to pubsub""" |
|
406 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "get", mock_ap_get) |
|
407 monkeypatch.setattr(plugin_comp_ap_gateway.treq, "json_content", mock_treq_json) |
|
408 monkeypatch.setattr(ap_gateway, "apGet", mock_ap_get) |
|
409 |
|
410 items, rsm_resp = await ap_gateway.getAPItems(TEST_AP_ACCOUNT, 2) |
|
411 |
|
412 assert rsm_resp.count == 4 |
|
413 assert rsm_resp.index == 0 |
|
414 assert rsm_resp.first == "https://example.org/users/test_user/statuses/4" |
|
415 assert rsm_resp.last == "https://example.org/users/test_user/statuses/3" |
|
416 |
|
417 assert items[0].entry.title.toXml() == ( |
|
418 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
419 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 4</p></div>" |
|
420 "</title>" |
|
421 ) |
|
422 author_uri = str( |
|
423 [e for e in items[0].entry.author.elements() if e.name == "uri"][0] |
|
424 ) |
|
425 assert author_uri == "xmpp:test_user\\40example.org@ap.test.example" |
|
426 assert str(items[0].entry.published) == "2021-12-16T17:25:03Z" |
|
427 |
|
428 assert items[1].entry.title.toXml() == ( |
|
429 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
430 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 3</p></div>" |
|
431 "</title>" |
|
432 ) |
|
433 author_uri = str( |
|
434 [e for e in items[1].entry.author.elements() if e.name == "uri"][0] |
|
435 ) |
|
436 assert author_uri == "xmpp:test_user\\40example.org@ap.test.example" |
|
437 assert str(items[1].entry.published) == "2021-12-16T17:26:03Z" |
|
438 |
|
439 items, rsm_resp = await ap_gateway.getAPItems( |
|
440 TEST_AP_ACCOUNT, |
|
441 max_items=2, |
|
442 after_id="https://example.org/users/test_user/statuses/3", |
|
443 ) |
|
444 |
|
445 assert rsm_resp.count == 4 |
|
446 assert rsm_resp.index == 2 |
|
447 assert rsm_resp.first == "https://example.org/users/test_user/statuses/2" |
|
448 assert rsm_resp.last == "https://example.org/users/test_user/statuses/1" |
|
449 |
|
450 assert items[0].entry.title.toXml() == ( |
|
451 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
452 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 2</p></div>" |
|
453 "</title>" |
|
454 ) |
|
455 author_uri = str( |
|
456 [e for e in items[0].entry.author.elements() if e.name == "uri"][0] |
|
457 ) |
|
458 assert author_uri == "xmpp:test_user\\40example.org@ap.test.example" |
|
459 assert str(items[0].entry.published) == "2021-12-16T17:27:03Z" |
|
460 |
|
461 assert items[1].entry.title.toXml() == ( |
|
462 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
463 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 1</p></div>" |
|
464 "</title>" |
|
465 ) |
|
466 author_uri = str( |
|
467 [e for e in items[1].entry.author.elements() if e.name == "uri"][0] |
|
468 ) |
|
469 assert author_uri == "xmpp:test_user\\40example.org@ap.test.example" |
|
470 assert str(items[1].entry.published) == "2021-12-16T17:28:03Z" |
|
471 |
|
472 items, rsm_resp = await ap_gateway.getAPItems( |
|
473 TEST_AP_ACCOUNT, |
|
474 max_items=1, |
|
475 start_index=2 |
|
476 ) |
|
477 |
|
478 assert rsm_resp.count == 4 |
|
479 assert rsm_resp.index == 2 |
|
480 assert rsm_resp.first == "https://example.org/users/test_user/statuses/2" |
|
481 assert rsm_resp.last == "https://example.org/users/test_user/statuses/2" |
|
482 assert len(items) == 1 |
|
483 |
|
484 assert items[0].entry.title.toXml() == ( |
|
485 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
486 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 2</p></div>" |
|
487 "</title>" |
|
488 ) |
|
489 assert str(items[0].entry.published) == "2021-12-16T17:27:03Z" |
|
490 |
|
491 items, rsm_resp = await ap_gateway.getAPItems( |
|
492 TEST_AP_ACCOUNT, |
|
493 max_items=3, |
|
494 chronological_pagination=False |
|
495 ) |
|
496 assert rsm_resp.count == 4 |
|
497 assert rsm_resp.index == 1 |
|
498 assert rsm_resp.first == "https://example.org/users/test_user/statuses/3" |
|
499 assert rsm_resp.last == "https://example.org/users/test_user/statuses/1" |
|
500 assert len(items) == 3 |
|
501 assert items[0].entry.title.toXml() == ( |
|
502 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
503 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 3</p></div>" |
|
504 "</title>" |
|
505 ) |
|
506 assert items[2].entry.title.toXml() == ( |
|
507 "<title xmlns='http://www.w3.org/2005/Atom' type='xhtml'>" |
|
508 "<div xmlns='http://www.w3.org/1999/xhtml'><p>test message 1</p></div>" |
|
509 "</title>" |
|
510 ) |
|
511 |
|
512 def ap_request_params(self, ap_gateway, type_=None, url=None, query_data=None): |
|
513 """Generate parameters for HTTPAPGServer's AP*Request |
|
514 |
|
515 @param type_: one of the AP query type (e.g. "outbox") |
|
516 @param url: URL to query (mutually exclusif with type_) |
|
517 @param query_data: query data as returned by parse.parse_qs |
|
518 @return dict with kw params to use |
|
519 """ |
|
520 assert type_ and url is None or url and type_ is None |
|
521 if type_ is not None: |
|
522 path = f"_ap/{type_}/some_user%40test.example" |
|
523 else: |
|
524 url_parsed = parse.urlparse(url) |
|
525 path = url_parsed.path.lstrip("/") |
|
526 type_ = path.split("/")[1] |
|
527 if query_data is None: |
|
528 query_data = parse.parse_qs(url_parsed.query) |
|
529 |
|
530 if query_data: |
|
531 uri = f"{path}?{parse.urlencode(query_data, doseq=True)}" |
|
532 else: |
|
533 uri = path |
|
534 |
|
535 test_jid = jid.JID("some_user@test.example") |
|
536 request = Request(MagicMock()) |
|
537 request.path = path.encode() |
|
538 request.uri = uri.encode() |
|
539 ap_url = parse.urljoin( |
|
540 f"https://{ap_gateway.public_url}", |
|
541 path |
|
542 ) |
|
543 kwargs = { |
|
544 "request": request, |
|
545 "account_jid": test_jid, |
|
546 "node": None, |
|
547 "ap_account": test_jid.full(), |
|
548 "ap_url": ap_url, |
|
549 } |
|
550 if type_ == "outbox" and query_data: |
|
551 kwargs["query_data"] = query_data |
|
552 return kwargs |
|
553 |
|
554 @ed |
|
555 async def test_pubsub_to_ap_conversion(self, ap_gateway, monkeypatch): |
|
556 """Pubsub nodes are converted to AP collections""" |
|
557 monkeypatch.setattr(ap_gateway._p, "getItems", mock_getItems) |
|
558 outbox = await ap_gateway.server.resource.APOutboxRequest( |
|
559 **self.ap_request_params(ap_gateway, "outbox") |
|
560 ) |
|
561 assert outbox["@context"] == "https://www.w3.org/ns/activitystreams" |
|
562 assert outbox["id"] == "https://test.example/_ap/outbox/some_user%40test.example" |
|
563 assert outbox["totalItems"] == len(XMPP_ITEMS) |
|
564 assert outbox["type"] == "OrderedCollection" |
|
565 assert outbox["first"] |
|
566 assert outbox["last"] |
|
567 |
|
568 first_page = await ap_gateway.server.resource.APOutboxPageRequest( |
|
569 **self.ap_request_params(ap_gateway, url=outbox["first"]) |
|
570 ) |
|
571 assert first_page["@context"] == "https://www.w3.org/ns/activitystreams" |
|
572 assert first_page["id"] == "https://test.example/_ap/outbox/some_user%40test.example?page=first" |
|
573 assert first_page["type"] == "OrderedCollectionPage" |
|
574 assert first_page["partOf"] == outbox["id"] |
|
575 assert len(first_page["orderedItems"]) == len(XMPP_ITEMS) |
|
576 first_item = first_page["orderedItems"][0] |
|
577 assert first_item["@context"] == "https://www.w3.org/ns/activitystreams" |
|
578 assert first_item["id"] == "https://test.example/_ap/item/some_user%40test.example/4" |
|
579 assert first_item["actor"] == "https://test.example/_ap/actor/some_user%40test.example" |
|
580 assert first_item["type"] == "Create" |
|
581 first_item_obj = first_item["object"] |
|
582 assert first_item_obj["id"] == first_item["id"] |
|
583 assert first_item_obj["type"] == "Note" |
|
584 assert first_item_obj["published"] == "2022-01-28T16:02:19Z" |
|
585 assert first_item_obj["attributedTo"] == first_item["actor"] |
|
586 assert first_item_obj["content"] == "<div><p>XMPP item 4</p></div>" |
|
587 assert first_item_obj["to"] == "https://www.w3.org/ns/activitystreams#Public" |