Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/emojireactv...
[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 reaction undos" do
329 user = insert(:user)
330
331 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
332 {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌")
333
334 data =
335 File.read!("test/fixtures/mastodon-undo-like.json")
336 |> Poison.decode!()
337 |> Map.put("object", reaction_activity.data["id"])
338 |> Map.put("actor", user.ap_id)
339
340 {:ok, activity} = Transmogrifier.handle_incoming(data)
341
342 assert activity.actor == user.ap_id
343 assert activity.data["id"] == data["id"]
344 assert activity.data["type"] == "Undo"
345 end
346
347 test "it returns an error for incoming unlikes wihout a like activity" do
348 user = insert(:user)
349 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
350
351 data =
352 File.read!("test/fixtures/mastodon-undo-like.json")
353 |> Poison.decode!()
354 |> Map.put("object", activity.data["object"])
355
356 assert Transmogrifier.handle_incoming(data) == :error
357 end
358
359 test "it works for incoming unlikes with an existing like activity" do
360 user = insert(:user)
361 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
362
363 like_data =
364 File.read!("test/fixtures/mastodon-like.json")
365 |> Poison.decode!()
366 |> Map.put("object", activity.data["object"])
367
368 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
369
370 data =
371 File.read!("test/fixtures/mastodon-undo-like.json")
372 |> Poison.decode!()
373 |> Map.put("object", like_data)
374 |> Map.put("actor", like_data["actor"])
375
376 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
377
378 assert data["actor"] == "http://mastodon.example.org/users/admin"
379 assert data["type"] == "Undo"
380 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
381 assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
382 end
383
384 test "it works for incoming unlikes with an existing like activity and a compact object" do
385 user = insert(:user)
386 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
387
388 like_data =
389 File.read!("test/fixtures/mastodon-like.json")
390 |> Poison.decode!()
391 |> Map.put("object", activity.data["object"])
392
393 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
394
395 data =
396 File.read!("test/fixtures/mastodon-undo-like.json")
397 |> Poison.decode!()
398 |> Map.put("object", like_data["id"])
399 |> Map.put("actor", like_data["actor"])
400
401 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
402
403 assert data["actor"] == "http://mastodon.example.org/users/admin"
404 assert data["type"] == "Undo"
405 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
406 assert data["object"] == "http://mastodon.example.org/users/admin#likes/2"
407 end
408
409 test "it works for incoming emoji reactions" do
410 user = insert(:user)
411 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
412
413 data =
414 File.read!("test/fixtures/emoji-reaction.json")
415 |> Poison.decode!()
416 |> Map.put("object", activity.data["object"])
417
418 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
419
420 assert data["actor"] == "http://mastodon.example.org/users/admin"
421 assert data["type"] == "EmojiReact"
422 assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
423 assert data["object"] == activity.data["object"]
424 assert data["content"] == "👌"
425 end
426
427 test "it reject invalid emoji reactions" do
428 user = insert(:user)
429 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
430
431 data =
432 File.read!("test/fixtures/emoji-reaction-too-long.json")
433 |> Poison.decode!()
434 |> Map.put("object", activity.data["object"])
435
436 assert {:error, _} = Transmogrifier.handle_incoming(data)
437
438 data =
439 File.read!("test/fixtures/emoji-reaction-no-emoji.json")
440 |> Poison.decode!()
441 |> Map.put("object", activity.data["object"])
442
443 assert {:error, _} = Transmogrifier.handle_incoming(data)
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 incomming unfollows with an existing follow" do
770 user = insert(:user)
771
772 follow_data =
773 File.read!("test/fixtures/mastodon-follow-activity.json")
774 |> Poison.decode!()
775 |> Map.put("object", user.ap_id)
776
777 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
778
779 data =
780 File.read!("test/fixtures/mastodon-unfollow-activity.json")
781 |> Poison.decode!()
782 |> Map.put("object", follow_data)
783
784 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
785
786 assert data["type"] == "Undo"
787 assert data["object"]["type"] == "Follow"
788 assert data["object"]["object"] == user.ap_id
789 assert data["actor"] == "http://mastodon.example.org/users/admin"
790
791 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
792 end
793
794 test "it works for incoming follows to locked account" do
795 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
796 user = insert(:user, locked: true)
797
798 data =
799 File.read!("test/fixtures/mastodon-follow-activity.json")
800 |> Poison.decode!()
801 |> Map.put("object", user.ap_id)
802
803 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
804
805 assert data["type"] == "Follow"
806 assert data["object"] == user.ap_id
807 assert data["state"] == "pending"
808 assert data["actor"] == "http://mastodon.example.org/users/admin"
809
810 assert [^pending_follower] = User.get_follow_requests(user)
811 end
812
813 test "it works for incoming blocks" do
814 user = insert(:user)
815
816 data =
817 File.read!("test/fixtures/mastodon-block-activity.json")
818 |> Poison.decode!()
819 |> Map.put("object", user.ap_id)
820
821 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
822
823 assert data["type"] == "Block"
824 assert data["object"] == user.ap_id
825 assert data["actor"] == "http://mastodon.example.org/users/admin"
826
827 blocker = User.get_cached_by_ap_id(data["actor"])
828
829 assert User.blocks?(blocker, user)
830 end
831
832 test "incoming blocks successfully tear down any follow relationship" do
833 blocker = insert(:user)
834 blocked = insert(:user)
835
836 data =
837 File.read!("test/fixtures/mastodon-block-activity.json")
838 |> Poison.decode!()
839 |> Map.put("object", blocked.ap_id)
840 |> Map.put("actor", blocker.ap_id)
841
842 {:ok, blocker} = User.follow(blocker, blocked)
843 {:ok, blocked} = User.follow(blocked, blocker)
844
845 assert User.following?(blocker, blocked)
846 assert User.following?(blocked, blocker)
847
848 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
849
850 assert data["type"] == "Block"
851 assert data["object"] == blocked.ap_id
852 assert data["actor"] == blocker.ap_id
853
854 blocker = User.get_cached_by_ap_id(data["actor"])
855 blocked = User.get_cached_by_ap_id(data["object"])
856
857 assert User.blocks?(blocker, blocked)
858
859 refute User.following?(blocker, blocked)
860 refute User.following?(blocked, blocker)
861 end
862
863 test "it works for incoming accepts which were pre-accepted" do
864 follower = insert(:user)
865 followed = insert(:user)
866
867 {:ok, follower} = User.follow(follower, followed)
868 assert User.following?(follower, followed) == true
869
870 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
871
872 accept_data =
873 File.read!("test/fixtures/mastodon-accept-activity.json")
874 |> Poison.decode!()
875 |> Map.put("actor", followed.ap_id)
876
877 object =
878 accept_data["object"]
879 |> Map.put("actor", follower.ap_id)
880 |> Map.put("id", follow_activity.data["id"])
881
882 accept_data = Map.put(accept_data, "object", object)
883
884 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
885 refute activity.local
886
887 assert activity.data["object"] == follow_activity.data["id"]
888
889 assert activity.data["id"] == accept_data["id"]
890
891 follower = User.get_cached_by_id(follower.id)
892
893 assert User.following?(follower, followed) == true
894 end
895
896 test "it works for incoming accepts which were orphaned" do
897 follower = insert(:user)
898 followed = insert(:user, locked: true)
899
900 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
901
902 accept_data =
903 File.read!("test/fixtures/mastodon-accept-activity.json")
904 |> Poison.decode!()
905 |> Map.put("actor", followed.ap_id)
906
907 accept_data =
908 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
909
910 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
911 assert activity.data["object"] == follow_activity.data["id"]
912
913 follower = User.get_cached_by_id(follower.id)
914
915 assert User.following?(follower, followed) == true
916 end
917
918 test "it works for incoming accepts which are referenced by IRI only" do
919 follower = insert(:user)
920 followed = insert(:user, locked: true)
921
922 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
923
924 accept_data =
925 File.read!("test/fixtures/mastodon-accept-activity.json")
926 |> Poison.decode!()
927 |> Map.put("actor", followed.ap_id)
928 |> Map.put("object", follow_activity.data["id"])
929
930 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
931 assert activity.data["object"] == follow_activity.data["id"]
932
933 follower = User.get_cached_by_id(follower.id)
934
935 assert User.following?(follower, followed) == true
936 end
937
938 test "it fails for incoming accepts which cannot be correlated" do
939 follower = insert(:user)
940 followed = insert(:user, locked: true)
941
942 accept_data =
943 File.read!("test/fixtures/mastodon-accept-activity.json")
944 |> Poison.decode!()
945 |> Map.put("actor", followed.ap_id)
946
947 accept_data =
948 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
949
950 :error = Transmogrifier.handle_incoming(accept_data)
951
952 follower = User.get_cached_by_id(follower.id)
953
954 refute User.following?(follower, followed) == true
955 end
956
957 test "it fails for incoming rejects which cannot be correlated" do
958 follower = insert(:user)
959 followed = insert(:user, locked: true)
960
961 accept_data =
962 File.read!("test/fixtures/mastodon-reject-activity.json")
963 |> Poison.decode!()
964 |> Map.put("actor", followed.ap_id)
965
966 accept_data =
967 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
968
969 :error = Transmogrifier.handle_incoming(accept_data)
970
971 follower = User.get_cached_by_id(follower.id)
972
973 refute User.following?(follower, followed) == true
974 end
975
976 test "it works for incoming rejects which are orphaned" do
977 follower = insert(:user)
978 followed = insert(:user, locked: true)
979
980 {:ok, follower} = User.follow(follower, followed)
981 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
982
983 assert User.following?(follower, followed) == true
984
985 reject_data =
986 File.read!("test/fixtures/mastodon-reject-activity.json")
987 |> Poison.decode!()
988 |> Map.put("actor", followed.ap_id)
989
990 reject_data =
991 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
992
993 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
994 refute activity.local
995 assert activity.data["id"] == reject_data["id"]
996
997 follower = User.get_cached_by_id(follower.id)
998
999 assert User.following?(follower, followed) == false
1000 end
1001
1002 test "it works for incoming rejects which are referenced by IRI only" do
1003 follower = insert(:user)
1004 followed = insert(:user, locked: true)
1005
1006 {:ok, follower} = User.follow(follower, followed)
1007 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1008
1009 assert User.following?(follower, followed) == true
1010
1011 reject_data =
1012 File.read!("test/fixtures/mastodon-reject-activity.json")
1013 |> Poison.decode!()
1014 |> Map.put("actor", followed.ap_id)
1015 |> Map.put("object", follow_activity.data["id"])
1016
1017 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
1018
1019 follower = User.get_cached_by_id(follower.id)
1020
1021 assert User.following?(follower, followed) == false
1022 end
1023
1024 test "it rejects activities without a valid ID" do
1025 user = insert(:user)
1026
1027 data =
1028 File.read!("test/fixtures/mastodon-follow-activity.json")
1029 |> Poison.decode!()
1030 |> Map.put("object", user.ap_id)
1031 |> Map.put("id", "")
1032
1033 :error = Transmogrifier.handle_incoming(data)
1034 end
1035
1036 test "skip converting the content when it is nil" do
1037 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
1038
1039 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
1040
1041 result =
1042 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
1043
1044 assert result["content"] == nil
1045 end
1046
1047 test "it converts content of object to html" do
1048 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
1049
1050 {:ok, %{"content" => content_markdown}} =
1051 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
1052
1053 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
1054 Fetcher.fetch_object_from_id(object_id)
1055
1056 assert content_markdown ==
1057 "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..."
1058
1059 assert content ==
1060 "<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>"
1061
1062 assert object.data["mediaType"] == "text/html"
1063 end
1064
1065 test "it remaps video URLs as attachments if necessary" do
1066 {:ok, object} =
1067 Fetcher.fetch_object_from_id(
1068 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1069 )
1070
1071 attachment = %{
1072 "type" => "Link",
1073 "mediaType" => "video/mp4",
1074 "url" => [
1075 %{
1076 "href" =>
1077 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1078 "mediaType" => "video/mp4"
1079 }
1080 ]
1081 }
1082
1083 assert object.data["url"] ==
1084 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1085
1086 assert object.data["attachment"] == [attachment]
1087 end
1088
1089 test "it accepts Flag activities" do
1090 user = insert(:user)
1091 other_user = insert(:user)
1092
1093 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1094 object = Object.normalize(activity)
1095
1096 note_obj = %{
1097 "type" => "Note",
1098 "id" => activity.data["id"],
1099 "content" => "test post",
1100 "published" => object.data["published"],
1101 "actor" => AccountView.render("show.json", %{user: user})
1102 }
1103
1104 message = %{
1105 "@context" => "https://www.w3.org/ns/activitystreams",
1106 "cc" => [user.ap_id],
1107 "object" => [user.ap_id, activity.data["id"]],
1108 "type" => "Flag",
1109 "content" => "blocked AND reported!!!",
1110 "actor" => other_user.ap_id
1111 }
1112
1113 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1114
1115 assert activity.data["object"] == [user.ap_id, note_obj]
1116 assert activity.data["content"] == "blocked AND reported!!!"
1117 assert activity.data["actor"] == other_user.ap_id
1118 assert activity.data["cc"] == [user.ap_id]
1119 end
1120
1121 test "it correctly processes messages with non-array to field" do
1122 user = insert(:user)
1123
1124 message = %{
1125 "@context" => "https://www.w3.org/ns/activitystreams",
1126 "to" => "https://www.w3.org/ns/activitystreams#Public",
1127 "type" => "Create",
1128 "object" => %{
1129 "content" => "blah blah blah",
1130 "type" => "Note",
1131 "attributedTo" => user.ap_id,
1132 "inReplyTo" => nil
1133 },
1134 "actor" => user.ap_id
1135 }
1136
1137 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1138
1139 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1140 end
1141
1142 test "it correctly processes messages with non-array cc field" do
1143 user = insert(:user)
1144
1145 message = %{
1146 "@context" => "https://www.w3.org/ns/activitystreams",
1147 "to" => user.follower_address,
1148 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1149 "type" => "Create",
1150 "object" => %{
1151 "content" => "blah blah blah",
1152 "type" => "Note",
1153 "attributedTo" => user.ap_id,
1154 "inReplyTo" => nil
1155 },
1156 "actor" => user.ap_id
1157 }
1158
1159 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1160
1161 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1162 assert [user.follower_address] == activity.data["to"]
1163 end
1164
1165 test "it accepts Move activities" do
1166 old_user = insert(:user)
1167 new_user = insert(:user)
1168
1169 message = %{
1170 "@context" => "https://www.w3.org/ns/activitystreams",
1171 "type" => "Move",
1172 "actor" => old_user.ap_id,
1173 "object" => old_user.ap_id,
1174 "target" => new_user.ap_id
1175 }
1176
1177 assert :error = Transmogrifier.handle_incoming(message)
1178
1179 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1180
1181 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1182 assert activity.actor == old_user.ap_id
1183 assert activity.data["actor"] == old_user.ap_id
1184 assert activity.data["object"] == old_user.ap_id
1185 assert activity.data["target"] == new_user.ap_id
1186 assert activity.data["type"] == "Move"
1187 end
1188 end
1189
1190 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1191 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1192 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1193
1194 setup do
1195 data =
1196 "test/fixtures/mastodon-post-activity.json"
1197 |> File.read!()
1198 |> Poison.decode!()
1199
1200 items = get_in(data, ["object", "replies", "first", "items"])
1201 assert length(items) > 0
1202
1203 %{data: data, items: items}
1204 end
1205
1206 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1207 data: data,
1208 items: items
1209 } do
1210 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1211
1212 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1213
1214 for id <- items do
1215 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1216 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1217 end
1218 end
1219
1220 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1221 %{data: data} do
1222 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1223
1224 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1225
1226 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1227 end
1228 end
1229
1230 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1231 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1232 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1233
1234 setup do
1235 user = insert(:user)
1236
1237 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1238
1239 {:ok, reply1} =
1240 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1241
1242 {:ok, reply2} =
1243 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1244
1245 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1246
1247 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1248
1249 Repo.delete(activity.object)
1250 Repo.delete(activity)
1251
1252 %{federation_output: federation_output, replies_uris: replies_uris}
1253 end
1254
1255 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1256 federation_output: federation_output,
1257 replies_uris: replies_uris
1258 } do
1259 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1260
1261 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1262
1263 for id <- replies_uris do
1264 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1265 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1266 end
1267 end
1268
1269 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1270 %{federation_output: federation_output} do
1271 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1272
1273 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1274
1275 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1276 end
1277 end
1278
1279 describe "prepare outgoing" do
1280 test "it inlines private announced objects" do
1281 user = insert(:user)
1282
1283 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1284
1285 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1286
1287 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1288
1289 assert modified["object"]["content"] == "hey"
1290 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1291 end
1292
1293 test "it turns mentions into tags" do
1294 user = insert(:user)
1295 other_user = insert(:user)
1296
1297 {:ok, activity} =
1298 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1299
1300 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1301 object = modified["object"]
1302
1303 expected_mention = %{
1304 "href" => other_user.ap_id,
1305 "name" => "@#{other_user.nickname}",
1306 "type" => "Mention"
1307 }
1308
1309 expected_tag = %{
1310 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1311 "type" => "Hashtag",
1312 "name" => "#2hu"
1313 }
1314
1315 assert Enum.member?(object["tag"], expected_tag)
1316 assert Enum.member?(object["tag"], expected_mention)
1317 end
1318
1319 test "it adds the sensitive property" do
1320 user = insert(:user)
1321
1322 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1323 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1324
1325 assert modified["object"]["sensitive"]
1326 end
1327
1328 test "it adds the json-ld context and the conversation property" do
1329 user = insert(:user)
1330
1331 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1332 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1333
1334 assert modified["@context"] ==
1335 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1336
1337 assert modified["object"]["conversation"] == modified["context"]
1338 end
1339
1340 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1341 user = insert(:user)
1342
1343 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1344 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1345
1346 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1347 end
1348
1349 test "it strips internal hashtag data" do
1350 user = insert(:user)
1351
1352 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1353
1354 expected_tag = %{
1355 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1356 "type" => "Hashtag",
1357 "name" => "#2hu"
1358 }
1359
1360 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1361
1362 assert modified["object"]["tag"] == [expected_tag]
1363 end
1364
1365 test "it strips internal fields" do
1366 user = insert(:user)
1367
1368 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1369
1370 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1371
1372 assert length(modified["object"]["tag"]) == 2
1373
1374 assert is_nil(modified["object"]["emoji"])
1375 assert is_nil(modified["object"]["like_count"])
1376 assert is_nil(modified["object"]["announcements"])
1377 assert is_nil(modified["object"]["announcement_count"])
1378 assert is_nil(modified["object"]["context_id"])
1379 end
1380
1381 test "it strips internal fields of article" do
1382 activity = insert(:article_activity)
1383
1384 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1385
1386 assert length(modified["object"]["tag"]) == 2
1387
1388 assert is_nil(modified["object"]["emoji"])
1389 assert is_nil(modified["object"]["like_count"])
1390 assert is_nil(modified["object"]["announcements"])
1391 assert is_nil(modified["object"]["announcement_count"])
1392 assert is_nil(modified["object"]["context_id"])
1393 assert is_nil(modified["object"]["likes"])
1394 end
1395
1396 test "the directMessage flag is present" do
1397 user = insert(:user)
1398 other_user = insert(:user)
1399
1400 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1401
1402 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1403
1404 assert modified["directMessage"] == false
1405
1406 {:ok, activity} =
1407 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1408
1409 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1410
1411 assert modified["directMessage"] == false
1412
1413 {:ok, activity} =
1414 CommonAPI.post(user, %{
1415 "status" => "@#{other_user.nickname} :moominmamma:",
1416 "visibility" => "direct"
1417 })
1418
1419 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1420
1421 assert modified["directMessage"] == true
1422 end
1423
1424 test "it strips BCC field" do
1425 user = insert(:user)
1426 {:ok, list} = Pleroma.List.create("foo", user)
1427
1428 {:ok, activity} =
1429 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1430
1431 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1432
1433 assert is_nil(modified["bcc"])
1434 end
1435
1436 test "it can handle Listen activities" do
1437 listen_activity = insert(:listen)
1438
1439 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1440
1441 assert modified["type"] == "Listen"
1442
1443 user = insert(:user)
1444
1445 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1446
1447 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1448 end
1449 end
1450
1451 describe "user upgrade" do
1452 test "it upgrades a user to activitypub" do
1453 user =
1454 insert(:user, %{
1455 nickname: "rye@niu.moe",
1456 local: false,
1457 ap_id: "https://niu.moe/users/rye",
1458 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1459 })
1460
1461 user_two = insert(:user)
1462 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1463
1464 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1465 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1466 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1467
1468 user = User.get_cached_by_id(user.id)
1469 assert user.note_count == 1
1470
1471 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1472 ObanHelpers.perform_all()
1473
1474 assert user.ap_enabled
1475 assert user.note_count == 1
1476 assert user.follower_address == "https://niu.moe/users/rye/followers"
1477 assert user.following_address == "https://niu.moe/users/rye/following"
1478
1479 user = User.get_cached_by_id(user.id)
1480 assert user.note_count == 1
1481
1482 activity = Activity.get_by_id(activity.id)
1483 assert user.follower_address in activity.recipients
1484
1485 assert %{
1486 "url" => [
1487 %{
1488 "href" =>
1489 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1490 }
1491 ]
1492 } = user.avatar
1493
1494 assert %{
1495 "url" => [
1496 %{
1497 "href" =>
1498 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1499 }
1500 ]
1501 } = user.banner
1502
1503 refute "..." in activity.recipients
1504
1505 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1506 refute user.follower_address in unrelated_activity.recipients
1507
1508 user_two = User.get_cached_by_id(user_two.id)
1509 assert User.following?(user_two, user)
1510 refute "..." in User.following(user_two)
1511 end
1512 end
1513
1514 describe "actor rewriting" do
1515 test "it fixes the actor URL property to be a proper URI" do
1516 data = %{
1517 "url" => %{"href" => "http://example.com"}
1518 }
1519
1520 rewritten = Transmogrifier.maybe_fix_user_object(data)
1521 assert rewritten["url"] == "http://example.com"
1522 end
1523 end
1524
1525 describe "actor origin containment" do
1526 test "it rejects activities which reference objects with bogus origins" do
1527 data = %{
1528 "@context" => "https://www.w3.org/ns/activitystreams",
1529 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1530 "actor" => "http://mastodon.example.org/users/admin",
1531 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1532 "object" => "https://info.pleroma.site/activity.json",
1533 "type" => "Announce"
1534 }
1535
1536 assert capture_log(fn ->
1537 :error = Transmogrifier.handle_incoming(data)
1538 end) =~ "Object containment failed"
1539 end
1540
1541 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1542 data = %{
1543 "@context" => "https://www.w3.org/ns/activitystreams",
1544 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1545 "actor" => "http://mastodon.example.org/users/admin",
1546 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1547 "object" => "https://info.pleroma.site/activity2.json",
1548 "type" => "Announce"
1549 }
1550
1551 assert capture_log(fn ->
1552 :error = Transmogrifier.handle_incoming(data)
1553 end) =~ "Object containment failed"
1554 end
1555
1556 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1557 data = %{
1558 "@context" => "https://www.w3.org/ns/activitystreams",
1559 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1560 "actor" => "http://mastodon.example.org/users/admin",
1561 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1562 "object" => "https://info.pleroma.site/activity3.json",
1563 "type" => "Announce"
1564 }
1565
1566 assert capture_log(fn ->
1567 :error = Transmogrifier.handle_incoming(data)
1568 end) =~ "Object containment failed"
1569 end
1570 end
1571
1572 describe "reserialization" do
1573 test "successfully reserializes a message with inReplyTo == nil" do
1574 user = insert(:user)
1575
1576 message = %{
1577 "@context" => "https://www.w3.org/ns/activitystreams",
1578 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1579 "cc" => [],
1580 "type" => "Create",
1581 "object" => %{
1582 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1583 "cc" => [],
1584 "type" => "Note",
1585 "content" => "Hi",
1586 "inReplyTo" => nil,
1587 "attributedTo" => user.ap_id
1588 },
1589 "actor" => user.ap_id
1590 }
1591
1592 {:ok, activity} = Transmogrifier.handle_incoming(message)
1593
1594 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1595 end
1596
1597 test "successfully reserializes a message with AS2 objects in IR" do
1598 user = insert(:user)
1599
1600 message = %{
1601 "@context" => "https://www.w3.org/ns/activitystreams",
1602 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1603 "cc" => [],
1604 "type" => "Create",
1605 "object" => %{
1606 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1607 "cc" => [],
1608 "type" => "Note",
1609 "content" => "Hi",
1610 "inReplyTo" => nil,
1611 "attributedTo" => user.ap_id,
1612 "tag" => [
1613 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1614 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1615 ]
1616 },
1617 "actor" => user.ap_id
1618 }
1619
1620 {:ok, activity} = Transmogrifier.handle_incoming(message)
1621
1622 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1623 end
1624 end
1625
1626 test "Rewrites Answers to Notes" do
1627 user = insert(:user)
1628
1629 {:ok, poll_activity} =
1630 CommonAPI.post(user, %{
1631 "status" => "suya...",
1632 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1633 })
1634
1635 poll_object = Object.normalize(poll_activity)
1636 # TODO: Replace with CommonAPI vote creation when implemented
1637 data =
1638 File.read!("test/fixtures/mastodon-vote.json")
1639 |> Poison.decode!()
1640 |> Kernel.put_in(["to"], user.ap_id)
1641 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1642 |> Kernel.put_in(["object", "to"], user.ap_id)
1643
1644 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1645 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1646
1647 assert data["object"]["type"] == "Note"
1648 end
1649
1650 describe "fix_explicit_addressing" do
1651 setup do
1652 user = insert(:user)
1653 [user: user]
1654 end
1655
1656 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1657 explicitly_mentioned_actors = [
1658 "https://pleroma.gold/users/user1",
1659 "https://pleroma.gold/user2"
1660 ]
1661
1662 object = %{
1663 "actor" => user.ap_id,
1664 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1665 "cc" => [],
1666 "tag" =>
1667 Enum.map(explicitly_mentioned_actors, fn href ->
1668 %{"type" => "Mention", "href" => href}
1669 end)
1670 }
1671
1672 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1673 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1674 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1675 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1676 end
1677
1678 test "does not move actor's follower collection to cc", %{user: user} do
1679 object = %{
1680 "actor" => user.ap_id,
1681 "to" => [user.follower_address],
1682 "cc" => []
1683 }
1684
1685 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1686 assert user.follower_address in fixed_object["to"]
1687 refute user.follower_address in fixed_object["cc"]
1688 end
1689
1690 test "removes recipient's follower collection from cc", %{user: user} do
1691 recipient = insert(:user)
1692
1693 object = %{
1694 "actor" => user.ap_id,
1695 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1696 "cc" => [user.follower_address, recipient.follower_address]
1697 }
1698
1699 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1700
1701 assert user.follower_address in fixed_object["cc"]
1702 refute recipient.follower_address in fixed_object["cc"]
1703 refute recipient.follower_address in fixed_object["to"]
1704 end
1705 end
1706
1707 describe "fix_summary/1" do
1708 test "returns fixed object" do
1709 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1710 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1711 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1712 end
1713 end
1714
1715 describe "fix_in_reply_to/2" do
1716 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1717
1718 setup do
1719 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1720 [data: data]
1721 end
1722
1723 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1724 assert Transmogrifier.fix_in_reply_to(data) == data
1725 end
1726
1727 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1728 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1729
1730 object_with_reply =
1731 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1732
1733 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1734 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1735 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1736
1737 object_with_reply =
1738 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1739
1740 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1741 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1742 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1743
1744 object_with_reply =
1745 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1746
1747 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1748 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1749 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1750
1751 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1752 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1753 assert modified_object["inReplyTo"] == []
1754 assert modified_object["inReplyToAtomUri"] == ""
1755 end
1756
1757 @tag capture_log: true
1758 test "returns modified object when allowed incoming reply", %{data: data} do
1759 object_with_reply =
1760 Map.put(
1761 data["object"],
1762 "inReplyTo",
1763 "https://shitposter.club/notice/2827873"
1764 )
1765
1766 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1767 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1768
1769 assert modified_object["inReplyTo"] ==
1770 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1771
1772 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1773
1774 assert modified_object["conversation"] ==
1775 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1776
1777 assert modified_object["context"] ==
1778 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1779 end
1780 end
1781
1782 describe "fix_url/1" do
1783 test "fixes data for object when url is map" do
1784 object = %{
1785 "url" => %{
1786 "type" => "Link",
1787 "mimeType" => "video/mp4",
1788 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1789 }
1790 }
1791
1792 assert Transmogrifier.fix_url(object) == %{
1793 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1794 }
1795 end
1796
1797 test "fixes data for video object" do
1798 object = %{
1799 "type" => "Video",
1800 "url" => [
1801 %{
1802 "type" => "Link",
1803 "mimeType" => "video/mp4",
1804 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1805 },
1806 %{
1807 "type" => "Link",
1808 "mimeType" => "video/mp4",
1809 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1810 },
1811 %{
1812 "type" => "Link",
1813 "mimeType" => "text/html",
1814 "href" => "https://peertube.-2d4c2d1630e3"
1815 },
1816 %{
1817 "type" => "Link",
1818 "mimeType" => "text/html",
1819 "href" => "https://peertube.-2d4c2d16377-42"
1820 }
1821 ]
1822 }
1823
1824 assert Transmogrifier.fix_url(object) == %{
1825 "attachment" => [
1826 %{
1827 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1828 "mimeType" => "video/mp4",
1829 "type" => "Link"
1830 }
1831 ],
1832 "type" => "Video",
1833 "url" => "https://peertube.-2d4c2d1630e3"
1834 }
1835 end
1836
1837 test "fixes url for not Video object" do
1838 object = %{
1839 "type" => "Text",
1840 "url" => [
1841 %{
1842 "type" => "Link",
1843 "mimeType" => "text/html",
1844 "href" => "https://peertube.-2d4c2d1630e3"
1845 },
1846 %{
1847 "type" => "Link",
1848 "mimeType" => "text/html",
1849 "href" => "https://peertube.-2d4c2d16377-42"
1850 }
1851 ]
1852 }
1853
1854 assert Transmogrifier.fix_url(object) == %{
1855 "type" => "Text",
1856 "url" => "https://peertube.-2d4c2d1630e3"
1857 }
1858
1859 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1860 "type" => "Text",
1861 "url" => ""
1862 }
1863 end
1864
1865 test "retunrs not modified object" do
1866 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1867 end
1868 end
1869
1870 describe "get_obj_helper/2" do
1871 test "returns nil when cannot normalize object" do
1872 assert capture_log(fn ->
1873 refute Transmogrifier.get_obj_helper("test-obj-id")
1874 end) =~ "Unsupported URI scheme"
1875 end
1876
1877 @tag capture_log: true
1878 test "returns {:ok, %Object{}} for success case" do
1879 assert {:ok, %Object{}} =
1880 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1881 end
1882 end
1883
1884 describe "fix_attachments/1" do
1885 test "returns not modified object" do
1886 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1887 assert Transmogrifier.fix_attachments(data) == data
1888 end
1889
1890 test "returns modified object when attachment is map" do
1891 assert Transmogrifier.fix_attachments(%{
1892 "attachment" => %{
1893 "mediaType" => "video/mp4",
1894 "url" => "https://peertube.moe/stat-480.mp4"
1895 }
1896 }) == %{
1897 "attachment" => [
1898 %{
1899 "mediaType" => "video/mp4",
1900 "url" => [
1901 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1902 ]
1903 }
1904 ]
1905 }
1906 end
1907
1908 test "returns modified object when attachment is list" do
1909 assert Transmogrifier.fix_attachments(%{
1910 "attachment" => [
1911 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1912 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1913 ]
1914 }) == %{
1915 "attachment" => [
1916 %{
1917 "mediaType" => "video/mp4",
1918 "url" => [
1919 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1920 ]
1921 },
1922 %{
1923 "mediaType" => "video/mp4",
1924 "url" => [
1925 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1926 ]
1927 }
1928 ]
1929 }
1930 end
1931 end
1932
1933 describe "fix_emoji/1" do
1934 test "returns not modified object when object not contains tags" do
1935 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1936 assert Transmogrifier.fix_emoji(data) == data
1937 end
1938
1939 test "returns object with emoji when object contains list tags" do
1940 assert Transmogrifier.fix_emoji(%{
1941 "tag" => [
1942 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1943 %{"type" => "Hashtag"}
1944 ]
1945 }) == %{
1946 "emoji" => %{"bib" => "/test"},
1947 "tag" => [
1948 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1949 %{"type" => "Hashtag"}
1950 ]
1951 }
1952 end
1953
1954 test "returns object with emoji when object contains map tag" do
1955 assert Transmogrifier.fix_emoji(%{
1956 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1957 }) == %{
1958 "emoji" => %{"bib" => "/test"},
1959 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1960 }
1961 end
1962 end
1963
1964 describe "set_replies/1" do
1965 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1966
1967 test "returns unmodified object if activity doesn't have self-replies" do
1968 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1969 assert Transmogrifier.set_replies(data) == data
1970 end
1971
1972 test "sets `replies` collection with a limited number of self-replies" do
1973 [user, another_user] = insert_list(2, :user)
1974
1975 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
1976
1977 {:ok, %{id: id2} = self_reply1} =
1978 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
1979
1980 {:ok, self_reply2} =
1981 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
1982
1983 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1984 {:ok, _} =
1985 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
1986
1987 {:ok, _} =
1988 CommonAPI.post(user, %{
1989 "status" => "self-reply to self-reply",
1990 "in_reply_to_status_id" => id2
1991 })
1992
1993 {:ok, _} =
1994 CommonAPI.post(another_user, %{
1995 "status" => "another user's reply",
1996 "in_reply_to_status_id" => id1
1997 })
1998
1999 object = Object.normalize(activity)
2000 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
2001
2002 assert %{"type" => "Collection", "items" => ^replies_uris} =
2003 Transmogrifier.set_replies(object.data)["replies"]
2004 end
2005 end
2006
2007 test "take_emoji_tags/1" do
2008 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
2009
2010 assert Transmogrifier.take_emoji_tags(user) == [
2011 %{
2012 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
2013 "id" => "https://example.org/firefox.png",
2014 "name" => ":firefox:",
2015 "type" => "Emoji",
2016 "updated" => "1970-01-01T00:00:00Z"
2017 }
2018 ]
2019 end
2020 end