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