Merge branch 'stable' into release/2.0.0
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Object
11 alias Pleroma.Object.Fetcher
12 alias Pleroma.Tests.ObanHelpers
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.AdminAPI.AccountView
17 alias Pleroma.Web.CommonAPI
18
19 import Mock
20 import Pleroma.Factory
21 import ExUnit.CaptureLog
22
23 setup_all do
24 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
25 :ok
26 end
27
28 clear_config([:instance, :max_remote_account_fields])
29
30 describe "handle_incoming" do
31 test "it ignores an incoming notice if we already have it" do
32 activity = insert(:note_activity)
33
34 data =
35 File.read!("test/fixtures/mastodon-post-activity.json")
36 |> Poison.decode!()
37 |> Map.put("object", Object.normalize(activity).data)
38
39 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
40
41 assert activity == returned_activity
42 end
43
44 @tag capture_log: true
45 test "it fetches reply-to activities if we don't have them" do
46 data =
47 File.read!("test/fixtures/mastodon-post-activity.json")
48 |> Poison.decode!()
49
50 object =
51 data["object"]
52 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
53
54 data = Map.put(data, "object", object)
55 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
56 returned_object = Object.normalize(returned_activity, false)
57
58 assert activity =
59 Activity.get_create_by_object_ap_id(
60 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
61 )
62
63 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
64 end
65
66 test "it does not fetch reply-to activities beyond max replies depth limit" do
67 data =
68 File.read!("test/fixtures/mastodon-post-activity.json")
69 |> Poison.decode!()
70
71 object =
72 data["object"]
73 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
74
75 data = Map.put(data, "object", object)
76
77 with_mock Pleroma.Web.Federator,
78 allowed_thread_distance?: fn _ -> false end do
79 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
80
81 returned_object = Object.normalize(returned_activity, false)
82
83 refute Activity.get_create_by_object_ap_id(
84 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
85 )
86
87 assert returned_object.data["inReplyToAtomUri"] ==
88 "https://shitposter.club/notice/2827873"
89 end
90 end
91
92 test "it does not crash if the object in inReplyTo can't be fetched" do
93 data =
94 File.read!("test/fixtures/mastodon-post-activity.json")
95 |> Poison.decode!()
96
97 object =
98 data["object"]
99 |> Map.put("inReplyTo", "https://404.site/whatever")
100
101 data =
102 data
103 |> Map.put("object", object)
104
105 assert capture_log(fn ->
106 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
107 end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
108 end
109
110 test "it works for incoming notices" do
111 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
112
113 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
114
115 assert data["id"] ==
116 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
117
118 assert data["context"] ==
119 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
120
121 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
122
123 assert data["cc"] == [
124 "http://mastodon.example.org/users/admin/followers",
125 "http://localtesting.pleroma.lol/users/lain"
126 ]
127
128 assert data["actor"] == "http://mastodon.example.org/users/admin"
129
130 object_data = Object.normalize(data["object"]).data
131
132 assert object_data["id"] ==
133 "http://mastodon.example.org/users/admin/statuses/99512778738411822"
134
135 assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
136
137 assert object_data["cc"] == [
138 "http://mastodon.example.org/users/admin/followers",
139 "http://localtesting.pleroma.lol/users/lain"
140 ]
141
142 assert object_data["actor"] == "http://mastodon.example.org/users/admin"
143 assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
144
145 assert object_data["context"] ==
146 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
147
148 assert object_data["sensitive"] == true
149
150 user = User.get_cached_by_ap_id(object_data["actor"])
151
152 assert user.note_count == 1
153 end
154
155 test "it works for incoming notices with hashtags" do
156 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
157
158 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
159 object = Object.normalize(data["object"])
160
161 assert Enum.at(object.data["tag"], 2) == "moo"
162 end
163
164 test "it works for incoming questions" do
165 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
166
167 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
168
169 object = Object.normalize(activity)
170
171 assert Enum.all?(object.data["oneOf"], fn choice ->
172 choice["name"] in [
173 "Dunno",
174 "Everyone knows that!",
175 "25 char limit is dumb",
176 "I can't even fit a funny"
177 ]
178 end)
179 end
180
181 test "it works for incoming listens" do
182 data = %{
183 "@context" => "https://www.w3.org/ns/activitystreams",
184 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
185 "cc" => [],
186 "type" => "Listen",
187 "id" => "http://mastodon.example.org/users/admin/listens/1234/activity",
188 "actor" => "http://mastodon.example.org/users/admin",
189 "object" => %{
190 "type" => "Audio",
191 "id" => "http://mastodon.example.org/users/admin/listens/1234",
192 "attributedTo" => "http://mastodon.example.org/users/admin",
193 "title" => "lain radio episode 1",
194 "artist" => "lain",
195 "album" => "lain radio",
196 "length" => 180_000
197 }
198 }
199
200 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
201
202 object = Object.normalize(activity)
203
204 assert object.data["title"] == "lain radio episode 1"
205 assert object.data["artist"] == "lain"
206 assert object.data["album"] == "lain radio"
207 assert object.data["length"] == 180_000
208 end
209
210 test "it rewrites Note votes to Answers and increments vote counters on question activities" do
211 user = insert(:user)
212
213 {:ok, activity} =
214 CommonAPI.post(user, %{
215 "status" => "suya...",
216 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
217 })
218
219 object = Object.normalize(activity)
220
221 data =
222 File.read!("test/fixtures/mastodon-vote.json")
223 |> Poison.decode!()
224 |> Kernel.put_in(["to"], user.ap_id)
225 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
226 |> Kernel.put_in(["object", "to"], user.ap_id)
227
228 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
229 answer_object = Object.normalize(activity)
230 assert answer_object.data["type"] == "Answer"
231 object = Object.get_by_ap_id(object.data["id"])
232
233 assert Enum.any?(
234 object.data["oneOf"],
235 fn
236 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
237 _ -> false
238 end
239 )
240 end
241
242 test "it works for incoming notices with contentMap" do
243 data =
244 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
245
246 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
247 object = Object.normalize(data["object"])
248
249 assert object.data["content"] ==
250 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
251 end
252
253 test "it works for incoming notices with to/cc not being an array (kroeg)" do
254 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
255
256 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
257 object = Object.normalize(data["object"])
258
259 assert object.data["content"] ==
260 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
261 end
262
263 test "it works for incoming announces with actor being inlined (kroeg)" do
264 data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
265
266 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
267
268 assert data["actor"] == "https://puckipedia.com/"
269 end
270
271 test "it works for incoming notices with tag not being an array (kroeg)" do
272 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
273
274 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
275 object = Object.normalize(data["object"])
276
277 assert object.data["emoji"] == %{
278 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
279 }
280
281 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
282
283 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
284 object = Object.normalize(data["object"])
285
286 assert "test" in object.data["tag"]
287 end
288
289 test "it works for incoming notices with url not being a string (prismo)" do
290 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
291
292 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
293 object = Object.normalize(data["object"])
294
295 assert object.data["url"] == "https://prismo.news/posts/83"
296 end
297
298 test "it cleans up incoming notices which are not really DMs" do
299 user = insert(:user)
300 other_user = insert(:user)
301
302 to = [user.ap_id, other_user.ap_id]
303
304 data =
305 File.read!("test/fixtures/mastodon-post-activity.json")
306 |> Poison.decode!()
307 |> Map.put("to", to)
308 |> Map.put("cc", [])
309
310 object =
311 data["object"]
312 |> Map.put("to", to)
313 |> Map.put("cc", [])
314
315 data = Map.put(data, "object", object)
316
317 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
318
319 assert data["to"] == []
320 assert data["cc"] == to
321
322 object_data = Object.normalize(activity).data
323
324 assert object_data["to"] == []
325 assert object_data["cc"] == to
326 end
327
328 test "it works for incoming likes" do
329 user = insert(:user)
330 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
331
332 data =
333 File.read!("test/fixtures/mastodon-like.json")
334 |> Poison.decode!()
335 |> Map.put("object", activity.data["object"])
336
337 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
338
339 assert data["actor"] == "http://mastodon.example.org/users/admin"
340 assert data["type"] == "Like"
341 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
342 assert data["object"] == activity.data["object"]
343 end
344
345 test "it works for incoming misskey likes, turning them into EmojiReacts" do
346 user = insert(:user)
347 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
348
349 data =
350 File.read!("test/fixtures/misskey-like.json")
351 |> Poison.decode!()
352 |> Map.put("object", activity.data["object"])
353
354 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
355
356 assert data["actor"] == data["actor"]
357 assert data["type"] == "EmojiReact"
358 assert data["id"] == data["id"]
359 assert data["object"] == activity.data["object"]
360 assert data["content"] == "🍮"
361 end
362
363 test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do
364 user = insert(:user)
365 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
366
367 data =
368 File.read!("test/fixtures/misskey-like.json")
369 |> Poison.decode!()
370 |> Map.put("object", activity.data["object"])
371 |> Map.put("_misskey_reaction", "⭐")
372
373 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
374
375 assert data["actor"] == data["actor"]
376 assert data["type"] == "EmojiReact"
377 assert data["id"] == data["id"]
378 assert data["object"] == activity.data["object"]
379 assert data["content"] == "⭐"
380 end
381
382 test "it works for incoming emoji reactions" do
383 user = insert(:user)
384 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
385
386 data =
387 File.read!("test/fixtures/emoji-reaction.json")
388 |> Poison.decode!()
389 |> Map.put("object", activity.data["object"])
390
391 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
392
393 assert data["actor"] == "http://mastodon.example.org/users/admin"
394 assert data["type"] == "EmojiReact"
395 assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
396 assert data["object"] == activity.data["object"]
397 assert data["content"] == "👌"
398 end
399
400 test "it reject invalid emoji reactions" do
401 user = insert(:user)
402 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
403
404 data =
405 File.read!("test/fixtures/emoji-reaction-too-long.json")
406 |> Poison.decode!()
407 |> Map.put("object", activity.data["object"])
408
409 assert :error = Transmogrifier.handle_incoming(data)
410
411 data =
412 File.read!("test/fixtures/emoji-reaction-no-emoji.json")
413 |> Poison.decode!()
414 |> Map.put("object", activity.data["object"])
415
416 assert :error = Transmogrifier.handle_incoming(data)
417 end
418
419 test "it works for incoming emoji reaction undos" do
420 user = insert(:user)
421
422 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
423 {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌")
424
425 data =
426 File.read!("test/fixtures/mastodon-undo-like.json")
427 |> Poison.decode!()
428 |> Map.put("object", reaction_activity.data["id"])
429 |> Map.put("actor", user.ap_id)
430
431 {:ok, activity} = Transmogrifier.handle_incoming(data)
432
433 assert activity.actor == user.ap_id
434 assert activity.data["id"] == data["id"]
435 assert activity.data["type"] == "Undo"
436 end
437
438 test "it returns an error for incoming unlikes wihout a like activity" do
439 user = insert(:user)
440 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
441
442 data =
443 File.read!("test/fixtures/mastodon-undo-like.json")
444 |> Poison.decode!()
445 |> Map.put("object", activity.data["object"])
446
447 assert Transmogrifier.handle_incoming(data) == :error
448 end
449
450 test "it works for incoming unlikes with an existing like activity" do
451 user = insert(:user)
452 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
453
454 like_data =
455 File.read!("test/fixtures/mastodon-like.json")
456 |> Poison.decode!()
457 |> Map.put("object", activity.data["object"])
458
459 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
460
461 data =
462 File.read!("test/fixtures/mastodon-undo-like.json")
463 |> Poison.decode!()
464 |> Map.put("object", like_data)
465 |> Map.put("actor", like_data["actor"])
466
467 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
468
469 assert data["actor"] == "http://mastodon.example.org/users/admin"
470 assert data["type"] == "Undo"
471 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
472 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
473 end
474
475 test "it works for incoming unlikes with an existing like activity and a compact object" do
476 user = insert(:user)
477 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
478
479 like_data =
480 File.read!("test/fixtures/mastodon-like.json")
481 |> Poison.decode!()
482 |> Map.put("object", activity.data["object"])
483
484 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
485
486 data =
487 File.read!("test/fixtures/mastodon-undo-like.json")
488 |> Poison.decode!()
489 |> Map.put("object", like_data["id"])
490 |> Map.put("actor", like_data["actor"])
491
492 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
493
494 assert data["actor"] == "http://mastodon.example.org/users/admin"
495 assert data["type"] == "Undo"
496 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
497 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
498 end
499
500 test "it works for incoming announces" do
501 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
502
503 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
504
505 assert data["actor"] == "http://mastodon.example.org/users/admin"
506 assert data["type"] == "Announce"
507
508 assert data["id"] ==
509 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
510
511 assert data["object"] ==
512 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
513
514 assert Activity.get_create_by_object_ap_id(data["object"])
515 end
516
517 test "it works for incoming announces with an existing activity" do
518 user = insert(:user)
519 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
520
521 data =
522 File.read!("test/fixtures/mastodon-announce.json")
523 |> Poison.decode!()
524 |> Map.put("object", activity.data["object"])
525
526 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
527
528 assert data["actor"] == "http://mastodon.example.org/users/admin"
529 assert data["type"] == "Announce"
530
531 assert data["id"] ==
532 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
533
534 assert data["object"] == activity.data["object"]
535
536 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
537 end
538
539 test "it works for incoming announces with an inlined activity" do
540 data =
541 File.read!("test/fixtures/mastodon-announce-private.json")
542 |> Poison.decode!()
543
544 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
545
546 assert data["actor"] == "http://mastodon.example.org/users/admin"
547 assert data["type"] == "Announce"
548
549 assert data["id"] ==
550 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
551
552 object = Object.normalize(data["object"])
553
554 assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368"
555 assert object.data["content"] == "this is a private toot"
556 end
557
558 @tag capture_log: true
559 test "it rejects incoming announces with an inlined activity from another origin" do
560 data =
561 File.read!("test/fixtures/bogus-mastodon-announce.json")
562 |> Poison.decode!()
563
564 assert :error = Transmogrifier.handle_incoming(data)
565 end
566
567 test "it does not clobber the addressing on announce activities" do
568 user = insert(:user)
569 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
570
571 data =
572 File.read!("test/fixtures/mastodon-announce.json")
573 |> Poison.decode!()
574 |> Map.put("object", Object.normalize(activity).data["id"])
575 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
576 |> Map.put("cc", [])
577
578 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
579
580 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
581 end
582
583 test "it ensures that as:Public activities make it to their followers collection" do
584 user = insert(:user)
585
586 data =
587 File.read!("test/fixtures/mastodon-post-activity.json")
588 |> Poison.decode!()
589 |> Map.put("actor", user.ap_id)
590 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
591 |> Map.put("cc", [])
592
593 object =
594 data["object"]
595 |> Map.put("attributedTo", user.ap_id)
596 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
597 |> Map.put("cc", [])
598 |> Map.put("id", user.ap_id <> "/activities/12345678")
599
600 data = Map.put(data, "object", object)
601
602 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
603
604 assert data["cc"] == [User.ap_followers(user)]
605 end
606
607 test "it ensures that address fields become lists" do
608 user = insert(:user)
609
610 data =
611 File.read!("test/fixtures/mastodon-post-activity.json")
612 |> Poison.decode!()
613 |> Map.put("actor", user.ap_id)
614 |> Map.put("to", nil)
615 |> Map.put("cc", nil)
616
617 object =
618 data["object"]
619 |> Map.put("attributedTo", user.ap_id)
620 |> Map.put("to", nil)
621 |> Map.put("cc", nil)
622 |> Map.put("id", user.ap_id <> "/activities/12345678")
623
624 data = Map.put(data, "object", object)
625
626 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
627
628 assert !is_nil(data["to"])
629 assert !is_nil(data["cc"])
630 end
631
632 test "it strips internal likes" do
633 data =
634 File.read!("test/fixtures/mastodon-post-activity.json")
635 |> Poison.decode!()
636
637 likes = %{
638 "first" =>
639 "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
640 "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
641 "totalItems" => 3,
642 "type" => "OrderedCollection"
643 }
644
645 object = Map.put(data["object"], "likes", likes)
646 data = Map.put(data, "object", object)
647
648 {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
649
650 refute Map.has_key?(object.data, "likes")
651 end
652
653 test "it strips internal reactions" do
654 user = insert(:user)
655 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
656 {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "📢")
657
658 %{object: object} = Activity.get_by_id_with_object(activity.id)
659 assert Map.has_key?(object.data, "reactions")
660 assert Map.has_key?(object.data, "reaction_count")
661
662 object_data = Transmogrifier.strip_internal_fields(object.data)
663 refute Map.has_key?(object_data, "reactions")
664 refute Map.has_key?(object_data, "reaction_count")
665 end
666
667 test "it works for incoming update activities" do
668 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
669
670 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
671 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
672
673 object =
674 update_data["object"]
675 |> Map.put("actor", data["actor"])
676 |> Map.put("id", data["actor"])
677
678 update_data =
679 update_data
680 |> Map.put("actor", data["actor"])
681 |> Map.put("object", object)
682
683 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
684
685 assert data["id"] == update_data["id"]
686
687 user = User.get_cached_by_ap_id(data["actor"])
688 assert user.name == "gargle"
689
690 assert user.avatar["url"] == [
691 %{
692 "href" =>
693 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
694 }
695 ]
696
697 assert user.banner["url"] == [
698 %{
699 "href" =>
700 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
701 }
702 ]
703
704 assert user.bio == "<p>Some bio</p>"
705 end
706
707 test "it works with alsoKnownAs" do
708 {:ok, %Activity{data: %{"actor" => actor}}} =
709 "test/fixtures/mastodon-post-activity.json"
710 |> File.read!()
711 |> Poison.decode!()
712 |> Transmogrifier.handle_incoming()
713
714 assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
715
716 {:ok, _activity} =
717 "test/fixtures/mastodon-update.json"
718 |> File.read!()
719 |> Poison.decode!()
720 |> Map.put("actor", actor)
721 |> Map.update!("object", fn object ->
722 object
723 |> Map.put("actor", actor)
724 |> Map.put("id", actor)
725 |> Map.put("alsoKnownAs", [
726 "http://mastodon.example.org/users/foo",
727 "http://example.org/users/bar"
728 ])
729 end)
730 |> Transmogrifier.handle_incoming()
731
732 assert User.get_cached_by_ap_id(actor).also_known_as == [
733 "http://mastodon.example.org/users/foo",
734 "http://example.org/users/bar"
735 ]
736 end
737
738 test "it works with custom profile fields" do
739 {:ok, activity} =
740 "test/fixtures/mastodon-post-activity.json"
741 |> File.read!()
742 |> Poison.decode!()
743 |> Transmogrifier.handle_incoming()
744
745 user = User.get_cached_by_ap_id(activity.actor)
746
747 assert User.fields(user) == [
748 %{"name" => "foo", "value" => "bar"},
749 %{"name" => "foo1", "value" => "bar1"}
750 ]
751
752 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
753
754 object =
755 update_data["object"]
756 |> Map.put("actor", user.ap_id)
757 |> Map.put("id", user.ap_id)
758
759 update_data =
760 update_data
761 |> Map.put("actor", user.ap_id)
762 |> Map.put("object", object)
763
764 {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
765
766 user = User.get_cached_by_ap_id(user.ap_id)
767
768 assert User.fields(user) == [
769 %{"name" => "foo", "value" => "updated"},
770 %{"name" => "foo1", "value" => "updated"}
771 ]
772
773 Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
774
775 update_data =
776 put_in(update_data, ["object", "attachment"], [
777 %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
778 %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
779 %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
780 ])
781
782 {:ok, _} = Transmogrifier.handle_incoming(update_data)
783
784 user = User.get_cached_by_ap_id(user.ap_id)
785
786 assert User.fields(user) == [
787 %{"name" => "foo", "value" => "updated"},
788 %{"name" => "foo1", "value" => "updated"}
789 ]
790
791 update_data = put_in(update_data, ["object", "attachment"], [])
792
793 {:ok, _} = Transmogrifier.handle_incoming(update_data)
794
795 user = User.get_cached_by_ap_id(user.ap_id)
796
797 assert User.fields(user) == []
798 end
799
800 test "it works for incoming update activities which lock the account" do
801 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
802
803 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
804 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
805
806 object =
807 update_data["object"]
808 |> Map.put("actor", data["actor"])
809 |> Map.put("id", data["actor"])
810 |> Map.put("manuallyApprovesFollowers", true)
811
812 update_data =
813 update_data
814 |> Map.put("actor", data["actor"])
815 |> Map.put("object", object)
816
817 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
818
819 user = User.get_cached_by_ap_id(data["actor"])
820 assert user.locked == true
821 end
822
823 test "it works for incoming deletes" do
824 activity = insert(:note_activity)
825 deleting_user = insert(:user)
826
827 data =
828 File.read!("test/fixtures/mastodon-delete.json")
829 |> Poison.decode!()
830
831 object =
832 data["object"]
833 |> Map.put("id", activity.data["object"])
834
835 data =
836 data
837 |> Map.put("object", object)
838 |> Map.put("actor", deleting_user.ap_id)
839
840 {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
841 Transmogrifier.handle_incoming(data)
842
843 assert id == data["id"]
844 refute Activity.get_by_id(activity.id)
845 assert actor == deleting_user.ap_id
846 end
847
848 test "it fails for incoming deletes with spoofed origin" do
849 activity = insert(:note_activity)
850
851 data =
852 File.read!("test/fixtures/mastodon-delete.json")
853 |> Poison.decode!()
854
855 object =
856 data["object"]
857 |> Map.put("id", activity.data["object"])
858
859 data =
860 data
861 |> Map.put("object", object)
862
863 assert capture_log(fn ->
864 :error = Transmogrifier.handle_incoming(data)
865 end) =~
866 "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}"
867
868 assert Activity.get_by_id(activity.id)
869 end
870
871 @tag capture_log: true
872 test "it works for incoming user deletes" do
873 %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
874
875 data =
876 File.read!("test/fixtures/mastodon-delete-user.json")
877 |> Poison.decode!()
878
879 {:ok, _} = Transmogrifier.handle_incoming(data)
880 ObanHelpers.perform_all()
881
882 refute User.get_cached_by_ap_id(ap_id)
883 end
884
885 test "it fails for incoming user deletes with spoofed origin" do
886 %{ap_id: ap_id} = insert(:user)
887
888 data =
889 File.read!("test/fixtures/mastodon-delete-user.json")
890 |> Poison.decode!()
891 |> Map.put("actor", ap_id)
892
893 assert capture_log(fn ->
894 assert :error == Transmogrifier.handle_incoming(data)
895 end) =~ "Object containment failed"
896
897 assert User.get_cached_by_ap_id(ap_id)
898 end
899
900 test "it works for incoming unannounces with an existing notice" do
901 user = insert(:user)
902 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
903
904 announce_data =
905 File.read!("test/fixtures/mastodon-announce.json")
906 |> Poison.decode!()
907 |> Map.put("object", activity.data["object"])
908
909 {:ok, %Activity{data: announce_data, local: false}} =
910 Transmogrifier.handle_incoming(announce_data)
911
912 data =
913 File.read!("test/fixtures/mastodon-undo-announce.json")
914 |> Poison.decode!()
915 |> Map.put("object", announce_data)
916 |> Map.put("actor", announce_data["actor"])
917
918 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
919
920 assert data["type"] == "Undo"
921 assert object_data = data["object"]
922 assert object_data["type"] == "Announce"
923 assert object_data["object"] == activity.data["object"]
924
925 assert object_data["id"] ==
926 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
927 end
928
929 test "it works for incomming unfollows with an existing follow" do
930 user = insert(:user)
931
932 follow_data =
933 File.read!("test/fixtures/mastodon-follow-activity.json")
934 |> Poison.decode!()
935 |> Map.put("object", user.ap_id)
936
937 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
938
939 data =
940 File.read!("test/fixtures/mastodon-unfollow-activity.json")
941 |> Poison.decode!()
942 |> Map.put("object", follow_data)
943
944 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
945
946 assert data["type"] == "Undo"
947 assert data["object"]["type"] == "Follow"
948 assert data["object"]["object"] == user.ap_id
949 assert data["actor"] == "http://mastodon.example.org/users/admin"
950
951 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
952 end
953
954 test "it works for incoming follows to locked account" do
955 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
956 user = insert(:user, locked: true)
957
958 data =
959 File.read!("test/fixtures/mastodon-follow-activity.json")
960 |> Poison.decode!()
961 |> Map.put("object", user.ap_id)
962
963 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
964
965 assert data["type"] == "Follow"
966 assert data["object"] == user.ap_id
967 assert data["state"] == "pending"
968 assert data["actor"] == "http://mastodon.example.org/users/admin"
969
970 assert [^pending_follower] = User.get_follow_requests(user)
971 end
972
973 test "it works for incoming blocks" do
974 user = insert(:user)
975
976 data =
977 File.read!("test/fixtures/mastodon-block-activity.json")
978 |> Poison.decode!()
979 |> Map.put("object", user.ap_id)
980
981 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
982
983 assert data["type"] == "Block"
984 assert data["object"] == user.ap_id
985 assert data["actor"] == "http://mastodon.example.org/users/admin"
986
987 blocker = User.get_cached_by_ap_id(data["actor"])
988
989 assert User.blocks?(blocker, user)
990 end
991
992 test "incoming blocks successfully tear down any follow relationship" do
993 blocker = insert(:user)
994 blocked = insert(:user)
995
996 data =
997 File.read!("test/fixtures/mastodon-block-activity.json")
998 |> Poison.decode!()
999 |> Map.put("object", blocked.ap_id)
1000 |> Map.put("actor", blocker.ap_id)
1001
1002 {:ok, blocker} = User.follow(blocker, blocked)
1003 {:ok, blocked} = User.follow(blocked, blocker)
1004
1005 assert User.following?(blocker, blocked)
1006 assert User.following?(blocked, blocker)
1007
1008 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
1009
1010 assert data["type"] == "Block"
1011 assert data["object"] == blocked.ap_id
1012 assert data["actor"] == blocker.ap_id
1013
1014 blocker = User.get_cached_by_ap_id(data["actor"])
1015 blocked = User.get_cached_by_ap_id(data["object"])
1016
1017 assert User.blocks?(blocker, blocked)
1018
1019 refute User.following?(blocker, blocked)
1020 refute User.following?(blocked, blocker)
1021 end
1022
1023 test "it works for incoming unblocks with an existing block" do
1024 user = insert(:user)
1025
1026 block_data =
1027 File.read!("test/fixtures/mastodon-block-activity.json")
1028 |> Poison.decode!()
1029 |> Map.put("object", user.ap_id)
1030
1031 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
1032
1033 data =
1034 File.read!("test/fixtures/mastodon-unblock-activity.json")
1035 |> Poison.decode!()
1036 |> Map.put("object", block_data)
1037
1038 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
1039 assert data["type"] == "Undo"
1040 assert data["object"]["type"] == "Block"
1041 assert data["object"]["object"] == user.ap_id
1042 assert data["actor"] == "http://mastodon.example.org/users/admin"
1043
1044 blocker = User.get_cached_by_ap_id(data["actor"])
1045
1046 refute User.blocks?(blocker, user)
1047 end
1048
1049 test "it works for incoming accepts which were pre-accepted" do
1050 follower = insert(:user)
1051 followed = insert(:user)
1052
1053 {:ok, follower} = User.follow(follower, followed)
1054 assert User.following?(follower, followed) == true
1055
1056 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1057
1058 accept_data =
1059 File.read!("test/fixtures/mastodon-accept-activity.json")
1060 |> Poison.decode!()
1061 |> Map.put("actor", followed.ap_id)
1062
1063 object =
1064 accept_data["object"]
1065 |> Map.put("actor", follower.ap_id)
1066 |> Map.put("id", follow_activity.data["id"])
1067
1068 accept_data = Map.put(accept_data, "object", object)
1069
1070 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
1071 refute activity.local
1072
1073 assert activity.data["object"] == follow_activity.data["id"]
1074
1075 assert activity.data["id"] == accept_data["id"]
1076
1077 follower = User.get_cached_by_id(follower.id)
1078
1079 assert User.following?(follower, followed) == true
1080 end
1081
1082 test "it works for incoming accepts which were orphaned" do
1083 follower = insert(:user)
1084 followed = insert(:user, locked: true)
1085
1086 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1087
1088 accept_data =
1089 File.read!("test/fixtures/mastodon-accept-activity.json")
1090 |> Poison.decode!()
1091 |> Map.put("actor", followed.ap_id)
1092
1093 accept_data =
1094 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1095
1096 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
1097 assert activity.data["object"] == follow_activity.data["id"]
1098
1099 follower = User.get_cached_by_id(follower.id)
1100
1101 assert User.following?(follower, followed) == true
1102 end
1103
1104 test "it works for incoming accepts which are referenced by IRI only" do
1105 follower = insert(:user)
1106 followed = insert(:user, locked: true)
1107
1108 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1109
1110 accept_data =
1111 File.read!("test/fixtures/mastodon-accept-activity.json")
1112 |> Poison.decode!()
1113 |> Map.put("actor", followed.ap_id)
1114 |> Map.put("object", follow_activity.data["id"])
1115
1116 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
1117 assert activity.data["object"] == follow_activity.data["id"]
1118
1119 follower = User.get_cached_by_id(follower.id)
1120
1121 assert User.following?(follower, followed) == true
1122 end
1123
1124 test "it fails for incoming accepts which cannot be correlated" do
1125 follower = insert(:user)
1126 followed = insert(:user, locked: true)
1127
1128 accept_data =
1129 File.read!("test/fixtures/mastodon-accept-activity.json")
1130 |> Poison.decode!()
1131 |> Map.put("actor", followed.ap_id)
1132
1133 accept_data =
1134 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1135
1136 :error = Transmogrifier.handle_incoming(accept_data)
1137
1138 follower = User.get_cached_by_id(follower.id)
1139
1140 refute User.following?(follower, followed) == true
1141 end
1142
1143 test "it fails for incoming rejects which cannot be correlated" do
1144 follower = insert(:user)
1145 followed = insert(:user, locked: true)
1146
1147 accept_data =
1148 File.read!("test/fixtures/mastodon-reject-activity.json")
1149 |> Poison.decode!()
1150 |> Map.put("actor", followed.ap_id)
1151
1152 accept_data =
1153 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1154
1155 :error = Transmogrifier.handle_incoming(accept_data)
1156
1157 follower = User.get_cached_by_id(follower.id)
1158
1159 refute User.following?(follower, followed) == true
1160 end
1161
1162 test "it works for incoming rejects which are orphaned" do
1163 follower = insert(:user)
1164 followed = insert(:user, locked: true)
1165
1166 {:ok, follower} = User.follow(follower, followed)
1167 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
1168
1169 assert User.following?(follower, followed) == true
1170
1171 reject_data =
1172 File.read!("test/fixtures/mastodon-reject-activity.json")
1173 |> Poison.decode!()
1174 |> Map.put("actor", followed.ap_id)
1175
1176 reject_data =
1177 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
1178
1179 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
1180 refute activity.local
1181 assert activity.data["id"] == reject_data["id"]
1182
1183 follower = User.get_cached_by_id(follower.id)
1184
1185 assert User.following?(follower, followed) == false
1186 end
1187
1188 test "it works for incoming rejects which are referenced by IRI only" do
1189 follower = insert(:user)
1190 followed = insert(:user, locked: true)
1191
1192 {:ok, follower} = User.follow(follower, followed)
1193 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1194
1195 assert User.following?(follower, followed) == true
1196
1197 reject_data =
1198 File.read!("test/fixtures/mastodon-reject-activity.json")
1199 |> Poison.decode!()
1200 |> Map.put("actor", followed.ap_id)
1201 |> Map.put("object", follow_activity.data["id"])
1202
1203 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
1204
1205 follower = User.get_cached_by_id(follower.id)
1206
1207 assert User.following?(follower, followed) == false
1208 end
1209
1210 test "it rejects activities without a valid ID" do
1211 user = insert(:user)
1212
1213 data =
1214 File.read!("test/fixtures/mastodon-follow-activity.json")
1215 |> Poison.decode!()
1216 |> Map.put("object", user.ap_id)
1217 |> Map.put("id", "")
1218
1219 :error = Transmogrifier.handle_incoming(data)
1220 end
1221
1222 test "it remaps video URLs as attachments if necessary" do
1223 {:ok, object} =
1224 Fetcher.fetch_object_from_id(
1225 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1226 )
1227
1228 attachment = %{
1229 "type" => "Link",
1230 "mediaType" => "video/mp4",
1231 "href" =>
1232 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1233 "mimeType" => "video/mp4",
1234 "size" => 5_015_880,
1235 "url" => [
1236 %{
1237 "href" =>
1238 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1239 "mediaType" => "video/mp4",
1240 "type" => "Link"
1241 }
1242 ],
1243 "width" => 480
1244 }
1245
1246 assert object.data["url"] ==
1247 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1248
1249 assert object.data["attachment"] == [attachment]
1250 end
1251
1252 test "it accepts Flag activities" do
1253 user = insert(:user)
1254 other_user = insert(:user)
1255
1256 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1257 object = Object.normalize(activity)
1258
1259 note_obj = %{
1260 "type" => "Note",
1261 "id" => activity.data["id"],
1262 "content" => "test post",
1263 "published" => object.data["published"],
1264 "actor" => AccountView.render("show.json", %{user: user})
1265 }
1266
1267 message = %{
1268 "@context" => "https://www.w3.org/ns/activitystreams",
1269 "cc" => [user.ap_id],
1270 "object" => [user.ap_id, activity.data["id"]],
1271 "type" => "Flag",
1272 "content" => "blocked AND reported!!!",
1273 "actor" => other_user.ap_id
1274 }
1275
1276 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1277
1278 assert activity.data["object"] == [user.ap_id, note_obj]
1279 assert activity.data["content"] == "blocked AND reported!!!"
1280 assert activity.data["actor"] == other_user.ap_id
1281 assert activity.data["cc"] == [user.ap_id]
1282 end
1283
1284 test "it correctly processes messages with non-array to field" do
1285 user = insert(:user)
1286
1287 message = %{
1288 "@context" => "https://www.w3.org/ns/activitystreams",
1289 "to" => "https://www.w3.org/ns/activitystreams#Public",
1290 "type" => "Create",
1291 "object" => %{
1292 "content" => "blah blah blah",
1293 "type" => "Note",
1294 "attributedTo" => user.ap_id,
1295 "inReplyTo" => nil
1296 },
1297 "actor" => user.ap_id
1298 }
1299
1300 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1301
1302 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1303 end
1304
1305 test "it correctly processes messages with non-array cc field" do
1306 user = insert(:user)
1307
1308 message = %{
1309 "@context" => "https://www.w3.org/ns/activitystreams",
1310 "to" => user.follower_address,
1311 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1312 "type" => "Create",
1313 "object" => %{
1314 "content" => "blah blah blah",
1315 "type" => "Note",
1316 "attributedTo" => user.ap_id,
1317 "inReplyTo" => nil
1318 },
1319 "actor" => user.ap_id
1320 }
1321
1322 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1323
1324 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1325 assert [user.follower_address] == activity.data["to"]
1326 end
1327
1328 test "it accepts Move activities" do
1329 old_user = insert(:user)
1330 new_user = insert(:user)
1331
1332 message = %{
1333 "@context" => "https://www.w3.org/ns/activitystreams",
1334 "type" => "Move",
1335 "actor" => old_user.ap_id,
1336 "object" => old_user.ap_id,
1337 "target" => new_user.ap_id
1338 }
1339
1340 assert :error = Transmogrifier.handle_incoming(message)
1341
1342 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1343
1344 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1345 assert activity.actor == old_user.ap_id
1346 assert activity.data["actor"] == old_user.ap_id
1347 assert activity.data["object"] == old_user.ap_id
1348 assert activity.data["target"] == new_user.ap_id
1349 assert activity.data["type"] == "Move"
1350 end
1351 end
1352
1353 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1354 clear_config([:activitypub, :note_replies_output_limit]) do
1355 Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5)
1356 end
1357
1358 clear_config([:instance, :federation_incoming_replies_max_depth])
1359
1360 setup do
1361 data =
1362 "test/fixtures/mastodon-post-activity.json"
1363 |> File.read!()
1364 |> Poison.decode!()
1365
1366 items = get_in(data, ["object", "replies", "first", "items"])
1367 assert length(items) > 0
1368
1369 %{data: data, items: items}
1370 end
1371
1372 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1373 data: data,
1374 items: items
1375 } do
1376 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1377
1378 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1379
1380 for id <- items do
1381 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1382 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1383 end
1384 end
1385
1386 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1387 %{data: data} do
1388 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1389
1390 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1391
1392 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1393 end
1394 end
1395
1396 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1397 clear_config([:activitypub, :note_replies_output_limit]) do
1398 Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5)
1399 end
1400
1401 clear_config([:instance, :federation_incoming_replies_max_depth])
1402
1403 setup do
1404 user = insert(:user)
1405
1406 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1407
1408 {:ok, reply1} =
1409 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1410
1411 {:ok, reply2} =
1412 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1413
1414 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1415
1416 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1417
1418 Repo.delete(activity.object)
1419 Repo.delete(activity)
1420
1421 %{federation_output: federation_output, replies_uris: replies_uris}
1422 end
1423
1424 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1425 federation_output: federation_output,
1426 replies_uris: replies_uris
1427 } do
1428 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1429
1430 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1431
1432 for id <- replies_uris do
1433 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1434 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1435 end
1436 end
1437
1438 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1439 %{federation_output: federation_output} do
1440 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1441
1442 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1443
1444 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1445 end
1446 end
1447
1448 describe "prepare outgoing" do
1449 test "it inlines private announced objects" do
1450 user = insert(:user)
1451
1452 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1453
1454 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1455
1456 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1457
1458 assert modified["object"]["content"] == "hey"
1459 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1460 end
1461
1462 test "it turns mentions into tags" do
1463 user = insert(:user)
1464 other_user = insert(:user)
1465
1466 {:ok, activity} =
1467 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1468
1469 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1470 object = modified["object"]
1471
1472 expected_mention = %{
1473 "href" => other_user.ap_id,
1474 "name" => "@#{other_user.nickname}",
1475 "type" => "Mention"
1476 }
1477
1478 expected_tag = %{
1479 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1480 "type" => "Hashtag",
1481 "name" => "#2hu"
1482 }
1483
1484 assert Enum.member?(object["tag"], expected_tag)
1485 assert Enum.member?(object["tag"], expected_mention)
1486 end
1487
1488 test "it adds the sensitive property" do
1489 user = insert(:user)
1490
1491 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1492 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1493
1494 assert modified["object"]["sensitive"]
1495 end
1496
1497 test "it adds the json-ld context and the conversation property" do
1498 user = insert(:user)
1499
1500 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1501 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1502
1503 assert modified["@context"] ==
1504 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1505
1506 assert modified["object"]["conversation"] == modified["context"]
1507 end
1508
1509 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1510 user = insert(:user)
1511
1512 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1513 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1514
1515 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1516 end
1517
1518 test "it strips internal hashtag data" do
1519 user = insert(:user)
1520
1521 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1522
1523 expected_tag = %{
1524 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1525 "type" => "Hashtag",
1526 "name" => "#2hu"
1527 }
1528
1529 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1530
1531 assert modified["object"]["tag"] == [expected_tag]
1532 end
1533
1534 test "it strips internal fields" do
1535 user = insert(:user)
1536
1537 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1538
1539 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1540
1541 assert length(modified["object"]["tag"]) == 2
1542
1543 assert is_nil(modified["object"]["emoji"])
1544 assert is_nil(modified["object"]["like_count"])
1545 assert is_nil(modified["object"]["announcements"])
1546 assert is_nil(modified["object"]["announcement_count"])
1547 assert is_nil(modified["object"]["context_id"])
1548 end
1549
1550 test "it strips internal fields of article" do
1551 activity = insert(:article_activity)
1552
1553 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1554
1555 assert length(modified["object"]["tag"]) == 2
1556
1557 assert is_nil(modified["object"]["emoji"])
1558 assert is_nil(modified["object"]["like_count"])
1559 assert is_nil(modified["object"]["announcements"])
1560 assert is_nil(modified["object"]["announcement_count"])
1561 assert is_nil(modified["object"]["context_id"])
1562 assert is_nil(modified["object"]["likes"])
1563 end
1564
1565 test "the directMessage flag is present" do
1566 user = insert(:user)
1567 other_user = insert(:user)
1568
1569 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1570
1571 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1572
1573 assert modified["directMessage"] == false
1574
1575 {:ok, activity} =
1576 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1577
1578 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1579
1580 assert modified["directMessage"] == false
1581
1582 {:ok, activity} =
1583 CommonAPI.post(user, %{
1584 "status" => "@#{other_user.nickname} :moominmamma:",
1585 "visibility" => "direct"
1586 })
1587
1588 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1589
1590 assert modified["directMessage"] == true
1591 end
1592
1593 test "it strips BCC field" do
1594 user = insert(:user)
1595 {:ok, list} = Pleroma.List.create("foo", user)
1596
1597 {:ok, activity} =
1598 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1599
1600 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1601
1602 assert is_nil(modified["bcc"])
1603 end
1604
1605 test "it can handle Listen activities" do
1606 listen_activity = insert(:listen)
1607
1608 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1609
1610 assert modified["type"] == "Listen"
1611
1612 user = insert(:user)
1613
1614 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1615
1616 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1617 end
1618 end
1619
1620 describe "user upgrade" do
1621 test "it upgrades a user to activitypub" do
1622 user =
1623 insert(:user, %{
1624 nickname: "rye@niu.moe",
1625 local: false,
1626 ap_id: "https://niu.moe/users/rye",
1627 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1628 })
1629
1630 user_two = insert(:user)
1631 Pleroma.FollowingRelationship.follow(user_two, user, "accept")
1632
1633 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1634 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1635 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1636
1637 user = User.get_cached_by_id(user.id)
1638 assert user.note_count == 1
1639
1640 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1641 ObanHelpers.perform_all()
1642
1643 assert user.ap_enabled
1644 assert user.note_count == 1
1645 assert user.follower_address == "https://niu.moe/users/rye/followers"
1646 assert user.following_address == "https://niu.moe/users/rye/following"
1647
1648 user = User.get_cached_by_id(user.id)
1649 assert user.note_count == 1
1650
1651 activity = Activity.get_by_id(activity.id)
1652 assert user.follower_address in activity.recipients
1653
1654 assert %{
1655 "url" => [
1656 %{
1657 "href" =>
1658 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1659 }
1660 ]
1661 } = user.avatar
1662
1663 assert %{
1664 "url" => [
1665 %{
1666 "href" =>
1667 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1668 }
1669 ]
1670 } = user.banner
1671
1672 refute "..." in activity.recipients
1673
1674 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1675 refute user.follower_address in unrelated_activity.recipients
1676
1677 user_two = User.get_cached_by_id(user_two.id)
1678 assert User.following?(user_two, user)
1679 refute "..." in User.following(user_two)
1680 end
1681 end
1682
1683 describe "actor rewriting" do
1684 test "it fixes the actor URL property to be a proper URI" do
1685 data = %{
1686 "url" => %{"href" => "http://example.com"}
1687 }
1688
1689 rewritten = Transmogrifier.maybe_fix_user_object(data)
1690 assert rewritten["url"] == "http://example.com"
1691 end
1692 end
1693
1694 describe "actor origin containment" do
1695 test "it rejects activities which reference objects with bogus origins" do
1696 data = %{
1697 "@context" => "https://www.w3.org/ns/activitystreams",
1698 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1699 "actor" => "http://mastodon.example.org/users/admin",
1700 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1701 "object" => "https://info.pleroma.site/activity.json",
1702 "type" => "Announce"
1703 }
1704
1705 assert capture_log(fn ->
1706 :error = Transmogrifier.handle_incoming(data)
1707 end) =~ "Object containment failed"
1708 end
1709
1710 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1711 data = %{
1712 "@context" => "https://www.w3.org/ns/activitystreams",
1713 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1714 "actor" => "http://mastodon.example.org/users/admin",
1715 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1716 "object" => "https://info.pleroma.site/activity2.json",
1717 "type" => "Announce"
1718 }
1719
1720 assert capture_log(fn ->
1721 :error = Transmogrifier.handle_incoming(data)
1722 end) =~ "Object containment failed"
1723 end
1724
1725 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1726 data = %{
1727 "@context" => "https://www.w3.org/ns/activitystreams",
1728 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1729 "actor" => "http://mastodon.example.org/users/admin",
1730 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1731 "object" => "https://info.pleroma.site/activity3.json",
1732 "type" => "Announce"
1733 }
1734
1735 assert capture_log(fn ->
1736 :error = Transmogrifier.handle_incoming(data)
1737 end) =~ "Object containment failed"
1738 end
1739 end
1740
1741 describe "reserialization" do
1742 test "successfully reserializes a message with inReplyTo == nil" do
1743 user = insert(:user)
1744
1745 message = %{
1746 "@context" => "https://www.w3.org/ns/activitystreams",
1747 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1748 "cc" => [],
1749 "type" => "Create",
1750 "object" => %{
1751 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1752 "cc" => [],
1753 "type" => "Note",
1754 "content" => "Hi",
1755 "inReplyTo" => nil,
1756 "attributedTo" => user.ap_id
1757 },
1758 "actor" => user.ap_id
1759 }
1760
1761 {:ok, activity} = Transmogrifier.handle_incoming(message)
1762
1763 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1764 end
1765
1766 test "successfully reserializes a message with AS2 objects in IR" do
1767 user = insert(:user)
1768
1769 message = %{
1770 "@context" => "https://www.w3.org/ns/activitystreams",
1771 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1772 "cc" => [],
1773 "type" => "Create",
1774 "object" => %{
1775 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1776 "cc" => [],
1777 "type" => "Note",
1778 "content" => "Hi",
1779 "inReplyTo" => nil,
1780 "attributedTo" => user.ap_id,
1781 "tag" => [
1782 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1783 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1784 ]
1785 },
1786 "actor" => user.ap_id
1787 }
1788
1789 {:ok, activity} = Transmogrifier.handle_incoming(message)
1790
1791 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1792 end
1793 end
1794
1795 test "Rewrites Answers to Notes" do
1796 user = insert(:user)
1797
1798 {:ok, poll_activity} =
1799 CommonAPI.post(user, %{
1800 "status" => "suya...",
1801 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1802 })
1803
1804 poll_object = Object.normalize(poll_activity)
1805 # TODO: Replace with CommonAPI vote creation when implemented
1806 data =
1807 File.read!("test/fixtures/mastodon-vote.json")
1808 |> Poison.decode!()
1809 |> Kernel.put_in(["to"], user.ap_id)
1810 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1811 |> Kernel.put_in(["object", "to"], user.ap_id)
1812
1813 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1814 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1815
1816 assert data["object"]["type"] == "Note"
1817 end
1818
1819 describe "fix_explicit_addressing" do
1820 setup do
1821 user = insert(:user)
1822 [user: user]
1823 end
1824
1825 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1826 explicitly_mentioned_actors = [
1827 "https://pleroma.gold/users/user1",
1828 "https://pleroma.gold/user2"
1829 ]
1830
1831 object = %{
1832 "actor" => user.ap_id,
1833 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1834 "cc" => [],
1835 "tag" =>
1836 Enum.map(explicitly_mentioned_actors, fn href ->
1837 %{"type" => "Mention", "href" => href}
1838 end)
1839 }
1840
1841 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1842 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1843 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1844 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1845 end
1846
1847 test "does not move actor's follower collection to cc", %{user: user} do
1848 object = %{
1849 "actor" => user.ap_id,
1850 "to" => [user.follower_address],
1851 "cc" => []
1852 }
1853
1854 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1855 assert user.follower_address in fixed_object["to"]
1856 refute user.follower_address in fixed_object["cc"]
1857 end
1858
1859 test "removes recipient's follower collection from cc", %{user: user} do
1860 recipient = insert(:user)
1861
1862 object = %{
1863 "actor" => user.ap_id,
1864 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1865 "cc" => [user.follower_address, recipient.follower_address]
1866 }
1867
1868 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1869
1870 assert user.follower_address in fixed_object["cc"]
1871 refute recipient.follower_address in fixed_object["cc"]
1872 refute recipient.follower_address in fixed_object["to"]
1873 end
1874 end
1875
1876 describe "fix_summary/1" do
1877 test "returns fixed object" do
1878 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1879 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1880 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1881 end
1882 end
1883
1884 describe "fix_in_reply_to/2" do
1885 clear_config([:instance, :federation_incoming_replies_max_depth])
1886
1887 setup do
1888 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1889 [data: data]
1890 end
1891
1892 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1893 assert Transmogrifier.fix_in_reply_to(data) == data
1894 end
1895
1896 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1897 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1898
1899 object_with_reply =
1900 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1901
1902 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1903 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1904 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1905
1906 object_with_reply =
1907 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1908
1909 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1910 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1911 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1912
1913 object_with_reply =
1914 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1915
1916 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1917 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1918 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1919
1920 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1921 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1922 assert modified_object["inReplyTo"] == []
1923 assert modified_object["inReplyToAtomUri"] == ""
1924 end
1925
1926 @tag capture_log: true
1927 test "returns modified object when allowed incoming reply", %{data: data} do
1928 object_with_reply =
1929 Map.put(
1930 data["object"],
1931 "inReplyTo",
1932 "https://shitposter.club/notice/2827873"
1933 )
1934
1935 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1936 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1937
1938 assert modified_object["inReplyTo"] ==
1939 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1940
1941 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1942
1943 assert modified_object["conversation"] ==
1944 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1945
1946 assert modified_object["context"] ==
1947 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1948 end
1949 end
1950
1951 describe "fix_url/1" do
1952 test "fixes data for object when url is map" do
1953 object = %{
1954 "url" => %{
1955 "type" => "Link",
1956 "mimeType" => "video/mp4",
1957 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1958 }
1959 }
1960
1961 assert Transmogrifier.fix_url(object) == %{
1962 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1963 }
1964 end
1965
1966 test "fixes data for video object" do
1967 object = %{
1968 "type" => "Video",
1969 "url" => [
1970 %{
1971 "type" => "Link",
1972 "mimeType" => "video/mp4",
1973 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1974 },
1975 %{
1976 "type" => "Link",
1977 "mimeType" => "video/mp4",
1978 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1979 },
1980 %{
1981 "type" => "Link",
1982 "mimeType" => "text/html",
1983 "href" => "https://peertube.-2d4c2d1630e3"
1984 },
1985 %{
1986 "type" => "Link",
1987 "mimeType" => "text/html",
1988 "href" => "https://peertube.-2d4c2d16377-42"
1989 }
1990 ]
1991 }
1992
1993 assert Transmogrifier.fix_url(object) == %{
1994 "attachment" => [
1995 %{
1996 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1997 "mimeType" => "video/mp4",
1998 "type" => "Link"
1999 }
2000 ],
2001 "type" => "Video",
2002 "url" => "https://peertube.-2d4c2d1630e3"
2003 }
2004 end
2005
2006 test "fixes url for not Video object" do
2007 object = %{
2008 "type" => "Text",
2009 "url" => [
2010 %{
2011 "type" => "Link",
2012 "mimeType" => "text/html",
2013 "href" => "https://peertube.-2d4c2d1630e3"
2014 },
2015 %{
2016 "type" => "Link",
2017 "mimeType" => "text/html",
2018 "href" => "https://peertube.-2d4c2d16377-42"
2019 }
2020 ]
2021 }
2022
2023 assert Transmogrifier.fix_url(object) == %{
2024 "type" => "Text",
2025 "url" => "https://peertube.-2d4c2d1630e3"
2026 }
2027
2028 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
2029 "type" => "Text",
2030 "url" => ""
2031 }
2032 end
2033
2034 test "retunrs not modified object" do
2035 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
2036 end
2037 end
2038
2039 describe "get_obj_helper/2" do
2040 test "returns nil when cannot normalize object" do
2041 assert capture_log(fn ->
2042 refute Transmogrifier.get_obj_helper("test-obj-id")
2043 end) =~ "Unsupported URI scheme"
2044 end
2045
2046 @tag capture_log: true
2047 test "returns {:ok, %Object{}} for success case" do
2048 assert {:ok, %Object{}} =
2049 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
2050 end
2051 end
2052
2053 describe "fix_attachments/1" do
2054 test "returns not modified object" do
2055 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2056 assert Transmogrifier.fix_attachments(data) == data
2057 end
2058
2059 test "returns modified object when attachment is map" do
2060 assert Transmogrifier.fix_attachments(%{
2061 "attachment" => %{
2062 "mediaType" => "video/mp4",
2063 "url" => "https://peertube.moe/stat-480.mp4"
2064 }
2065 }) == %{
2066 "attachment" => [
2067 %{
2068 "mediaType" => "video/mp4",
2069 "url" => [
2070 %{
2071 "href" => "https://peertube.moe/stat-480.mp4",
2072 "mediaType" => "video/mp4",
2073 "type" => "Link"
2074 }
2075 ]
2076 }
2077 ]
2078 }
2079 end
2080
2081 test "returns modified object when attachment is list" do
2082 assert Transmogrifier.fix_attachments(%{
2083 "attachment" => [
2084 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
2085 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
2086 ]
2087 }) == %{
2088 "attachment" => [
2089 %{
2090 "mediaType" => "video/mp4",
2091 "url" => [
2092 %{
2093 "href" => "https://pe.er/stat-480.mp4",
2094 "mediaType" => "video/mp4",
2095 "type" => "Link"
2096 }
2097 ]
2098 },
2099 %{
2100 "href" => "https://pe.er/stat-480.mp4",
2101 "mediaType" => "video/mp4",
2102 "mimeType" => "video/mp4",
2103 "url" => [
2104 %{
2105 "href" => "https://pe.er/stat-480.mp4",
2106 "mediaType" => "video/mp4",
2107 "type" => "Link"
2108 }
2109 ]
2110 }
2111 ]
2112 }
2113 end
2114 end
2115
2116 describe "fix_emoji/1" do
2117 test "returns not modified object when object not contains tags" do
2118 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2119 assert Transmogrifier.fix_emoji(data) == data
2120 end
2121
2122 test "returns object with emoji when object contains list tags" do
2123 assert Transmogrifier.fix_emoji(%{
2124 "tag" => [
2125 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
2126 %{"type" => "Hashtag"}
2127 ]
2128 }) == %{
2129 "emoji" => %{"bib" => "/test"},
2130 "tag" => [
2131 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
2132 %{"type" => "Hashtag"}
2133 ]
2134 }
2135 end
2136
2137 test "returns object with emoji when object contains map tag" do
2138 assert Transmogrifier.fix_emoji(%{
2139 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
2140 }) == %{
2141 "emoji" => %{"bib" => "/test"},
2142 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
2143 }
2144 end
2145 end
2146
2147 describe "set_replies/1" do
2148 clear_config([:activitypub, :note_replies_output_limit]) do
2149 Pleroma.Config.put([:activitypub, :note_replies_output_limit], 2)
2150 end
2151
2152 test "returns unmodified object if activity doesn't have self-replies" do
2153 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2154 assert Transmogrifier.set_replies(data) == data
2155 end
2156
2157 test "sets `replies` collection with a limited number of self-replies" do
2158 [user, another_user] = insert_list(2, :user)
2159
2160 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
2161
2162 {:ok, %{id: id2} = self_reply1} =
2163 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
2164
2165 {:ok, self_reply2} =
2166 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
2167
2168 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
2169 {:ok, _} =
2170 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
2171
2172 {:ok, _} =
2173 CommonAPI.post(user, %{
2174 "status" => "self-reply to self-reply",
2175 "in_reply_to_status_id" => id2
2176 })
2177
2178 {:ok, _} =
2179 CommonAPI.post(another_user, %{
2180 "status" => "another user's reply",
2181 "in_reply_to_status_id" => id1
2182 })
2183
2184 object = Object.normalize(activity)
2185 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
2186
2187 assert %{"type" => "Collection", "items" => ^replies_uris} =
2188 Transmogrifier.set_replies(object.data)["replies"]
2189 end
2190 end
2191 end