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