Merge branch 'stable' into stable-sync/2.1.2
[akkoma] / test / web / common_api / common_api_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.CommonAPITest do
6 use Pleroma.DataCase
7 use Oban.Testing, repo: Pleroma.Repo
8
9 alias Pleroma.Activity
10 alias Pleroma.Chat
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Notification
13 alias Pleroma.Object
14 alias Pleroma.Repo
15 alias Pleroma.User
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.ActivityPub.Transmogrifier
18 alias Pleroma.Web.ActivityPub.Visibility
19 alias Pleroma.Web.AdminAPI.AccountView
20 alias Pleroma.Web.CommonAPI
21
22 import Pleroma.Factory
23 import Mock
24 import Ecto.Query, only: [from: 2]
25
26 require Pleroma.Constants
27
28 setup do: clear_config([:instance, :safe_dm_mentions])
29 setup do: clear_config([:instance, :limit])
30 setup do: clear_config([:instance, :max_pinned_statuses])
31
32 describe "blocking" do
33 setup do
34 blocker = insert(:user)
35 blocked = insert(:user)
36 User.follow(blocker, blocked)
37 User.follow(blocked, blocker)
38 %{blocker: blocker, blocked: blocked}
39 end
40
41 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
42 clear_config([:instance, :federating], true)
43
44 with_mock Pleroma.Web.Federator,
45 publish: fn _ -> nil end do
46 assert {:ok, block} = CommonAPI.block(blocker, blocked)
47
48 assert block.local
49 assert User.blocks?(blocker, blocked)
50 refute User.following?(blocker, blocked)
51 refute User.following?(blocked, blocker)
52
53 assert called(Pleroma.Web.Federator.publish(block))
54 end
55 end
56
57 test "it blocks and does not federate if outgoing blocks are disabled", %{
58 blocker: blocker,
59 blocked: blocked
60 } do
61 clear_config([:instance, :federating], true)
62 clear_config([:activitypub, :outgoing_blocks], false)
63
64 with_mock Pleroma.Web.Federator,
65 publish: fn _ -> nil end do
66 assert {:ok, block} = CommonAPI.block(blocker, blocked)
67
68 assert block.local
69 assert User.blocks?(blocker, blocked)
70 refute User.following?(blocker, blocked)
71 refute User.following?(blocked, blocker)
72
73 refute called(Pleroma.Web.Federator.publish(block))
74 end
75 end
76 end
77
78 describe "posting chat messages" do
79 setup do: clear_config([:instance, :chat_limit])
80
81 test "it posts a chat message without content but with an attachment" do
82 author = insert(:user)
83 recipient = insert(:user)
84
85 file = %Plug.Upload{
86 content_type: "image/jpg",
87 path: Path.absname("test/fixtures/image.jpg"),
88 filename: "an_image.jpg"
89 }
90
91 {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
92
93 with_mocks([
94 {
95 Pleroma.Web.Streamer,
96 [],
97 [
98 stream: fn _, _ ->
99 nil
100 end
101 ]
102 },
103 {
104 Pleroma.Web.Push,
105 [],
106 [
107 send: fn _ -> nil end
108 ]
109 }
110 ]) do
111 {:ok, activity} =
112 CommonAPI.post_chat_message(
113 author,
114 recipient,
115 nil,
116 media_id: upload.id
117 )
118
119 notification =
120 Notification.for_user_and_activity(recipient, activity)
121 |> Repo.preload(:activity)
122
123 assert called(Pleroma.Web.Push.send(notification))
124 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
125 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
126
127 assert activity
128 end
129 end
130
131 test "it adds html newlines" do
132 author = insert(:user)
133 recipient = insert(:user)
134
135 other_user = insert(:user)
136
137 {:ok, activity} =
138 CommonAPI.post_chat_message(
139 author,
140 recipient,
141 "uguu\nuguuu"
142 )
143
144 assert other_user.ap_id not in activity.recipients
145
146 object = Object.normalize(activity, false)
147
148 assert object.data["content"] == "uguu<br/>uguuu"
149 end
150
151 test "it linkifies" do
152 author = insert(:user)
153 recipient = insert(:user)
154
155 other_user = insert(:user)
156
157 {:ok, activity} =
158 CommonAPI.post_chat_message(
159 author,
160 recipient,
161 "https://example.org is the site of @#{other_user.nickname} #2hu"
162 )
163
164 assert other_user.ap_id not in activity.recipients
165
166 object = Object.normalize(activity, false)
167
168 assert object.data["content"] ==
169 "<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{
170 other_user.id
171 }\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>"
172 end
173
174 test "it posts a chat message" do
175 author = insert(:user)
176 recipient = insert(:user)
177
178 {:ok, activity} =
179 CommonAPI.post_chat_message(
180 author,
181 recipient,
182 "a test message <script>alert('uuu')</script> :firefox:"
183 )
184
185 assert activity.data["type"] == "Create"
186 assert activity.local
187 object = Object.normalize(activity)
188
189 assert object.data["type"] == "ChatMessage"
190 assert object.data["to"] == [recipient.ap_id]
191
192 assert object.data["content"] ==
193 "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
194
195 assert object.data["emoji"] == %{
196 "firefox" => "http://localhost:4001/emoji/Firefox.gif"
197 }
198
199 assert Chat.get(author.id, recipient.ap_id)
200 assert Chat.get(recipient.id, author.ap_id)
201
202 assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
203 end
204
205 test "it reject messages over the local limit" do
206 Pleroma.Config.put([:instance, :chat_limit], 2)
207
208 author = insert(:user)
209 recipient = insert(:user)
210
211 {:error, message} =
212 CommonAPI.post_chat_message(
213 author,
214 recipient,
215 "123"
216 )
217
218 assert message == :content_too_long
219 end
220
221 test "it reject messages via MRF" do
222 clear_config([:mrf_keyword, :reject], ["GNO"])
223 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
224
225 author = insert(:user)
226 recipient = insert(:user)
227
228 assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
229 CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
230 end
231 end
232
233 describe "unblocking" do
234 test "it works even without an existing block activity" do
235 blocked = insert(:user)
236 blocker = insert(:user)
237 User.block(blocker, blocked)
238
239 assert User.blocks?(blocker, blocked)
240 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
241 refute User.blocks?(blocker, blocked)
242 end
243 end
244
245 describe "deletion" do
246 test "it works with pruned objects" do
247 user = insert(:user)
248
249 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
250
251 clear_config([:instance, :federating], true)
252
253 Object.normalize(post, false)
254 |> Object.prune()
255
256 with_mock Pleroma.Web.Federator,
257 publish: fn _ -> nil end do
258 assert {:ok, delete} = CommonAPI.delete(post.id, user)
259 assert delete.local
260 assert called(Pleroma.Web.Federator.publish(delete))
261 end
262
263 refute Activity.get_by_id(post.id)
264 end
265
266 test "it allows users to delete their posts" do
267 user = insert(:user)
268
269 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
270
271 clear_config([:instance, :federating], true)
272
273 with_mock Pleroma.Web.Federator,
274 publish: fn _ -> nil end do
275 assert {:ok, delete} = CommonAPI.delete(post.id, user)
276 assert delete.local
277 assert called(Pleroma.Web.Federator.publish(delete))
278 end
279
280 refute Activity.get_by_id(post.id)
281 end
282
283 test "it does not allow a user to delete their posts" do
284 user = insert(:user)
285 other_user = insert(:user)
286
287 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
288
289 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
290 assert Activity.get_by_id(post.id)
291 end
292
293 test "it allows moderators to delete other user's posts" do
294 user = insert(:user)
295 moderator = insert(:user, is_moderator: true)
296
297 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
298
299 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
300 assert delete.local
301
302 refute Activity.get_by_id(post.id)
303 end
304
305 test "it allows admins to delete other user's posts" do
306 user = insert(:user)
307 moderator = insert(:user, is_admin: true)
308
309 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
310
311 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
312 assert delete.local
313
314 refute Activity.get_by_id(post.id)
315 end
316
317 test "superusers deleting non-local posts won't federate the delete" do
318 # This is the user of the ingested activity
319 _user =
320 insert(:user,
321 local: false,
322 ap_id: "http://mastodon.example.org/users/admin",
323 last_refreshed_at: NaiveDateTime.utc_now()
324 )
325
326 moderator = insert(:user, is_admin: true)
327
328 data =
329 File.read!("test/fixtures/mastodon-post-activity.json")
330 |> Jason.decode!()
331
332 {:ok, post} = Transmogrifier.handle_incoming(data)
333
334 with_mock Pleroma.Web.Federator,
335 publish: fn _ -> nil end do
336 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
337 assert delete.local
338 refute called(Pleroma.Web.Federator.publish(:_))
339 end
340
341 refute Activity.get_by_id(post.id)
342 end
343 end
344
345 test "favoriting race condition" do
346 user = insert(:user)
347 users_serial = insert_list(10, :user)
348 users = insert_list(10, :user)
349
350 {:ok, activity} = CommonAPI.post(user, %{status: "."})
351
352 users_serial
353 |> Enum.map(fn user ->
354 CommonAPI.favorite(user, activity.id)
355 end)
356
357 object = Object.get_by_ap_id(activity.data["object"])
358 assert object.data["like_count"] == 10
359
360 users
361 |> Enum.map(fn user ->
362 Task.async(fn ->
363 CommonAPI.favorite(user, activity.id)
364 end)
365 end)
366 |> Enum.map(&Task.await/1)
367
368 object = Object.get_by_ap_id(activity.data["object"])
369 assert object.data["like_count"] == 20
370 end
371
372 test "repeating race condition" do
373 user = insert(:user)
374 users_serial = insert_list(10, :user)
375 users = insert_list(10, :user)
376
377 {:ok, activity} = CommonAPI.post(user, %{status: "."})
378
379 users_serial
380 |> Enum.map(fn user ->
381 CommonAPI.repeat(activity.id, user)
382 end)
383
384 object = Object.get_by_ap_id(activity.data["object"])
385 assert object.data["announcement_count"] == 10
386
387 users
388 |> Enum.map(fn user ->
389 Task.async(fn ->
390 CommonAPI.repeat(activity.id, user)
391 end)
392 end)
393 |> Enum.map(&Task.await/1)
394
395 object = Object.get_by_ap_id(activity.data["object"])
396 assert object.data["announcement_count"] == 20
397 end
398
399 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
400 user = insert(:user)
401 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
402
403 [participation] = Participation.for_user(user)
404
405 {:ok, convo_reply} =
406 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
407
408 assert Visibility.is_direct?(convo_reply)
409
410 assert activity.data["context"] == convo_reply.data["context"]
411 end
412
413 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
414 har = insert(:user)
415 jafnhar = insert(:user)
416 tridi = insert(:user)
417
418 {:ok, activity} =
419 CommonAPI.post(har, %{
420 status: "@#{jafnhar.nickname} hey",
421 visibility: "direct"
422 })
423
424 assert har.ap_id in activity.recipients
425 assert jafnhar.ap_id in activity.recipients
426
427 [participation] = Participation.for_user(har)
428
429 {:ok, activity} =
430 CommonAPI.post(har, %{
431 status: "I don't really like @#{tridi.nickname}",
432 visibility: "direct",
433 in_reply_to_status_id: activity.id,
434 in_reply_to_conversation_id: participation.id
435 })
436
437 assert har.ap_id in activity.recipients
438 assert jafnhar.ap_id in activity.recipients
439 refute tridi.ap_id in activity.recipients
440 end
441
442 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
443 har = insert(:user)
444 jafnhar = insert(:user)
445 tridi = insert(:user)
446
447 Pleroma.Config.put([:instance, :safe_dm_mentions], true)
448
449 {:ok, activity} =
450 CommonAPI.post(har, %{
451 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
452 visibility: "direct"
453 })
454
455 refute tridi.ap_id in activity.recipients
456 assert jafnhar.ap_id in activity.recipients
457 end
458
459 test "it de-duplicates tags" do
460 user = insert(:user)
461 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
462
463 object = Object.normalize(activity)
464
465 assert object.data["tag"] == ["2hu"]
466 end
467
468 test "it adds emoji in the object" do
469 user = insert(:user)
470 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
471
472 assert Object.normalize(activity).data["emoji"]["firefox"]
473 end
474
475 describe "posting" do
476 test "deactivated users can't post" do
477 user = insert(:user, deactivated: true)
478 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
479 end
480
481 test "it supports explicit addressing" do
482 user = insert(:user)
483 user_two = insert(:user)
484 user_three = insert(:user)
485 user_four = insert(:user)
486
487 {:ok, activity} =
488 CommonAPI.post(user, %{
489 status:
490 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
491 to: [user_two.nickname, user_four.nickname, "nonexistent"]
492 })
493
494 assert user.ap_id in activity.recipients
495 assert user_two.ap_id in activity.recipients
496 assert user_four.ap_id in activity.recipients
497 refute user_three.ap_id in activity.recipients
498 end
499
500 test "it filters out obviously bad tags when accepting a post as HTML" do
501 user = insert(:user)
502
503 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
504
505 {:ok, activity} =
506 CommonAPI.post(user, %{
507 status: post,
508 content_type: "text/html"
509 })
510
511 object = Object.normalize(activity)
512
513 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
514 assert object.data["source"] == post
515 end
516
517 test "it filters out obviously bad tags when accepting a post as Markdown" do
518 user = insert(:user)
519
520 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
521
522 {:ok, activity} =
523 CommonAPI.post(user, %{
524 status: post,
525 content_type: "text/markdown"
526 })
527
528 object = Object.normalize(activity)
529
530 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
531 assert object.data["source"] == post
532 end
533
534 test "it does not allow replies to direct messages that are not direct messages themselves" do
535 user = insert(:user)
536
537 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
538
539 assert {:ok, _} =
540 CommonAPI.post(user, %{
541 status: "suya..",
542 visibility: "direct",
543 in_reply_to_status_id: activity.id
544 })
545
546 Enum.each(["public", "private", "unlisted"], fn visibility ->
547 assert {:error, "The message visibility must be direct"} =
548 CommonAPI.post(user, %{
549 status: "suya..",
550 visibility: visibility,
551 in_reply_to_status_id: activity.id
552 })
553 end)
554 end
555
556 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
557 user = insert(:user)
558 other_user = insert(:user)
559 third_user = insert(:user)
560
561 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
562
563 {:ok, open_answer} =
564 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
565
566 # The OP is implicitly added
567 assert user.ap_id in open_answer.recipients
568
569 {:ok, secret_answer} =
570 CommonAPI.post(other_user, %{
571 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
572 in_reply_to_status_id: post.id,
573 visibility: "direct"
574 })
575
576 assert third_user.ap_id in secret_answer.recipients
577
578 # The OP is not added
579 refute user.ap_id in secret_answer.recipients
580 end
581
582 test "it allows to address a list" do
583 user = insert(:user)
584 {:ok, list} = Pleroma.List.create("foo", user)
585
586 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
587
588 assert activity.data["bcc"] == [list.ap_id]
589 assert activity.recipients == [list.ap_id, user.ap_id]
590 assert activity.data["listMessage"] == list.ap_id
591 end
592
593 test "it returns error when status is empty and no attachments" do
594 user = insert(:user)
595
596 assert {:error, "Cannot post an empty status without attachments"} =
597 CommonAPI.post(user, %{status: ""})
598 end
599
600 test "it validates character limits are correctly enforced" do
601 Pleroma.Config.put([:instance, :limit], 5)
602
603 user = insert(:user)
604
605 assert {:error, "The status is over the character limit"} =
606 CommonAPI.post(user, %{status: "foobar"})
607
608 assert {:ok, activity} = CommonAPI.post(user, %{status: "12345"})
609 end
610
611 test "it can handle activities that expire" do
612 user = insert(:user)
613
614 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
615
616 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
617
618 assert_enqueued(
619 worker: Pleroma.Workers.PurgeExpiredActivity,
620 args: %{activity_id: activity.id},
621 scheduled_at: expires_at
622 )
623 end
624 end
625
626 describe "reactions" do
627 test "reacting to a status with an emoji" do
628 user = insert(:user)
629 other_user = insert(:user)
630
631 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
632
633 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
634
635 assert reaction.data["actor"] == user.ap_id
636 assert reaction.data["content"] == "👍"
637
638 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
639
640 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
641 end
642
643 test "unreacting to a status with an emoji" do
644 user = insert(:user)
645 other_user = insert(:user)
646
647 clear_config([:instance, :federating], true)
648
649 with_mock Pleroma.Web.Federator,
650 publish: fn _ -> nil end do
651 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
652 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
653
654 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
655
656 assert unreaction.data["type"] == "Undo"
657 assert unreaction.data["object"] == reaction.data["id"]
658 assert unreaction.local
659
660 # On federation, it contains the undone (and deleted) object
661 unreaction_with_object = %{
662 unreaction
663 | data: Map.put(unreaction.data, "object", reaction.data)
664 }
665
666 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
667 end
668 end
669
670 test "repeating a status" do
671 user = insert(:user)
672 other_user = insert(:user)
673
674 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
675
676 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
677 assert Visibility.is_public?(announce_activity)
678 end
679
680 test "can't repeat a repeat" do
681 user = insert(:user)
682 other_user = insert(:user)
683 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
684
685 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
686
687 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
688 end
689
690 test "repeating a status privately" do
691 user = insert(:user)
692 other_user = insert(:user)
693
694 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
695
696 {:ok, %Activity{} = announce_activity} =
697 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
698
699 assert Visibility.is_private?(announce_activity)
700 refute Visibility.visible_for_user?(announce_activity, nil)
701 end
702
703 test "favoriting a status" do
704 user = insert(:user)
705 other_user = insert(:user)
706
707 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
708
709 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
710 assert data["type"] == "Like"
711 assert data["actor"] == user.ap_id
712 assert data["object"] == post_activity.data["object"]
713 end
714
715 test "retweeting a status twice returns the status" do
716 user = insert(:user)
717 other_user = insert(:user)
718
719 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
720 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
721 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
722 end
723
724 test "favoriting a status twice returns ok, but without the like activity" do
725 user = insert(:user)
726 other_user = insert(:user)
727
728 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
729 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
730 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
731 end
732 end
733
734 describe "pinned statuses" do
735 setup do
736 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
737
738 user = insert(:user)
739 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
740
741 [user: user, activity: activity]
742 end
743
744 test "pin status", %{user: user, activity: activity} do
745 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
746
747 id = activity.id
748 user = refresh_record(user)
749
750 assert %User{pinned_activities: [^id]} = user
751 end
752
753 test "pin poll", %{user: user} do
754 {:ok, activity} =
755 CommonAPI.post(user, %{
756 status: "How is fediverse today?",
757 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
758 })
759
760 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
761
762 id = activity.id
763 user = refresh_record(user)
764
765 assert %User{pinned_activities: [^id]} = user
766 end
767
768 test "unlisted statuses can be pinned", %{user: user} do
769 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
770 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
771 end
772
773 test "only self-authored can be pinned", %{activity: activity} do
774 user = insert(:user)
775
776 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
777 end
778
779 test "max pinned statuses", %{user: user, activity: activity_one} do
780 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
781
782 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
783
784 user = refresh_record(user)
785
786 assert {:error, "You have already pinned the maximum number of statuses"} =
787 CommonAPI.pin(activity_two.id, user)
788 end
789
790 test "unpin status", %{user: user, activity: activity} do
791 {:ok, activity} = CommonAPI.pin(activity.id, user)
792
793 user = refresh_record(user)
794
795 id = activity.id
796
797 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
798
799 user = refresh_record(user)
800
801 assert %User{pinned_activities: []} = user
802 end
803
804 test "should unpin when deleting a status", %{user: user, activity: activity} do
805 {:ok, activity} = CommonAPI.pin(activity.id, user)
806
807 user = refresh_record(user)
808
809 assert {:ok, _} = CommonAPI.delete(activity.id, user)
810
811 user = refresh_record(user)
812
813 assert %User{pinned_activities: []} = user
814 end
815 end
816
817 describe "mute tests" do
818 setup do
819 user = insert(:user)
820
821 activity = insert(:note_activity)
822
823 [user: user, activity: activity]
824 end
825
826 test "marks notifications as read after mute" do
827 author = insert(:user)
828 activity = insert(:note_activity, user: author)
829
830 friend1 = insert(:user)
831 friend2 = insert(:user)
832
833 {:ok, reply_activity} =
834 CommonAPI.post(
835 friend2,
836 %{
837 status: "@#{author.nickname} @#{friend1.nickname} test reply",
838 in_reply_to_status_id: activity.id
839 }
840 )
841
842 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
843 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
844
845 assert Repo.aggregate(
846 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
847 :count
848 ) == 1
849
850 unread_notifications =
851 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
852
853 assert Enum.any?(unread_notifications, fn n ->
854 n.type == "favourite" && n.activity_id == favorite_activity.id
855 end)
856
857 assert Enum.any?(unread_notifications, fn n ->
858 n.type == "reblog" && n.activity_id == repeat_activity.id
859 end)
860
861 assert Enum.any?(unread_notifications, fn n ->
862 n.type == "mention" && n.activity_id == reply_activity.id
863 end)
864
865 {:ok, _} = CommonAPI.add_mute(author, activity)
866 assert CommonAPI.thread_muted?(author, activity)
867
868 assert Repo.aggregate(
869 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
870 :count
871 ) == 1
872
873 read_notifications =
874 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
875
876 assert Enum.any?(read_notifications, fn n ->
877 n.type == "favourite" && n.activity_id == favorite_activity.id
878 end)
879
880 assert Enum.any?(read_notifications, fn n ->
881 n.type == "reblog" && n.activity_id == repeat_activity.id
882 end)
883
884 assert Enum.any?(read_notifications, fn n ->
885 n.type == "mention" && n.activity_id == reply_activity.id
886 end)
887 end
888
889 test "add mute", %{user: user, activity: activity} do
890 {:ok, _} = CommonAPI.add_mute(user, activity)
891 assert CommonAPI.thread_muted?(user, activity)
892 end
893
894 test "remove mute", %{user: user, activity: activity} do
895 CommonAPI.add_mute(user, activity)
896 {:ok, _} = CommonAPI.remove_mute(user, activity)
897 refute CommonAPI.thread_muted?(user, activity)
898 end
899
900 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
901 CommonAPI.add_mute(user, activity)
902 {:error, _} = CommonAPI.add_mute(user, activity)
903 end
904 end
905
906 describe "reports" do
907 test "creates a report" do
908 reporter = insert(:user)
909 target_user = insert(:user)
910
911 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
912
913 reporter_ap_id = reporter.ap_id
914 target_ap_id = target_user.ap_id
915 activity_ap_id = activity.data["id"]
916 comment = "foobar"
917
918 report_data = %{
919 account_id: target_user.id,
920 comment: comment,
921 status_ids: [activity.id]
922 }
923
924 note_obj = %{
925 "type" => "Note",
926 "id" => activity_ap_id,
927 "content" => "foobar",
928 "published" => activity.object.data["published"],
929 "actor" => AccountView.render("show.json", %{user: target_user})
930 }
931
932 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
933
934 assert %Activity{
935 actor: ^reporter_ap_id,
936 data: %{
937 "type" => "Flag",
938 "content" => ^comment,
939 "object" => [^target_ap_id, ^note_obj],
940 "state" => "open"
941 }
942 } = flag_activity
943 end
944
945 test "updates report state" do
946 [reporter, target_user] = insert_pair(:user)
947 activity = insert(:note_activity, user: target_user)
948
949 {:ok, %Activity{id: report_id}} =
950 CommonAPI.report(reporter, %{
951 account_id: target_user.id,
952 comment: "I feel offended",
953 status_ids: [activity.id]
954 })
955
956 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
957
958 assert report.data["state"] == "resolved"
959
960 [reported_user, activity_id] = report.data["object"]
961
962 assert reported_user == target_user.ap_id
963 assert activity_id == activity.data["id"]
964 end
965
966 test "does not update report state when state is unsupported" do
967 [reporter, target_user] = insert_pair(:user)
968 activity = insert(:note_activity, user: target_user)
969
970 {:ok, %Activity{id: report_id}} =
971 CommonAPI.report(reporter, %{
972 account_id: target_user.id,
973 comment: "I feel offended",
974 status_ids: [activity.id]
975 })
976
977 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
978 end
979
980 test "updates state of multiple reports" do
981 [reporter, target_user] = insert_pair(:user)
982 activity = insert(:note_activity, user: target_user)
983
984 {:ok, %Activity{id: first_report_id}} =
985 CommonAPI.report(reporter, %{
986 account_id: target_user.id,
987 comment: "I feel offended",
988 status_ids: [activity.id]
989 })
990
991 {:ok, %Activity{id: second_report_id}} =
992 CommonAPI.report(reporter, %{
993 account_id: target_user.id,
994 comment: "I feel very offended!",
995 status_ids: [activity.id]
996 })
997
998 {:ok, report_ids} =
999 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1000
1001 first_report = Activity.get_by_id(first_report_id)
1002 second_report = Activity.get_by_id(second_report_id)
1003
1004 assert report_ids -- [first_report_id, second_report_id] == []
1005 assert first_report.data["state"] == "resolved"
1006 assert second_report.data["state"] == "resolved"
1007 end
1008 end
1009
1010 describe "reblog muting" do
1011 setup do
1012 muter = insert(:user)
1013
1014 muted = insert(:user)
1015
1016 [muter: muter, muted: muted]
1017 end
1018
1019 test "add a reblog mute", %{muter: muter, muted: muted} do
1020 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1021
1022 assert User.showing_reblogs?(muter, muted) == false
1023 end
1024
1025 test "remove a reblog mute", %{muter: muter, muted: muted} do
1026 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1027 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1028
1029 assert User.showing_reblogs?(muter, muted) == true
1030 end
1031 end
1032
1033 describe "follow/2" do
1034 test "directly follows a non-locked local user" do
1035 [follower, followed] = insert_pair(:user)
1036 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1037
1038 assert User.following?(follower, followed)
1039 end
1040 end
1041
1042 describe "unfollow/2" do
1043 test "also unsubscribes a user" do
1044 [follower, followed] = insert_pair(:user)
1045 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1046 {:ok, _subscription} = User.subscribe(follower, followed)
1047
1048 assert User.subscribed_to?(follower, followed)
1049
1050 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1051
1052 refute User.subscribed_to?(follower, followed)
1053 end
1054
1055 test "cancels a pending follow for a local user" do
1056 follower = insert(:user)
1057 followed = insert(:user, locked: true)
1058
1059 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1060 CommonAPI.follow(follower, followed)
1061
1062 assert User.get_follow_state(follower, followed) == :follow_pending
1063 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1064 assert User.get_follow_state(follower, followed) == nil
1065
1066 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1067 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1068
1069 assert %{
1070 data: %{
1071 "type" => "Undo",
1072 "object" => %{"type" => "Follow", "state" => "cancelled"}
1073 }
1074 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1075 end
1076
1077 test "cancels a pending follow for a remote user" do
1078 follower = insert(:user)
1079 followed = insert(:user, locked: true, local: false, ap_enabled: true)
1080
1081 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1082 CommonAPI.follow(follower, followed)
1083
1084 assert User.get_follow_state(follower, followed) == :follow_pending
1085 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1086 assert User.get_follow_state(follower, followed) == nil
1087
1088 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1089 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1090
1091 assert %{
1092 data: %{
1093 "type" => "Undo",
1094 "object" => %{"type" => "Follow", "state" => "cancelled"}
1095 }
1096 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1097 end
1098 end
1099
1100 describe "accept_follow_request/2" do
1101 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1102 user = insert(:user, locked: true)
1103 follower = insert(:user)
1104 follower_two = insert(:user)
1105
1106 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1107 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1108 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1109
1110 assert follow_activity.data["state"] == "pending"
1111 assert follow_activity_two.data["state"] == "pending"
1112 assert follow_activity_three.data["state"] == "pending"
1113
1114 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1115
1116 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1117 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1118 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1119 end
1120
1121 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1122 user = insert(:user, locked: true)
1123 follower = insert(:user)
1124 follower_two = insert(:user)
1125
1126 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1127 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1128 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1129
1130 assert follow_activity.data["state"] == "pending"
1131 assert follow_activity_two.data["state"] == "pending"
1132 assert follow_activity_three.data["state"] == "pending"
1133
1134 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1135
1136 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1137 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1138 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1139 end
1140
1141 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1142 user = insert(:user, locked: true)
1143 not_follower = insert(:user)
1144 CommonAPI.accept_follow_request(not_follower, user)
1145
1146 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1147 end
1148 end
1149
1150 describe "vote/3" do
1151 test "does not allow to vote twice" do
1152 user = insert(:user)
1153 other_user = insert(:user)
1154
1155 {:ok, activity} =
1156 CommonAPI.post(user, %{
1157 status: "Am I cute?",
1158 poll: %{options: ["Yes", "No"], expires_in: 20}
1159 })
1160
1161 object = Object.normalize(activity)
1162
1163 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1164
1165 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1166 end
1167 end
1168
1169 describe "listen/2" do
1170 test "returns a valid activity" do
1171 user = insert(:user)
1172
1173 {:ok, activity} =
1174 CommonAPI.listen(user, %{
1175 title: "lain radio episode 1",
1176 album: "lain radio",
1177 artist: "lain",
1178 length: 180_000
1179 })
1180
1181 object = Object.normalize(activity)
1182
1183 assert object.data["title"] == "lain radio episode 1"
1184
1185 assert Visibility.get_visibility(activity) == "public"
1186 end
1187
1188 test "respects visibility=private" do
1189 user = insert(:user)
1190
1191 {:ok, activity} =
1192 CommonAPI.listen(user, %{
1193 title: "lain radio episode 1",
1194 album: "lain radio",
1195 artist: "lain",
1196 length: 180_000,
1197 visibility: "private"
1198 })
1199
1200 object = Object.normalize(activity)
1201
1202 assert object.data["title"] == "lain radio episode 1"
1203
1204 assert Visibility.get_visibility(activity) == "private"
1205 end
1206 end
1207
1208 describe "get_user/1" do
1209 test "gets user by ap_id" do
1210 user = insert(:user)
1211 assert CommonAPI.get_user(user.ap_id) == user
1212 end
1213
1214 test "gets user by guessed nickname" do
1215 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1216 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1217 end
1218
1219 test "fallback" do
1220 assert %User{
1221 name: "",
1222 ap_id: "",
1223 nickname: "erroruser@example.com"
1224 } = CommonAPI.get_user("")
1225 end
1226 end
1227 end