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