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