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