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