Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.Transmogrifier
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.CommonAPI
17
18 import Mock
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
21
22 setup_all do
23 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
24 :ok
25 end
26
27 setup do: clear_config([:instance, :max_remote_account_fields])
28
29 describe "handle_incoming" do
30 test "it works for incoming notices with tag not being an array (kroeg)" do
31 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
32
33 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
34 object = Object.normalize(data["object"])
35
36 assert object.data["emoji"] == %{
37 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
38 }
39
40 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
41
42 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
43 object = Object.normalize(data["object"])
44
45 assert "test" in object.data["tag"]
46 end
47
48 test "it works for incoming notices with url not being a string (prismo)" do
49 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
50
51 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
52 object = Object.normalize(data["object"])
53
54 assert object.data["url"] == "https://prismo.news/posts/83"
55 end
56
57 test "it cleans up incoming notices which are not really DMs" do
58 user = insert(:user)
59 other_user = insert(:user)
60
61 to = [user.ap_id, other_user.ap_id]
62
63 data =
64 File.read!("test/fixtures/mastodon-post-activity.json")
65 |> Poison.decode!()
66 |> Map.put("to", to)
67 |> Map.put("cc", [])
68
69 object =
70 data["object"]
71 |> Map.put("to", to)
72 |> Map.put("cc", [])
73
74 data = Map.put(data, "object", object)
75
76 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
77
78 assert data["to"] == []
79 assert data["cc"] == to
80
81 object_data = Object.normalize(activity).data
82
83 assert object_data["to"] == []
84 assert object_data["cc"] == to
85 end
86
87 test "it ignores an incoming notice if we already have it" do
88 activity = insert(:note_activity)
89
90 data =
91 File.read!("test/fixtures/mastodon-post-activity.json")
92 |> Poison.decode!()
93 |> Map.put("object", Object.normalize(activity).data)
94
95 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
96
97 assert activity == returned_activity
98 end
99
100 @tag capture_log: true
101 test "it fetches reply-to activities if we don't have them" do
102 data =
103 File.read!("test/fixtures/mastodon-post-activity.json")
104 |> Poison.decode!()
105
106 object =
107 data["object"]
108 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
109
110 data = Map.put(data, "object", object)
111 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
112 returned_object = Object.normalize(returned_activity, false)
113
114 assert activity =
115 Activity.get_create_by_object_ap_id(
116 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
117 )
118
119 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
120 end
121
122 test "it does not fetch reply-to activities beyond max replies depth limit" do
123 data =
124 File.read!("test/fixtures/mastodon-post-activity.json")
125 |> Poison.decode!()
126
127 object =
128 data["object"]
129 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
130
131 data = Map.put(data, "object", object)
132
133 with_mock Pleroma.Web.Federator,
134 allowed_thread_distance?: fn _ -> false end do
135 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
136
137 returned_object = Object.normalize(returned_activity, false)
138
139 refute Activity.get_create_by_object_ap_id(
140 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
141 )
142
143 assert returned_object.data["inReplyToAtomUri"] ==
144 "https://shitposter.club/notice/2827873"
145 end
146 end
147
148 test "it does not crash if the object in inReplyTo can't be fetched" do
149 data =
150 File.read!("test/fixtures/mastodon-post-activity.json")
151 |> Poison.decode!()
152
153 object =
154 data["object"]
155 |> Map.put("inReplyTo", "https://404.site/whatever")
156
157 data =
158 data
159 |> Map.put("object", object)
160
161 assert capture_log(fn ->
162 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
163 end) =~ "[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 inReplyToAtomUri 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 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1084
1085 object_with_reply =
1086 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1087
1088 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1089 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1090 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1091
1092 object_with_reply =
1093 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1094
1095 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1096 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1097 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1098
1099 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1100 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1101 assert modified_object["inReplyTo"] == []
1102 assert modified_object["inReplyToAtomUri"] == ""
1103 end
1104
1105 @tag capture_log: true
1106 test "returns modified object when allowed incoming reply", %{data: data} do
1107 object_with_reply =
1108 Map.put(
1109 data["object"],
1110 "inReplyTo",
1111 "https://shitposter.club/notice/2827873"
1112 )
1113
1114 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1115 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1116
1117 assert modified_object["inReplyTo"] ==
1118 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1119
1120 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1121
1122 assert modified_object["context"] ==
1123 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1124 end
1125 end
1126
1127 describe "fix_url/1" do
1128 test "fixes data for object when url is map" do
1129 object = %{
1130 "url" => %{
1131 "type" => "Link",
1132 "mimeType" => "video/mp4",
1133 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1134 }
1135 }
1136
1137 assert Transmogrifier.fix_url(object) == %{
1138 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1139 }
1140 end
1141
1142 test "fixes data for video object" do
1143 object = %{
1144 "type" => "Video",
1145 "url" => [
1146 %{
1147 "type" => "Link",
1148 "mimeType" => "video/mp4",
1149 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1150 },
1151 %{
1152 "type" => "Link",
1153 "mimeType" => "video/mp4",
1154 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1155 },
1156 %{
1157 "type" => "Link",
1158 "mimeType" => "text/html",
1159 "href" => "https://peertube.-2d4c2d1630e3"
1160 },
1161 %{
1162 "type" => "Link",
1163 "mimeType" => "text/html",
1164 "href" => "https://peertube.-2d4c2d16377-42"
1165 }
1166 ]
1167 }
1168
1169 assert Transmogrifier.fix_url(object) == %{
1170 "attachment" => [
1171 %{
1172 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1173 "mimeType" => "video/mp4",
1174 "type" => "Link"
1175 }
1176 ],
1177 "type" => "Video",
1178 "url" => "https://peertube.-2d4c2d1630e3"
1179 }
1180 end
1181
1182 test "fixes url for not Video object" do
1183 object = %{
1184 "type" => "Text",
1185 "url" => [
1186 %{
1187 "type" => "Link",
1188 "mimeType" => "text/html",
1189 "href" => "https://peertube.-2d4c2d1630e3"
1190 },
1191 %{
1192 "type" => "Link",
1193 "mimeType" => "text/html",
1194 "href" => "https://peertube.-2d4c2d16377-42"
1195 }
1196 ]
1197 }
1198
1199 assert Transmogrifier.fix_url(object) == %{
1200 "type" => "Text",
1201 "url" => "https://peertube.-2d4c2d1630e3"
1202 }
1203
1204 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
1205 "type" => "Text",
1206 "url" => ""
1207 }
1208 end
1209
1210 test "retunrs not modified object" do
1211 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
1212 end
1213 end
1214
1215 describe "get_obj_helper/2" do
1216 test "returns nil when cannot normalize object" do
1217 assert capture_log(fn ->
1218 refute Transmogrifier.get_obj_helper("test-obj-id")
1219 end) =~ "Unsupported URI scheme"
1220 end
1221
1222 @tag capture_log: true
1223 test "returns {:ok, %Object{}} for success case" do
1224 assert {:ok, %Object{}} =
1225 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
1226 end
1227 end
1228
1229 describe "fix_attachments/1" do
1230 test "returns not modified object" do
1231 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1232 assert Transmogrifier.fix_attachments(data) == data
1233 end
1234
1235 test "returns modified object when attachment is map" do
1236 assert Transmogrifier.fix_attachments(%{
1237 "attachment" => %{
1238 "mediaType" => "video/mp4",
1239 "url" => "https://peertube.moe/stat-480.mp4"
1240 }
1241 }) == %{
1242 "attachment" => [
1243 %{
1244 "mediaType" => "video/mp4",
1245 "type" => "Document",
1246 "url" => [
1247 %{
1248 "href" => "https://peertube.moe/stat-480.mp4",
1249 "mediaType" => "video/mp4",
1250 "type" => "Link"
1251 }
1252 ]
1253 }
1254 ]
1255 }
1256 end
1257
1258 test "returns modified object when attachment is list" do
1259 assert Transmogrifier.fix_attachments(%{
1260 "attachment" => [
1261 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1262 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1263 ]
1264 }) == %{
1265 "attachment" => [
1266 %{
1267 "mediaType" => "video/mp4",
1268 "type" => "Document",
1269 "url" => [
1270 %{
1271 "href" => "https://pe.er/stat-480.mp4",
1272 "mediaType" => "video/mp4",
1273 "type" => "Link"
1274 }
1275 ]
1276 },
1277 %{
1278 "mediaType" => "video/mp4",
1279 "type" => "Document",
1280 "url" => [
1281 %{
1282 "href" => "https://pe.er/stat-480.mp4",
1283 "mediaType" => "video/mp4",
1284 "type" => "Link"
1285 }
1286 ]
1287 }
1288 ]
1289 }
1290 end
1291 end
1292
1293 describe "fix_emoji/1" do
1294 test "returns not modified object when object not contains tags" do
1295 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1296 assert Transmogrifier.fix_emoji(data) == data
1297 end
1298
1299 test "returns object with emoji when object contains list tags" do
1300 assert Transmogrifier.fix_emoji(%{
1301 "tag" => [
1302 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1303 %{"type" => "Hashtag"}
1304 ]
1305 }) == %{
1306 "emoji" => %{"bib" => "/test"},
1307 "tag" => [
1308 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
1309 %{"type" => "Hashtag"}
1310 ]
1311 }
1312 end
1313
1314 test "returns object with emoji when object contains map tag" do
1315 assert Transmogrifier.fix_emoji(%{
1316 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
1317 }) == %{
1318 "emoji" => %{"bib" => "/test"},
1319 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
1320 }
1321 end
1322 end
1323
1324 describe "set_replies/1" do
1325 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
1326
1327 test "returns unmodified object if activity doesn't have self-replies" do
1328 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1329 assert Transmogrifier.set_replies(data) == data
1330 end
1331
1332 test "sets `replies` collection with a limited number of self-replies" do
1333 [user, another_user] = insert_list(2, :user)
1334
1335 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"})
1336
1337 {:ok, %{id: id2} = self_reply1} =
1338 CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1})
1339
1340 {:ok, self_reply2} =
1341 CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1})
1342
1343 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
1344 {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1})
1345
1346 {:ok, _} =
1347 CommonAPI.post(user, %{
1348 status: "self-reply to self-reply",
1349 in_reply_to_status_id: id2
1350 })
1351
1352 {:ok, _} =
1353 CommonAPI.post(another_user, %{
1354 status: "another user's reply",
1355 in_reply_to_status_id: id1
1356 })
1357
1358 object = Object.normalize(activity)
1359 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
1360
1361 assert %{"type" => "Collection", "items" => ^replies_uris} =
1362 Transmogrifier.set_replies(object.data)["replies"]
1363 end
1364 end
1365
1366 test "take_emoji_tags/1" do
1367 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
1368
1369 assert Transmogrifier.take_emoji_tags(user) == [
1370 %{
1371 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
1372 "id" => "https://example.org/firefox.png",
1373 "name" => ":firefox:",
1374 "type" => "Emoji",
1375 "updated" => "1970-01-01T00:00:00Z"
1376 }
1377 ]
1378 end
1379 end