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