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