Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.AdminAPI.AccountView
17 alias Pleroma.Web.CommonAPI
18
19 import Mock
20 import Pleroma.Factory
21 import ExUnit.CaptureLog
22
23 setup_all do
24 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
25 :ok
26 end
27
28 setup do: clear_config([:instance, :max_remote_account_fields])
29
30 describe "handle_incoming" do
31 test "it ignores an incoming notice if we already have it" do
32 activity = insert(:note_activity)
33
34 data =
35 File.read!("test/fixtures/mastodon-post-activity.json")
36 |> Poison.decode!()
37 |> Map.put("object", Object.normalize(activity).data)
38
39 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
40
41 assert activity == returned_activity
42 end
43
44 @tag capture_log: true
45 test "it fetches reply-to activities if we don't have them" do
46 data =
47 File.read!("test/fixtures/mastodon-post-activity.json")
48 |> Poison.decode!()
49
50 object =
51 data["object"]
52 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
53
54 data = Map.put(data, "object", object)
55 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
56 returned_object = Object.normalize(returned_activity, false)
57
58 assert activity =
59 Activity.get_create_by_object_ap_id(
60 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
61 )
62
63 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
64 end
65
66 test "it does not fetch reply-to activities beyond max replies depth limit" do
67 data =
68 File.read!("test/fixtures/mastodon-post-activity.json")
69 |> Poison.decode!()
70
71 object =
72 data["object"]
73 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
74
75 data = Map.put(data, "object", object)
76
77 with_mock Pleroma.Web.Federator,
78 allowed_thread_distance?: fn _ -> false end do
79 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
80
81 returned_object = Object.normalize(returned_activity, false)
82
83 refute Activity.get_create_by_object_ap_id(
84 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
85 )
86
87 assert returned_object.data["inReplyToAtomUri"] ==
88 "https://shitposter.club/notice/2827873"
89 end
90 end
91
92 test "it does not crash if the object in inReplyTo can't be fetched" do
93 data =
94 File.read!("test/fixtures/mastodon-post-activity.json")
95 |> Poison.decode!()
96
97 object =
98 data["object"]
99 |> Map.put("inReplyTo", "https://404.site/whatever")
100
101 data =
102 data
103 |> Map.put("object", object)
104
105 assert capture_log(fn ->
106 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
107 end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
108 end
109
110 test "it works for incoming notices" do
111 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
112
113 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
114
115 assert data["id"] ==
116 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
117
118 assert data["context"] ==
119 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
120
121 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
122
123 assert data["cc"] == [
124 "http://mastodon.example.org/users/admin/followers",
125 "http://localtesting.pleroma.lol/users/lain"
126 ]
127
128 assert data["actor"] == "http://mastodon.example.org/users/admin"
129
130 object_data = Object.normalize(data["object"]).data
131
132 assert object_data["id"] ==
133 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
134
135 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
136
137 assert object_data["cc"] == [
138 "http://mastodon.example.org/users/admin/followers",
139 "http://localtesting.pleroma.lol/users/lain"
140 ]
141
142 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
143 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
144
145 assert object_data["context"] ==
146 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
147
148 assert object_data["sensitive"] == true
149
150 user = User.get_cached_by_ap_id(object_data["actor"])
151
152 assert user.note_count == 1
153 end
154
155 test "it works for incoming notices with hashtags" do
156 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
157
158 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
159 object = Object.normalize(data["object"])
160
161 assert Enum.at(object.data["tag"], 2) == "moo"
162 end
163
164 test "it works for incoming questions" do
165 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
166
167 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
168
169 object = Object.normalize(activity)
170
171 assert Enum.all?(object.data["oneOf"], fn choice ->
172 choice["name"] in [
173 "Dunno",
174 "Everyone knows that!",
175 "25 char limit is dumb",
176 "I can't even fit a funny"
177 ]
178 end)
179 end
180
181 test "it works for incoming listens" do
182 data = %{
183 "@context" => "https://www.w3.org/ns/activitystreams",
184 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
185 "cc" => [],
186 "type" => "Listen",
187 "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
188 "actor" => "http://mastodon.example.org/users/admin",
189 "object" => %{
190 "type" => "Audio",
191 "id" => "http://mastodon.example.org/users/admin/listens/1234",
192 "attributedTo" => "http://mastodon.example.org/users/admin",
193 "title" => "lain radio episode 1",
194 "artist" => "lain",
195 "album" => "lain radio",
196 "length" => 180_000
197 }
198 }
199
200 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
201
202 object = Object.normalize(activity)
203
204 assert object.data["title"] == "lain radio episode 1"
205 assert object.data["artist"] == "lain"
206 assert object.data["album"] == "lain radio"
207 assert object.data["length"] == 180_000
208 end
209
210 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
211 user = insert(:user)
212
213 {:ok, activity} =
214 CommonAPI.post(user, %{
215 "status" => "suya...",
216 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
217 })
218
219 object = Object.normalize(activity)
220
221 data =
222 File.read!("test/fixtures/mastodon-vote.json")
223 |> Poison.decode!()
224 |> Kernel.put_in(["to"], user.ap_id)
225 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
226 |> Kernel.put_in(["object", "to"], user.ap_id)
227
228 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
229 answer_object = Object.normalize(activity)
230 assert answer_object.data["type"] == "Answer"
231 object = Object.get_by_ap_id(object.data["id"])
232
233 assert Enum.any?(
234 object.data["oneOf"],
235 fn
236 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
237 _ -> false
238 end
239 )
240 end
241
242 test "it works for incoming notices with contentMap" do
243 data =
244 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
245
246 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
247 object = Object.normalize(data["object"])
248
249 assert object.data["content"] ==
250 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
251 end
252
253 test "it works for incoming notices with to/cc not being an array (kroeg)" do
254 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
255
256 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
257 object = Object.normalize(data["object"])
258
259 assert object.data["content"] ==
260 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
261 end
262
263 test "it works for incoming announces with actor being inlined (kroeg)" do
264 data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
265
266 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
267
268 assert data["actor"] == "https://puckipedia.com/"
269 end
270
271 test "it works for incoming notices with tag not being an array (kroeg)" do
272 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
273
274 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
275 object = Object.normalize(data["object"])
276
277 assert object.data["emoji"] == %{
278 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
279 }
280
281 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
282
283 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
284 object = Object.normalize(data["object"])
285
286 assert "test" in object.data["tag"]
287 end
288
289 test "it works for incoming notices with url not being a string (prismo)" do
290 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
291
292 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
293 object = Object.normalize(data["object"])
294
295 assert object.data["url"] == "https://prismo.news/posts/83"
296 end
297
298 test "it cleans up incoming notices which are not really DMs" do
299 user = insert(:user)
300 other_user = insert(:user)
301
302 to = [user.ap_id, other_user.ap_id]
303
304 data =
305 File.read!("test/fixtures/mastodon-post-activity.json")
306 |> Poison.decode!()
307 |> Map.put("to", to)
308 |> Map.put("cc", [])
309
310 object =
311 data["object"]
312 |> Map.put("to", to)
313 |> Map.put("cc", [])
314
315 data = Map.put(data, "object", object)
316
317 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
318
319 assert data["to"] == []
320 assert data["cc"] == to
321
322 object_data = Object.normalize(activity).data
323
324 assert object_data["to"] == []
325 assert object_data["cc"] == to
326 end
327
328 test "it works for incoming emoji reactions" do
329 user = insert(:user)
330 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
331
332 data =
333 File.read!("test/fixtures/emoji-reaction.json")
334 |> Poison.decode!()
335 |> Map.put("object", activity.data["object"])
336
337 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
338
339 assert data["actor"] == "http://mastodon.example.org/users/admin"
340 assert data["type"] == "EmojiReact"
341 assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
342 assert data["object"] == activity.data["object"]
343 assert data["content"] == "👌"
344 end
345
346 test "it reject invalid emoji reactions" do
347 user = insert(:user)
348 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
349
350 data =
351 File.read!("test/fixtures/emoji-reaction-too-long.json")
352 |> Poison.decode!()
353 |> Map.put("object", activity.data["object"])
354
355 assert :error = Transmogrifier.handle_incoming(data)
356
357 data =
358 File.read!("test/fixtures/emoji-reaction-no-emoji.json")
359 |> Poison.decode!()
360 |> Map.put("object", activity.data["object"])
361
362 assert :error = Transmogrifier.handle_incoming(data)
363 end
364
365 test "it works for incoming emoji reaction undos" do
366 user = insert(:user)
367
368 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
369 {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌")
370
371 data =
372 File.read!("test/fixtures/mastodon-undo-like.json")
373 |> Poison.decode!()
374 |> Map.put("object", reaction_activity.data["id"])
375 |> Map.put("actor", user.ap_id)
376
377 {:ok, activity} = Transmogrifier.handle_incoming(data)
378
379 assert activity.actor == user.ap_id
380 assert activity.data["id"] == data["id"]
381 assert activity.data["type"] == "Undo"
382 end
383
384 test "it returns an error for incoming unlikes wihout a like activity" do
385 user = insert(:user)
386 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
387
388 data =
389 File.read!("test/fixtures/mastodon-undo-like.json")
390 |> Poison.decode!()
391 |> Map.put("object", activity.data["object"])
392
393 assert Transmogrifier.handle_incoming(data) == :error
394 end
395
396 test "it works for incoming unlikes with an existing like activity" do
397 user = insert(:user)
398 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
399
400 like_data =
401 File.read!("test/fixtures/mastodon-like.json")
402 |> Poison.decode!()
403 |> Map.put("object", activity.data["object"])
404
405 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
406
407 data =
408 File.read!("test/fixtures/mastodon-undo-like.json")
409 |> Poison.decode!()
410 |> Map.put("object", like_data)
411 |> Map.put("actor", like_data["actor"])
412
413 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
414
415 assert data["actor"] == "http://mastodon.example.org/users/admin"
416 assert data["type"] == "Undo"
417 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
418 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
419 end
420
421 test "it works for incoming unlikes with an existing like activity and a compact object" do
422 user = insert(:user)
423 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
424
425 like_data =
426 File.read!("test/fixtures/mastodon-like.json")
427 |> Poison.decode!()
428 |> Map.put("object", activity.data["object"])
429
430 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
431
432 data =
433 File.read!("test/fixtures/mastodon-undo-like.json")
434 |> Poison.decode!()
435 |> Map.put("object", like_data["id"])
436 |> Map.put("actor", like_data["actor"])
437
438 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
439
440 assert data["actor"] == "http://mastodon.example.org/users/admin"
441 assert data["type"] == "Undo"
442 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
443 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
444 end
445
446 test "it works for incoming announces" do
447 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
448
449 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
450
451 assert data["actor"] == "http://mastodon.example.org/users/admin"
452 assert data["type"] == "Announce"
453
454 assert data["id"] ==
455 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
456
457 assert data["object"] ==
458 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
459
460 assert Activity.get_create_by_object_ap_id(data["object"])
461 end
462
463 test "it works for incoming announces with an existing activity" do
464 user = insert(:user)
465 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
466
467 data =
468 File.read!("test/fixtures/mastodon-announce.json")
469 |> Poison.decode!()
470 |> Map.put("object", activity.data["object"])
471
472 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
473
474 assert data["actor"] == "http://mastodon.example.org/users/admin"
475 assert data["type"] == "Announce"
476
477 assert data["id"] ==
478 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
479
480 assert data["object"] == activity.data["object"]
481
482 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
483 end
484
485 test "it works for incoming announces with an inlined activity" do
486 data =
487 File.read!("test/fixtures/mastodon-announce-private.json")
488 |> Poison.decode!()
489
490 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
491
492 assert data["actor"] == "http://mastodon.example.org/users/admin"
493 assert data["type"] == "Announce"
494
495 assert data["id"] ==
496 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
497
498 object = Object.normalize(data["object"])
499
500 assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368"
501 assert object.data["content"] == "this is a private toot"
502 end
503
504 @tag capture_log: true
505 test "it rejects incoming announces with an inlined activity from another origin" do
506 data =
507 File.read!("test/fixtures/bogus-mastodon-announce.json")
508 |> Poison.decode!()
509
510 assert :error = Transmogrifier.handle_incoming(data)
511 end
512
513 test "it does not clobber the addressing on announce activities" do
514 user = insert(:user)
515 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
516
517 data =
518 File.read!("test/fixtures/mastodon-announce.json")
519 |> Poison.decode!()
520 |> Map.put("object", Object.normalize(activity).data["id"])
521 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
522 |> Map.put("cc", [])
523
524 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
525
526 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
527 end
528
529 test "it ensures that as:Public activities make it to their followers collection" do
530 user = insert(:user)
531
532 data =
533 File.read!("test/fixtures/mastodon-post-activity.json")
534 |> Poison.decode!()
535 |> Map.put("actor", user.ap_id)
536 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
537 |> Map.put("cc", [])
538
539 object =
540 data["object"]
541 |> Map.put("attributedTo", user.ap_id)
542 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
543 |> Map.put("cc", [])
544 |> Map.put("id", user.ap_id <> "/activities/12345678")
545
546 data = Map.put(data, "object", object)
547
548 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
549
550 assert data["cc"] == [User.ap_followers(user)]
551 end
552
553 test "it ensures that address fields become lists" do
554 user = insert(:user)
555
556 data =
557 File.read!("test/fixtures/mastodon-post-activity.json")
558 |> Poison.decode!()
559 |> Map.put("actor", user.ap_id)
560 |> Map.put("to", nil)
561 |> Map.put("cc", nil)
562
563 object =
564 data["object"]
565 |> Map.put("attributedTo", user.ap_id)
566 |> Map.put("to", nil)
567 |> Map.put("cc", nil)
568 |> Map.put("id", user.ap_id <> "/activities/12345678")
569
570 data = Map.put(data, "object", object)
571
572 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
573
574 assert !is_nil(data["to"])
575 assert !is_nil(data["cc"])
576 end
577
578 test "it strips internal likes" do
579 data =
580 File.read!("test/fixtures/mastodon-post-activity.json")
581 |> Poison.decode!()
582
583 likes = %{
584 "first" =>
585 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
586 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
587 "totalItems" => 3,
588 "type" => "OrderedCollection"
589 }
590
591 object = Map.put(data["object"], "likes", likes)
592 data = Map.put(data, "object", object)
593
594 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
595
596 refute Map.has_key?(object.data, "likes")
597 end
598
599 test "it strips internal reactions" do
600 user = insert(:user)
601 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
602 {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
603
604 %{object: object} = Activity.get_by_id_with_object(activity.id)
605 assert Map.has_key?(object.data, "reactions")
606 assert Map.has_key?(object.data, "reaction_count")
607
608 object_data = Transmogrifier.strip_internal_fields(object.data)
609 refute Map.has_key?(object_data, "reactions")
610 refute Map.has_key?(object_data, "reaction_count")
611 end
612
613 test "it works for incoming update activities" do
614 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
615
616 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
617 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
618
619 object =
620 update_data["object"]
621 |> Map.put("actor", data["actor"])
622 |> Map.put("id", data["actor"])
623
624 update_data =
625 update_data
626 |> Map.put("actor", data["actor"])
627 |> Map.put("object", object)
628
629 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
630
631 assert data["id"] == update_data["id"]
632
633 user = User.get_cached_by_ap_id(data["actor"])
634 assert user.name == "gargle"
635
636 assert user.avatar["url"] == [
637 %{
638 "href" =>
639 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
640 }
641 ]
642
643 assert user.banner["url"] == [
644 %{
645 "href" =>
646 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
647 }
648 ]
649
650 assert user.bio == "<p>Some bio</p>"
651 end
652
653 test "it works with alsoKnownAs" do
654 {:ok, %Activity{data: %{"actor" => actor}}} =
655 "test/fixtures/mastodon-post-activity.json"
656 |> File.read!()
657 |> Poison.decode!()
658 |> Transmogrifier.handle_incoming()
659
660 assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
661
662 {:ok, _activity} =
663 "test/fixtures/mastodon-update.json"
664 |> File.read!()
665 |> Poison.decode!()
666 |> Map.put("actor", actor)
667 |> Map.update!("object", fn object ->
668 object
669 |> Map.put("actor", actor)
670 |> Map.put("id", actor)
671 |> Map.put("alsoKnownAs", [
672 "http://mastodon.example.org/users/foo",
673 "http://example.org/users/bar"
674 ])
675 end)
676 |> Transmogrifier.handle_incoming()
677
678 assert User.get_cached_by_ap_id(actor).also_known_as == [
679 "http://mastodon.example.org/users/foo",
680 "http://example.org/users/bar"
681 ]
682 end
683
684 test "it works with custom profile fields" do
685 {:ok, activity} =
686 "test/fixtures/mastodon-post-activity.json"
687 |> File.read!()
688 |> Poison.decode!()
689 |> Transmogrifier.handle_incoming()
690
691 user = User.get_cached_by_ap_id(activity.actor)
692
693 assert user.fields == [
694 %{"name" => "foo", "value" => "bar"},
695 %{"name" => "foo1", "value" => "bar1"}
696 ]
697
698 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
699
700 object =
701 update_data["object"]
702 |> Map.put("actor", user.ap_id)
703 |> Map.put("id", user.ap_id)
704
705 update_data =
706 update_data
707 |> Map.put("actor", user.ap_id)
708 |> Map.put("object", object)
709
710 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
711
712 user = User.get_cached_by_ap_id(user.ap_id)
713
714 assert user.fields == [
715 %{"name" => "foo", "value" => "updated"},
716 %{"name" => "foo1", "value" => "updated"}
717 ]
718
719 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
720
721 update_data =
722 put_in(update_data, ["object", "attachment"], [
723 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
724 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
725 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
726 ])
727
728 {:ok, _} = Transmogrifier.handle_incoming(update_data)
729
730 user = User.get_cached_by_ap_id(user.ap_id)
731
732 assert user.fields == [
733 %{"name" => "foo", "value" => "updated"},
734 %{"name" => "foo1", "value" => "updated"}
735 ]
736
737 update_data = put_in(update_data, ["object", "attachment"], [])
738
739 {:ok, _} = Transmogrifier.handle_incoming(update_data)
740
741 user = User.get_cached_by_ap_id(user.ap_id)
742
743 assert user.fields == []
744 end
745
746 test "it works for incoming update activities which lock the account" do
747 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
748
749 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
750 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
751
752 object =
753 update_data["object"]
754 |> Map.put("actor", data["actor"])
755 |> Map.put("id", data["actor"])
756 |> Map.put("manuallyApprovesFollowers", true)
757
758 update_data =
759 update_data
760 |> Map.put("actor", data["actor"])
761 |> Map.put("object", object)
762
763 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
764
765 user = User.get_cached_by_ap_id(data["actor"])
766 assert user.locked == true
767 end
768
769 test "it works for incoming unannounces with an existing notice" do
770 user = insert(:user)
771 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
772
773 announce_data =
774 File.read!("test/fixtures/mastodon-announce.json")
775 |> Poison.decode!()
776 |> Map.put("object", activity.data["object"])
777
778 {:ok, %Activity{data: announce_data, local: false}} =
779 Transmogrifier.handle_incoming(announce_data)
780
781 data =
782 File.read!("test/fixtures/mastodon-undo-announce.json")
783 |> Poison.decode!()
784 |> Map.put("object", announce_data)
785 |> Map.put("actor", announce_data["actor"])
786
787 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
788
789 assert data["type"] == "Undo"
790 assert object_data = data["object"]
791 assert object_data["type"] == "Announce"
792 assert object_data["object"] == activity.data["object"]
793
794 assert object_data["id"] ==
795 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
796 end
797
798 test "it works for incomming unfollows with an existing follow" do
799 user = insert(:user)
800
801 follow_data =
802 File.read!("test/fixtures/mastodon-follow-activity.json")
803 |> Poison.decode!()
804 |> Map.put("object", user.ap_id)
805
806 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
807
808 data =
809 File.read!("test/fixtures/mastodon-unfollow-activity.json")
810 |> Poison.decode!()
811 |> Map.put("object", follow_data)
812
813 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
814
815 assert data["type"] == "Undo"
816 assert data["object"]["type"] == "Follow"
817 assert data["object"]["object"] == user.ap_id
818 assert data["actor"] == "http://mastodon.example.org/users/admin"
819
820 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
821 end
822
823 test "it works for incoming follows to locked account" do
824 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
825 user = insert(:user, locked: true)
826
827 data =
828 File.read!("test/fixtures/mastodon-follow-activity.json")
829 |> Poison.decode!()
830 |> Map.put("object", user.ap_id)
831
832 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
833
834 assert data["type"] == "Follow"
835 assert data["object"] == user.ap_id
836 assert data["state"] == "pending"
837 assert data["actor"] == "http://mastodon.example.org/users/admin"
838
839 assert [^pending_follower] = User.get_follow_requests(user)
840 end
841
842 test "it works for incoming blocks" do
843 user = insert(:user)
844
845 data =
846 File.read!("test/fixtures/mastodon-block-activity.json")
847 |> Poison.decode!()
848 |> Map.put("object", user.ap_id)
849
850 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
851
852 assert data["type"] == "Block"
853 assert data["object"] == user.ap_id
854 assert data["actor"] == "http://mastodon.example.org/users/admin"
855
856 blocker = User.get_cached_by_ap_id(data["actor"])
857
858 assert User.blocks?(blocker, user)
859 end
860
861 test "incoming blocks successfully tear down any follow relationship" do
862 blocker = insert(:user)
863 blocked = insert(:user)
864
865 data =
866 File.read!("test/fixtures/mastodon-block-activity.json")
867 |> Poison.decode!()
868 |> Map.put("object", blocked.ap_id)
869 |> Map.put("actor", blocker.ap_id)
870
871 {:ok, blocker} = User.follow(blocker, blocked)
872 {:ok, blocked} = User.follow(blocked, blocker)
873
874 assert User.following?(blocker, blocked)
875 assert User.following?(blocked, blocker)
876
877 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
878
879 assert data["type"] == "Block"
880 assert data["object"] == blocked.ap_id
881 assert data["actor"] == blocker.ap_id
882
883 blocker = User.get_cached_by_ap_id(data["actor"])
884 blocked = User.get_cached_by_ap_id(data["object"])
885
886 assert User.blocks?(blocker, blocked)
887
888 refute User.following?(blocker, blocked)
889 refute User.following?(blocked, blocker)
890 end
891
892 test "it works for incoming unblocks with an existing block" do
893 user = insert(:user)
894
895 block_data =
896 File.read!("test/fixtures/mastodon-block-activity.json")
897 |> Poison.decode!()
898 |> Map.put("object", user.ap_id)
899
900 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
901
902 data =
903 File.read!("test/fixtures/mastodon-unblock-activity.json")
904 |> Poison.decode!()
905 |> Map.put("object", block_data)
906
907 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
908 assert data["type"] == "Undo"
909 assert data["object"]["type"] == "Block"
910 assert data["object"]["object"] == user.ap_id
911 assert data["actor"] == "http://mastodon.example.org/users/admin"
912
913 blocker = User.get_cached_by_ap_id(data["actor"])
914
915 refute User.blocks?(blocker, user)
916 end
917
918 test "it works for incoming accepts which were pre-accepted" do
919 follower = insert(:user)
920 followed = insert(:user)
921
922 {:ok, follower} = User.follow(follower, followed)
923 assert User.following?(follower, followed) == true
924
925 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
926
927 accept_data =
928 File.read!("test/fixtures/mastodon-accept-activity.json")
929 |> Poison.decode!()
930 |> Map.put("actor", followed.ap_id)
931
932 object =
933 accept_data["object"]
934 |> Map.put("actor", follower.ap_id)
935 |> Map.put("id", follow_activity.data["id"])
936
937 accept_data = Map.put(accept_data, "object", object)
938
939 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
940 refute activity.local
941
942 assert activity.data["object"] == follow_activity.data["id"]
943
944 assert activity.data["id"] == accept_data["id"]
945
946 follower = User.get_cached_by_id(follower.id)
947
948 assert User.following?(follower, followed) == true
949 end
950
951 test "it works for incoming accepts which were orphaned" do
952 follower = insert(:user)
953 followed = insert(:user, locked: true)
954
955 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
956
957 accept_data =
958 File.read!("test/fixtures/mastodon-accept-activity.json")
959 |> Poison.decode!()
960 |> Map.put("actor", followed.ap_id)
961
962 accept_data =
963 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
964
965 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
966 assert activity.data["object"] == follow_activity.data["id"]
967
968 follower = User.get_cached_by_id(follower.id)
969
970 assert User.following?(follower, followed) == true
971 end
972
973 test "it works for incoming accepts which are referenced by IRI only" do
974 follower = insert(:user)
975 followed = insert(:user, locked: true)
976
977 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
978
979 accept_data =
980 File.read!("test/fixtures/mastodon-accept-activity.json")
981 |> Poison.decode!()
982 |> Map.put("actor", followed.ap_id)
983 |> Map.put("object", follow_activity.data["id"])
984
985 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
986 assert activity.data["object"] == follow_activity.data["id"]
987
988 follower = User.get_cached_by_id(follower.id)
989
990 assert User.following?(follower, followed) == true
991 end
992
993 test "it fails for incoming accepts which cannot be correlated" do
994 follower = insert(:user)
995 followed = insert(:user, locked: true)
996
997 accept_data =
998 File.read!("test/fixtures/mastodon-accept-activity.json")
999 |> Poison.decode!()
1000 |> Map.put("actor", followed.ap_id)
1001
1002 accept_data =
1003 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1004
1005 :error = Transmogrifier.handle_incoming(accept_data)
1006
1007 follower = User.get_cached_by_id(follower.id)
1008
1009 refute User.following?(follower, followed) == true
1010 end
1011
1012 test "it fails for incoming rejects which cannot be correlated" do
1013 follower = insert(:user)
1014 followed = insert(:user, locked: true)
1015
1016 accept_data =
1017 File.read!("test/fixtures/mastodon-reject-activity.json")
1018 |> Poison.decode!()
1019 |> Map.put("actor", followed.ap_id)
1020
1021 accept_data =
1022 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1023
1024 :error = Transmogrifier.handle_incoming(accept_data)
1025
1026 follower = User.get_cached_by_id(follower.id)
1027
1028 refute User.following?(follower, followed) == true
1029 end
1030
1031 test "it works for incoming rejects which are orphaned" do
1032 follower = insert(:user)
1033 followed = insert(:user, locked: true)
1034
1035 {:ok, follower} = User.follow(follower, followed)
1036 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
1037
1038 assert User.following?(follower, followed) == true
1039
1040 reject_data =
1041 File.read!("test/fixtures/mastodon-reject-activity.json")
1042 |> Poison.decode!()
1043 |> Map.put("actor", followed.ap_id)
1044
1045 reject_data =
1046 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
1047
1048 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
1049 refute activity.local
1050 assert activity.data["id"] == reject_data["id"]
1051
1052 follower = User.get_cached_by_id(follower.id)
1053
1054 assert User.following?(follower, followed) == false
1055 end
1056
1057 test "it works for incoming rejects which are referenced by IRI only" do
1058 follower = insert(:user)
1059 followed = insert(:user, locked: true)
1060
1061 {:ok, follower} = User.follow(follower, followed)
1062 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1063
1064 assert User.following?(follower, followed) == true
1065
1066 reject_data =
1067 File.read!("test/fixtures/mastodon-reject-activity.json")
1068 |> Poison.decode!()
1069 |> Map.put("actor", followed.ap_id)
1070 |> Map.put("object", follow_activity.data["id"])
1071
1072 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
1073
1074 follower = User.get_cached_by_id(follower.id)
1075
1076 assert User.following?(follower, followed) == false
1077 end
1078
1079 test "it rejects activities without a valid ID" do
1080 user = insert(:user)
1081
1082 data =
1083 File.read!("test/fixtures/mastodon-follow-activity.json")
1084 |> Poison.decode!()
1085 |> Map.put("object", user.ap_id)
1086 |> Map.put("id", "")
1087
1088 :error = Transmogrifier.handle_incoming(data)
1089 end
1090
1091 test "skip converting the content when it is nil" do
1092 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
1093
1094 {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
1095
1096 result =
1097 Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
1098
1099 assert result["content"] == nil
1100 end
1101
1102 test "it converts content of object to html" do
1103 object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
1104
1105 {:ok, %{"content" => content_markdown}} =
1106 Fetcher.fetch_and_contain_remote_object_from_id(object_id)
1107
1108 {:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
1109 Fetcher.fetch_object_from_id(object_id)
1110
1111 assert content_markdown ==
1112 "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
1113
1114 assert content ==
1115 "<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
1116
1117 assert object.data["mediaType"] == "text/html"
1118 end
1119
1120 test "it remaps video URLs as attachments if necessary" do
1121 {:ok, object} =
1122 Fetcher.fetch_object_from_id(
1123 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1124 )
1125
1126 attachment = %{
1127 "type" => "Link",
1128 "mediaType" => "video/mp4",
1129 "url" => [
1130 %{
1131 "href" =>
1132 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1133 "mediaType" => "video/mp4"
1134 }
1135 ]
1136 }
1137
1138 assert object.data["url"] ==
1139 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1140
1141 assert object.data["attachment"] == [attachment]
1142 end
1143
1144 test "it accepts Flag activities" do
1145 user = insert(:user)
1146 other_user = insert(:user)
1147
1148 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1149 object = Object.normalize(activity)
1150
1151 note_obj = %{
1152 "type" => "Note",
1153 "id" => activity.data["id"],
1154 "content" => "test post",
1155 "published" => object.data["published"],
1156 "actor" => AccountView.render("show.json", %{user: user})
1157 }
1158
1159 message = %{
1160 "@context" => "https://www.w3.org/ns/activitystreams",
1161 "cc" => [user.ap_id],
1162 "object" => [user.ap_id, activity.data["id"]],
1163 "type" => "Flag",
1164 "content" => "blocked AND reported!!!",
1165 "actor" => other_user.ap_id
1166 }
1167
1168 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1169
1170 assert activity.data["object"] == [user.ap_id, note_obj]
1171 assert activity.data["content"] == "blocked AND reported!!!"
1172 assert activity.data["actor"] == other_user.ap_id
1173 assert activity.data["cc"] == [user.ap_id]
1174 end
1175
1176 test "it correctly processes messages with non-array to field" do
1177 user = insert(:user)
1178
1179 message = %{
1180 "@context" => "https://www.w3.org/ns/activitystreams",
1181 "to" => "https://www.w3.org/ns/activitystreams#Public",
1182 "type" => "Create",
1183 "object" => %{
1184 "content" => "blah blah blah",
1185 "type" => "Note",
1186 "attributedTo" => user.ap_id,
1187 "inReplyTo" => nil
1188 },
1189 "actor" => user.ap_id
1190 }
1191
1192 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1193
1194 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1195 end
1196
1197 test "it correctly processes messages with non-array cc field" do
1198 user = insert(:user)
1199
1200 message = %{
1201 "@context" => "https://www.w3.org/ns/activitystreams",
1202 "to" => user.follower_address,
1203 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1204 "type" => "Create",
1205 "object" => %{
1206 "content" => "blah blah blah",
1207 "type" => "Note",
1208 "attributedTo" => user.ap_id,
1209 "inReplyTo" => nil
1210 },
1211 "actor" => user.ap_id
1212 }
1213
1214 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1215
1216 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1217 assert [user.follower_address] == activity.data["to"]
1218 end
1219
1220 test "it accepts Move activities" do
1221 old_user = insert(:user)
1222 new_user = insert(:user)
1223
1224 message = %{
1225 "@context" => "https://www.w3.org/ns/activitystreams",
1226 "type" => "Move",
1227 "actor" => old_user.ap_id,
1228 "object" => old_user.ap_id,
1229 "target" => new_user.ap_id
1230 }
1231
1232 assert :error = Transmogrifier.handle_incoming(message)
1233
1234 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1235
1236 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1237 assert activity.actor == old_user.ap_id
1238 assert activity.data["actor"] == old_user.ap_id
1239 assert activity.data["object"] == old_user.ap_id
1240 assert activity.data["target"] == new_user.ap_id
1241 assert activity.data["type"] == "Move"
1242 end
1243 end
1244
1245 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1246 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1247 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1248
1249 setup do
1250 data =
1251 "test/fixtures/mastodon-post-activity.json"
1252 |> File.read!()
1253 |> Poison.decode!()
1254
1255 items = get_in(data, ["object", "replies", "first", "items"])
1256 assert length(items) > 0
1257
1258 %{data: data, items: items}
1259 end
1260
1261 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1262 data: data,
1263 items: items
1264 } do
1265 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1266
1267 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1268
1269 for id <- items do
1270 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1271 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1272 end
1273 end
1274
1275 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1276 %{data: data} do
1277 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1278
1279 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1280
1281 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1282 end
1283 end
1284
1285 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1286 setup do: clear_config([:activitypub, :note_replies_output_limit], 5)
1287 setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
1288
1289 setup do
1290 user = insert(:user)
1291
1292 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1293
1294 {:ok, reply1} =
1295 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1296
1297 {:ok, reply2} =
1298 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1299
1300 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1301
1302 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1303
1304 Repo.delete(activity.object)
1305 Repo.delete(activity)
1306
1307 %{federation_output: federation_output, replies_uris: replies_uris}
1308 end
1309
1310 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1311 federation_output: federation_output,
1312 replies_uris: replies_uris
1313 } do
1314 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1315
1316 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1317
1318 for id <- replies_uris do
1319 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1320 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1321 end
1322 end
1323
1324 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1325 %{federation_output: federation_output} do
1326 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1327
1328 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1329
1330 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
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, :follow_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 setup do: 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 %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
1957 ]
1958 }
1959 ]
1960 }
1961 end
1962
1963 test "returns modified object when attachment is list" do
1964 assert Transmogrifier.fix_attachments(%{
1965 "attachment" => [
1966 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
1967 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
1968 ]
1969 }) == %{
1970 "attachment" => [
1971 %{
1972 "mediaType" => "video/mp4",
1973 "url" => [
1974 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1975 ]
1976 },
1977 %{
1978 "mediaType" => "video/mp4",
1979 "url" => [
1980 %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
1981 ]
1982 }
1983 ]
1984 }
1985 end
1986 end
1987
1988 describe "fix_emoji/1" do
1989 test "returns not modified object when object not contains tags" do
1990 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1991 assert Transmogrifier.fix_emoji(data) == data
1992 end
1993
1994 test "returns object with emoji when object contains list tags" do
1995 assert Transmogrifier.fix_emoji(%{
1996 "tag" => [
1997 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
1998 %{"type" => "Hashtag"}
1999 ]
2000 }) == %{
2001 "emoji" => %{"bib" => "/test"},
2002 "tag" => [
2003 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
2004 %{"type" => "Hashtag"}
2005 ]
2006 }
2007 end
2008
2009 test "returns object with emoji when object contains map tag" do
2010 assert Transmogrifier.fix_emoji(%{
2011 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
2012 }) == %{
2013 "emoji" => %{"bib" => "/test"},
2014 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
2015 }
2016 end
2017 end
2018
2019 describe "set_replies/1" do
2020 setup do: clear_config([:activitypub, :note_replies_output_limit], 2)
2021
2022 test "returns unmodified object if activity doesn't have self-replies" do
2023 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2024 assert Transmogrifier.set_replies(data) == data
2025 end
2026
2027 test "sets `replies` collection with a limited number of self-replies" do
2028 [user, another_user] = insert_list(2, :user)
2029
2030 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
2031
2032 {:ok, %{id: id2} = self_reply1} =
2033 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
2034
2035 {:ok, self_reply2} =
2036 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
2037
2038 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
2039 {:ok, _} =
2040 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
2041
2042 {:ok, _} =
2043 CommonAPI.post(user, %{
2044 "status" => "self-reply to self-reply",
2045 "in_reply_to_status_id" => id2
2046 })
2047
2048 {:ok, _} =
2049 CommonAPI.post(another_user, %{
2050 "status" => "another user's reply",
2051 "in_reply_to_status_id" => id1
2052 })
2053
2054 object = Object.normalize(activity)
2055 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
2056
2057 assert %{"type" => "Collection", "items" => ^replies_uris} =
2058 Transmogrifier.set_replies(object.data)["replies"]
2059 end
2060 end
2061
2062 test "take_emoji_tags/1" do
2063 user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}})
2064
2065 assert Transmogrifier.take_emoji_tags(user) == [
2066 %{
2067 "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
2068 "id" => "https://example.org/firefox.png",
2069 "name" => ":firefox:",
2070 "type" => "Emoji",
2071 "updated" => "1970-01-01T00:00:00Z"
2072 }
2073 ]
2074 end
2075 end