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