Merge branch 'develop' into refactor/deactivated_user_field
[akkoma] / test / pleroma / web / common_api_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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, is_active: false)
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 "author can repeat own private statuses" do
748 author = insert(:user)
749 follower = insert(:user)
750 CommonAPI.follow(follower, author)
751
752 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
753
754 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
755
756 assert Visibility.is_private?(announce_activity)
757 refute Visibility.visible_for_user?(announce_activity, nil)
758
759 assert Visibility.visible_for_user?(activity, follower)
760 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
761 end
762
763 test "favoriting a status" do
764 user = insert(:user)
765 other_user = insert(:user)
766
767 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
768
769 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
770 assert data["type"] == "Like"
771 assert data["actor"] == user.ap_id
772 assert data["object"] == post_activity.data["object"]
773 end
774
775 test "retweeting a status twice returns the status" do
776 user = insert(:user)
777 other_user = insert(:user)
778
779 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
780 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
781 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
782 end
783
784 test "favoriting a status twice returns ok, but without the like activity" do
785 user = insert(:user)
786 other_user = insert(:user)
787
788 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
789 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
790 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
791 end
792 end
793
794 describe "pinned statuses" do
795 setup do
796 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
797
798 user = insert(:user)
799 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
800
801 [user: user, activity: activity]
802 end
803
804 test "pin status", %{user: user, activity: activity} do
805 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
806
807 id = activity.id
808 user = refresh_record(user)
809
810 assert %User{pinned_activities: [^id]} = user
811 end
812
813 test "pin poll", %{user: user} do
814 {:ok, activity} =
815 CommonAPI.post(user, %{
816 status: "How is fediverse today?",
817 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
818 })
819
820 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
821
822 id = activity.id
823 user = refresh_record(user)
824
825 assert %User{pinned_activities: [^id]} = user
826 end
827
828 test "unlisted statuses can be pinned", %{user: user} do
829 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
830 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
831 end
832
833 test "only self-authored can be pinned", %{activity: activity} do
834 user = insert(:user)
835
836 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
837 end
838
839 test "max pinned statuses", %{user: user, activity: activity_one} do
840 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
841
842 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
843
844 user = refresh_record(user)
845
846 assert {:error, "You have already pinned the maximum number of statuses"} =
847 CommonAPI.pin(activity_two.id, user)
848 end
849
850 test "unpin status", %{user: user, activity: activity} do
851 {:ok, activity} = CommonAPI.pin(activity.id, user)
852
853 user = refresh_record(user)
854
855 id = activity.id
856
857 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
858
859 user = refresh_record(user)
860
861 assert %User{pinned_activities: []} = user
862 end
863
864 test "should unpin when deleting a status", %{user: user, activity: activity} do
865 {:ok, activity} = CommonAPI.pin(activity.id, user)
866
867 user = refresh_record(user)
868
869 assert {:ok, _} = CommonAPI.delete(activity.id, user)
870
871 user = refresh_record(user)
872
873 assert %User{pinned_activities: []} = user
874 end
875 end
876
877 describe "mute tests" do
878 setup do
879 user = insert(:user)
880
881 activity = insert(:note_activity)
882
883 [user: user, activity: activity]
884 end
885
886 test "marks notifications as read after mute" do
887 author = insert(:user)
888 activity = insert(:note_activity, user: author)
889
890 friend1 = insert(:user)
891 friend2 = insert(:user)
892
893 {:ok, reply_activity} =
894 CommonAPI.post(
895 friend2,
896 %{
897 status: "@#{author.nickname} @#{friend1.nickname} test reply",
898 in_reply_to_status_id: activity.id
899 }
900 )
901
902 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
903 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
904
905 assert Repo.aggregate(
906 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
907 :count
908 ) == 1
909
910 unread_notifications =
911 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
912
913 assert Enum.any?(unread_notifications, fn n ->
914 n.type == "favourite" && n.activity_id == favorite_activity.id
915 end)
916
917 assert Enum.any?(unread_notifications, fn n ->
918 n.type == "reblog" && n.activity_id == repeat_activity.id
919 end)
920
921 assert Enum.any?(unread_notifications, fn n ->
922 n.type == "mention" && n.activity_id == reply_activity.id
923 end)
924
925 {:ok, _} = CommonAPI.add_mute(author, activity)
926 assert CommonAPI.thread_muted?(author, activity)
927
928 assert Repo.aggregate(
929 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
930 :count
931 ) == 1
932
933 read_notifications =
934 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
935
936 assert Enum.any?(read_notifications, fn n ->
937 n.type == "favourite" && n.activity_id == favorite_activity.id
938 end)
939
940 assert Enum.any?(read_notifications, fn n ->
941 n.type == "reblog" && n.activity_id == repeat_activity.id
942 end)
943
944 assert Enum.any?(read_notifications, fn n ->
945 n.type == "mention" && n.activity_id == reply_activity.id
946 end)
947 end
948
949 test "add mute", %{user: user, activity: activity} do
950 {:ok, _} = CommonAPI.add_mute(user, activity)
951 assert CommonAPI.thread_muted?(user, activity)
952 end
953
954 test "add expiring mute", %{user: user, activity: activity} do
955 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
956 assert CommonAPI.thread_muted?(user, activity)
957
958 worker = Pleroma.Workers.MuteExpireWorker
959 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
960
961 assert_enqueued(
962 worker: worker,
963 args: args
964 )
965
966 assert :ok = perform_job(worker, args)
967 refute CommonAPI.thread_muted?(user, activity)
968 end
969
970 test "remove mute", %{user: user, activity: activity} do
971 CommonAPI.add_mute(user, activity)
972 {:ok, _} = CommonAPI.remove_mute(user, activity)
973 refute CommonAPI.thread_muted?(user, activity)
974 end
975
976 test "remove mute by ids", %{user: user, activity: activity} do
977 CommonAPI.add_mute(user, activity)
978 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
979 refute CommonAPI.thread_muted?(user, activity)
980 end
981
982 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
983 CommonAPI.add_mute(user, activity)
984 {:error, _} = CommonAPI.add_mute(user, activity)
985 end
986 end
987
988 describe "reports" do
989 test "creates a report" do
990 reporter = insert(:user)
991 target_user = insert(:user)
992
993 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
994
995 reporter_ap_id = reporter.ap_id
996 target_ap_id = target_user.ap_id
997 activity_ap_id = activity.data["id"]
998 comment = "foobar"
999
1000 report_data = %{
1001 account_id: target_user.id,
1002 comment: comment,
1003 status_ids: [activity.id]
1004 }
1005
1006 note_obj = %{
1007 "type" => "Note",
1008 "id" => activity_ap_id,
1009 "content" => "foobar",
1010 "published" => activity.object.data["published"],
1011 "actor" => AccountView.render("show.json", %{user: target_user})
1012 }
1013
1014 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1015
1016 assert %Activity{
1017 actor: ^reporter_ap_id,
1018 data: %{
1019 "type" => "Flag",
1020 "content" => ^comment,
1021 "object" => [^target_ap_id, ^note_obj],
1022 "state" => "open"
1023 }
1024 } = flag_activity
1025 end
1026
1027 test "updates report state" do
1028 [reporter, target_user] = insert_pair(:user)
1029 activity = insert(:note_activity, user: target_user)
1030
1031 {:ok, %Activity{id: report_id}} =
1032 CommonAPI.report(reporter, %{
1033 account_id: target_user.id,
1034 comment: "I feel offended",
1035 status_ids: [activity.id]
1036 })
1037
1038 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1039
1040 assert report.data["state"] == "resolved"
1041
1042 [reported_user, activity_id] = report.data["object"]
1043
1044 assert reported_user == target_user.ap_id
1045 assert activity_id == activity.data["id"]
1046 end
1047
1048 test "does not update report state when state is unsupported" do
1049 [reporter, target_user] = insert_pair(:user)
1050 activity = insert(:note_activity, user: target_user)
1051
1052 {:ok, %Activity{id: report_id}} =
1053 CommonAPI.report(reporter, %{
1054 account_id: target_user.id,
1055 comment: "I feel offended",
1056 status_ids: [activity.id]
1057 })
1058
1059 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1060 end
1061
1062 test "updates state of multiple reports" do
1063 [reporter, target_user] = insert_pair(:user)
1064 activity = insert(:note_activity, user: target_user)
1065
1066 {:ok, %Activity{id: first_report_id}} =
1067 CommonAPI.report(reporter, %{
1068 account_id: target_user.id,
1069 comment: "I feel offended",
1070 status_ids: [activity.id]
1071 })
1072
1073 {:ok, %Activity{id: second_report_id}} =
1074 CommonAPI.report(reporter, %{
1075 account_id: target_user.id,
1076 comment: "I feel very offended!",
1077 status_ids: [activity.id]
1078 })
1079
1080 {:ok, report_ids} =
1081 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1082
1083 first_report = Activity.get_by_id(first_report_id)
1084 second_report = Activity.get_by_id(second_report_id)
1085
1086 assert report_ids -- [first_report_id, second_report_id] == []
1087 assert first_report.data["state"] == "resolved"
1088 assert second_report.data["state"] == "resolved"
1089 end
1090 end
1091
1092 describe "reblog muting" do
1093 setup do
1094 muter = insert(:user)
1095
1096 muted = insert(:user)
1097
1098 [muter: muter, muted: muted]
1099 end
1100
1101 test "add a reblog mute", %{muter: muter, muted: muted} do
1102 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1103
1104 assert User.showing_reblogs?(muter, muted) == false
1105 end
1106
1107 test "remove a reblog mute", %{muter: muter, muted: muted} do
1108 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1109 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1110
1111 assert User.showing_reblogs?(muter, muted) == true
1112 end
1113 end
1114
1115 describe "follow/2" do
1116 test "directly follows a non-locked local user" do
1117 [follower, followed] = insert_pair(:user)
1118 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1119
1120 assert User.following?(follower, followed)
1121 end
1122 end
1123
1124 describe "unfollow/2" do
1125 test "also unsubscribes a user" do
1126 [follower, followed] = insert_pair(:user)
1127 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1128 {:ok, _subscription} = User.subscribe(follower, followed)
1129
1130 assert User.subscribed_to?(follower, followed)
1131
1132 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1133
1134 refute User.subscribed_to?(follower, followed)
1135 end
1136
1137 test "cancels a pending follow for a local user" do
1138 follower = insert(:user)
1139 followed = insert(:user, is_locked: true)
1140
1141 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1142 CommonAPI.follow(follower, followed)
1143
1144 assert User.get_follow_state(follower, followed) == :follow_pending
1145 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1146 assert User.get_follow_state(follower, followed) == nil
1147
1148 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1149 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1150
1151 assert %{
1152 data: %{
1153 "type" => "Undo",
1154 "object" => %{"type" => "Follow", "state" => "cancelled"}
1155 }
1156 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1157 end
1158
1159 test "cancels a pending follow for a remote user" do
1160 follower = insert(:user)
1161 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1162
1163 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1164 CommonAPI.follow(follower, followed)
1165
1166 assert User.get_follow_state(follower, followed) == :follow_pending
1167 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1168 assert User.get_follow_state(follower, followed) == nil
1169
1170 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1171 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1172
1173 assert %{
1174 data: %{
1175 "type" => "Undo",
1176 "object" => %{"type" => "Follow", "state" => "cancelled"}
1177 }
1178 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1179 end
1180 end
1181
1182 describe "accept_follow_request/2" do
1183 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1184 user = insert(:user, is_locked: true)
1185 follower = insert(:user)
1186 follower_two = insert(:user)
1187
1188 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1189 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1190 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1191
1192 assert follow_activity.data["state"] == "pending"
1193 assert follow_activity_two.data["state"] == "pending"
1194 assert follow_activity_three.data["state"] == "pending"
1195
1196 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1197
1198 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1199 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1200 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1201 end
1202
1203 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1204 user = insert(:user, is_locked: true)
1205 follower = insert(:user)
1206 follower_two = insert(:user)
1207
1208 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1209 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1210 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1211
1212 assert follow_activity.data["state"] == "pending"
1213 assert follow_activity_two.data["state"] == "pending"
1214 assert follow_activity_three.data["state"] == "pending"
1215
1216 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1217
1218 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1219 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1220 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1221 end
1222
1223 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1224 user = insert(:user, is_locked: true)
1225 not_follower = insert(:user)
1226 CommonAPI.accept_follow_request(not_follower, user)
1227
1228 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1229 end
1230 end
1231
1232 describe "vote/3" do
1233 test "does not allow to vote twice" do
1234 user = insert(:user)
1235 other_user = insert(:user)
1236
1237 {:ok, activity} =
1238 CommonAPI.post(user, %{
1239 status: "Am I cute?",
1240 poll: %{options: ["Yes", "No"], expires_in: 20}
1241 })
1242
1243 object = Object.normalize(activity, fetch: false)
1244
1245 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1246
1247 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1248 end
1249 end
1250
1251 describe "listen/2" do
1252 test "returns a valid activity" do
1253 user = insert(:user)
1254
1255 {:ok, activity} =
1256 CommonAPI.listen(user, %{
1257 title: "lain radio episode 1",
1258 album: "lain radio",
1259 artist: "lain",
1260 length: 180_000
1261 })
1262
1263 object = Object.normalize(activity, fetch: false)
1264
1265 assert object.data["title"] == "lain radio episode 1"
1266
1267 assert Visibility.get_visibility(activity) == "public"
1268 end
1269
1270 test "respects visibility=private" do
1271 user = insert(:user)
1272
1273 {:ok, activity} =
1274 CommonAPI.listen(user, %{
1275 title: "lain radio episode 1",
1276 album: "lain radio",
1277 artist: "lain",
1278 length: 180_000,
1279 visibility: "private"
1280 })
1281
1282 object = Object.normalize(activity, fetch: false)
1283
1284 assert object.data["title"] == "lain radio episode 1"
1285
1286 assert Visibility.get_visibility(activity) == "private"
1287 end
1288 end
1289
1290 describe "get_user/1" do
1291 test "gets user by ap_id" do
1292 user = insert(:user)
1293 assert CommonAPI.get_user(user.ap_id) == user
1294 end
1295
1296 test "gets user by guessed nickname" do
1297 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1298 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1299 end
1300
1301 test "fallback" do
1302 assert %User{
1303 name: "",
1304 ap_id: "",
1305 nickname: "erroruser@example.com"
1306 } = CommonAPI.get_user("")
1307 end
1308 end
1309
1310 describe "with `local` visibility" do
1311 setup do: clear_config([:instance, :federating], true)
1312
1313 test "post" do
1314 user = insert(:user)
1315
1316 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1317 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1318
1319 assert Visibility.is_local_public?(activity)
1320 assert_not_called(Pleroma.Web.Federator.publish(activity))
1321 end
1322 end
1323
1324 test "delete" do
1325 user = insert(:user)
1326
1327 {:ok, %Activity{id: activity_id}} =
1328 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1329
1330 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1331 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1332 CommonAPI.delete(activity_id, user)
1333
1334 assert Visibility.is_local_public?(activity)
1335 assert_not_called(Pleroma.Web.Federator.publish(activity))
1336 end
1337 end
1338
1339 test "repeat" 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 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1347 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1348 CommonAPI.repeat(activity_id, user)
1349
1350 assert Visibility.is_local_public?(activity)
1351 refute called(Pleroma.Web.Federator.publish(activity))
1352 end
1353 end
1354
1355 test "unrepeat" do
1356 user = insert(:user)
1357 other_user = insert(:user)
1358
1359 {:ok, %Activity{id: activity_id}} =
1360 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1361
1362 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1363
1364 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1365 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1366 CommonAPI.unrepeat(activity_id, user)
1367
1368 assert Visibility.is_local_public?(activity)
1369 refute called(Pleroma.Web.Federator.publish(activity))
1370 end
1371 end
1372
1373 test "favorite" do
1374 user = insert(:user)
1375 other_user = insert(:user)
1376
1377 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1378
1379 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1380 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1381 CommonAPI.favorite(user, activity.id)
1382
1383 assert Visibility.is_local_public?(activity)
1384 refute called(Pleroma.Web.Federator.publish(activity))
1385 end
1386 end
1387
1388 test "unfavorite" do
1389 user = insert(:user)
1390 other_user = insert(:user)
1391
1392 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1393
1394 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1395
1396 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1397 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1398 assert Visibility.is_local_public?(activity)
1399 refute called(Pleroma.Web.Federator.publish(activity))
1400 end
1401 end
1402
1403 test "react_with_emoji" do
1404 user = insert(:user)
1405 other_user = insert(:user)
1406 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1407
1408 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1409 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1410 CommonAPI.react_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
1417 test "unreact_with_emoji" do
1418 user = insert(:user)
1419 other_user = insert(:user)
1420 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1421
1422 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1423
1424 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1425 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1426 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1427
1428 assert Visibility.is_local_public?(activity)
1429 refute called(Pleroma.Web.Federator.publish(activity))
1430 end
1431 end
1432 end
1433 end