Merge branch 'release/2.0.3' into 'stable'
[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} =
874 insert(:user, ap_id: "http://mastodon.example.org/users/admin", local: false)
875
876 data =
877 File.read!("test/fixtures/mastodon-delete-user.json")
878 |> Poison.decode!()
879
880 {:ok, _} = Transmogrifier.handle_incoming(data)
881 ObanHelpers.perform_all()
882
883 refute User.get_cached_by_ap_id(ap_id)
884 end
885
886 test "it fails for incoming user deletes with spoofed origin" do
887 %{ap_id: ap_id} = insert(:user)
888
889 data =
890 File.read!("test/fixtures/mastodon-delete-user.json")
891 |> Poison.decode!()
892 |> Map.put("actor", ap_id)
893
894 assert capture_log(fn ->
895 assert :error == Transmogrifier.handle_incoming(data)
896 end) =~ "Object containment failed"
897
898 assert User.get_cached_by_ap_id(ap_id)
899 end
900
901 test "it works for incoming unannounces with an existing notice" do
902 user = insert(:user)
903 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
904
905 announce_data =
906 File.read!("test/fixtures/mastodon-announce.json")
907 |> Poison.decode!()
908 |> Map.put("object", activity.data["object"])
909
910 {:ok, %Activity{data: announce_data, local: false}} =
911 Transmogrifier.handle_incoming(announce_data)
912
913 data =
914 File.read!("test/fixtures/mastodon-undo-announce.json")
915 |> Poison.decode!()
916 |> Map.put("object", announce_data)
917 |> Map.put("actor", announce_data["actor"])
918
919 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
920
921 assert data["type"] == "Undo"
922 assert object_data = data["object"]
923 assert object_data["type"] == "Announce"
924 assert object_data["object"] == activity.data["object"]
925
926 assert object_data["id"] ==
927 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
928 end
929
930 test "it works for incomming unfollows with an existing follow" do
931 user = insert(:user)
932
933 follow_data =
934 File.read!("test/fixtures/mastodon-follow-activity.json")
935 |> Poison.decode!()
936 |> Map.put("object", user.ap_id)
937
938 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
939
940 data =
941 File.read!("test/fixtures/mastodon-unfollow-activity.json")
942 |> Poison.decode!()
943 |> Map.put("object", follow_data)
944
945 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
946
947 assert data["type"] == "Undo"
948 assert data["object"]["type"] == "Follow"
949 assert data["object"]["object"] == user.ap_id
950 assert data["actor"] == "http://mastodon.example.org/users/admin"
951
952 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
953 end
954
955 test "it works for incoming follows to locked account" do
956 pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
957 user = insert(:user, locked: true)
958
959 data =
960 File.read!("test/fixtures/mastodon-follow-activity.json")
961 |> Poison.decode!()
962 |> Map.put("object", user.ap_id)
963
964 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
965
966 assert data["type"] == "Follow"
967 assert data["object"] == user.ap_id
968 assert data["state"] == "pending"
969 assert data["actor"] == "http://mastodon.example.org/users/admin"
970
971 assert [^pending_follower] = User.get_follow_requests(user)
972 end
973
974 test "it works for incoming blocks" do
975 user = insert(:user)
976
977 data =
978 File.read!("test/fixtures/mastodon-block-activity.json")
979 |> Poison.decode!()
980 |> Map.put("object", user.ap_id)
981
982 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
983
984 assert data["type"] == "Block"
985 assert data["object"] == user.ap_id
986 assert data["actor"] == "http://mastodon.example.org/users/admin"
987
988 blocker = User.get_cached_by_ap_id(data["actor"])
989
990 assert User.blocks?(blocker, user)
991 end
992
993 test "incoming blocks successfully tear down any follow relationship" do
994 blocker = insert(:user)
995 blocked = insert(:user)
996
997 data =
998 File.read!("test/fixtures/mastodon-block-activity.json")
999 |> Poison.decode!()
1000 |> Map.put("object", blocked.ap_id)
1001 |> Map.put("actor", blocker.ap_id)
1002
1003 {:ok, blocker} = User.follow(blocker, blocked)
1004 {:ok, blocked} = User.follow(blocked, blocker)
1005
1006 assert User.following?(blocker, blocked)
1007 assert User.following?(blocked, blocker)
1008
1009 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
1010
1011 assert data["type"] == "Block"
1012 assert data["object"] == blocked.ap_id
1013 assert data["actor"] == blocker.ap_id
1014
1015 blocker = User.get_cached_by_ap_id(data["actor"])
1016 blocked = User.get_cached_by_ap_id(data["object"])
1017
1018 assert User.blocks?(blocker, blocked)
1019
1020 refute User.following?(blocker, blocked)
1021 refute User.following?(blocked, blocker)
1022 end
1023
1024 test "it works for incoming unblocks with an existing block" do
1025 user = insert(:user)
1026
1027 block_data =
1028 File.read!("test/fixtures/mastodon-block-activity.json")
1029 |> Poison.decode!()
1030 |> Map.put("object", user.ap_id)
1031
1032 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
1033
1034 data =
1035 File.read!("test/fixtures/mastodon-unblock-activity.json")
1036 |> Poison.decode!()
1037 |> Map.put("object", block_data)
1038
1039 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
1040 assert data["type"] == "Undo"
1041 assert data["object"]["type"] == "Block"
1042 assert data["object"]["object"] == user.ap_id
1043 assert data["actor"] == "http://mastodon.example.org/users/admin"
1044
1045 blocker = User.get_cached_by_ap_id(data["actor"])
1046
1047 refute User.blocks?(blocker, user)
1048 end
1049
1050 test "it works for incoming accepts which were pre-accepted" do
1051 follower = insert(:user)
1052 followed = insert(:user)
1053
1054 {:ok, follower} = User.follow(follower, followed)
1055 assert User.following?(follower, followed) == true
1056
1057 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1058
1059 accept_data =
1060 File.read!("test/fixtures/mastodon-accept-activity.json")
1061 |> Poison.decode!()
1062 |> Map.put("actor", followed.ap_id)
1063
1064 object =
1065 accept_data["object"]
1066 |> Map.put("actor", follower.ap_id)
1067 |> Map.put("id", follow_activity.data["id"])
1068
1069 accept_data = Map.put(accept_data, "object", object)
1070
1071 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
1072 refute activity.local
1073
1074 assert activity.data["object"] == follow_activity.data["id"]
1075
1076 assert activity.data["id"] == accept_data["id"]
1077
1078 follower = User.get_cached_by_id(follower.id)
1079
1080 assert User.following?(follower, followed) == true
1081 end
1082
1083 test "it works for incoming accepts which were orphaned" do
1084 follower = insert(:user)
1085 followed = insert(:user, locked: true)
1086
1087 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1088
1089 accept_data =
1090 File.read!("test/fixtures/mastodon-accept-activity.json")
1091 |> Poison.decode!()
1092 |> Map.put("actor", followed.ap_id)
1093
1094 accept_data =
1095 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1096
1097 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
1098 assert activity.data["object"] == follow_activity.data["id"]
1099
1100 follower = User.get_cached_by_id(follower.id)
1101
1102 assert User.following?(follower, followed) == true
1103 end
1104
1105 test "it works for incoming accepts which are referenced by IRI only" do
1106 follower = insert(:user)
1107 followed = insert(:user, locked: true)
1108
1109 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1110
1111 accept_data =
1112 File.read!("test/fixtures/mastodon-accept-activity.json")
1113 |> Poison.decode!()
1114 |> Map.put("actor", followed.ap_id)
1115 |> Map.put("object", follow_activity.data["id"])
1116
1117 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
1118 assert activity.data["object"] == follow_activity.data["id"]
1119
1120 follower = User.get_cached_by_id(follower.id)
1121
1122 assert User.following?(follower, followed) == true
1123 end
1124
1125 test "it fails for incoming accepts which cannot be correlated" do
1126 follower = insert(:user)
1127 followed = insert(:user, locked: true)
1128
1129 accept_data =
1130 File.read!("test/fixtures/mastodon-accept-activity.json")
1131 |> Poison.decode!()
1132 |> Map.put("actor", followed.ap_id)
1133
1134 accept_data =
1135 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1136
1137 :error = Transmogrifier.handle_incoming(accept_data)
1138
1139 follower = User.get_cached_by_id(follower.id)
1140
1141 refute User.following?(follower, followed) == true
1142 end
1143
1144 test "it fails for incoming rejects which cannot be correlated" do
1145 follower = insert(:user)
1146 followed = insert(:user, locked: true)
1147
1148 accept_data =
1149 File.read!("test/fixtures/mastodon-reject-activity.json")
1150 |> Poison.decode!()
1151 |> Map.put("actor", followed.ap_id)
1152
1153 accept_data =
1154 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
1155
1156 :error = Transmogrifier.handle_incoming(accept_data)
1157
1158 follower = User.get_cached_by_id(follower.id)
1159
1160 refute User.following?(follower, followed) == true
1161 end
1162
1163 test "it works for incoming rejects which are orphaned" do
1164 follower = insert(:user)
1165 followed = insert(:user, locked: true)
1166
1167 {:ok, follower} = User.follow(follower, followed)
1168 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
1169
1170 assert User.following?(follower, followed) == true
1171
1172 reject_data =
1173 File.read!("test/fixtures/mastodon-reject-activity.json")
1174 |> Poison.decode!()
1175 |> Map.put("actor", followed.ap_id)
1176
1177 reject_data =
1178 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
1179
1180 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
1181 refute activity.local
1182 assert activity.data["id"] == reject_data["id"]
1183
1184 follower = User.get_cached_by_id(follower.id)
1185
1186 assert User.following?(follower, followed) == false
1187 end
1188
1189 test "it works for incoming rejects which are referenced by IRI only" do
1190 follower = insert(:user)
1191 followed = insert(:user, locked: true)
1192
1193 {:ok, follower} = User.follow(follower, followed)
1194 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
1195
1196 assert User.following?(follower, followed) == true
1197
1198 reject_data =
1199 File.read!("test/fixtures/mastodon-reject-activity.json")
1200 |> Poison.decode!()
1201 |> Map.put("actor", followed.ap_id)
1202 |> Map.put("object", follow_activity.data["id"])
1203
1204 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
1205
1206 follower = User.get_cached_by_id(follower.id)
1207
1208 assert User.following?(follower, followed) == false
1209 end
1210
1211 test "it rejects activities without a valid ID" do
1212 user = insert(:user)
1213
1214 data =
1215 File.read!("test/fixtures/mastodon-follow-activity.json")
1216 |> Poison.decode!()
1217 |> Map.put("object", user.ap_id)
1218 |> Map.put("id", "")
1219
1220 :error = Transmogrifier.handle_incoming(data)
1221 end
1222
1223 test "it remaps video URLs as attachments if necessary" do
1224 {:ok, object} =
1225 Fetcher.fetch_object_from_id(
1226 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1227 )
1228
1229 attachment = %{
1230 "type" => "Link",
1231 "mediaType" => "video/mp4",
1232 "href" =>
1233 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1234 "mimeType" => "video/mp4",
1235 "size" => 5_015_880,
1236 "url" => [
1237 %{
1238 "href" =>
1239 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1240 "mediaType" => "video/mp4",
1241 "type" => "Link"
1242 }
1243 ],
1244 "width" => 480
1245 }
1246
1247 assert object.data["url"] ==
1248 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
1249
1250 assert object.data["attachment"] == [attachment]
1251 end
1252
1253 test "it accepts Flag activities" do
1254 user = insert(:user)
1255 other_user = insert(:user)
1256
1257 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
1258 object = Object.normalize(activity)
1259
1260 note_obj = %{
1261 "type" => "Note",
1262 "id" => activity.data["id"],
1263 "content" => "test post",
1264 "published" => object.data["published"],
1265 "actor" => AccountView.render("show.json", %{user: user})
1266 }
1267
1268 message = %{
1269 "@context" => "https://www.w3.org/ns/activitystreams",
1270 "cc" => [user.ap_id],
1271 "object" => [user.ap_id, activity.data["id"]],
1272 "type" => "Flag",
1273 "content" => "blocked AND reported!!!",
1274 "actor" => other_user.ap_id
1275 }
1276
1277 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1278
1279 assert activity.data["object"] == [user.ap_id, note_obj]
1280 assert activity.data["content"] == "blocked AND reported!!!"
1281 assert activity.data["actor"] == other_user.ap_id
1282 assert activity.data["cc"] == [user.ap_id]
1283 end
1284
1285 test "it correctly processes messages with non-array to field" do
1286 user = insert(:user)
1287
1288 message = %{
1289 "@context" => "https://www.w3.org/ns/activitystreams",
1290 "to" => "https://www.w3.org/ns/activitystreams#Public",
1291 "type" => "Create",
1292 "object" => %{
1293 "content" => "blah blah blah",
1294 "type" => "Note",
1295 "attributedTo" => user.ap_id,
1296 "inReplyTo" => nil
1297 },
1298 "actor" => user.ap_id
1299 }
1300
1301 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1302
1303 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
1304 end
1305
1306 test "it correctly processes messages with non-array cc field" do
1307 user = insert(:user)
1308
1309 message = %{
1310 "@context" => "https://www.w3.org/ns/activitystreams",
1311 "to" => user.follower_address,
1312 "cc" => "https://www.w3.org/ns/activitystreams#Public",
1313 "type" => "Create",
1314 "object" => %{
1315 "content" => "blah blah blah",
1316 "type" => "Note",
1317 "attributedTo" => user.ap_id,
1318 "inReplyTo" => nil
1319 },
1320 "actor" => user.ap_id
1321 }
1322
1323 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
1324
1325 assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
1326 assert [user.follower_address] == activity.data["to"]
1327 end
1328
1329 test "it accepts Move activities" do
1330 old_user = insert(:user)
1331 new_user = insert(:user)
1332
1333 message = %{
1334 "@context" => "https://www.w3.org/ns/activitystreams",
1335 "type" => "Move",
1336 "actor" => old_user.ap_id,
1337 "object" => old_user.ap_id,
1338 "target" => new_user.ap_id
1339 }
1340
1341 assert :error = Transmogrifier.handle_incoming(message)
1342
1343 {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
1344
1345 assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
1346 assert activity.actor == old_user.ap_id
1347 assert activity.data["actor"] == old_user.ap_id
1348 assert activity.data["object"] == old_user.ap_id
1349 assert activity.data["target"] == new_user.ap_id
1350 assert activity.data["type"] == "Move"
1351 end
1352 end
1353
1354 describe "`handle_incoming/2`, Mastodon format `replies` handling" do
1355 clear_config([:activitypub, :note_replies_output_limit]) do
1356 Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5)
1357 end
1358
1359 clear_config([:instance, :federation_incoming_replies_max_depth])
1360
1361 setup do
1362 data =
1363 "test/fixtures/mastodon-post-activity.json"
1364 |> File.read!()
1365 |> Poison.decode!()
1366
1367 items = get_in(data, ["object", "replies", "first", "items"])
1368 assert length(items) > 0
1369
1370 %{data: data, items: items}
1371 end
1372
1373 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1374 data: data,
1375 items: items
1376 } do
1377 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
1378
1379 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1380
1381 for id <- items do
1382 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1383 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1384 end
1385 end
1386
1387 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1388 %{data: data} do
1389 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1390
1391 {:ok, _activity} = Transmogrifier.handle_incoming(data)
1392
1393 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1394 end
1395 end
1396
1397 describe "`handle_incoming/2`, Pleroma format `replies` handling" do
1398 clear_config([:activitypub, :note_replies_output_limit]) do
1399 Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5)
1400 end
1401
1402 clear_config([:instance, :federation_incoming_replies_max_depth])
1403
1404 setup do
1405 user = insert(:user)
1406
1407 {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"})
1408
1409 {:ok, reply1} =
1410 CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id})
1411
1412 {:ok, reply2} =
1413 CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id})
1414
1415 replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
1416
1417 {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
1418
1419 Repo.delete(activity.object)
1420 Repo.delete(activity)
1421
1422 %{federation_output: federation_output, replies_uris: replies_uris}
1423 end
1424
1425 test "schedules background fetching of `replies` items if max thread depth limit allows", %{
1426 federation_output: federation_output,
1427 replies_uris: replies_uris
1428 } do
1429 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1)
1430
1431 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1432
1433 for id <- replies_uris do
1434 job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
1435 assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
1436 end
1437 end
1438
1439 test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
1440 %{federation_output: federation_output} do
1441 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1442
1443 {:ok, _activity} = Transmogrifier.handle_incoming(federation_output)
1444
1445 assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
1446 end
1447 end
1448
1449 describe "prepare outgoing" do
1450 test "it inlines private announced objects" do
1451 user = insert(:user)
1452
1453 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"})
1454
1455 {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user)
1456
1457 {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
1458
1459 assert modified["object"]["content"] == "hey"
1460 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1461 end
1462
1463 test "it turns mentions into tags" do
1464 user = insert(:user)
1465 other_user = insert(:user)
1466
1467 {:ok, activity} =
1468 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
1469
1470 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1471 object = modified["object"]
1472
1473 expected_mention = %{
1474 "href" => other_user.ap_id,
1475 "name" => "@#{other_user.nickname}",
1476 "type" => "Mention"
1477 }
1478
1479 expected_tag = %{
1480 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1481 "type" => "Hashtag",
1482 "name" => "#2hu"
1483 }
1484
1485 assert Enum.member?(object["tag"], expected_tag)
1486 assert Enum.member?(object["tag"], expected_mention)
1487 end
1488
1489 test "it adds the sensitive property" do
1490 user = insert(:user)
1491
1492 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
1493 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1494
1495 assert modified["object"]["sensitive"]
1496 end
1497
1498 test "it adds the json-ld context and the conversation property" do
1499 user = insert(:user)
1500
1501 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1502 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1503
1504 assert modified["@context"] ==
1505 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
1506
1507 assert modified["object"]["conversation"] == modified["context"]
1508 end
1509
1510 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
1511 user = insert(:user)
1512
1513 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
1514 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1515
1516 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
1517 end
1518
1519 test "it strips internal hashtag data" do
1520 user = insert(:user)
1521
1522 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1523
1524 expected_tag = %{
1525 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1526 "type" => "Hashtag",
1527 "name" => "#2hu"
1528 }
1529
1530 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1531
1532 assert modified["object"]["tag"] == [expected_tag]
1533 end
1534
1535 test "it strips internal fields" do
1536 user = insert(:user)
1537
1538 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1539
1540 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1541
1542 assert length(modified["object"]["tag"]) == 2
1543
1544 assert is_nil(modified["object"]["emoji"])
1545 assert is_nil(modified["object"]["like_count"])
1546 assert is_nil(modified["object"]["announcements"])
1547 assert is_nil(modified["object"]["announcement_count"])
1548 assert is_nil(modified["object"]["context_id"])
1549 end
1550
1551 test "it strips internal fields of article" do
1552 activity = insert(:article_activity)
1553
1554 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1555
1556 assert length(modified["object"]["tag"]) == 2
1557
1558 assert is_nil(modified["object"]["emoji"])
1559 assert is_nil(modified["object"]["like_count"])
1560 assert is_nil(modified["object"]["announcements"])
1561 assert is_nil(modified["object"]["announcement_count"])
1562 assert is_nil(modified["object"]["context_id"])
1563 assert is_nil(modified["object"]["likes"])
1564 end
1565
1566 test "the directMessage flag is present" do
1567 user = insert(:user)
1568 other_user = insert(:user)
1569
1570 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1571
1572 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1573
1574 assert modified["directMessage"] == false
1575
1576 {:ok, activity} =
1577 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1578
1579 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1580
1581 assert modified["directMessage"] == false
1582
1583 {:ok, activity} =
1584 CommonAPI.post(user, %{
1585 "status" => "@#{other_user.nickname} :moominmamma:",
1586 "visibility" => "direct"
1587 })
1588
1589 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1590
1591 assert modified["directMessage"] == true
1592 end
1593
1594 test "it strips BCC field" do
1595 user = insert(:user)
1596 {:ok, list} = Pleroma.List.create("foo", user)
1597
1598 {:ok, activity} =
1599 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1600
1601 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1602
1603 assert is_nil(modified["bcc"])
1604 end
1605
1606 test "it can handle Listen activities" do
1607 listen_activity = insert(:listen)
1608
1609 {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
1610
1611 assert modified["type"] == "Listen"
1612
1613 user = insert(:user)
1614
1615 {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
1616
1617 {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
1618 end
1619 end
1620
1621 describe "user upgrade" do
1622 test "it upgrades a user to activitypub" do
1623 user =
1624 insert(:user, %{
1625 nickname: "rye@niu.moe",
1626 local: false,
1627 ap_id: "https://niu.moe/users/rye",
1628 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1629 })
1630
1631 user_two = insert(:user)
1632 Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
1633
1634 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1635 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1636 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1637
1638 user = User.get_cached_by_id(user.id)
1639 assert user.note_count == 1
1640
1641 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1642 ObanHelpers.perform_all()
1643
1644 assert user.ap_enabled
1645 assert user.note_count == 1
1646 assert user.follower_address == "https://niu.moe/users/rye/followers"
1647 assert user.following_address == "https://niu.moe/users/rye/following"
1648
1649 user = User.get_cached_by_id(user.id)
1650 assert user.note_count == 1
1651
1652 activity = Activity.get_by_id(activity.id)
1653 assert user.follower_address in activity.recipients
1654
1655 assert %{
1656 "url" => [
1657 %{
1658 "href" =>
1659 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1660 }
1661 ]
1662 } = user.avatar
1663
1664 assert %{
1665 "url" => [
1666 %{
1667 "href" =>
1668 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1669 }
1670 ]
1671 } = user.banner
1672
1673 refute "..." in activity.recipients
1674
1675 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1676 refute user.follower_address in unrelated_activity.recipients
1677
1678 user_two = User.get_cached_by_id(user_two.id)
1679 assert User.following?(user_two, user)
1680 refute "..." in User.following(user_two)
1681 end
1682 end
1683
1684 describe "actor rewriting" do
1685 test "it fixes the actor URL property to be a proper URI" do
1686 data = %{
1687 "url" => %{"href" => "http://example.com"}
1688 }
1689
1690 rewritten = Transmogrifier.maybe_fix_user_object(data)
1691 assert rewritten["url"] == "http://example.com"
1692 end
1693 end
1694
1695 describe "actor origin containment" do
1696 test "it rejects activities which reference objects with bogus origins" do
1697 data = %{
1698 "@context" => "https://www.w3.org/ns/activitystreams",
1699 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1700 "actor" => "http://mastodon.example.org/users/admin",
1701 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1702 "object" => "https://info.pleroma.site/activity.json",
1703 "type" => "Announce"
1704 }
1705
1706 assert capture_log(fn ->
1707 :error = Transmogrifier.handle_incoming(data)
1708 end) =~ "Object containment failed"
1709 end
1710
1711 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1712 data = %{
1713 "@context" => "https://www.w3.org/ns/activitystreams",
1714 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1715 "actor" => "http://mastodon.example.org/users/admin",
1716 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1717 "object" => "https://info.pleroma.site/activity2.json",
1718 "type" => "Announce"
1719 }
1720
1721 assert capture_log(fn ->
1722 :error = Transmogrifier.handle_incoming(data)
1723 end) =~ "Object containment failed"
1724 end
1725
1726 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1727 data = %{
1728 "@context" => "https://www.w3.org/ns/activitystreams",
1729 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1730 "actor" => "http://mastodon.example.org/users/admin",
1731 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1732 "object" => "https://info.pleroma.site/activity3.json",
1733 "type" => "Announce"
1734 }
1735
1736 assert capture_log(fn ->
1737 :error = Transmogrifier.handle_incoming(data)
1738 end) =~ "Object containment failed"
1739 end
1740 end
1741
1742 describe "reserialization" do
1743 test "successfully reserializes a message with inReplyTo == nil" do
1744 user = insert(:user)
1745
1746 message = %{
1747 "@context" => "https://www.w3.org/ns/activitystreams",
1748 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1749 "cc" => [],
1750 "type" => "Create",
1751 "object" => %{
1752 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1753 "cc" => [],
1754 "type" => "Note",
1755 "content" => "Hi",
1756 "inReplyTo" => nil,
1757 "attributedTo" => user.ap_id
1758 },
1759 "actor" => user.ap_id
1760 }
1761
1762 {:ok, activity} = Transmogrifier.handle_incoming(message)
1763
1764 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1765 end
1766
1767 test "successfully reserializes a message with AS2 objects in IR" do
1768 user = insert(:user)
1769
1770 message = %{
1771 "@context" => "https://www.w3.org/ns/activitystreams",
1772 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1773 "cc" => [],
1774 "type" => "Create",
1775 "object" => %{
1776 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1777 "cc" => [],
1778 "type" => "Note",
1779 "content" => "Hi",
1780 "inReplyTo" => nil,
1781 "attributedTo" => user.ap_id,
1782 "tag" => [
1783 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1784 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1785 ]
1786 },
1787 "actor" => user.ap_id
1788 }
1789
1790 {:ok, activity} = Transmogrifier.handle_incoming(message)
1791
1792 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1793 end
1794 end
1795
1796 test "Rewrites Answers to Notes" do
1797 user = insert(:user)
1798
1799 {:ok, poll_activity} =
1800 CommonAPI.post(user, %{
1801 "status" => "suya...",
1802 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
1803 })
1804
1805 poll_object = Object.normalize(poll_activity)
1806 # TODO: Replace with CommonAPI vote creation when implemented
1807 data =
1808 File.read!("test/fixtures/mastodon-vote.json")
1809 |> Poison.decode!()
1810 |> Kernel.put_in(["to"], user.ap_id)
1811 |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
1812 |> Kernel.put_in(["object", "to"], user.ap_id)
1813
1814 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
1815 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
1816
1817 assert data["object"]["type"] == "Note"
1818 end
1819
1820 describe "fix_explicit_addressing" do
1821 setup do
1822 user = insert(:user)
1823 [user: user]
1824 end
1825
1826 test "moves non-explicitly mentioned actors to cc", %{user: user} do
1827 explicitly_mentioned_actors = [
1828 "https://pleroma.gold/users/user1",
1829 "https://pleroma.gold/user2"
1830 ]
1831
1832 object = %{
1833 "actor" => user.ap_id,
1834 "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
1835 "cc" => [],
1836 "tag" =>
1837 Enum.map(explicitly_mentioned_actors, fn href ->
1838 %{"type" => "Mention", "href" => href}
1839 end)
1840 }
1841
1842 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1843 assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
1844 refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
1845 assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
1846 end
1847
1848 test "does not move actor's follower collection to cc", %{user: user} do
1849 object = %{
1850 "actor" => user.ap_id,
1851 "to" => [user.follower_address],
1852 "cc" => []
1853 }
1854
1855 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1856 assert user.follower_address in fixed_object["to"]
1857 refute user.follower_address in fixed_object["cc"]
1858 end
1859
1860 test "removes recipient's follower collection from cc", %{user: user} do
1861 recipient = insert(:user)
1862
1863 object = %{
1864 "actor" => user.ap_id,
1865 "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
1866 "cc" => [user.follower_address, recipient.follower_address]
1867 }
1868
1869 fixed_object = Transmogrifier.fix_explicit_addressing(object)
1870
1871 assert user.follower_address in fixed_object["cc"]
1872 refute recipient.follower_address in fixed_object["cc"]
1873 refute recipient.follower_address in fixed_object["to"]
1874 end
1875 end
1876
1877 describe "fix_summary/1" do
1878 test "returns fixed object" do
1879 assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
1880 assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
1881 assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
1882 end
1883 end
1884
1885 describe "fix_in_reply_to/2" do
1886 clear_config([:instance, :federation_incoming_replies_max_depth])
1887
1888 setup do
1889 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
1890 [data: data]
1891 end
1892
1893 test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
1894 assert Transmogrifier.fix_in_reply_to(data) == data
1895 end
1896
1897 test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
1898 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
1899
1900 object_with_reply =
1901 Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
1902
1903 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1904 assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
1905 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1906
1907 object_with_reply =
1908 Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
1909
1910 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1911 assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
1912 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1913
1914 object_with_reply =
1915 Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
1916
1917 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1918 assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
1919 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1920
1921 object_with_reply = Map.put(data["object"], "inReplyTo", [])
1922 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1923 assert modified_object["inReplyTo"] == []
1924 assert modified_object["inReplyToAtomUri"] == ""
1925 end
1926
1927 @tag capture_log: true
1928 test "returns modified object when allowed incoming reply", %{data: data} do
1929 object_with_reply =
1930 Map.put(
1931 data["object"],
1932 "inReplyTo",
1933 "https://shitposter.club/notice/2827873"
1934 )
1935
1936 Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
1937 modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
1938
1939 assert modified_object["inReplyTo"] ==
1940 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
1941
1942 assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
1943
1944 assert modified_object["conversation"] ==
1945 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1946
1947 assert modified_object["context"] ==
1948 "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
1949 end
1950 end
1951
1952 describe "fix_url/1" do
1953 test "fixes data for object when url is map" do
1954 object = %{
1955 "url" => %{
1956 "type" => "Link",
1957 "mimeType" => "video/mp4",
1958 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1959 }
1960 }
1961
1962 assert Transmogrifier.fix_url(object) == %{
1963 "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1964 }
1965 end
1966
1967 test "fixes data for video object" do
1968 object = %{
1969 "type" => "Video",
1970 "url" => [
1971 %{
1972 "type" => "Link",
1973 "mimeType" => "video/mp4",
1974 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
1975 },
1976 %{
1977 "type" => "Link",
1978 "mimeType" => "video/mp4",
1979 "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
1980 },
1981 %{
1982 "type" => "Link",
1983 "mimeType" => "text/html",
1984 "href" => "https://peertube.-2d4c2d1630e3"
1985 },
1986 %{
1987 "type" => "Link",
1988 "mimeType" => "text/html",
1989 "href" => "https://peertube.-2d4c2d16377-42"
1990 }
1991 ]
1992 }
1993
1994 assert Transmogrifier.fix_url(object) == %{
1995 "attachment" => [
1996 %{
1997 "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
1998 "mimeType" => "video/mp4",
1999 "type" => "Link"
2000 }
2001 ],
2002 "type" => "Video",
2003 "url" => "https://peertube.-2d4c2d1630e3"
2004 }
2005 end
2006
2007 test "fixes url for not Video object" do
2008 object = %{
2009 "type" => "Text",
2010 "url" => [
2011 %{
2012 "type" => "Link",
2013 "mimeType" => "text/html",
2014 "href" => "https://peertube.-2d4c2d1630e3"
2015 },
2016 %{
2017 "type" => "Link",
2018 "mimeType" => "text/html",
2019 "href" => "https://peertube.-2d4c2d16377-42"
2020 }
2021 ]
2022 }
2023
2024 assert Transmogrifier.fix_url(object) == %{
2025 "type" => "Text",
2026 "url" => "https://peertube.-2d4c2d1630e3"
2027 }
2028
2029 assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
2030 "type" => "Text",
2031 "url" => ""
2032 }
2033 end
2034
2035 test "retunrs not modified object" do
2036 assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
2037 end
2038 end
2039
2040 describe "get_obj_helper/2" do
2041 test "returns nil when cannot normalize object" do
2042 assert capture_log(fn ->
2043 refute Transmogrifier.get_obj_helper("test-obj-id")
2044 end) =~ "Unsupported URI scheme"
2045 end
2046
2047 @tag capture_log: true
2048 test "returns {:ok, %Object{}} for success case" do
2049 assert {:ok, %Object{}} =
2050 Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
2051 end
2052 end
2053
2054 describe "fix_attachments/1" do
2055 test "returns not modified object" do
2056 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2057 assert Transmogrifier.fix_attachments(data) == data
2058 end
2059
2060 test "returns modified object when attachment is map" do
2061 assert Transmogrifier.fix_attachments(%{
2062 "attachment" => %{
2063 "mediaType" => "video/mp4",
2064 "url" => "https://peertube.moe/stat-480.mp4"
2065 }
2066 }) == %{
2067 "attachment" => [
2068 %{
2069 "mediaType" => "video/mp4",
2070 "url" => [
2071 %{
2072 "href" => "https://peertube.moe/stat-480.mp4",
2073 "mediaType" => "video/mp4",
2074 "type" => "Link"
2075 }
2076 ]
2077 }
2078 ]
2079 }
2080 end
2081
2082 test "returns modified object when attachment is list" do
2083 assert Transmogrifier.fix_attachments(%{
2084 "attachment" => [
2085 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
2086 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
2087 ]
2088 }) == %{
2089 "attachment" => [
2090 %{
2091 "mediaType" => "video/mp4",
2092 "url" => [
2093 %{
2094 "href" => "https://pe.er/stat-480.mp4",
2095 "mediaType" => "video/mp4",
2096 "type" => "Link"
2097 }
2098 ]
2099 },
2100 %{
2101 "href" => "https://pe.er/stat-480.mp4",
2102 "mediaType" => "video/mp4",
2103 "mimeType" => "video/mp4",
2104 "url" => [
2105 %{
2106 "href" => "https://pe.er/stat-480.mp4",
2107 "mediaType" => "video/mp4",
2108 "type" => "Link"
2109 }
2110 ]
2111 }
2112 ]
2113 }
2114 end
2115 end
2116
2117 describe "fix_emoji/1" do
2118 test "returns not modified object when object not contains tags" do
2119 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2120 assert Transmogrifier.fix_emoji(data) == data
2121 end
2122
2123 test "returns object with emoji when object contains list tags" do
2124 assert Transmogrifier.fix_emoji(%{
2125 "tag" => [
2126 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
2127 %{"type" => "Hashtag"}
2128 ]
2129 }) == %{
2130 "emoji" => %{"bib" => "/test"},
2131 "tag" => [
2132 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
2133 %{"type" => "Hashtag"}
2134 ]
2135 }
2136 end
2137
2138 test "returns object with emoji when object contains map tag" do
2139 assert Transmogrifier.fix_emoji(%{
2140 "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
2141 }) == %{
2142 "emoji" => %{"bib" => "/test"},
2143 "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
2144 }
2145 end
2146 end
2147
2148 describe "set_replies/1" do
2149 clear_config([:activitypub, :note_replies_output_limit]) do
2150 Pleroma.Config.put([:activitypub, :note_replies_output_limit], 2)
2151 end
2152
2153 test "returns unmodified object if activity doesn't have self-replies" do
2154 data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
2155 assert Transmogrifier.set_replies(data) == data
2156 end
2157
2158 test "sets `replies` collection with a limited number of self-replies" do
2159 [user, another_user] = insert_list(2, :user)
2160
2161 {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"})
2162
2163 {:ok, %{id: id2} = self_reply1} =
2164 CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1})
2165
2166 {:ok, self_reply2} =
2167 CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1})
2168
2169 # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2
2170 {:ok, _} =
2171 CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1})
2172
2173 {:ok, _} =
2174 CommonAPI.post(user, %{
2175 "status" => "self-reply to self-reply",
2176 "in_reply_to_status_id" => id2
2177 })
2178
2179 {:ok, _} =
2180 CommonAPI.post(another_user, %{
2181 "status" => "another user's reply",
2182 "in_reply_to_status_id" => id1
2183 })
2184
2185 object = Object.normalize(activity)
2186 replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end)
2187
2188 assert %{"type" => "Collection", "items" => ^replies_uris} =
2189 Transmogrifier.set_replies(object.data)["replies"]
2190 end
2191 end
2192 end