TransmogrifierTest: Fix tests.
[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 incoming update activities" do
405 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
406
407 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
408 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
409
410 object =
411 update_data["object"]
412 |> Map.put("actor", data["actor"])
413 |> Map.put("id", data["actor"])
414
415 update_data =
416 update_data
417 |> Map.put("actor", data["actor"])
418 |> Map.put("object", object)
419
420 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
421
422 assert data["id"] == update_data["id"]
423
424 user = User.get_cached_by_ap_id(data["actor"])
425 assert user.name == "gargle"
426
427 assert user.avatar["url"] == [
428 %{
429 "href" =>
430 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
431 }
432 ]
433
434 assert user.banner["url"] == [
435 %{
436 "href" =>
437 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
438 }
439 ]
440
441 assert user.bio == "<p>Some bio</p>"
442 end
443
444 test "it works with alsoKnownAs" do
445 {:ok, %Activity{data: %{"actor" => actor}}} =
446 "test/fixtures/mastodon-post-activity.json"
447 |> File.read!()
448 |> Poison.decode!()
449 |> Transmogrifier.handle_incoming()
450
451 assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
452
453 {:ok, _activity} =
454 "test/fixtures/mastodon-update.json"
455 |> File.read!()
456 |> Poison.decode!()
457 |> Map.put("actor", actor)
458 |> Map.update!("object", fn object ->
459 object
460 |> Map.put("actor", actor)
461 |> Map.put("id", actor)
462 |> Map.put("alsoKnownAs", [
463 "http://mastodon.example.org/users/foo",
464 "http://example.org/users/bar"
465 ])
466 end)
467 |> Transmogrifier.handle_incoming()
468
469 assert User.get_cached_by_ap_id(actor).also_known_as == [
470 "http://mastodon.example.org/users/foo",
471 "http://example.org/users/bar"
472 ]
473 end
474
475 test "it works with custom profile fields" do
476 {:ok, activity} =
477 "test/fixtures/mastodon-post-activity.json"
478 |> File.read!()
479 |> Poison.decode!()
480 |> Transmogrifier.handle_incoming()
481
482 user = User.get_cached_by_ap_id(activity.actor)
483
484 assert user.fields == [
485 %{"name" => "foo", "value" => "bar"},
486 %{"name" => "foo1", "value" => "bar1"}
487 ]
488
489 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
490
491 object =
492 update_data["object"]
493 |> Map.put("actor", user.ap_id)
494 |> Map.put("id", user.ap_id)
495
496 update_data =
497 update_data
498 |> Map.put("actor", user.ap_id)
499 |> Map.put("object", object)
500
501 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
502
503 user = User.get_cached_by_ap_id(user.ap_id)
504
505 assert user.fields == [
506 %{"name" => "foo", "value" => "updated"},
507 %{"name" => "foo1", "value" => "updated"}
508 ]
509
510 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
511
512 update_data =
513 put_in(update_data, ["object", "attachment"], [
514 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
515 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
516 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
517 ])
518
519 {:ok, _} = Transmogrifier.handle_incoming(update_data)
520
521 user = User.get_cached_by_ap_id(user.ap_id)
522
523 assert user.fields == [
524 %{"name" => "foo", "value" => "updated"},
525 %{"name" => "foo1", "value" => "updated"}
526 ]
527
528 update_data = put_in(update_data, ["object", "attachment"], [])
529
530 {:ok, _} = Transmogrifier.handle_incoming(update_data)
531
532 user = User.get_cached_by_ap_id(user.ap_id)
533
534 assert user.fields == []
535 end
536
537 test "it works for incoming update activities which lock the account" do
538 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
539
540 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
541 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
542
543 object =
544 update_data["object"]
545 |> Map.put("actor", data["actor"])
546 |> Map.put("id", data["actor"])
547 |> Map.put("manuallyApprovesFollowers", true)
548
549 update_data =
550 update_data
551 |> Map.put("actor", data["actor"])
552 |> Map.put("object", object)
553
554 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
555
556 user = User.get_cached_by_ap_id(data["actor"])
557 assert user.locked == true
558 end
559
560 test "it works for incomming unfollows with an existing follow" do
561 user = insert(:user)
562
563 follow_data =
564 File.read!("test/fixtures/mastodon-follow-activity.json")
565 |> Poison.decode!()
566 |> Map.put("object", user.ap_id)
567
568 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
569
570 data =
571 File.read!("test/fixtures/mastodon-unfollow-activity.json")
572 |> Poison.decode!()
573 |> Map.put("object", follow_data)
574
575 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
576
577 assert data["type"] == "Undo"
578 assert data["object"]["type"] == "Follow"
579 assert data["object"]["object"] == user.ap_id
580 assert data["actor"] == "http://mastodon.example.org/users/admin"
581
582 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
583 end
584
585 test "it works for incoming follows to locked account" do
586 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
587 user = insert(:user, locked: true)
588
589 data =
590 File.read!("test/fixtures/mastodon-follow-activity.json")
591 |> Poison.decode!()
592 |> Map.put("object", user.ap_id)
593
594 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
595
596 assert data["type"] == "Follow"
597 assert data["object"] == user.ap_id
598 assert data["state"] == "pending"
599 assert data["actor"] == "http://mastodon.example.org/users/admin"
600
601 assert [^pending_follower] = User.get_follow_requests(user)
602 end
603
604 test "it works for incoming blocks" do
605 user = insert(:user)
606
607 data =
608 File.read!("test/fixtures/mastodon-block-activity.json")
609 |> Poison.decode!()
610 |> Map.put("object", user.ap_id)
611
612 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
613
614 assert data["type"] == "Block"
615 assert data["object"] == user.ap_id
616 assert data["actor"] == "http://mastodon.example.org/users/admin"
617
618 blocker = User.get_cached_by_ap_id(data["actor"])
619
620 assert User.blocks?(blocker, user)
621 end
622
623 test "incoming blocks successfully tear down any follow relationship" do
624 blocker = insert(:user)
625 blocked = insert(:user)
626
627 data =
628 File.read!("test/fixtures/mastodon-block-activity.json")
629 |> Poison.decode!()
630 |> Map.put("object", blocked.ap_id)
631 |> Map.put("actor", blocker.ap_id)
632
633 {:ok, blocker} = User.follow(blocker, blocked)
634 {:ok, blocked} = User.follow(blocked, blocker)
635
636 assert User.following?(blocker, blocked)
637 assert User.following?(blocked, blocker)
638
639 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
640
641 assert data["type"] == "Block"
642 assert data["object"] == blocked.ap_id
643 assert data["actor"] == blocker.ap_id
644
645 blocker = User.get_cached_by_ap_id(data["actor"])
646 blocked = User.get_cached_by_ap_id(data["object"])
647
648 assert User.blocks?(blocker, blocked)
649
650 refute User.following?(blocker, blocked)
651 refute User.following?(blocked, blocker)
652 end
653
654 test "it works for incoming accepts which were pre-accepted" do
655 follower = insert(:user)
656 followed = insert(:user)
657
658 {:ok, follower} = User.follow(follower, followed)
659 assert User.following?(follower, followed) == true
660
661 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
662
663 accept_data =
664 File.read!("test/fixtures/mastodon-accept-activity.json")
665 |> Poison.decode!()
666 |> Map.put("actor", followed.ap_id)
667
668 object =
669 accept_data["object"]
670 |> Map.put("actor", follower.ap_id)
671 |> Map.put("id", follow_activity.data["id"])
672
673 accept_data = Map.put(accept_data, "object", object)
674
675 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
676 refute activity.local
677
678 assert activity.data["object"] == follow_activity.data["id"]
679
680 assert activity.data["id"] == accept_data["id"]
681
682 follower = User.get_cached_by_id(follower.id)
683
684 assert User.following?(follower, followed) == true
685 end
686
687 test "it works for incoming accepts which were orphaned" do
688 follower = insert(:user)
689 followed = insert(:user, locked: true)
690
691 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
692
693 accept_data =
694 File.read!("test/fixtures/mastodon-accept-activity.json")
695 |> Poison.decode!()
696 |> Map.put("actor", followed.ap_id)
697
698 accept_data =
699 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
700
701 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
702 assert activity.data["object"] == follow_activity.data["id"]
703
704 follower = User.get_cached_by_id(follower.id)
705
706 assert User.following?(follower, followed) == true
707 end
708
709 test "it works for incoming accepts which are referenced by IRI only" do
710 follower = insert(:user)
711 followed = insert(:user, locked: true)
712
713 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
714
715 accept_data =
716 File.read!("test/fixtures/mastodon-accept-activity.json")
717 |> Poison.decode!()
718 |> Map.put("actor", followed.ap_id)
719 |> Map.put("object", follow_activity.data["id"])
720
721 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
722 assert activity.data["object"] == follow_activity.data["id"]
723
724 follower = User.get_cached_by_id(follower.id)
725
726 assert User.following?(follower, followed) == true
727
728 follower = User.get_by_id(follower.id)
729 assert follower.following_count == 1
730
731 followed = User.get_by_id(followed.id)
732 assert followed.follower_count == 1
733 end
734
735 test "it fails for incoming accepts which cannot be correlated" do
736 follower = insert(:user)
737 followed = insert(:user, locked: true)
738
739 accept_data =
740 File.read!("test/fixtures/mastodon-accept-activity.json")
741 |> Poison.decode!()
742 |> Map.put("actor", followed.ap_id)
743
744 accept_data =
745 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
746
747 :error = Transmogrifier.handle_incoming(accept_data)
748
749 follower = User.get_cached_by_id(follower.id)
750
751 refute User.following?(follower, followed) == true
752 end
753
754 test "it fails for incoming rejects which cannot be correlated" do
755 follower = insert(:user)
756 followed = insert(:user, locked: true)
757
758 accept_data =
759 File.read!("test/fixtures/mastodon-reject-activity.json")
760 |> Poison.decode!()
761 |> Map.put("actor", followed.ap_id)
762
763 accept_data =
764 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
765
766 :error = Transmogrifier.handle_incoming(accept_data)
767
768 follower = User.get_cached_by_id(follower.id)
769
770 refute User.following?(follower, followed) == true
771 end
772
773 test "it works for incoming rejects which are orphaned" do
774 follower = insert(:user)
775 followed = insert(:user, locked: true)
776
777 {:ok, follower} = User.follow(follower, followed)
778 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
779
780 assert User.following?(follower, followed) == true
781
782 reject_data =
783 File.read!("test/fixtures/mastodon-reject-activity.json")
784 |> Poison.decode!()
785 |> Map.put("actor", followed.ap_id)
786
787 reject_data =
788 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
789
790 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
791 refute activity.local
792 assert activity.data["id"] == reject_data["id"]
793
794 follower = User.get_cached_by_id(follower.id)
795
796 assert User.following?(follower, followed) == false
797 end
798
799 test "it works for incoming rejects which are referenced by IRI only" do
800 follower = insert(:user)
801 followed = insert(:user, locked: true)
802
803 {:ok, follower} = User.follow(follower, followed)
804 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
805
806 assert User.following?(follower, followed) == true
807
808 reject_data =
809 File.read!("test/fixtures/mastodon-reject-activity.json")
810 |> Poison.decode!()
811 |> Map.put("actor", followed.ap_id)
812 |> Map.put("object", follow_activity.data["id"])
813
814 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
815
816 follower = User.get_cached_by_id(follower.id)
817
818 assert User.following?(follower, followed) == false
819 end
820
821 test "it rejects activities without a valid ID" do
822 user = insert(:user)
823
824 data =
825 File.read!("test/fixtures/mastodon-follow-activity.json")
826 |> Poison.decode!()
827 |> Map.put("object", user.ap_id)
828 |> Map.put("id", "")
829
830 :error = Transmogrifier.handle_incoming(data)
831 end
832
833 test "skip converting the content when it is nil" do
834 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
835
836 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
837
838 result =
839 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
840
841 assert result["content"] == nil
842 end
843
844 test "it converts content of object to html" do
845 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
846
847 {:ok, %{"content" => content_markdown}} =
848 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
849
850 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
851 Fetcher.fetch_object_from_id(object_id)
852
853 assert content_markdown ==
854 "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..."
855
856 assert content ==
857 "<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>"
858
859 assert object.data["mediaType"] == "text/html"
860 end
861
862 test "it remaps video URLs as attachments if necessary" do
863 {:ok, object} =
864 Fetcher.fetch_object_from_id(
865 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
866 )
867
868 attachment = %{
869 "type" => "Link",
870 "mediaType" => "video/mp4",
871 "url" => [
872 %{
873 "href" =>
874 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
875 "mediaType" => "video/mp4"
876 }
877 ]
878 }
879
880 assert object.data["url"] ==
881 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
882
883 assert object.data["attachment"] == [attachment]
884 end
885
886 test "it accepts Flag activities" do
887 user = insert(:user)
888 other_user = insert(:user)
889
890 {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
891 object = Object.normalize(activity)
892
893 note_obj = %{
894 "type" => "Note",
895 "id" => activity.data["id"],
896 "content" => "test post",
897 "published" => object.data["published"],
898 "actor" => AccountView.render("show.json", %{user: user})
899 }
900
901 message = %{
902 "@context" => "https://www.w3.org/ns/activitystreams",
903 "cc" => [user.ap_id],
904 "object" => [user.ap_id, activity.data["id"]],
905 "type" => "Flag",
906 "content" => "blocked AND reported!!!",
907 "actor" => other_user.ap_id
908 }
909
910 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
911
912 assert activity.data["object"] == [user.ap_id, note_obj]
913 assert activity.data["content"] == "blocked AND reported!!!"
914 assert activity.data["actor"] == other_user.ap_id
915 assert activity.data["cc"] == [user.ap_id]
916 end
917
918 test "it correctly processes messages with non-array to field" do
919 user = insert(:user)
920
921 message = %{
922 "@context" => "https://www.w3.org/ns/activitystreams",
923 "to" => "https://www.w3.org/ns/activitystreams#Public",
924 "type" => "Create",
925 "object" => %{
926 "content" => "blah blah blah",
927 "type" => "Note",
928 "attributedTo" => user.ap_id,
929 "inReplyTo" => nil
930 },
931 "actor" => user.ap_id
932 }
933
934 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
935
936 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
937 end
938
939 test "it correctly processes messages with non-array cc field" do
940 user = insert(:user)
941
942 message = %{
943 "@context" => "https://www.w3.org/ns/activitystreams",
944 "to" => user.follower_address,
945 "cc" => "https://www.w3.org/ns/activitystreams#Public",
946 "type" => "Create",
947 "object" => %{
948 "content" => "blah blah blah",
949 "type" => "Note",
950 "attributedTo" => user.ap_id,
951 "inReplyTo" => nil
952 },
953 "actor" => user.ap_id
954 }
955
956 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
957
958 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
959 assert [user.follower_address] == activity.data["to"]
960 end
961
962 test "it accepts Move activities" do
963 old_user = insert(:user)
964 new_user = insert(:user)
965
966 message = %{
967 "@context" => "https://www.w3.org/ns/activitystreams",
968 "type" => "Move",
969 "actor" => old_user.ap_id,
970 "object" => old_user.ap_id,
971 "target" => new_user.ap_id
972 }
973
974 assert :error = Transmogrifier.handle_incoming(message)
975
976 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
977
978 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
979 assert activity.actor == old_user.ap_id
980 assert activity.data["actor"] == old_user.ap_id
981 assert activity.data["object"] == old_user.ap_id
982 assert activity.data["target"] == new_user.ap_id
983 assert activity.data["type"] == "Move"
984 end
985 end
986
987 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
988 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
989 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
990
991 setup do
992 data =
993 "test/fixtures/mastodon-post-activity.json"
994 |> File.read!()
995 |> Poison.decode!()
996
997 items = get_in(data, ["object", "replies", "first", "items"])
998 assert length(items) > 0
999
1000 %{data: data, items: items}
1001 end
1002
1003 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1004 data: data,
1005 items: items
1006 } do
1007 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1008
1009 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1010
1011 for id <- items do
1012 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1013 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1014 end
1015 end
1016
1017 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1018 %{data: data} do
1019 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1020
1021 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1022
1023 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1024 end
1025 end
1026
1027 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1028 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1029 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1030
1031 setup do
1032 user = insert(:user)
1033
1034 {:ok, activity} = CommonAPI.post(user, %{status: "post1"})
1035
1036 {:ok, reply1} =
1037 CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
1038
1039 {:ok, reply2} =
1040 CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
1041
1042 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1043
1044 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1045
1046 Repo.delete(activity.object)
1047 Repo.delete(activity)
1048
1049 %{federation_output: federation_output, replies_uris: replies_uris}
1050 end
1051
1052 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1053 federation_output: federation_output,
1054 replies_uris: replies_uris
1055 } do
1056 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1057
1058 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1059
1060 for id <- replies_uris do
1061 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1062 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1063 end
1064 end
1065
1066 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1067 %{federation_output: federation_output} do
1068 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1069
1070 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1071
1072 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1073 end
1074 end
1075
1076 describe "prepare outgoing" do
1077 test "it inlines private announced objects" do
1078 user = insert(:user)
1079
1080 {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
1081
1082 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1083
1084 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1085
1086 assert modified["object"]["content"] == "hey"
1087 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1088 end
1089
1090 test "it turns mentions into tags" do
1091 user = insert(:user)
1092 other_user = insert(:user)
1093
1094 {:ok, activity} =
1095 CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
1096
1097 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1098 object = modified["object"]
1099
1100 expected_mention = %{
1101 "href" => other_user.ap_id,
1102 "name" => "@#{other_user.nickname}",
1103 "type" => "Mention"
1104 }
1105
1106 expected_tag = %{
1107 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1108 "type" => "Hashtag",
1109 "name" => "#2hu"
1110 }
1111
1112 assert Enum.member?(object["tag"], expected_tag)
1113 assert Enum.member?(object["tag"], expected_mention)
1114 end
1115
1116 test "it adds the sensitive property" do
1117 user = insert(:user)
1118
1119 {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
1120 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1121
1122 assert modified["object"]["sensitive"]
1123 end
1124
1125 test "it adds the json-ld context and the conversation property" do
1126 user = insert(:user)
1127
1128 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
1129 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1130
1131 assert modified["@context"] ==
1132 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1133
1134 assert modified["object"]["conversation"] == modified["context"]
1135 end
1136
1137 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1138 user = insert(:user)
1139
1140 {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
1141 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1142
1143 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1144 end
1145
1146 test "it strips internal hashtag data" do
1147 user = insert(:user)
1148
1149 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
1150
1151 expected_tag = %{
1152 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1153 "type" => "Hashtag",
1154 "name" => "#2hu"
1155 }
1156
1157 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1158
1159 assert modified["object"]["tag"] == [expected_tag]
1160 end
1161
1162 test "it strips internal fields" do
1163 user = insert(:user)
1164
1165 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu :firefox:"})
1166
1167 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1168
1169 assert length(modified["object"]["tag"]) == 2
1170
1171 assert is_nil(modified["object"]["emoji"])
1172 assert is_nil(modified["object"]["like_count"])
1173 assert is_nil(modified["object"]["announcements"])
1174 assert is_nil(modified["object"]["announcement_count"])
1175 assert is_nil(modified["object"]["context_id"])
1176 end
1177
1178 test "it strips internal fields of article" do
1179 activity = insert(:article_activity)
1180
1181 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1182
1183 assert length(modified["object"]["tag"]) == 2
1184
1185 assert is_nil(modified["object"]["emoji"])
1186 assert is_nil(modified["object"]["like_count"])
1187 assert is_nil(modified["object"]["announcements"])
1188 assert is_nil(modified["object"]["announcement_count"])
1189 assert is_nil(modified["object"]["context_id"])
1190 assert is_nil(modified["object"]["likes"])
1191 end
1192
1193 test "the directMessage flag is present" do
1194 user = insert(:user)
1195 other_user = insert(:user)
1196
1197 {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
1198
1199 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1200
1201 assert modified["directMessage"] == false
1202
1203 {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
1204
1205 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1206
1207 assert modified["directMessage"] == false
1208
1209 {:ok, activity} =
1210 CommonAPI.post(user, %{
1211 status: "@#{other_user.nickname} :moominmamma:",
1212 visibility: "direct"
1213 })
1214
1215 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1216
1217 assert modified["directMessage"] == true
1218 end
1219
1220 test "it strips BCC field" do
1221 user = insert(:user)
1222 {:ok, list} = Pleroma.List.create("foo", user)
1223
1224 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
1225
1226 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1227
1228 assert is_nil(modified["bcc"])
1229 end
1230
1231 test "it can handle Listen activities" do
1232 listen_activity = insert(:listen)
1233
1234 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1235
1236 assert modified["type"] == "Listen"
1237
1238 user = insert(:user)
1239
1240 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1241
1242 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1243 end
1244 end
1245
1246 describe "user upgrade" do
1247 test "it upgrades a user to activitypub" do
1248 user =
1249 insert(:user, %{
1250 nickname: "rye@niu.moe",
1251 local: false,
1252 ap_id: "https://niu.moe/users/rye",
1253 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1254 })
1255
1256 user_two = insert(:user)
1257 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1258
1259 {:ok, activity} = CommonAPI.post(user, %{status: "test"})
1260 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
1261 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1262
1263 user = User.get_cached_by_id(user.id)
1264 assert user.note_count == 1
1265
1266 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1267 ObanHelpers.perform_all()
1268
1269 assert user.ap_enabled
1270 assert user.note_count == 1
1271 assert user.follower_address == "https://niu.moe/users/rye/followers"
1272 assert user.following_address == "https://niu.moe/users/rye/following"
1273
1274 user = User.get_cached_by_id(user.id)
1275 assert user.note_count == 1
1276
1277 activity = Activity.get_by_id(activity.id)
1278 assert user.follower_address in activity.recipients
1279
1280 assert %{
1281 "url" => [
1282 %{
1283 "href" =>
1284 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1285 }
1286 ]
1287 } = user.avatar
1288
1289 assert %{
1290 "url" => [
1291 %{
1292 "href" =>
1293 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1294 }
1295 ]
1296 } = user.banner
1297
1298 refute "..." in activity.recipients
1299
1300 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1301 refute user.follower_address in unrelated_activity.recipients
1302
1303 user_two = User.get_cached_by_id(user_two.id)
1304 assert User.following?(user_two, user)
1305 refute "..." in User.following(user_two)
1306 end
1307 end
1308
1309 describe "actor rewriting" do
1310 test "it fixes the actor URL property to be a proper URI" do
1311 data = %{
1312 "url" => %{"href" => "http://example.com"}
1313 }
1314
1315 rewritten = Transmogrifier.maybe_fix_user_object(data)
1316 assert rewritten["url"] == "http://example.com"
1317 end
1318 end
1319
1320 describe "actor origin containment" do
1321 test "it rejects activities which reference objects with bogus origins" do
1322 data = %{
1323 "@context" => "https://www.w3.org/ns/activitystreams",
1324 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1325 "actor" => "http://mastodon.example.org/users/admin",
1326 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1327 "object" => "https://info.pleroma.site/activity.json",
1328 "type" => "Announce"
1329 }
1330
1331 assert capture_log(fn ->
1332 {:error, _} = Transmogrifier.handle_incoming(data)
1333 end) =~ "Object containment failed"
1334 end
1335
1336 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1337 data = %{
1338 "@context" => "https://www.w3.org/ns/activitystreams",
1339 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1340 "actor" => "http://mastodon.example.org/users/admin",
1341 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1342 "object" => "https://info.pleroma.site/activity2.json",
1343 "type" => "Announce"
1344 }
1345
1346 assert capture_log(fn ->
1347 {:error, _} = Transmogrifier.handle_incoming(data)
1348 end) =~ "Object containment failed"
1349 end
1350
1351 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1352 data = %{
1353 "@context" => "https://www.w3.org/ns/activitystreams",
1354 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1355 "actor" => "http://mastodon.example.org/users/admin",
1356 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1357 "object" => "https://info.pleroma.site/activity3.json",
1358 "type" => "Announce"
1359 }
1360
1361 assert capture_log(fn ->
1362 {:error, _} = Transmogrifier.handle_incoming(data)
1363 end) =~ "Object containment failed"
1364 end
1365 end
1366
1367 describe "reserialization" do
1368 test "successfully reserializes a message with inReplyTo == nil" do
1369 user = insert(:user)
1370
1371 message = %{
1372 "@context" => "https://www.w3.org/ns/activitystreams",
1373 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1374 "cc" => [],
1375 "type" => "Create",
1376 "object" => %{
1377 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1378 "cc" => [],
1379 "type" => "Note",
1380 "content" => "Hi",
1381 "inReplyTo" => nil,
1382 "attributedTo" => user.ap_id
1383 },
1384 "actor" => user.ap_id
1385 }
1386
1387 {:ok, activity} = Transmogrifier.handle_incoming(message)
1388
1389 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1390 end
1391
1392 test "successfully reserializes a message with AS2 objects in IR" do
1393 user = insert(:user)
1394
1395 message = %{
1396 "@context" => "https://www.w3.org/ns/activitystreams",
1397 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1398 "cc" => [],
1399 "type" => "Create",
1400 "object" => %{
1401 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1402 "cc" => [],
1403 "type" => "Note",
1404 "content" => "Hi",
1405 "inReplyTo" => nil,
1406 "attributedTo" => user.ap_id,
1407 "tag" => [
1408 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1409 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1410 ]
1411 },
1412 "actor" => user.ap_id
1413 }
1414
1415 {:ok, activity} = Transmogrifier.handle_incoming(message)
1416
1417 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1418 end
1419 end
1420
1421 test "Rewrites Answers to Notes" do
1422 user = insert(:user)
1423
1424 {:ok, poll_activity} =
1425 CommonAPI.post(user, %{
1426 status: "suya...",
1427 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1428 })
1429
1430 poll_object = Object.normalize(poll_activity)
1431 # TODO: Replace with CommonAPI vote creation when implemented
1432 data =
1433 File.read!("test/fixtures/mastodon-vote.json")
1434 |> Poison.decode!()
1435 |> Kernel.put_in(["to"], user.ap_id)
1436 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1437 |> Kernel.put_in(["object", "to"], user.ap_id)
1438
1439 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1440 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1441
1442 assert data["object"]["type"] == "Note"
1443 end
1444
1445 describe "fix_explicit_addressing" do
1446 setup do
1447 user = insert(:user)
1448 [user: user]
1449 end
1450
1451 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1452 explicitly_mentioned_actors = [
1453 "https://pleroma.gold/users/user1",
1454 "https://pleroma.gold/user2"
1455 ]
1456
1457 object = %{
1458 "actor" => user.ap_id,
1459 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1460 "cc" => [],
1461 "tag" =>
1462 Enum.map(explicitly_mentioned_actors, fn href ->
1463 %{"type" => "Mention", "href" => href}
1464 end)
1465 }
1466
1467 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1468 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1469 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1470 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1471 end
1472
1473 test "does not move actor's follower collection to cc", %{user: user} do
1474 object = %{
1475 "actor" => user.ap_id,
1476 "to" => [user.follower_address],
1477 "cc" => []
1478 }
1479
1480 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1481 assert user.follower_address in fixed_object["to"]
1482 refute user.follower_address in fixed_object["cc"]
1483 end
1484
1485 test "removes recipient's follower collection from cc", %{user: user} do
1486 recipient = insert(:user)
1487
1488 object = %{
1489 "actor" => user.ap_id,
1490 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1491 "cc" => [user.follower_address, recipient.follower_address]
1492 }
1493
1494 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1495
1496 assert user.follower_address in fixed_object["cc"]
1497 refute recipient.follower_address in fixed_object["cc"]
1498 refute recipient.follower_address in fixed_object["to"]
1499 end
1500 end
1501
1502 describe "fix_summary/1" do
1503 test "returns fixed object" do
1504 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1505 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1506 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1507 end
1508 end
1509
1510 describe "fix_in_reply_to/2" do
1511 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1512
1513 setup do
1514 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1515 [data: data]
1516 end
1517
1518 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1519 assert Transmogrifier.fix_in_reply_to(data) == data
1520 end
1521
1522 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1523 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1524
1525 object_with_reply =
1526 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1527
1528 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1529 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1530 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1531
1532 object_with_reply =
1533 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1534
1535 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1536 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1537 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1538
1539 object_with_reply =
1540 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1541
1542 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1543 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1544 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1545
1546 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1547 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1548 assert modified_object["inReplyTo"] == []
1549 assert modified_object["inReplyToAtomUri"] == ""
1550 end
1551
1552 @tag capture_log: true
1553 test "returns modified object when allowed incoming reply", %{data: data} do
1554 object_with_reply =
1555 Map.put(
1556 data["object"],
1557 "inReplyTo",
1558 "https://shitposter.club/notice/2827873"
1559 )
1560
1561 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1562 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1563
1564 assert modified_object["inReplyTo"] ==
1565 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1566
1567 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1568
1569 assert modified_object["conversation"] ==
1570 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1571
1572 assert modified_object["context"] ==
1573 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1574 end
1575 end
1576
1577 describe "fix_url/1" do
1578 test "fixes data for object when url is map" do
1579 object = %{
1580 "url" => %{
1581 "type" => "Link",
1582 "mimeType" => "video/mp4",
1583 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1584 }
1585 }
1586
1587 assert Transmogrifier.fix_url(object) == %{
1588 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1589 }
1590 end
1591
1592 test "fixes data for video object" do
1593 object = %{
1594 "type" => "Video",
1595 "url" => [
1596 %{
1597 "type" => "Link",
1598 "mimeType" => "video/mp4",
1599 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1600 },
1601 %{
1602 "type" => "Link",
1603 "mimeType" => "video/mp4",
1604 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1605 },
1606 %{
1607 "type" => "Link",
1608 "mimeType" => "text/html",
1609 "href" => "https://peertube.-2d4c2d1630e3"
1610 },
1611 %{
1612 "type" => "Link",
1613 "mimeType" => "text/html",
1614 "href" => "https://peertube.-2d4c2d16377-42"
1615 }
1616 ]
1617 }
1618
1619 assert Transmogrifier.fix_url(object) == %{
1620 "attachment" => [
1621 %{
1622 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1623 "mimeType" => "video/mp4",
1624 "type" => "Link"
1625 }
1626 ],
1627 "type" => "Video",
1628 "url" => "https://peertube.-2d4c2d1630e3"
1629 }
1630 end
1631
1632 test "fixes url for not Video object" do
1633 object = %{
1634 "type" => "Text",
1635 "url" => [
1636 %{
1637 "type" => "Link",
1638 "mimeType" => "text/html",
1639 "href" => "https://peertube.-2d4c2d1630e3"
1640 },
1641 %{
1642 "type" => "Link",
1643 "mimeType" => "text/html",
1644 "href" => "https://peertube.-2d4c2d16377-42"
1645 }
1646 ]
1647 }
1648
1649 assert Transmogrifier.fix_url(object) == %{
1650 "type" => "Text",
1651 "url" => "https://peertube.-2d4c2d1630e3"
1652 }
1653
1654 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1655 "type" => "Text",
1656 "url" => ""
1657 }
1658 end
1659
1660 test "retunrs not modified object" do
1661 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1662 end
1663 end
1664
1665 describe "get_obj_helper/2" do
1666 test "returns nil when cannot normalize object" do
1667 assert capture_log(fn ->
1668 refute Transmogrifier.get_obj_helper("test-obj-id")
1669 end) =~ "Unsupported URI scheme"
1670 end
1671
1672 @tag capture_log: true
1673 test "returns {:ok, %Object{}} for success case" do
1674 assert {:ok, %Object{}} =
1675 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1676 end
1677 end
1678
1679 describe "fix_attachments/1" do
1680 test "returns not modified object" do
1681 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1682 assert Transmogrifier.fix_attachments(data) == data
1683 end
1684
1685 test "returns modified object when attachment is map" do
1686 assert Transmogrifier.fix_attachments(%{
1687 "attachment" => %{
1688 "mediaType" => "video/mp4",
1689 "url" => "https://peertube.moe/stat-480.mp4"
1690 }
1691 }) == %{
1692 "attachment" => [
1693 %{
1694 "mediaType" => "video/mp4",
1695 "url" => [
1696 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1697 ]
1698 }
1699 ]
1700 }
1701 end
1702
1703 test "returns modified object when attachment is list" do
1704 assert Transmogrifier.fix_attachments(%{
1705 "attachment" => [
1706 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1707 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1708 ]
1709 }) == %{
1710 "attachment" => [
1711 %{
1712 "mediaType" => "video/mp4",
1713 "url" => [
1714 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1715 ]
1716 },
1717 %{
1718 "mediaType" => "video/mp4",
1719 "url" => [
1720 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1721 ]
1722 }
1723 ]
1724 }
1725 end
1726 end
1727
1728 describe "fix_emoji/1" do
1729 test "returns not modified object when object not contains tags" do
1730 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1731 assert Transmogrifier.fix_emoji(data) == data
1732 end
1733
1734 test "returns object with emoji when object contains list tags" do
1735 assert Transmogrifier.fix_emoji(%{
1736 "tag" => [
1737 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1738 %{"type" => "Hashtag"}
1739 ]
1740 }) == %{
1741 "emoji" => %{"bib" => "/test"},
1742 "tag" => [
1743 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1744 %{"type" => "Hashtag"}
1745 ]
1746 }
1747 end
1748
1749 test "returns object with emoji when object contains map tag" do
1750 assert Transmogrifier.fix_emoji(%{
1751 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1752 }) == %{
1753 "emoji" => %{"bib" => "/test"},
1754 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1755 }
1756 end
1757 end
1758
1759 describe "set_replies/1" do
1760 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1761
1762 test "returns unmodified object if activity doesn't have self-replies" do
1763 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1764 assert Transmogrifier.set_replies(data) == data
1765 end
1766
1767 test "sets `replies` collection with a limited number of self-replies" do
1768 [user, another_user] = insert_list(2, :user)
1769
1770 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1771
1772 {:ok, %{id: id2} = self_reply1} =
1773 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1774
1775 {:ok, self_reply2} =
1776 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1777
1778 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1779 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1780
1781 {:ok, _} =
1782 CommonAPI.post(user, %{
1783 status: "self-reply to self-reply",
1784 in_reply_to_status_id: id2
1785 })
1786
1787 {:ok, _} =
1788 CommonAPI.post(another_user, %{
1789 status: "another user's reply",
1790 in_reply_to_status_id: id1
1791 })
1792
1793 object = Object.normalize(activity)
1794 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1795
1796 assert %{"type" => "Collection", "items" => ^replies_uris} =
1797 Transmogrifier.set_replies(object.data)["replies"]
1798 end
1799 end
1800
1801 test "take_emoji_tags/1" do
1802 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1803
1804 assert Transmogrifier.take_emoji_tags(user) == [
1805 %{
1806 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1807 "id" => "https://example.org/firefox.png",
1808 "name" => ":firefox:",
1809 "type" => "Emoji",
1810 "updated" => "1970-01-01T00:00:00Z"
1811 }
1812 ]
1813 end
1814 end