f7b7d1a9f2b89063495a584705ff0293da8a495e
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.Transmogrifier
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.CommonAPI
17
18 import Mock
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
21
22 setup_all do
23 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
24 :ok
25 end
26
27 setup do: clear_config([:instance, :max_remote_account_fields])
28
29 describe "handle_incoming" do
30 test "it works for incoming notices with tag not being an array (kroeg)" do
31 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
32
33 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
34 object = Object.normalize(data["object"])
35
36 assert object.data["emoji"] == %{
37 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
38 }
39
40 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
41
42 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
43 object = Object.normalize(data["object"])
44
45 assert "test" in object.data["tag"]
46 end
47
48 test "it works for incoming notices with url not being a string (prismo)" do
49 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
50
51 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
52 object = Object.normalize(data["object"])
53
54 assert object.data["url"] == "https://prismo.news/posts/83"
55 end
56
57 test "it cleans up incoming notices which are not really DMs" do
58 user = insert(:user)
59 other_user = insert(:user)
60
61 to = [user.ap_id, other_user.ap_id]
62
63 data =
64 File.read!("test/fixtures/mastodon-post-activity.json")
65 |> Poison.decode!()
66 |> Map.put("to", to)
67 |> Map.put("cc", [])
68
69 object =
70 data["object"]
71 |> Map.put("to", to)
72 |> Map.put("cc", [])
73
74 data = Map.put(data, "object", object)
75
76 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
77
78 assert data["to"] == []
79 assert data["cc"] == to
80
81 object_data = Object.normalize(activity).data
82
83 assert object_data["to"] == []
84 assert object_data["cc"] == to
85 end
86
87 test "it ignores an incoming notice if we already have it" do
88 activity = insert(:note_activity)
89
90 data =
91 File.read!("test/fixtures/mastodon-post-activity.json")
92 |> Poison.decode!()
93 |> Map.put("object", Object.normalize(activity).data)
94
95 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
96
97 assert activity == returned_activity
98 end
99
100 @tag capture_log: true
101 test "it fetches reply-to activities if we don't have them" do
102 data =
103 File.read!("test/fixtures/mastodon-post-activity.json")
104 |> Poison.decode!()
105
106 object =
107 data["object"]
108 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
109
110 data = Map.put(data, "object", object)
111 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
112 returned_object = Object.normalize(returned_activity, false)
113
114 assert activity =
115 Activity.get_create_by_object_ap_id(
116 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
117 )
118
119 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
120 end
121
122 test "it does not fetch reply-to activities beyond max replies depth limit" do
123 data =
124 File.read!("test/fixtures/mastodon-post-activity.json")
125 |> Poison.decode!()
126
127 object =
128 data["object"]
129 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
130
131 data = Map.put(data, "object", object)
132
133 with_mock Pleroma.Web.Federator,
134 allowed_thread_distance?: fn _ -> false end do
135 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
136
137 returned_object = Object.normalize(returned_activity, false)
138
139 refute Activity.get_create_by_object_ap_id(
140 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
141 )
142
143 assert returned_object.data["inReplyToAtomUri"] ==
144 "https://shitposter.club/notice/2827873"
145 end
146 end
147
148 test "it does not crash if the object in inReplyTo can't be fetched" do
149 data =
150 File.read!("test/fixtures/mastodon-post-activity.json")
151 |> Poison.decode!()
152
153 object =
154 data["object"]
155 |> Map.put("inReplyTo", "https://404.site/whatever")
156
157 data =
158 data
159 |> Map.put("object", object)
160
161 assert capture_log(fn ->
162 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
163 end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
164 end
165
166 test "it works for incoming notices" do
167 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
168
169 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
170
171 assert data["id"] ==
172 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
173
174 assert data["context"] ==
175 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
176
177 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
178
179 assert data["cc"] == [
180 "http://mastodon.example.org/users/admin/followers",
181 "http://localtesting.pleroma.lol/users/lain"
182 ]
183
184 assert data["actor"] == "http://mastodon.example.org/users/admin"
185
186 object_data = Object.normalize(data["object"]).data
187
188 assert object_data["id"] ==
189 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
190
191 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
192
193 assert object_data["cc"] == [
194 "http://mastodon.example.org/users/admin/followers",
195 "http://localtesting.pleroma.lol/users/lain"
196 ]
197
198 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
199 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
200
201 assert object_data["context"] ==
202 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
203
204 assert object_data["sensitive"] == true
205
206 user = User.get_cached_by_ap_id(object_data["actor"])
207
208 assert user.note_count == 1
209 end
210
211 test "it works for incoming notices with hashtags" do
212 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
213
214 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
215 object = Object.normalize(data["object"])
216
217 assert Enum.at(object.data["tag"], 2) == "moo"
218 end
219
220 test "it works for incoming questions" do
221 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
222
223 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
224
225 object = Object.normalize(activity)
226
227 assert Enum.all?(object.data["oneOf"], fn choice ->
228 choice["name"] in [
229 "Dunno",
230 "Everyone knows that!",
231 "25 char limit is dumb",
232 "I can't even fit a funny"
233 ]
234 end)
235 end
236
237 test "it works for incoming listens" do
238 data = %{
239 "@context" => "https://www.w3.org/ns/activitystreams",
240 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
241 "cc" => [],
242 "type" => "Listen",
243 "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
244 "actor" => "http://mastodon.example.org/users/admin",
245 "object" => %{
246 "type" => "Audio",
247 "id" => "http://mastodon.example.org/users/admin/listens/1234",
248 "attributedTo" => "http://mastodon.example.org/users/admin",
249 "title" => "lain radio episode 1",
250 "artist" => "lain",
251 "album" => "lain radio",
252 "length" => 180_000
253 }
254 }
255
256 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
257
258 object = Object.normalize(activity)
259
260 assert object.data["title"] == "lain radio episode 1"
261 assert object.data["artist"] == "lain"
262 assert object.data["album"] == "lain radio"
263 assert object.data["length"] == 180_000
264 end
265
266 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
267 user = insert(:user)
268
269 {:ok, activity} =
270 CommonAPI.post(user, %{
271 status: "suya...",
272 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
273 })
274
275 object = Object.normalize(activity)
276
277 data =
278 File.read!("test/fixtures/mastodon-vote.json")
279 |> Poison.decode!()
280 |> Kernel.put_in(["to"], user.ap_id)
281 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
282 |> Kernel.put_in(["object", "to"], user.ap_id)
283
284 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
285 answer_object = Object.normalize(activity)
286 assert answer_object.data["type"] == "Answer"
287 object = Object.get_by_ap_id(object.data["id"])
288
289 assert Enum.any?(
290 object.data["oneOf"],
291 fn
292 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
293 _ -> false
294 end
295 )
296 end
297
298 test "it works for incoming notices with contentMap" do
299 data =
300 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
301
302 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
303 object = Object.normalize(data["object"])
304
305 assert object.data["content"] ==
306 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
307 end
308
309 test "it works for incoming notices with to/cc not being an array (kroeg)" do
310 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
311
312 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
313 object = Object.normalize(data["object"])
314
315 assert object.data["content"] ==
316 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
317 end
318
319 test "it ensures that as:Public activities make it to their followers collection" do
320 user = insert(:user)
321
322 data =
323 File.read!("test/fixtures/mastodon-post-activity.json")
324 |> Poison.decode!()
325 |> Map.put("actor", user.ap_id)
326 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
327 |> Map.put("cc", [])
328
329 object =
330 data["object"]
331 |> Map.put("attributedTo", user.ap_id)
332 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
333 |> Map.put("cc", [])
334 |> Map.put("id", user.ap_id <> "/activities/12345678")
335
336 data = Map.put(data, "object", object)
337
338 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
339
340 assert data["cc"] == [User.ap_followers(user)]
341 end
342
343 test "it ensures that address fields become lists" do
344 user = insert(:user)
345
346 data =
347 File.read!("test/fixtures/mastodon-post-activity.json")
348 |> Poison.decode!()
349 |> Map.put("actor", user.ap_id)
350 |> Map.put("to", nil)
351 |> Map.put("cc", nil)
352
353 object =
354 data["object"]
355 |> Map.put("attributedTo", user.ap_id)
356 |> Map.put("to", nil)
357 |> Map.put("cc", nil)
358 |> Map.put("id", user.ap_id <> "/activities/12345678")
359
360 data = Map.put(data, "object", object)
361
362 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
363
364 assert !is_nil(data["to"])
365 assert !is_nil(data["cc"])
366 end
367
368 test "it strips internal likes" do
369 data =
370 File.read!("test/fixtures/mastodon-post-activity.json")
371 |> Poison.decode!()
372
373 likes = %{
374 "first" =>
375 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
376 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
377 "totalItems" => 3,
378 "type" => "OrderedCollection"
379 }
380
381 object = Map.put(data["object"], "likes", likes)
382 data = Map.put(data, "object", object)
383
384 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
385
386 refute Map.has_key?(object.data, "likes")
387 end
388
389 test "it strips internal reactions" do
390 user = insert(:user)
391 {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
392 {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
393
394 %{object: object} = Activity.get_by_id_with_object(activity.id)
395 assert Map.has_key?(object.data, "reactions")
396 assert Map.has_key?(object.data, "reaction_count")
397
398 object_data = Transmogrifier.strip_internal_fields(object.data)
399 refute Map.has_key?(object_data, "reactions")
400 refute Map.has_key?(object_data, "reaction_count")
401 end
402
403 test "it works for incomming unfollows with an existing follow" do
404 user = insert(:user)
405
406 follow_data =
407 File.read!("test/fixtures/mastodon-follow-activity.json")
408 |> Poison.decode!()
409 |> Map.put("object", user.ap_id)
410
411 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
412
413 data =
414 File.read!("test/fixtures/mastodon-unfollow-activity.json")
415 |> Poison.decode!()
416 |> Map.put("object", follow_data)
417
418 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
419
420 assert data["type"] == "Undo"
421 assert data["object"]["type"] == "Follow"
422 assert data["object"]["object"] == user.ap_id
423 assert data["actor"] == "http://mastodon.example.org/users/admin"
424
425 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
426 end
427
428 test "it works for incoming follows to locked account" do
429 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
430 user = insert(:user, locked: true)
431
432 data =
433 File.read!("test/fixtures/mastodon-follow-activity.json")
434 |> Poison.decode!()
435 |> Map.put("object", user.ap_id)
436
437 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
438
439 assert data["type"] == "Follow"
440 assert data["object"] == user.ap_id
441 assert data["state"] == "pending"
442 assert data["actor"] == "http://mastodon.example.org/users/admin"
443
444 assert [^pending_follower] = User.get_follow_requests(user)
445 end
446
447 test "it works for incoming accepts which were pre-accepted" do
448 follower = insert(:user)
449 followed = insert(:user)
450
451 {:ok, follower} = User.follow(follower, followed)
452 assert User.following?(follower, followed) == true
453
454 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
455
456 accept_data =
457 File.read!("test/fixtures/mastodon-accept-activity.json")
458 |> Poison.decode!()
459 |> Map.put("actor", followed.ap_id)
460
461 object =
462 accept_data["object"]
463 |> Map.put("actor", follower.ap_id)
464 |> Map.put("id", follow_activity.data["id"])
465
466 accept_data = Map.put(accept_data, "object", object)
467
468 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
469 refute activity.local
470
471 assert activity.data["object"] == follow_activity.data["id"]
472
473 assert activity.data["id"] == accept_data["id"]
474
475 follower = User.get_cached_by_id(follower.id)
476
477 assert User.following?(follower, followed) == true
478 end
479
480 test "it works for incoming accepts which were orphaned" do
481 follower = insert(:user)
482 followed = insert(:user, locked: true)
483
484 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
485
486 accept_data =
487 File.read!("test/fixtures/mastodon-accept-activity.json")
488 |> Poison.decode!()
489 |> Map.put("actor", followed.ap_id)
490
491 accept_data =
492 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
493
494 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
495 assert activity.data["object"] == follow_activity.data["id"]
496
497 follower = User.get_cached_by_id(follower.id)
498
499 assert User.following?(follower, followed) == true
500 end
501
502 test "it works for incoming accepts which are referenced by IRI only" do
503 follower = insert(:user)
504 followed = insert(:user, locked: true)
505
506 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
507
508 accept_data =
509 File.read!("test/fixtures/mastodon-accept-activity.json")
510 |> Poison.decode!()
511 |> Map.put("actor", followed.ap_id)
512 |> Map.put("object", follow_activity.data["id"])
513
514 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
515 assert activity.data["object"] == follow_activity.data["id"]
516
517 follower = User.get_cached_by_id(follower.id)
518
519 assert User.following?(follower, followed) == true
520
521 follower = User.get_by_id(follower.id)
522 assert follower.following_count == 1
523
524 followed = User.get_by_id(followed.id)
525 assert followed.follower_count == 1
526 end
527
528 test "it fails for incoming accepts which cannot be correlated" do
529 follower = insert(:user)
530 followed = insert(:user, locked: true)
531
532 accept_data =
533 File.read!("test/fixtures/mastodon-accept-activity.json")
534 |> Poison.decode!()
535 |> Map.put("actor", followed.ap_id)
536
537 accept_data =
538 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
539
540 :error = Transmogrifier.handle_incoming(accept_data)
541
542 follower = User.get_cached_by_id(follower.id)
543
544 refute User.following?(follower, followed) == true
545 end
546
547 test "it fails for incoming rejects which cannot be correlated" do
548 follower = insert(:user)
549 followed = insert(:user, locked: true)
550
551 accept_data =
552 File.read!("test/fixtures/mastodon-reject-activity.json")
553 |> Poison.decode!()
554 |> Map.put("actor", followed.ap_id)
555
556 accept_data =
557 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
558
559 :error = Transmogrifier.handle_incoming(accept_data)
560
561 follower = User.get_cached_by_id(follower.id)
562
563 refute User.following?(follower, followed) == true
564 end
565
566 test "it works for incoming rejects which are orphaned" do
567 follower = insert(:user)
568 followed = insert(:user, locked: true)
569
570 {:ok, follower} = User.follow(follower, followed)
571 {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, followed)
572
573 assert User.following?(follower, followed) == true
574
575 reject_data =
576 File.read!("test/fixtures/mastodon-reject-activity.json")
577 |> Poison.decode!()
578 |> Map.put("actor", followed.ap_id)
579
580 reject_data =
581 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
582
583 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
584 refute activity.local
585 assert activity.data["id"] == reject_data["id"]
586
587 follower = User.get_cached_by_id(follower.id)
588
589 assert User.following?(follower, followed) == false
590 end
591
592 test "it works for incoming rejects which are referenced by IRI only" do
593 follower = insert(:user)
594 followed = insert(:user, locked: true)
595
596 {:ok, follower} = User.follow(follower, followed)
597 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
598
599 assert User.following?(follower, followed) == true
600
601 reject_data =
602 File.read!("test/fixtures/mastodon-reject-activity.json")
603 |> Poison.decode!()
604 |> Map.put("actor", followed.ap_id)
605 |> Map.put("object", follow_activity.data["id"])
606
607 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
608
609 follower = User.get_cached_by_id(follower.id)
610
611 assert User.following?(follower, followed) == false
612 end
613
614 test "it rejects activities without a valid ID" do
615 user = insert(:user)
616
617 data =
618 File.read!("test/fixtures/mastodon-follow-activity.json")
619 |> Poison.decode!()
620 |> Map.put("object", user.ap_id)
621 |> Map.put("id", "")
622
623 :error = Transmogrifier.handle_incoming(data)
624 end
625
626 test "skip converting the content when it is nil" do
627 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
628
629 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
630
631 result =
632 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
633
634 assert result["content"] == nil
635 end
636
637 test "it converts content of object to html" do
638 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
639
640 {:ok, %{"content" => content_markdown}} =
641 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
642
643 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
644 Fetcher.fetch_object_from_id(object_id)
645
646 assert content_markdown ==
647 "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
648
649 assert content ==
650 "<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
651
652 assert object.data["mediaType"] == "text/html"
653 end
654
655 test "it remaps video URLs as attachments if necessary" do
656 {:ok, object} =
657 Fetcher.fetch_object_from_id(
658 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
659 )
660
661 assert object.data["url"] ==
662 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
663
664 assert object.data["attachment"] == [
665 %{
666 "type" => "Link",
667 "mediaType" => "video/mp4",
668 "url" => [
669 %{
670 "href" =>
671 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
672 "mediaType" => "video/mp4"
673 }
674 ]
675 }
676 ]
677
678 {:ok, object} =
679 Fetcher.fetch_object_from_id(
680 "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
681 )
682
683 assert object.data["attachment"] == [
684 %{
685 "type" => "Link",
686 "mediaType" => "video/mp4",
687 "url" => [
688 %{
689 "href" =>
690 "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
691 "mediaType" => "video/mp4"
692 }
693 ]
694 }
695 ]
696
697 assert object.data["url"] ==
698 "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
699 end
700
701 test "it accepts Flag activities" do
702 user = insert(:user)
703 other_user = insert(:user)
704
705 {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
706 object = Object.normalize(activity)
707
708 note_obj = %{
709 "type" => "Note",
710 "id" => activity.data["id"],
711 "content" => "test post",
712 "published" => object.data["published"],
713 "actor" => AccountView.render("show.json", %{user: user})
714 }
715
716 message = %{
717 "@context" => "https://www.w3.org/ns/activitystreams",
718 "cc" => [user.ap_id],
719 "object" => [user.ap_id, activity.data["id"]],
720 "type" => "Flag",
721 "content" => "blocked AND reported!!!",
722 "actor" => other_user.ap_id
723 }
724
725 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
726
727 assert activity.data["object"] == [user.ap_id, note_obj]
728 assert activity.data["content"] == "blocked AND reported!!!"
729 assert activity.data["actor"] == other_user.ap_id
730 assert activity.data["cc"] == [user.ap_id]
731 end
732
733 test "it correctly processes messages with non-array to field" do
734 user = insert(:user)
735
736 message = %{
737 "@context" => "https://www.w3.org/ns/activitystreams",
738 "to" => "https://www.w3.org/ns/activitystreams#Public",
739 "type" => "Create",
740 "object" => %{
741 "content" => "blah blah blah",
742 "type" => "Note",
743 "attributedTo" => user.ap_id,
744 "inReplyTo" => nil
745 },
746 "actor" => user.ap_id
747 }
748
749 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
750
751 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
752 end
753
754 test "it correctly processes messages with non-array cc field" do
755 user = insert(:user)
756
757 message = %{
758 "@context" => "https://www.w3.org/ns/activitystreams",
759 "to" => user.follower_address,
760 "cc" => "https://www.w3.org/ns/activitystreams#Public",
761 "type" => "Create",
762 "object" => %{
763 "content" => "blah blah blah",
764 "type" => "Note",
765 "attributedTo" => user.ap_id,
766 "inReplyTo" => nil
767 },
768 "actor" => user.ap_id
769 }
770
771 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
772
773 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
774 assert [user.follower_address] == activity.data["to"]
775 end
776
777 test "it accepts Move activities" do
778 old_user = insert(:user)
779 new_user = insert(:user)
780
781 message = %{
782 "@context" => "https://www.w3.org/ns/activitystreams",
783 "type" => "Move",
784 "actor" => old_user.ap_id,
785 "object" => old_user.ap_id,
786 "target" => new_user.ap_id
787 }
788
789 assert :error = Transmogrifier.handle_incoming(message)
790
791 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
792
793 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
794 assert activity.actor == old_user.ap_id
795 assert activity.data["actor"] == old_user.ap_id
796 assert activity.data["object"] == old_user.ap_id
797 assert activity.data["target"] == new_user.ap_id
798 assert activity.data["type"] == "Move"
799 end
800 end
801
802 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
803 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
804 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
805
806 setup do
807 data =
808 "test/fixtures/mastodon-post-activity.json"
809 |> File.read!()
810 |> Poison.decode!()
811
812 items = get_in(data, ["object", "replies", "first", "items"])
813 assert length(items) > 0
814
815 %{data: data, items: items}
816 end
817
818 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
819 data: data,
820 items: items
821 } do
822 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
823
824 {:ok, _activity} = Transmogrifier.handle_incoming(data)
825
826 for id <- items do
827 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
828 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
829 end
830 end
831
832 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
833 %{data: data} do
834 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
835
836 {:ok, _activity} = Transmogrifier.handle_incoming(data)
837
838 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
839 end
840 end
841
842 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
843 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
844 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
845
846 setup do
847 user = insert(:user)
848
849 {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
850
851 {:ok, reply1} =
852 CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
853
854 {:ok, reply2} =
855 CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
856
857 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
858
859 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
860
861 Repo.delete(activity.object)
862 Repo.delete(activity)
863
864 %{federation_output: federation_output, replies_uris: replies_uris}
865 end
866
867 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
868 federation_output: federation_output,
869 replies_uris: replies_uris
870 } do
871 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
872
873 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
874
875 for id <- replies_uris do
876 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
877 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
878 end
879 end
880
881 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
882 %{federation_output: federation_output} do
883 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
884
885 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
886
887 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
888 end
889 end
890
891 describe "prepare outgoing" do
892 test "it inlines private announced objects" do
893 user = insert(:user)
894
895 {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
896
897 {:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
898
899 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
900
901 assert modified["object"]["content"] == "hey"
902 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
903 end
904
905 test "it turns mentions into tags" do
906 user = insert(:user)
907 other_user = insert(:user)
908
909 {:ok, activity} =
910 CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
911
912 with_mock Pleroma.Notification,
913 get_notified_from_activity: fn _, _ -> [] end do
914 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
915
916 object = modified["object"]
917
918 expected_mention = %{
919 "href" => other_user.ap_id,
920 "name" => "@#{other_user.nickname}",
921 "type" => "Mention"
922 }
923
924 expected_tag = %{
925 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
926 "type" => "Hashtag",
927 "name" => "#2hu"
928 }
929
930 refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
931 assert Enum.member?(object["tag"], expected_tag)
932 assert Enum.member?(object["tag"], expected_mention)
933 end
934 end
935
936 test "it adds the sensitive property" do
937 user = insert(:user)
938
939 {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
940 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
941
942 assert modified["object"]["sensitive"]
943 end
944
945 test "it adds the json-ld context and the conversation property" do
946 user = insert(:user)
947
948 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
949 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
950
951 assert modified["@context"] ==
952 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
953
954 assert modified["object"]["conversation"] == modified["context"]
955 end
956
957 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
958 user = insert(:user)
959
960 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
961 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
962
963 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
964 end
965
966 test "it strips internal hashtag data" do
967 user = insert(:user)
968
969 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
970
971 expected_tag = %{
972 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
973 "type" => "Hashtag",
974 "name" => "#2hu"
975 }
976
977 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
978
979 assert modified["object"]["tag"] == [expected_tag]
980 end
981
982 test "it strips internal fields" do
983 user = insert(:user)
984
985 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu :firefox:"})
986
987 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
988
989 assert length(modified["object"]["tag"]) == 2
990
991 assert is_nil(modified["object"]["emoji"])
992 assert is_nil(modified["object"]["like_count"])
993 assert is_nil(modified["object"]["announcements"])
994 assert is_nil(modified["object"]["announcement_count"])
995 assert is_nil(modified["object"]["context_id"])
996 end
997
998 test "it strips internal fields of article" do
999 activity = insert(:article_activity)
1000
1001 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1002
1003 assert length(modified["object"]["tag"]) == 2
1004
1005 assert is_nil(modified["object"]["emoji"])
1006 assert is_nil(modified["object"]["like_count"])
1007 assert is_nil(modified["object"]["announcements"])
1008 assert is_nil(modified["object"]["announcement_count"])
1009 assert is_nil(modified["object"]["context_id"])
1010 assert is_nil(modified["object"]["likes"])
1011 end
1012
1013 test "the directMessage flag is present" do
1014 user = insert(:user)
1015 other_user = insert(:user)
1016
1017 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
1018
1019 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1020
1021 assert modified["directMessage"] == false
1022
1023 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
1024
1025 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1026
1027 assert modified["directMessage"] == false
1028
1029 {:ok, activity} =
1030 CommonAPI.post(user, %{
1031 status: "@#{other_user.nickname} :moominmamma:",
1032 visibility: "direct"
1033 })
1034
1035 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1036
1037 assert modified["directMessage"] == true
1038 end
1039
1040 test "it strips BCC field" do
1041 user = insert(:user)
1042 {:ok, list} = Pleroma.List.create("foo", user)
1043
1044 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1045
1046 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1047
1048 assert is_nil(modified["bcc"])
1049 end
1050
1051 test "it can handle Listen activities" do
1052 listen_activity = insert(:listen)
1053
1054 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1055
1056 assert modified["type"] == "Listen"
1057
1058 user = insert(:user)
1059
1060 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1061
1062 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1063 end
1064 end
1065
1066 describe "user upgrade" do
1067 test "it upgrades a user to activitypub" do
1068 user =
1069 insert(:user, %{
1070 nickname: "rye@niu.moe",
1071 local: false,
1072 ap_id: "https://niu.moe/users/rye",
1073 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1074 })
1075
1076 user_two = insert(:user)
1077 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1078
1079 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1080 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
1081 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1082
1083 user = User.get_cached_by_id(user.id)
1084 assert user.note_count == 1
1085
1086 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1087 ObanHelpers.perform_all()
1088
1089 assert user.ap_enabled
1090 assert user.note_count == 1
1091 assert user.follower_address == "https://niu.moe/users/rye/followers"
1092 assert user.following_address == "https://niu.moe/users/rye/following"
1093
1094 user = User.get_cached_by_id(user.id)
1095 assert user.note_count == 1
1096
1097 activity = Activity.get_by_id(activity.id)
1098 assert user.follower_address in activity.recipients
1099
1100 assert %{
1101 "url" => [
1102 %{
1103 "href" =>
1104 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1105 }
1106 ]
1107 } = user.avatar
1108
1109 assert %{
1110 "url" => [
1111 %{
1112 "href" =>
1113 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1114 }
1115 ]
1116 } = user.banner
1117
1118 refute "..." in activity.recipients
1119
1120 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1121 refute user.follower_address in unrelated_activity.recipients
1122
1123 user_two = User.get_cached_by_id(user_two.id)
1124 assert User.following?(user_two, user)
1125 refute "..." in User.following(user_two)
1126 end
1127 end
1128
1129 describe "actor rewriting" do
1130 test "it fixes the actor URL property to be a proper URI" do
1131 data = %{
1132 "url" => %{"href" => "http://example.com"}
1133 }
1134
1135 rewritten = Transmogrifier.maybe_fix_user_object(data)
1136 assert rewritten["url"] == "http://example.com"
1137 end
1138 end
1139
1140 describe "actor origin containment" do
1141 test "it rejects activities which reference objects with bogus origins" do
1142 data = %{
1143 "@context" => "https://www.w3.org/ns/activitystreams",
1144 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1145 "actor" => "http://mastodon.example.org/users/admin",
1146 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1147 "object" => "https://info.pleroma.site/activity.json",
1148 "type" => "Announce"
1149 }
1150
1151 assert capture_log(fn ->
1152 {:error, _} = Transmogrifier.handle_incoming(data)
1153 end) =~ "Object containment failed"
1154 end
1155
1156 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1157 data = %{
1158 "@context" => "https://www.w3.org/ns/activitystreams",
1159 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1160 "actor" => "http://mastodon.example.org/users/admin",
1161 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1162 "object" => "https://info.pleroma.site/activity2.json",
1163 "type" => "Announce"
1164 }
1165
1166 assert capture_log(fn ->
1167 {:error, _} = Transmogrifier.handle_incoming(data)
1168 end) =~ "Object containment failed"
1169 end
1170
1171 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1172 data = %{
1173 "@context" => "https://www.w3.org/ns/activitystreams",
1174 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1175 "actor" => "http://mastodon.example.org/users/admin",
1176 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1177 "object" => "https://info.pleroma.site/activity3.json",
1178 "type" => "Announce"
1179 }
1180
1181 assert capture_log(fn ->
1182 {:error, _} = Transmogrifier.handle_incoming(data)
1183 end) =~ "Object containment failed"
1184 end
1185 end
1186
1187 describe "reserialization" do
1188 test "successfully reserializes a message with inReplyTo == nil" do
1189 user = insert(:user)
1190
1191 message = %{
1192 "@context" => "https://www.w3.org/ns/activitystreams",
1193 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1194 "cc" => [],
1195 "type" => "Create",
1196 "object" => %{
1197 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1198 "cc" => [],
1199 "type" => "Note",
1200 "content" => "Hi",
1201 "inReplyTo" => nil,
1202 "attributedTo" => user.ap_id
1203 },
1204 "actor" => user.ap_id
1205 }
1206
1207 {:ok, activity} = Transmogrifier.handle_incoming(message)
1208
1209 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1210 end
1211
1212 test "successfully reserializes a message with AS2 objects in IR" do
1213 user = insert(:user)
1214
1215 message = %{
1216 "@context" => "https://www.w3.org/ns/activitystreams",
1217 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1218 "cc" => [],
1219 "type" => "Create",
1220 "object" => %{
1221 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1222 "cc" => [],
1223 "type" => "Note",
1224 "content" => "Hi",
1225 "inReplyTo" => nil,
1226 "attributedTo" => user.ap_id,
1227 "tag" => [
1228 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1229 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1230 ]
1231 },
1232 "actor" => user.ap_id
1233 }
1234
1235 {:ok, activity} = Transmogrifier.handle_incoming(message)
1236
1237 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1238 end
1239 end
1240
1241 test "Rewrites Answers to Notes" do
1242 user = insert(:user)
1243
1244 {:ok, poll_activity} =
1245 CommonAPI.post(user, %{
1246 status: "suya...",
1247 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1248 })
1249
1250 poll_object = Object.normalize(poll_activity)
1251 # TODO: Replace with CommonAPI vote creation when implemented
1252 data =
1253 File.read!("test/fixtures/mastodon-vote.json")
1254 |> Poison.decode!()
1255 |> Kernel.put_in(["to"], user.ap_id)
1256 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1257 |> Kernel.put_in(["object", "to"], user.ap_id)
1258
1259 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1260 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1261
1262 assert data["object"]["type"] == "Note"
1263 end
1264
1265 describe "fix_explicit_addressing" do
1266 setup do
1267 user = insert(:user)
1268 [user: user]
1269 end
1270
1271 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1272 explicitly_mentioned_actors = [
1273 "https://pleroma.gold/users/user1",
1274 "https://pleroma.gold/user2"
1275 ]
1276
1277 object = %{
1278 "actor" => user.ap_id,
1279 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1280 "cc" => [],
1281 "tag" =>
1282 Enum.map(explicitly_mentioned_actors, fn href ->
1283 %{"type" => "Mention", "href" => href}
1284 end)
1285 }
1286
1287 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1288 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1289 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1290 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1291 end
1292
1293 test "does not move actor's follower collection to cc", %{user: user} do
1294 object = %{
1295 "actor" => user.ap_id,
1296 "to" => [user.follower_address],
1297 "cc" => []
1298 }
1299
1300 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1301 assert user.follower_address in fixed_object["to"]
1302 refute user.follower_address in fixed_object["cc"]
1303 end
1304
1305 test "removes recipient's follower collection from cc", %{user: user} do
1306 recipient = insert(:user)
1307
1308 object = %{
1309 "actor" => user.ap_id,
1310 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1311 "cc" => [user.follower_address, recipient.follower_address]
1312 }
1313
1314 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1315
1316 assert user.follower_address in fixed_object["cc"]
1317 refute recipient.follower_address in fixed_object["cc"]
1318 refute recipient.follower_address in fixed_object["to"]
1319 end
1320 end
1321
1322 describe "fix_summary/1" do
1323 test "returns fixed object" do
1324 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1325 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1326 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1327 end
1328 end
1329
1330 describe "fix_in_reply_to/2" do
1331 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1332
1333 setup do
1334 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1335 [data: data]
1336 end
1337
1338 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1339 assert Transmogrifier.fix_in_reply_to(data) == data
1340 end
1341
1342 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1343 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1344
1345 object_with_reply =
1346 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1347
1348 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1349 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1350 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1351
1352 object_with_reply =
1353 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1354
1355 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1356 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1357 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1358
1359 object_with_reply =
1360 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1361
1362 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1363 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1364 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1365
1366 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1367 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1368 assert modified_object["inReplyTo"] == []
1369 assert modified_object["inReplyToAtomUri"] == ""
1370 end
1371
1372 @tag capture_log: true
1373 test "returns modified object when allowed incoming reply", %{data: data} do
1374 object_with_reply =
1375 Map.put(
1376 data["object"],
1377 "inReplyTo",
1378 "https://shitposter.club/notice/2827873"
1379 )
1380
1381 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1382 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1383
1384 assert modified_object["inReplyTo"] ==
1385 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1386
1387 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1388
1389 assert modified_object["context"] ==
1390 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1391 end
1392 end
1393
1394 describe "fix_url/1" do
1395 test "fixes data for object when url is map" do
1396 object = %{
1397 "url" => %{
1398 "type" => "Link",
1399 "mimeType" => "video/mp4",
1400 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1401 }
1402 }
1403
1404 assert Transmogrifier.fix_url(object) == %{
1405 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1406 }
1407 end
1408
1409 test "fixes data for video object" do
1410 object = %{
1411 "type" => "Video",
1412 "url" => [
1413 %{
1414 "type" => "Link",
1415 "mimeType" => "video/mp4",
1416 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1417 },
1418 %{
1419 "type" => "Link",
1420 "mimeType" => "video/mp4",
1421 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1422 },
1423 %{
1424 "type" => "Link",
1425 "mimeType" => "text/html",
1426 "href" => "https://peertube.-2d4c2d1630e3"
1427 },
1428 %{
1429 "type" => "Link",
1430 "mimeType" => "text/html",
1431 "href" => "https://peertube.-2d4c2d16377-42"
1432 }
1433 ]
1434 }
1435
1436 assert Transmogrifier.fix_url(object) == %{
1437 "attachment" => [
1438 %{
1439 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1440 "mimeType" => "video/mp4",
1441 "type" => "Link"
1442 }
1443 ],
1444 "type" => "Video",
1445 "url" => "https://peertube.-2d4c2d1630e3"
1446 }
1447 end
1448
1449 test "fixes url for not Video object" do
1450 object = %{
1451 "type" => "Text",
1452 "url" => [
1453 %{
1454 "type" => "Link",
1455 "mimeType" => "text/html",
1456 "href" => "https://peertube.-2d4c2d1630e3"
1457 },
1458 %{
1459 "type" => "Link",
1460 "mimeType" => "text/html",
1461 "href" => "https://peertube.-2d4c2d16377-42"
1462 }
1463 ]
1464 }
1465
1466 assert Transmogrifier.fix_url(object) == %{
1467 "type" => "Text",
1468 "url" => "https://peertube.-2d4c2d1630e3"
1469 }
1470
1471 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1472 "type" => "Text",
1473 "url" => ""
1474 }
1475 end
1476
1477 test "retunrs not modified object" do
1478 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1479 end
1480 end
1481
1482 describe "get_obj_helper/2" do
1483 test "returns nil when cannot normalize object" do
1484 assert capture_log(fn ->
1485 refute Transmogrifier.get_obj_helper("test-obj-id")
1486 end) =~ "Unsupported URI scheme"
1487 end
1488
1489 @tag capture_log: true
1490 test "returns {:ok, %Object{}} for success case" do
1491 assert {:ok, %Object{}} =
1492 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1493 end
1494 end
1495
1496 describe "fix_attachments/1" do
1497 test "returns not modified object" do
1498 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1499 assert Transmogrifier.fix_attachments(data) == data
1500 end
1501
1502 test "returns modified object when attachment is map" do
1503 assert Transmogrifier.fix_attachments(%{
1504 "attachment" => %{
1505 "mediaType" => "video/mp4",
1506 "url" => "https://peertube.moe/stat-480.mp4"
1507 }
1508 }) == %{
1509 "attachment" => [
1510 %{
1511 "mediaType" => "video/mp4",
1512 "url" => [
1513 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1514 ]
1515 }
1516 ]
1517 }
1518 end
1519
1520 test "returns modified object when attachment is list" do
1521 assert Transmogrifier.fix_attachments(%{
1522 "attachment" => [
1523 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1524 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1525 ]
1526 }) == %{
1527 "attachment" => [
1528 %{
1529 "mediaType" => "video/mp4",
1530 "url" => [
1531 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1532 ]
1533 },
1534 %{
1535 "mediaType" => "video/mp4",
1536 "url" => [
1537 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1538 ]
1539 }
1540 ]
1541 }
1542 end
1543 end
1544
1545 describe "fix_emoji/1" do
1546 test "returns not modified object when object not contains tags" do
1547 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1548 assert Transmogrifier.fix_emoji(data) == data
1549 end
1550
1551 test "returns object with emoji when object contains list tags" do
1552 assert Transmogrifier.fix_emoji(%{
1553 "tag" => [
1554 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1555 %{"type" => "Hashtag"}
1556 ]
1557 }) == %{
1558 "emoji" => %{"bib" => "/test"},
1559 "tag" => [
1560 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1561 %{"type" => "Hashtag"}
1562 ]
1563 }
1564 end
1565
1566 test "returns object with emoji when object contains map tag" do
1567 assert Transmogrifier.fix_emoji(%{
1568 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1569 }) == %{
1570 "emoji" => %{"bib" => "/test"},
1571 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1572 }
1573 end
1574 end
1575
1576 describe "set_replies/1" do
1577 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1578
1579 test "returns unmodified object if activity doesn't have self-replies" do
1580 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1581 assert Transmogrifier.set_replies(data) == data
1582 end
1583
1584 test "sets `replies` collection with a limited number of self-replies" do
1585 [user, another_user] = insert_list(2, :user)
1586
1587 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1588
1589 {:ok, %{id: id2} = self_reply1} =
1590 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1591
1592 {:ok, self_reply2} =
1593 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1594
1595 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1596 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1597
1598 {:ok, _} =
1599 CommonAPI.post(user, %{
1600 status: "self-reply to self-reply",
1601 in_reply_to_status_id: id2
1602 })
1603
1604 {:ok, _} =
1605 CommonAPI.post(another_user, %{
1606 status: "another user's reply",
1607 in_reply_to_status_id: id1
1608 })
1609
1610 object = Object.normalize(activity)
1611 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1612
1613 assert %{"type" => "Collection", "items" => ^replies_uris} =
1614 Transmogrifier.set_replies(object.data)["replies"]
1615 end
1616 end
1617
1618 test "take_emoji_tags/1" do
1619 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1620
1621 assert Transmogrifier.take_emoji_tags(user) == [
1622 %{
1623 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1624 "id" => "https://example.org/firefox.png",
1625 "name" => ":firefox:",
1626 "type" => "Emoji",
1627 "updated" => "1970-01-01T00:00:00Z"
1628 }
1629 ]
1630 end
1631 end