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