Handle poll votes
[akkoma] / test / web / activity_pub / transmogrifier_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 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.User
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.OStatus
16 alias Pleroma.Web.Websub.WebsubClientSubscription
17
18 import Pleroma.Factory
19 alias Pleroma.Web.CommonAPI
20
21 setup_all do
22 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
23 :ok
24 end
25
26 describe "handle_incoming" do
27 test "it ignores an incoming notice if we already have it" do
28 activity = insert(:note_activity)
29
30 data =
31 File.read!("test/fixtures/mastodon-post-activity.json")
32 |> Poison.decode!()
33 |> Map.put("object", activity.data["object"])
34
35 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
36
37 assert activity == returned_activity
38 end
39
40 test "it fetches replied-to activities if we don't have them" do
41 data =
42 File.read!("test/fixtures/mastodon-post-activity.json")
43 |> Poison.decode!()
44
45 object =
46 data["object"]
47 |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
48
49 data =
50 data
51 |> Map.put("object", object)
52
53 {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
54 returned_object = Object.normalize(returned_activity.data["object"])
55
56 assert activity =
57 Activity.get_create_by_object_ap_id(
58 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
59 )
60
61 assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
62 end
63
64 test "it works for incoming notices" do
65 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
66
67 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
68
69 assert data["id"] ==
70 "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
71
72 assert data["context"] ==
73 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
74
75 assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
76
77 assert data["cc"] == [
78 "http://mastodon.example.org/users/admin/followers",
79 "http://localtesting.pleroma.lol/users/lain"
80 ]
81
82 assert data["actor"] == "http://mastodon.example.org/users/admin"
83
84 object = Object.normalize(data["object"]).data
85 assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
86
87 assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
88
89 assert object["cc"] == [
90 "http://mastodon.example.org/users/admin/followers",
91 "http://localtesting.pleroma.lol/users/lain"
92 ]
93
94 assert object["actor"] == "http://mastodon.example.org/users/admin"
95 assert object["attributedTo"] == "http://mastodon.example.org/users/admin"
96
97 assert object["context"] ==
98 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
99
100 assert object["sensitive"] == true
101
102 user = User.get_cached_by_ap_id(object["actor"])
103
104 assert user.info.note_count == 1
105 end
106
107 test "it works for incoming notices with hashtags" do
108 data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
109
110 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
111 object = Object.normalize(data["object"])
112
113 assert Enum.at(object.data["tag"], 2) == "moo"
114 end
115
116 test "it works for incoming questions" do
117 data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
118
119 {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
120
121 object = Object.normalize(activity)
122
123 assert Enum.all?(object.data["oneOf"], fn choice ->
124 choice["name"] in [
125 "Dunno",
126 "Everyone knows that!",
127 "25 char limit is dumb",
128 "I can't even fit a funny"
129 ]
130 end)
131 end
132
133 test "in increments vote counters on question activities" do
134 user = insert(:user)
135
136 {:ok, activity} =
137 CommonAPI.post(user, %{
138 "status" => "suya...",
139 "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
140 })
141
142 object = Object.normalize(activity)
143
144 data =
145 File.read!("test/fixtures/mastodon-vote.json")
146 |> Poison.decode!()
147 |> Kernel.put_in(["to"], user.ap_id)
148 |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
149 |> Kernel.put_in(["object", "to"], user.ap_id)
150
151 {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
152
153 object = Object.get_by_ap_id(object.data["id"])
154
155 assert Enum.any?(
156 object.data["oneOf"],
157 fn
158 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
159 _ -> false
160 end
161 )
162 end
163
164 test "it works for incoming notices with contentMap" do
165 data =
166 File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
167
168 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
169 object = Object.normalize(data["object"])
170
171 assert object.data["content"] ==
172 "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
173 end
174
175 test "it works for incoming notices with to/cc not being an array (kroeg)" do
176 data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
177
178 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
179 object = Object.normalize(data["object"])
180
181 assert object.data["content"] ==
182 "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
183 end
184
185 test "it works for incoming announces with actor being inlined (kroeg)" do
186 data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
187
188 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
189
190 assert data["actor"] == "https://puckipedia.com/"
191 end
192
193 test "it works for incoming notices with tag not being an array (kroeg)" do
194 data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
195
196 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
197 object = Object.normalize(data["object"])
198
199 assert object.data["emoji"] == %{
200 "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
201 }
202
203 data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
204
205 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
206 object = Object.normalize(data["object"])
207
208 assert "test" in object.data["tag"]
209 end
210
211 test "it works for incoming notices with url not being a string (prismo)" do
212 data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
213
214 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
215 object = Object.normalize(data["object"])
216
217 assert object.data["url"] == "https://prismo.news/posts/83"
218 end
219
220 test "it cleans up incoming notices which are not really DMs" do
221 user = insert(:user)
222 other_user = insert(:user)
223
224 to = [user.ap_id, other_user.ap_id]
225
226 data =
227 File.read!("test/fixtures/mastodon-post-activity.json")
228 |> Poison.decode!()
229 |> Map.put("to", to)
230 |> Map.put("cc", [])
231
232 object =
233 data["object"]
234 |> Map.put("to", to)
235 |> Map.put("cc", [])
236
237 data = Map.put(data, "object", object)
238
239 {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
240
241 assert data["to"] == []
242 assert data["cc"] == to
243
244 object_data = Object.normalize(activity).data
245
246 assert object_data["to"] == []
247 assert object_data["cc"] == to
248 end
249
250 test "it works for incoming follow requests" do
251 user = insert(:user)
252
253 data =
254 File.read!("test/fixtures/mastodon-follow-activity.json")
255 |> Poison.decode!()
256 |> Map.put("object", user.ap_id)
257
258 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
259
260 assert data["actor"] == "http://mastodon.example.org/users/admin"
261 assert data["type"] == "Follow"
262 assert data["id"] == "http://mastodon.example.org/users/admin#follows/2"
263 assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
264 end
265
266 test "it rejects incoming follow requests from blocked users when deny_follow_blocked is enabled" do
267 Pleroma.Config.put([:user, :deny_follow_blocked], true)
268
269 user = insert(:user)
270 {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin")
271
272 {:ok, user} = User.block(user, target)
273
274 data =
275 File.read!("test/fixtures/mastodon-follow-activity.json")
276 |> Poison.decode!()
277 |> Map.put("object", user.ap_id)
278
279 {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
280
281 %Activity{} = activity = Activity.get_by_ap_id(id)
282
283 assert activity.data["state"] == "reject"
284 end
285
286 test "it works for incoming follow requests from hubzilla" do
287 user = insert(:user)
288
289 data =
290 File.read!("test/fixtures/hubzilla-follow-activity.json")
291 |> Poison.decode!()
292 |> Map.put("object", user.ap_id)
293 |> Utils.normalize_params()
294
295 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
296
297 assert data["actor"] == "https://hubzilla.example.org/channel/kaniini"
298 assert data["type"] == "Follow"
299 assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2"
300 assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
301 end
302
303 test "it works for incoming likes" do
304 user = insert(:user)
305 {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
306
307 data =
308 File.read!("test/fixtures/mastodon-like.json")
309 |> Poison.decode!()
310 |> Map.put("object", activity.data["object"])
311
312 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
313
314 assert data["actor"] == "http://mastodon.example.org/users/admin"
315 assert data["type"] == "Like"
316 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
317 assert data["object"] == activity.data["object"]
318 end
319
320 test "it returns an error for incoming unlikes wihout a like activity" do
321 user = insert(:user)
322 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
323
324 data =
325 File.read!("test/fixtures/mastodon-undo-like.json")
326 |> Poison.decode!()
327 |> Map.put("object", activity.data["object"])
328
329 assert Transmogrifier.handle_incoming(data) == :error
330 end
331
332 test "it works for incoming unlikes with an existing like activity" do
333 user = insert(:user)
334 {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
335
336 like_data =
337 File.read!("test/fixtures/mastodon-like.json")
338 |> Poison.decode!()
339 |> Map.put("object", activity.data["object"])
340
341 {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
342
343 data =
344 File.read!("test/fixtures/mastodon-undo-like.json")
345 |> Poison.decode!()
346 |> Map.put("object", like_data)
347 |> Map.put("actor", like_data["actor"])
348
349 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
350
351 assert data["actor"] == "http://mastodon.example.org/users/admin"
352 assert data["type"] == "Undo"
353 assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
354 assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
355 end
356
357 test "it works for incoming announces" do
358 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
359
360 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
361
362 assert data["actor"] == "http://mastodon.example.org/users/admin"
363 assert data["type"] == "Announce"
364
365 assert data["id"] ==
366 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
367
368 assert data["object"] ==
369 "http://mastodon.example.org/users/admin/statuses/99541947525187367"
370
371 assert Activity.get_create_by_object_ap_id(data["object"])
372 end
373
374 test "it works for incoming announces with an existing activity" do
375 user = insert(:user)
376 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
377
378 data =
379 File.read!("test/fixtures/mastodon-announce.json")
380 |> Poison.decode!()
381 |> Map.put("object", activity.data["object"])
382
383 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
384
385 assert data["actor"] == "http://mastodon.example.org/users/admin"
386 assert data["type"] == "Announce"
387
388 assert data["id"] ==
389 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
390
391 assert data["object"] == activity.data["object"]
392
393 assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
394 end
395
396 test "it does not clobber the addressing on announce activities" do
397 user = insert(:user)
398 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
399
400 data =
401 File.read!("test/fixtures/mastodon-announce.json")
402 |> Poison.decode!()
403 |> Map.put("object", Object.normalize(activity).data["id"])
404 |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
405 |> Map.put("cc", [])
406
407 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
408
409 assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
410 end
411
412 test "it ensures that as:Public activities make it to their followers collection" do
413 user = insert(:user)
414
415 data =
416 File.read!("test/fixtures/mastodon-post-activity.json")
417 |> Poison.decode!()
418 |> Map.put("actor", user.ap_id)
419 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
420 |> Map.put("cc", [])
421
422 object =
423 data["object"]
424 |> Map.put("attributedTo", user.ap_id)
425 |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
426 |> Map.put("cc", [])
427
428 data = Map.put(data, "object", object)
429
430 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
431
432 assert data["cc"] == [User.ap_followers(user)]
433 end
434
435 test "it ensures that address fields become lists" do
436 user = insert(:user)
437
438 data =
439 File.read!("test/fixtures/mastodon-post-activity.json")
440 |> Poison.decode!()
441 |> Map.put("actor", user.ap_id)
442 |> Map.put("to", nil)
443 |> Map.put("cc", nil)
444
445 object =
446 data["object"]
447 |> Map.put("attributedTo", user.ap_id)
448 |> Map.put("to", nil)
449 |> Map.put("cc", nil)
450
451 data = Map.put(data, "object", object)
452
453 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
454
455 assert !is_nil(data["to"])
456 assert !is_nil(data["cc"])
457 end
458
459 test "it works for incoming update activities" do
460 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
461
462 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
463 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
464
465 object =
466 update_data["object"]
467 |> Map.put("actor", data["actor"])
468 |> Map.put("id", data["actor"])
469
470 update_data =
471 update_data
472 |> Map.put("actor", data["actor"])
473 |> Map.put("object", object)
474
475 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
476
477 user = User.get_cached_by_ap_id(data["actor"])
478 assert user.name == "gargle"
479
480 assert user.avatar["url"] == [
481 %{
482 "href" =>
483 "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
484 }
485 ]
486
487 assert user.info.banner["url"] == [
488 %{
489 "href" =>
490 "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
491 }
492 ]
493
494 assert user.bio == "<p>Some bio</p>"
495 end
496
497 test "it works for incoming update activities which lock the account" do
498 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
499
500 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
501 update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
502
503 object =
504 update_data["object"]
505 |> Map.put("actor", data["actor"])
506 |> Map.put("id", data["actor"])
507 |> Map.put("manuallyApprovesFollowers", true)
508
509 update_data =
510 update_data
511 |> Map.put("actor", data["actor"])
512 |> Map.put("object", object)
513
514 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
515
516 user = User.get_cached_by_ap_id(data["actor"])
517 assert user.info.locked == true
518 end
519
520 test "it works for incoming deletes" do
521 activity = insert(:note_activity)
522
523 data =
524 File.read!("test/fixtures/mastodon-delete.json")
525 |> Poison.decode!()
526
527 object =
528 data["object"]
529 |> Map.put("id", activity.data["object"])
530
531 data =
532 data
533 |> Map.put("object", object)
534 |> Map.put("actor", activity.data["actor"])
535
536 {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
537
538 refute Activity.get_by_id(activity.id)
539 end
540
541 test "it fails for incoming deletes with spoofed origin" do
542 activity = insert(:note_activity)
543
544 data =
545 File.read!("test/fixtures/mastodon-delete.json")
546 |> Poison.decode!()
547
548 object =
549 data["object"]
550 |> Map.put("id", activity.data["object"])
551
552 data =
553 data
554 |> Map.put("object", object)
555
556 :error = Transmogrifier.handle_incoming(data)
557
558 assert Activity.get_by_id(activity.id)
559 end
560
561 test "it works for incoming unannounces with an existing notice" do
562 user = insert(:user)
563 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
564
565 announce_data =
566 File.read!("test/fixtures/mastodon-announce.json")
567 |> Poison.decode!()
568 |> Map.put("object", activity.data["object"])
569
570 {:ok, %Activity{data: announce_data, local: false}} =
571 Transmogrifier.handle_incoming(announce_data)
572
573 data =
574 File.read!("test/fixtures/mastodon-undo-announce.json")
575 |> Poison.decode!()
576 |> Map.put("object", announce_data)
577 |> Map.put("actor", announce_data["actor"])
578
579 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
580
581 assert data["type"] == "Undo"
582 assert data["object"]["type"] == "Announce"
583 assert data["object"]["object"] == activity.data["object"]
584
585 assert data["object"]["id"] ==
586 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
587 end
588
589 test "it works for incomming unfollows with an existing follow" do
590 user = insert(:user)
591
592 follow_data =
593 File.read!("test/fixtures/mastodon-follow-activity.json")
594 |> Poison.decode!()
595 |> Map.put("object", user.ap_id)
596
597 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
598
599 data =
600 File.read!("test/fixtures/mastodon-unfollow-activity.json")
601 |> Poison.decode!()
602 |> Map.put("object", follow_data)
603
604 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
605
606 assert data["type"] == "Undo"
607 assert data["object"]["type"] == "Follow"
608 assert data["object"]["object"] == user.ap_id
609 assert data["actor"] == "http://mastodon.example.org/users/admin"
610
611 refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
612 end
613
614 test "it works for incoming blocks" do
615 user = insert(:user)
616
617 data =
618 File.read!("test/fixtures/mastodon-block-activity.json")
619 |> Poison.decode!()
620 |> Map.put("object", user.ap_id)
621
622 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
623
624 assert data["type"] == "Block"
625 assert data["object"] == user.ap_id
626 assert data["actor"] == "http://mastodon.example.org/users/admin"
627
628 blocker = User.get_cached_by_ap_id(data["actor"])
629
630 assert User.blocks?(blocker, user)
631 end
632
633 test "incoming blocks successfully tear down any follow relationship" do
634 blocker = insert(:user)
635 blocked = insert(:user)
636
637 data =
638 File.read!("test/fixtures/mastodon-block-activity.json")
639 |> Poison.decode!()
640 |> Map.put("object", blocked.ap_id)
641 |> Map.put("actor", blocker.ap_id)
642
643 {:ok, blocker} = User.follow(blocker, blocked)
644 {:ok, blocked} = User.follow(blocked, blocker)
645
646 assert User.following?(blocker, blocked)
647 assert User.following?(blocked, blocker)
648
649 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
650
651 assert data["type"] == "Block"
652 assert data["object"] == blocked.ap_id
653 assert data["actor"] == blocker.ap_id
654
655 blocker = User.get_cached_by_ap_id(data["actor"])
656 blocked = User.get_cached_by_ap_id(data["object"])
657
658 assert User.blocks?(blocker, blocked)
659
660 refute User.following?(blocker, blocked)
661 refute User.following?(blocked, blocker)
662 end
663
664 test "it works for incoming unblocks with an existing block" do
665 user = insert(:user)
666
667 block_data =
668 File.read!("test/fixtures/mastodon-block-activity.json")
669 |> Poison.decode!()
670 |> Map.put("object", user.ap_id)
671
672 {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
673
674 data =
675 File.read!("test/fixtures/mastodon-unblock-activity.json")
676 |> Poison.decode!()
677 |> Map.put("object", block_data)
678
679 {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
680 assert data["type"] == "Undo"
681 assert data["object"]["type"] == "Block"
682 assert data["object"]["object"] == user.ap_id
683 assert data["actor"] == "http://mastodon.example.org/users/admin"
684
685 blocker = User.get_cached_by_ap_id(data["actor"])
686
687 refute User.blocks?(blocker, user)
688 end
689
690 test "it works for incoming accepts which were pre-accepted" do
691 follower = insert(:user)
692 followed = insert(:user)
693
694 {:ok, follower} = User.follow(follower, followed)
695 assert User.following?(follower, followed) == true
696
697 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
698
699 accept_data =
700 File.read!("test/fixtures/mastodon-accept-activity.json")
701 |> Poison.decode!()
702 |> Map.put("actor", followed.ap_id)
703
704 object =
705 accept_data["object"]
706 |> Map.put("actor", follower.ap_id)
707 |> Map.put("id", follow_activity.data["id"])
708
709 accept_data = Map.put(accept_data, "object", object)
710
711 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
712 refute activity.local
713
714 assert activity.data["object"] == follow_activity.data["id"]
715
716 follower = User.get_cached_by_id(follower.id)
717
718 assert User.following?(follower, followed) == true
719 end
720
721 test "it works for incoming accepts which were orphaned" do
722 follower = insert(:user)
723 followed = insert(:user, %{info: %User.Info{locked: true}})
724
725 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
726
727 accept_data =
728 File.read!("test/fixtures/mastodon-accept-activity.json")
729 |> Poison.decode!()
730 |> Map.put("actor", followed.ap_id)
731
732 accept_data =
733 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
734
735 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
736 assert activity.data["object"] == follow_activity.data["id"]
737
738 follower = User.get_cached_by_id(follower.id)
739
740 assert User.following?(follower, followed) == true
741 end
742
743 test "it works for incoming accepts which are referenced by IRI only" do
744 follower = insert(:user)
745 followed = insert(:user, %{info: %User.Info{locked: true}})
746
747 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
748
749 accept_data =
750 File.read!("test/fixtures/mastodon-accept-activity.json")
751 |> Poison.decode!()
752 |> Map.put("actor", followed.ap_id)
753 |> Map.put("object", follow_activity.data["id"])
754
755 {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
756 assert activity.data["object"] == follow_activity.data["id"]
757
758 follower = User.get_cached_by_id(follower.id)
759
760 assert User.following?(follower, followed) == true
761 end
762
763 test "it fails for incoming accepts which cannot be correlated" do
764 follower = insert(:user)
765 followed = insert(:user, %{info: %User.Info{locked: true}})
766
767 accept_data =
768 File.read!("test/fixtures/mastodon-accept-activity.json")
769 |> Poison.decode!()
770 |> Map.put("actor", followed.ap_id)
771
772 accept_data =
773 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
774
775 :error = Transmogrifier.handle_incoming(accept_data)
776
777 follower = User.get_cached_by_id(follower.id)
778
779 refute User.following?(follower, followed) == true
780 end
781
782 test "it fails for incoming rejects which cannot be correlated" do
783 follower = insert(:user)
784 followed = insert(:user, %{info: %User.Info{locked: true}})
785
786 accept_data =
787 File.read!("test/fixtures/mastodon-reject-activity.json")
788 |> Poison.decode!()
789 |> Map.put("actor", followed.ap_id)
790
791 accept_data =
792 Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
793
794 :error = Transmogrifier.handle_incoming(accept_data)
795
796 follower = User.get_cached_by_id(follower.id)
797
798 refute User.following?(follower, followed) == true
799 end
800
801 test "it works for incoming rejects which are orphaned" do
802 follower = insert(:user)
803 followed = insert(:user, %{info: %User.Info{locked: true}})
804
805 {:ok, follower} = User.follow(follower, followed)
806 {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
807
808 assert User.following?(follower, followed) == true
809
810 reject_data =
811 File.read!("test/fixtures/mastodon-reject-activity.json")
812 |> Poison.decode!()
813 |> Map.put("actor", followed.ap_id)
814
815 reject_data =
816 Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
817
818 {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
819 refute activity.local
820
821 follower = User.get_cached_by_id(follower.id)
822
823 assert User.following?(follower, followed) == false
824 end
825
826 test "it works for incoming rejects which are referenced by IRI only" do
827 follower = insert(:user)
828 followed = insert(:user, %{info: %User.Info{locked: true}})
829
830 {:ok, follower} = User.follow(follower, followed)
831 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
832
833 assert User.following?(follower, followed) == true
834
835 reject_data =
836 File.read!("test/fixtures/mastodon-reject-activity.json")
837 |> Poison.decode!()
838 |> Map.put("actor", followed.ap_id)
839 |> Map.put("object", follow_activity.data["id"])
840
841 {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
842
843 follower = User.get_cached_by_id(follower.id)
844
845 assert User.following?(follower, followed) == false
846 end
847
848 test "it rejects activities without a valid ID" do
849 user = insert(:user)
850
851 data =
852 File.read!("test/fixtures/mastodon-follow-activity.json")
853 |> Poison.decode!()
854 |> Map.put("object", user.ap_id)
855 |> Map.put("id", "")
856
857 :error = Transmogrifier.handle_incoming(data)
858 end
859
860 test "it remaps video URLs as attachments if necessary" do
861 {:ok, object} =
862 Fetcher.fetch_object_from_id(
863 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
864 )
865
866 attachment = %{
867 "type" => "Link",
868 "mediaType" => "video/mp4",
869 "href" =>
870 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
871 "mimeType" => "video/mp4",
872 "size" => 5_015_880,
873 "url" => [
874 %{
875 "href" =>
876 "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
877 "mediaType" => "video/mp4",
878 "type" => "Link"
879 }
880 ],
881 "width" => 480
882 }
883
884 assert object.data["url"] ==
885 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
886
887 assert object.data["attachment"] == [attachment]
888 end
889
890 test "it accepts Flag activities" do
891 user = insert(:user)
892 other_user = insert(:user)
893
894 {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
895 object = Object.normalize(activity.data["object"])
896
897 message = %{
898 "@context" => "https://www.w3.org/ns/activitystreams",
899 "cc" => [user.ap_id],
900 "object" => [user.ap_id, object.data["id"]],
901 "type" => "Flag",
902 "content" => "blocked AND reported!!!",
903 "actor" => other_user.ap_id
904 }
905
906 assert {:ok, activity} = Transmogrifier.handle_incoming(message)
907
908 assert activity.data["object"] == [user.ap_id, object.data["id"]]
909 assert activity.data["content"] == "blocked AND reported!!!"
910 assert activity.data["actor"] == other_user.ap_id
911 assert activity.data["cc"] == [user.ap_id]
912 end
913 end
914
915 describe "prepare outgoing" do
916 test "it turns mentions into tags" do
917 user = insert(:user)
918 other_user = insert(:user)
919
920 {:ok, activity} =
921 CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
922
923 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
924 object = modified["object"]
925
926 expected_mention = %{
927 "href" => other_user.ap_id,
928 "name" => "@#{other_user.nickname}",
929 "type" => "Mention"
930 }
931
932 expected_tag = %{
933 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
934 "type" => "Hashtag",
935 "name" => "#2hu"
936 }
937
938 assert Enum.member?(object["tag"], expected_tag)
939 assert Enum.member?(object["tag"], expected_mention)
940 end
941
942 test "it adds the sensitive property" do
943 user = insert(:user)
944
945 {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
946 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
947
948 assert modified["object"]["sensitive"]
949 end
950
951 test "it adds the json-ld context and the conversation property" do
952 user = insert(:user)
953
954 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
955 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
956
957 assert modified["@context"] ==
958 Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
959
960 assert modified["object"]["conversation"] == modified["context"]
961 end
962
963 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
964 user = insert(:user)
965
966 {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
967 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
968
969 assert modified["object"]["actor"] == modified["object"]["attributedTo"]
970 end
971
972 test "it translates ostatus IDs to external URLs" do
973 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
974 {:ok, [referent_activity]} = OStatus.handle_incoming(incoming)
975
976 user = insert(:user)
977
978 {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user)
979 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
980
981 assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29"
982 end
983
984 test "it translates ostatus reply_to IDs to external URLs" do
985 incoming = File.read!("test/fixtures/incoming_note_activity.xml")
986 {:ok, [referred_activity]} = OStatus.handle_incoming(incoming)
987
988 user = insert(:user)
989
990 {:ok, activity} =
991 CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
992
993 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
994
995 assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
996 end
997
998 test "it strips internal hashtag data" do
999 user = insert(:user)
1000
1001 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
1002
1003 expected_tag = %{
1004 "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
1005 "type" => "Hashtag",
1006 "name" => "#2hu"
1007 }
1008
1009 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1010
1011 assert modified["object"]["tag"] == [expected_tag]
1012 end
1013
1014 test "it strips internal fields" do
1015 user = insert(:user)
1016
1017 {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
1018
1019 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1020
1021 assert length(modified["object"]["tag"]) == 2
1022
1023 assert is_nil(modified["object"]["emoji"])
1024 assert is_nil(modified["object"]["like_count"])
1025 assert is_nil(modified["object"]["announcements"])
1026 assert is_nil(modified["object"]["announcement_count"])
1027 assert is_nil(modified["object"]["context_id"])
1028 end
1029
1030 test "it strips internal fields of article" do
1031 activity = insert(:article_activity)
1032
1033 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1034
1035 assert length(modified["object"]["tag"]) == 2
1036
1037 assert is_nil(modified["object"]["emoji"])
1038 assert is_nil(modified["object"]["like_count"])
1039 assert is_nil(modified["object"]["announcements"])
1040 assert is_nil(modified["object"]["announcement_count"])
1041 assert is_nil(modified["object"]["context_id"])
1042 end
1043
1044 test "it adds like collection to object" do
1045 activity = insert(:note_activity)
1046 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1047
1048 assert modified["object"]["likes"]["type"] == "OrderedCollection"
1049 assert modified["object"]["likes"]["totalItems"] == 0
1050 end
1051
1052 test "the directMessage flag is present" do
1053 user = insert(:user)
1054 other_user = insert(:user)
1055
1056 {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
1057
1058 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1059
1060 assert modified["directMessage"] == false
1061
1062 {:ok, activity} =
1063 CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
1064
1065 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1066
1067 assert modified["directMessage"] == false
1068
1069 {:ok, activity} =
1070 CommonAPI.post(user, %{
1071 "status" => "@#{other_user.nickname} :moominmamma:",
1072 "visibility" => "direct"
1073 })
1074
1075 {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
1076
1077 assert modified["directMessage"] == true
1078 end
1079 end
1080
1081 describe "user upgrade" do
1082 test "it upgrades a user to activitypub" do
1083 user =
1084 insert(:user, %{
1085 nickname: "rye@niu.moe",
1086 local: false,
1087 ap_id: "https://niu.moe/users/rye",
1088 follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
1089 })
1090
1091 user_two = insert(:user, %{following: [user.follower_address]})
1092
1093 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1094 {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
1095 assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
1096
1097 user = User.get_cached_by_id(user.id)
1098 assert user.info.note_count == 1
1099
1100 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
1101 assert user.info.ap_enabled
1102 assert user.info.note_count == 1
1103 assert user.follower_address == "https://niu.moe/users/rye/followers"
1104
1105 user = User.get_cached_by_id(user.id)
1106 assert user.info.note_count == 1
1107
1108 activity = Activity.get_by_id(activity.id)
1109 assert user.follower_address in activity.recipients
1110
1111 assert %{
1112 "url" => [
1113 %{
1114 "href" =>
1115 "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
1116 }
1117 ]
1118 } = user.avatar
1119
1120 assert %{
1121 "url" => [
1122 %{
1123 "href" =>
1124 "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
1125 }
1126 ]
1127 } = user.info.banner
1128
1129 refute "..." in activity.recipients
1130
1131 unrelated_activity = Activity.get_by_id(unrelated_activity.id)
1132 refute user.follower_address in unrelated_activity.recipients
1133
1134 user_two = User.get_cached_by_id(user_two.id)
1135 assert user.follower_address in user_two.following
1136 refute "..." in user_two.following
1137 end
1138 end
1139
1140 describe "maybe_retire_websub" do
1141 test "it deletes all websub client subscripitions with the user as topic" do
1142 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
1143 {:ok, ws} = Repo.insert(subscription)
1144
1145 subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
1146 {:ok, ws2} = Repo.insert(subscription)
1147
1148 Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
1149
1150 refute Repo.get(WebsubClientSubscription, ws.id)
1151 assert Repo.get(WebsubClientSubscription, ws2.id)
1152 end
1153 end
1154
1155 describe "actor rewriting" do
1156 test "it fixes the actor URL property to be a proper URI" do
1157 data = %{
1158 "url" => %{"href" => "http://example.com"}
1159 }
1160
1161 rewritten = Transmogrifier.maybe_fix_user_object(data)
1162 assert rewritten["url"] == "http://example.com"
1163 end
1164 end
1165
1166 describe "actor origin containment" do
1167 test "it rejects activities which reference objects with bogus origins" do
1168 data = %{
1169 "@context" => "https://www.w3.org/ns/activitystreams",
1170 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1171 "actor" => "http://mastodon.example.org/users/admin",
1172 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1173 "object" => "https://info.pleroma.site/activity.json",
1174 "type" => "Announce"
1175 }
1176
1177 :error = Transmogrifier.handle_incoming(data)
1178 end
1179
1180 test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
1181 data = %{
1182 "@context" => "https://www.w3.org/ns/activitystreams",
1183 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1184 "actor" => "http://mastodon.example.org/users/admin",
1185 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1186 "object" => "https://info.pleroma.site/activity2.json",
1187 "type" => "Announce"
1188 }
1189
1190 :error = Transmogrifier.handle_incoming(data)
1191 end
1192
1193 test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
1194 data = %{
1195 "@context" => "https://www.w3.org/ns/activitystreams",
1196 "id" => "http://mastodon.example.org/users/admin/activities/1234",
1197 "actor" => "http://mastodon.example.org/users/admin",
1198 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1199 "object" => "https://info.pleroma.site/activity3.json",
1200 "type" => "Announce"
1201 }
1202
1203 :error = Transmogrifier.handle_incoming(data)
1204 end
1205 end
1206
1207 describe "reserialization" do
1208 test "successfully reserializes a message with inReplyTo == nil" do
1209 user = insert(:user)
1210
1211 message = %{
1212 "@context" => "https://www.w3.org/ns/activitystreams",
1213 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1214 "cc" => [],
1215 "type" => "Create",
1216 "object" => %{
1217 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1218 "cc" => [],
1219 "type" => "Note",
1220 "content" => "Hi",
1221 "inReplyTo" => nil,
1222 "attributedTo" => user.ap_id
1223 },
1224 "actor" => user.ap_id
1225 }
1226
1227 {:ok, activity} = Transmogrifier.handle_incoming(message)
1228
1229 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1230 end
1231
1232 test "successfully reserializes a message with AS2 objects in IR" do
1233 user = insert(:user)
1234
1235 message = %{
1236 "@context" => "https://www.w3.org/ns/activitystreams",
1237 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1238 "cc" => [],
1239 "type" => "Create",
1240 "object" => %{
1241 "to" => ["https://www.w3.org/ns/activitystreams#Public"],
1242 "cc" => [],
1243 "type" => "Note",
1244 "content" => "Hi",
1245 "inReplyTo" => nil,
1246 "attributedTo" => user.ap_id,
1247 "tag" => [
1248 %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
1249 %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
1250 ]
1251 },
1252 "actor" => user.ap_id
1253 }
1254
1255 {:ok, activity} = Transmogrifier.handle_incoming(message)
1256
1257 {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
1258 end
1259 end
1260 end