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