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