Merge remote-tracking branch 'upstream/develop' into by-approval
[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) =~ "[warn] 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, skip_visibility_check: true})
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 correctly processes messages with weirdness in address fields" do
778 user = insert(:user)
779
780 message = %{
781 "@context" => "https://www.w3.org/ns/activitystreams",
782 "to" => [nil, user.follower_address],
783 "cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
784 "type" => "Create",
785 "object" => %{
786 "content" => "…",
787 "type" => "Note",
788 "attributedTo" => user.ap_id,
789 "inReplyTo" => nil
790 },
791 "actor" => user.ap_id
792 }
793
794 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
795
796 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
797 assert [user.follower_address] == activity.data["to"]
798 end
799
800 test "it accepts Move activities" do
801 old_user = insert(:user)
802 new_user = insert(:user)
803
804 message = %{
805 "@context" => "https://www.w3.org/ns/activitystreams",
806 "type" => "Move",
807 "actor" => old_user.ap_id,
808 "object" => old_user.ap_id,
809 "target" => new_user.ap_id
810 }
811
812 assert :error = Transmogrifier.handle_incoming(message)
813
814 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
815
816 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
817 assert activity.actor == old_user.ap_id
818 assert activity.data["actor"] == old_user.ap_id
819 assert activity.data["object"] == old_user.ap_id
820 assert activity.data["target"] == new_user.ap_id
821 assert activity.data["type"] == "Move"
822 end
823 end
824
825 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
826 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
827 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
828
829 setup do
830 data =
831 "test/fixtures/mastodon-post-activity.json"
832 |> File.read!()
833 |> Poison.decode!()
834
835 items = get_in(data, ["object", "replies", "first", "items"])
836 assert length(items) > 0
837
838 %{data: data, items: items}
839 end
840
841 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
842 data: data,
843 items: items
844 } do
845 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
846
847 {:ok, _activity} = Transmogrifier.handle_incoming(data)
848
849 for id <- items do
850 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
851 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
852 end
853 end
854
855 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
856 %{data: data} do
857 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
858
859 {:ok, _activity} = Transmogrifier.handle_incoming(data)
860
861 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
862 end
863 end
864
865 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
866 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
867 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
868
869 setup do
870 user = insert(:user)
871
872 {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
873
874 {:ok, reply1} =
875 CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
876
877 {:ok, reply2} =
878 CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
879
880 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
881
882 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
883
884 Repo.delete(activity.object)
885 Repo.delete(activity)
886
887 %{federation_output: federation_output, replies_uris: replies_uris}
888 end
889
890 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
891 federation_output: federation_output,
892 replies_uris: replies_uris
893 } do
894 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
895
896 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
897
898 for id <- replies_uris do
899 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
900 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
901 end
902 end
903
904 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
905 %{federation_output: federation_output} do
906 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
907
908 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
909
910 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
911 end
912 end
913
914 describe "prepare outgoing" do
915 test "it inlines private announced objects" do
916 user = insert(:user)
917
918 {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
919
920 {:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
921
922 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
923
924 assert modified["object"]["content"] == "hey"
925 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
926 end
927
928 test "it turns mentions into tags" do
929 user = insert(:user)
930 other_user = insert(:user)
931
932 {:ok, activity} =
933 CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
934
935 with_mock Pleroma.Notification,
936 get_notified_from_activity: fn _, _ -> [] end do
937 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
938
939 object = modified["object"]
940
941 expected_mention = %{
942 "href" => other_user.ap_id,
943 "name" => "@#{other_user.nickname}",
944 "type" => "Mention"
945 }
946
947 expected_tag = %{
948 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
949 "type" => "Hashtag",
950 "name" => "#2hu"
951 }
952
953 refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
954 assert Enum.member?(object["tag"], expected_tag)
955 assert Enum.member?(object["tag"], expected_mention)
956 end
957 end
958
959 test "it adds the sensitive property" do
960 user = insert(:user)
961
962 {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
963 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
964
965 assert modified["object"]["sensitive"]
966 end
967
968 test "it adds the json-ld context and the conversation property" do
969 user = insert(:user)
970
971 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
972 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
973
974 assert modified["@context"] ==
975 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
976
977 assert modified["object"]["conversation"] == modified["context"]
978 end
979
980 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
981 user = insert(:user)
982
983 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
984 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
985
986 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
987 end
988
989 test "it strips internal hashtag data" do
990 user = insert(:user)
991
992 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
993
994 expected_tag = %{
995 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
996 "type" => "Hashtag",
997 "name" => "#2hu"
998 }
999
1000 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1001
1002 assert modified["object"]["tag"] == [expected_tag]
1003 end
1004
1005 test "it strips internal fields" do
1006 user = insert(:user)
1007
1008 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu :firefox:"})
1009
1010 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1011
1012 assert length(modified["object"]["tag"]) == 2
1013
1014 assert is_nil(modified["object"]["emoji"])
1015 assert is_nil(modified["object"]["like_count"])
1016 assert is_nil(modified["object"]["announcements"])
1017 assert is_nil(modified["object"]["announcement_count"])
1018 assert is_nil(modified["object"]["context_id"])
1019 end
1020
1021 test "it strips internal fields of article" do
1022 activity = insert(:article_activity)
1023
1024 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1025
1026 assert length(modified["object"]["tag"]) == 2
1027
1028 assert is_nil(modified["object"]["emoji"])
1029 assert is_nil(modified["object"]["like_count"])
1030 assert is_nil(modified["object"]["announcements"])
1031 assert is_nil(modified["object"]["announcement_count"])
1032 assert is_nil(modified["object"]["context_id"])
1033 assert is_nil(modified["object"]["likes"])
1034 end
1035
1036 test "the directMessage flag is present" do
1037 user = insert(:user)
1038 other_user = insert(:user)
1039
1040 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
1041
1042 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1043
1044 assert modified["directMessage"] == false
1045
1046 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
1047
1048 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1049
1050 assert modified["directMessage"] == false
1051
1052 {:ok, activity} =
1053 CommonAPI.post(user, %{
1054 status: "@#{other_user.nickname} :moominmamma:",
1055 visibility: "direct"
1056 })
1057
1058 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1059
1060 assert modified["directMessage"] == true
1061 end
1062
1063 test "it strips BCC field" do
1064 user = insert(:user)
1065 {:ok, list} = Pleroma.List.create("foo", user)
1066
1067 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1068
1069 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1070
1071 assert is_nil(modified["bcc"])
1072 end
1073
1074 test "it can handle Listen activities" do
1075 listen_activity = insert(:listen)
1076
1077 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1078
1079 assert modified["type"] == "Listen"
1080
1081 user = insert(:user)
1082
1083 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1084
1085 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1086 end
1087 end
1088
1089 describe "user upgrade" do
1090 test "it upgrades a user to activitypub" do
1091 user =
1092 insert(:user, %{
1093 nickname: "rye@niu.moe",
1094 local: false,
1095 ap_id: "https://niu.moe/users/rye",
1096 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1097 })
1098
1099 user_two = insert(:user)
1100 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1101
1102 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1103 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
1104 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1105
1106 user = User.get_cached_by_id(user.id)
1107 assert user.note_count == 1
1108
1109 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1110 ObanHelpers.perform_all()
1111
1112 assert user.ap_enabled
1113 assert user.note_count == 1
1114 assert user.follower_address == "https://niu.moe/users/rye/followers"
1115 assert user.following_address == "https://niu.moe/users/rye/following"
1116
1117 user = User.get_cached_by_id(user.id)
1118 assert user.note_count == 1
1119
1120 activity = Activity.get_by_id(activity.id)
1121 assert user.follower_address in activity.recipients
1122
1123 assert %{
1124 "url" => [
1125 %{
1126 "href" =>
1127 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1128 }
1129 ]
1130 } = user.avatar
1131
1132 assert %{
1133 "url" => [
1134 %{
1135 "href" =>
1136 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1137 }
1138 ]
1139 } = user.banner
1140
1141 refute "..." in activity.recipients
1142
1143 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1144 refute user.follower_address in unrelated_activity.recipients
1145
1146 user_two = User.get_cached_by_id(user_two.id)
1147 assert User.following?(user_two, user)
1148 refute "..." in User.following(user_two)
1149 end
1150 end
1151
1152 describe "actor rewriting" do
1153 test "it fixes the actor URL property to be a proper URI" do
1154 data = %{
1155 "url" => %{"href" => "http://example.com"}
1156 }
1157
1158 rewritten = Transmogrifier.maybe_fix_user_object(data)
1159 assert rewritten["url"] == "http://example.com"
1160 end
1161 end
1162
1163 describe "actor origin containment" do
1164 test "it rejects activities which reference objects with bogus origins" do
1165 data = %{
1166 "@context" => "https://www.w3.org/ns/activitystreams",
1167 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1168 "actor" => "http://mastodon.example.org/users/admin",
1169 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1170 "object" => "https://info.pleroma.site/activity.json",
1171 "type" => "Announce"
1172 }
1173
1174 assert capture_log(fn ->
1175 {:error, _} = Transmogrifier.handle_incoming(data)
1176 end) =~ "Object containment failed"
1177 end
1178
1179 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1180 data = %{
1181 "@context" => "https://www.w3.org/ns/activitystreams",
1182 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1183 "actor" => "http://mastodon.example.org/users/admin",
1184 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1185 "object" => "https://info.pleroma.site/activity2.json",
1186 "type" => "Announce"
1187 }
1188
1189 assert capture_log(fn ->
1190 {:error, _} = Transmogrifier.handle_incoming(data)
1191 end) =~ "Object containment failed"
1192 end
1193
1194 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1195 data = %{
1196 "@context" => "https://www.w3.org/ns/activitystreams",
1197 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1198 "actor" => "http://mastodon.example.org/users/admin",
1199 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1200 "object" => "https://info.pleroma.site/activity3.json",
1201 "type" => "Announce"
1202 }
1203
1204 assert capture_log(fn ->
1205 {:error, _} = Transmogrifier.handle_incoming(data)
1206 end) =~ "Object containment failed"
1207 end
1208 end
1209
1210 describe "reserialization" do
1211 test "successfully reserializes a message with inReplyTo == nil" do
1212 user = insert(:user)
1213
1214 message = %{
1215 "@context" => "https://www.w3.org/ns/activitystreams",
1216 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1217 "cc" => [],
1218 "type" => "Create",
1219 "object" => %{
1220 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1221 "cc" => [],
1222 "type" => "Note",
1223 "content" => "Hi",
1224 "inReplyTo" => nil,
1225 "attributedTo" => user.ap_id
1226 },
1227 "actor" => user.ap_id
1228 }
1229
1230 {:ok, activity} = Transmogrifier.handle_incoming(message)
1231
1232 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1233 end
1234
1235 test "successfully reserializes a message with AS2 objects in IR" do
1236 user = insert(:user)
1237
1238 message = %{
1239 "@context" => "https://www.w3.org/ns/activitystreams",
1240 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1241 "cc" => [],
1242 "type" => "Create",
1243 "object" => %{
1244 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1245 "cc" => [],
1246 "type" => "Note",
1247 "content" => "Hi",
1248 "inReplyTo" => nil,
1249 "attributedTo" => user.ap_id,
1250 "tag" => [
1251 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1252 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1253 ]
1254 },
1255 "actor" => user.ap_id
1256 }
1257
1258 {:ok, activity} = Transmogrifier.handle_incoming(message)
1259
1260 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1261 end
1262 end
1263
1264 test "Rewrites Answers to Notes" do
1265 user = insert(:user)
1266
1267 {:ok, poll_activity} =
1268 CommonAPI.post(user, %{
1269 status: "suya...",
1270 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1271 })
1272
1273 poll_object = Object.normalize(poll_activity)
1274 # TODO: Replace with CommonAPI vote creation when implemented
1275 data =
1276 File.read!("test/fixtures/mastodon-vote.json")
1277 |> Poison.decode!()
1278 |> Kernel.put_in(["to"], user.ap_id)
1279 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1280 |> Kernel.put_in(["object", "to"], user.ap_id)
1281
1282 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1283 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1284
1285 assert data["object"]["type"] == "Note"
1286 end
1287
1288 describe "fix_explicit_addressing" do
1289 setup do
1290 user = insert(:user)
1291 [user: user]
1292 end
1293
1294 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1295 explicitly_mentioned_actors = [
1296 "https://pleroma.gold/users/user1",
1297 "https://pleroma.gold/user2"
1298 ]
1299
1300 object = %{
1301 "actor" => user.ap_id,
1302 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1303 "cc" => [],
1304 "tag" =>
1305 Enum.map(explicitly_mentioned_actors, fn href ->
1306 %{"type" => "Mention", "href" => href}
1307 end)
1308 }
1309
1310 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1311 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1312 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1313 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1314 end
1315
1316 test "does not move actor's follower collection to cc", %{user: user} do
1317 object = %{
1318 "actor" => user.ap_id,
1319 "to" => [user.follower_address],
1320 "cc" => []
1321 }
1322
1323 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1324 assert user.follower_address in fixed_object["to"]
1325 refute user.follower_address in fixed_object["cc"]
1326 end
1327
1328 test "removes recipient's follower collection from cc", %{user: user} do
1329 recipient = insert(:user)
1330
1331 object = %{
1332 "actor" => user.ap_id,
1333 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1334 "cc" => [user.follower_address, recipient.follower_address]
1335 }
1336
1337 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1338
1339 assert user.follower_address in fixed_object["cc"]
1340 refute recipient.follower_address in fixed_object["cc"]
1341 refute recipient.follower_address in fixed_object["to"]
1342 end
1343 end
1344
1345 describe "fix_summary/1" do
1346 test "returns fixed object" do
1347 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1348 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1349 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1350 end
1351 end
1352
1353 describe "fix_in_reply_to/2" do
1354 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1355
1356 setup do
1357 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1358 [data: data]
1359 end
1360
1361 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1362 assert Transmogrifier.fix_in_reply_to(data) == data
1363 end
1364
1365 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1366 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1367
1368 object_with_reply =
1369 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1370
1371 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1372 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1373 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1374
1375 object_with_reply =
1376 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1377
1378 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1379 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1380 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1381
1382 object_with_reply =
1383 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1384
1385 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1386 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1387 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1388
1389 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1390 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1391 assert modified_object["inReplyTo"] == []
1392 assert modified_object["inReplyToAtomUri"] == ""
1393 end
1394
1395 @tag capture_log: true
1396 test "returns modified object when allowed incoming reply", %{data: data} do
1397 object_with_reply =
1398 Map.put(
1399 data["object"],
1400 "inReplyTo",
1401 "https://shitposter.club/notice/2827873"
1402 )
1403
1404 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1405 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1406
1407 assert modified_object["inReplyTo"] ==
1408 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1409
1410 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1411
1412 assert modified_object["context"] ==
1413 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1414 end
1415 end
1416
1417 describe "fix_url/1" do
1418 test "fixes data for object when url is map" do
1419 object = %{
1420 "url" => %{
1421 "type" => "Link",
1422 "mimeType" => "video/mp4",
1423 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1424 }
1425 }
1426
1427 assert Transmogrifier.fix_url(object) == %{
1428 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1429 }
1430 end
1431
1432 test "fixes data for video object" do
1433 object = %{
1434 "type" => "Video",
1435 "url" => [
1436 %{
1437 "type" => "Link",
1438 "mimeType" => "video/mp4",
1439 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1440 },
1441 %{
1442 "type" => "Link",
1443 "mimeType" => "video/mp4",
1444 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1445 },
1446 %{
1447 "type" => "Link",
1448 "mimeType" => "text/html",
1449 "href" => "https://peertube.-2d4c2d1630e3"
1450 },
1451 %{
1452 "type" => "Link",
1453 "mimeType" => "text/html",
1454 "href" => "https://peertube.-2d4c2d16377-42"
1455 }
1456 ]
1457 }
1458
1459 assert Transmogrifier.fix_url(object) == %{
1460 "attachment" => [
1461 %{
1462 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1463 "mimeType" => "video/mp4",
1464 "type" => "Link"
1465 }
1466 ],
1467 "type" => "Video",
1468 "url" => "https://peertube.-2d4c2d1630e3"
1469 }
1470 end
1471
1472 test "fixes url for not Video object" do
1473 object = %{
1474 "type" => "Text",
1475 "url" => [
1476 %{
1477 "type" => "Link",
1478 "mimeType" => "text/html",
1479 "href" => "https://peertube.-2d4c2d1630e3"
1480 },
1481 %{
1482 "type" => "Link",
1483 "mimeType" => "text/html",
1484 "href" => "https://peertube.-2d4c2d16377-42"
1485 }
1486 ]
1487 }
1488
1489 assert Transmogrifier.fix_url(object) == %{
1490 "type" => "Text",
1491 "url" => "https://peertube.-2d4c2d1630e3"
1492 }
1493
1494 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1495 "type" => "Text",
1496 "url" => ""
1497 }
1498 end
1499
1500 test "retunrs not modified object" do
1501 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1502 end
1503 end
1504
1505 describe "get_obj_helper/2" do
1506 test "returns nil when cannot normalize object" do
1507 assert capture_log(fn ->
1508 refute Transmogrifier.get_obj_helper("test-obj-id")
1509 end) =~ "Unsupported URI scheme"
1510 end
1511
1512 @tag capture_log: true
1513 test "returns {:ok, %Object{}} for success case" do
1514 assert {:ok, %Object{}} =
1515 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1516 end
1517 end
1518
1519 describe "fix_attachments/1" do
1520 test "returns not modified object" do
1521 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1522 assert Transmogrifier.fix_attachments(data) == data
1523 end
1524
1525 test "returns modified object when attachment is map" do
1526 assert Transmogrifier.fix_attachments(%{
1527 "attachment" => %{
1528 "mediaType" => "video/mp4",
1529 "url" => "https://peertube.moe/stat-480.mp4"
1530 }
1531 }) == %{
1532 "attachment" => [
1533 %{
1534 "mediaType" => "video/mp4",
1535 "url" => [
1536 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1537 ]
1538 }
1539 ]
1540 }
1541 end
1542
1543 test "returns modified object when attachment is list" do
1544 assert Transmogrifier.fix_attachments(%{
1545 "attachment" => [
1546 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1547 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1548 ]
1549 }) == %{
1550 "attachment" => [
1551 %{
1552 "mediaType" => "video/mp4",
1553 "url" => [
1554 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1555 ]
1556 },
1557 %{
1558 "mediaType" => "video/mp4",
1559 "url" => [
1560 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1561 ]
1562 }
1563 ]
1564 }
1565 end
1566 end
1567
1568 describe "fix_emoji/1" do
1569 test "returns not modified object when object not contains tags" do
1570 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1571 assert Transmogrifier.fix_emoji(data) == data
1572 end
1573
1574 test "returns object with emoji when object contains list tags" do
1575 assert Transmogrifier.fix_emoji(%{
1576 "tag" => [
1577 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1578 %{"type" => "Hashtag"}
1579 ]
1580 }) == %{
1581 "emoji" => %{"bib" => "/test"},
1582 "tag" => [
1583 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1584 %{"type" => "Hashtag"}
1585 ]
1586 }
1587 end
1588
1589 test "returns object with emoji when object contains map tag" do
1590 assert Transmogrifier.fix_emoji(%{
1591 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1592 }) == %{
1593 "emoji" => %{"bib" => "/test"},
1594 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1595 }
1596 end
1597 end
1598
1599 describe "set_replies/1" do
1600 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1601
1602 test "returns unmodified object if activity doesn't have self-replies" do
1603 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1604 assert Transmogrifier.set_replies(data) == data
1605 end
1606
1607 test "sets `replies` collection with a limited number of self-replies" do
1608 [user, another_user] = insert_list(2, :user)
1609
1610 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1611
1612 {:ok, %{id: id2} = self_reply1} =
1613 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1614
1615 {:ok, self_reply2} =
1616 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1617
1618 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1619 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1620
1621 {:ok, _} =
1622 CommonAPI.post(user, %{
1623 status: "self-reply to self-reply",
1624 in_reply_to_status_id: id2
1625 })
1626
1627 {:ok, _} =
1628 CommonAPI.post(another_user, %{
1629 status: "another user's reply",
1630 in_reply_to_status_id: id1
1631 })
1632
1633 object = Object.normalize(activity)
1634 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1635
1636 assert %{"type" => "Collection", "items" => ^replies_uris} =
1637 Transmogrifier.set_replies(object.data)["replies"]
1638 end
1639 end
1640
1641 test "take_emoji_tags/1" do
1642 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1643
1644 assert Transmogrifier.take_emoji_tags(user) == [
1645 %{
1646 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1647 "id" => "https://example.org/firefox.png",
1648 "name" => ":firefox:",
1649 "type" => "Emoji",
1650 "updated" => "1970-01-01T00:00:00Z"
1651 }
1652 ]
1653 end
1654 end