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