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