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