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