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