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