Insert text representation of hashtags into object["hashtags"]
[akkoma] / test / pleroma / web / common_api_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.CommonAPITest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Chat
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Notification
13 alias Pleroma.Object
14 alias Pleroma.Repo
15 alias Pleroma.User
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.ActivityPub.Transmogrifier
18 alias Pleroma.Web.ActivityPub.Visibility
19 alias Pleroma.Web.AdminAPI.AccountView
20 alias Pleroma.Web.CommonAPI
21
22 import Pleroma.Factory
23 import Mock
24 import Ecto.Query, only: [from: 2]
25
26 require Pleroma.Constants
27
28 setup do: clear_config([:instance, :safe_dm_mentions])
29 setup do: clear_config([:instance, :limit])
30 setup do: clear_config([:instance, :max_pinned_statuses])
31
32 describe "posting polls" do
33 test "it posts a poll" do
34 user = insert(:user)
35
36 {:ok, activity} =
37 CommonAPI.post(user, %{
38 status: "who is the best",
39 poll: %{expires_in: 600, options: ["reimu", "marisa"]}
40 })
41
42 object = Object.normalize(activity)
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, 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, 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)
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, 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)
495
496 assert object.data["tag"] == []
497 assert object.data["hashtags"] == ["2hu"]
498 end
499
500 test "it adds emoji in the object" do
501 user = insert(:user)
502 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
503
504 assert Object.normalize(activity).data["emoji"]["firefox"]
505 end
506
507 describe "posting" do
508 test "deactivated users can't post" do
509 user = insert(:user, deactivated: true)
510 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
511 end
512
513 test "it supports explicit addressing" do
514 user = insert(:user)
515 user_two = insert(:user)
516 user_three = insert(:user)
517 user_four = insert(:user)
518
519 {:ok, activity} =
520 CommonAPI.post(user, %{
521 status:
522 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
523 to: [user_two.nickname, user_four.nickname, "nonexistent"]
524 })
525
526 assert user.ap_id in activity.recipients
527 assert user_two.ap_id in activity.recipients
528 assert user_four.ap_id in activity.recipients
529 refute user_three.ap_id in activity.recipients
530 end
531
532 test "it filters out obviously bad tags when accepting a post as HTML" do
533 user = insert(:user)
534
535 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
536
537 {:ok, activity} =
538 CommonAPI.post(user, %{
539 status: post,
540 content_type: "text/html"
541 })
542
543 object = Object.normalize(activity)
544
545 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
546 assert object.data["source"] == post
547 end
548
549 test "it filters out obviously bad tags when accepting a post as Markdown" do
550 user = insert(:user)
551
552 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
553
554 {:ok, activity} =
555 CommonAPI.post(user, %{
556 status: post,
557 content_type: "text/markdown"
558 })
559
560 object = Object.normalize(activity)
561
562 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
563 assert object.data["source"] == post
564 end
565
566 test "it does not allow replies to direct messages that are not direct messages themselves" do
567 user = insert(:user)
568
569 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
570
571 assert {:ok, _} =
572 CommonAPI.post(user, %{
573 status: "suya..",
574 visibility: "direct",
575 in_reply_to_status_id: activity.id
576 })
577
578 Enum.each(["public", "private", "unlisted"], fn visibility ->
579 assert {:error, "The message visibility must be direct"} =
580 CommonAPI.post(user, %{
581 status: "suya..",
582 visibility: visibility,
583 in_reply_to_status_id: activity.id
584 })
585 end)
586 end
587
588 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
589 user = insert(:user)
590 other_user = insert(:user)
591 third_user = insert(:user)
592
593 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
594
595 {:ok, open_answer} =
596 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
597
598 # The OP is implicitly added
599 assert user.ap_id in open_answer.recipients
600
601 {:ok, secret_answer} =
602 CommonAPI.post(other_user, %{
603 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
604 in_reply_to_status_id: post.id,
605 visibility: "direct"
606 })
607
608 assert third_user.ap_id in secret_answer.recipients
609
610 # The OP is not added
611 refute user.ap_id in secret_answer.recipients
612 end
613
614 test "it allows to address a list" do
615 user = insert(:user)
616 {:ok, list} = Pleroma.List.create("foo", user)
617
618 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
619
620 assert activity.data["bcc"] == [list.ap_id]
621 assert activity.recipients == [list.ap_id, user.ap_id]
622 assert activity.data["listMessage"] == list.ap_id
623 end
624
625 test "it returns error when status is empty and no attachments" do
626 user = insert(:user)
627
628 assert {:error, "Cannot post an empty status without attachments"} =
629 CommonAPI.post(user, %{status: ""})
630 end
631
632 test "it validates character limits are correctly enforced" do
633 Pleroma.Config.put([:instance, :limit], 5)
634
635 user = insert(:user)
636
637 assert {:error, "The status is over the character limit"} =
638 CommonAPI.post(user, %{status: "foobar"})
639
640 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
641 end
642
643 test "it can handle activities that expire" do
644 user = insert(:user)
645
646 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
647
648 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
649
650 assert_enqueued(
651 worker: Pleroma.Workers.PurgeExpiredActivity,
652 args: %{activity_id: activity.id},
653 scheduled_at: expires_at
654 )
655 end
656 end
657
658 describe "reactions" do
659 test "reacting to a status with an emoji" do
660 user = insert(:user)
661 other_user = insert(:user)
662
663 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
664
665 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
666
667 assert reaction.data["actor"] == user.ap_id
668 assert reaction.data["content"] == "👍"
669
670 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
671
672 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
673 end
674
675 test "unreacting to a status with an emoji" do
676 user = insert(:user)
677 other_user = insert(:user)
678
679 clear_config([:instance, :federating], true)
680
681 with_mock Pleroma.Web.Federator,
682 publish: fn _ -> nil end do
683 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
684 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
685
686 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
687
688 assert unreaction.data["type"] == "Undo"
689 assert unreaction.data["object"] == reaction.data["id"]
690 assert unreaction.local
691
692 # On federation, it contains the undone (and deleted) object
693 unreaction_with_object = %{
694 unreaction
695 | data: Map.put(unreaction.data, "object", reaction.data)
696 }
697
698 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
699 end
700 end
701
702 test "repeating a status" do
703 user = insert(:user)
704 other_user = insert(:user)
705
706 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
707
708 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
709 assert Visibility.is_public?(announce_activity)
710 end
711
712 test "can't repeat a repeat" do
713 user = insert(:user)
714 other_user = insert(:user)
715 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
716
717 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
718
719 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
720 end
721
722 test "repeating a status privately" do
723 user = insert(:user)
724 other_user = insert(:user)
725
726 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
727
728 {:ok, %Activity{} = announce_activity} =
729 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
730
731 assert Visibility.is_private?(announce_activity)
732 refute Visibility.visible_for_user?(announce_activity, nil)
733 end
734
735 test "favoriting a status" do
736 user = insert(:user)
737 other_user = insert(:user)
738
739 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
740
741 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
742 assert data["type"] == "Like"
743 assert data["actor"] == user.ap_id
744 assert data["object"] == post_activity.data["object"]
745 end
746
747 test "retweeting a status twice returns the status" do
748 user = insert(:user)
749 other_user = insert(:user)
750
751 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
752 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
753 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
754 end
755
756 test "favoriting a status twice returns ok, but without the like activity" do
757 user = insert(:user)
758 other_user = insert(:user)
759
760 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
761 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
762 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
763 end
764 end
765
766 describe "pinned statuses" do
767 setup do
768 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
769
770 user = insert(:user)
771 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
772
773 [user: user, activity: activity]
774 end
775
776 test "pin status", %{user: user, activity: activity} do
777 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
778
779 id = activity.id
780 user = refresh_record(user)
781
782 assert %User{pinned_activities: [^id]} = user
783 end
784
785 test "pin poll", %{user: user} do
786 {:ok, activity} =
787 CommonAPI.post(user, %{
788 status: "How is fediverse today?",
789 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
790 })
791
792 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
793
794 id = activity.id
795 user = refresh_record(user)
796
797 assert %User{pinned_activities: [^id]} = user
798 end
799
800 test "unlisted statuses can be pinned", %{user: user} do
801 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
802 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
803 end
804
805 test "only self-authored can be pinned", %{activity: activity} do
806 user = insert(:user)
807
808 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
809 end
810
811 test "max pinned statuses", %{user: user, activity: activity_one} do
812 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
813
814 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
815
816 user = refresh_record(user)
817
818 assert {:error, "You have already pinned the maximum number of statuses"} =
819 CommonAPI.pin(activity_two.id, user)
820 end
821
822 test "unpin status", %{user: user, activity: activity} do
823 {:ok, activity} = CommonAPI.pin(activity.id, user)
824
825 user = refresh_record(user)
826
827 id = activity.id
828
829 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
830
831 user = refresh_record(user)
832
833 assert %User{pinned_activities: []} = user
834 end
835
836 test "should unpin when deleting a status", %{user: user, activity: activity} do
837 {:ok, activity} = CommonAPI.pin(activity.id, user)
838
839 user = refresh_record(user)
840
841 assert {:ok, _} = CommonAPI.delete(activity.id, user)
842
843 user = refresh_record(user)
844
845 assert %User{pinned_activities: []} = user
846 end
847 end
848
849 describe "mute tests" do
850 setup do
851 user = insert(:user)
852
853 activity = insert(:note_activity)
854
855 [user: user, activity: activity]
856 end
857
858 test "marks notifications as read after mute" do
859 author = insert(:user)
860 activity = insert(:note_activity, user: author)
861
862 friend1 = insert(:user)
863 friend2 = insert(:user)
864
865 {:ok, reply_activity} =
866 CommonAPI.post(
867 friend2,
868 %{
869 status: "@#{author.nickname} @#{friend1.nickname} test reply",
870 in_reply_to_status_id: activity.id
871 }
872 )
873
874 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
875 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
876
877 assert Repo.aggregate(
878 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
879 :count
880 ) == 1
881
882 unread_notifications =
883 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
884
885 assert Enum.any?(unread_notifications, fn n ->
886 n.type == "favourite" && n.activity_id == favorite_activity.id
887 end)
888
889 assert Enum.any?(unread_notifications, fn n ->
890 n.type == "reblog" && n.activity_id == repeat_activity.id
891 end)
892
893 assert Enum.any?(unread_notifications, fn n ->
894 n.type == "mention" && n.activity_id == reply_activity.id
895 end)
896
897 {:ok, _} = CommonAPI.add_mute(author, activity)
898 assert CommonAPI.thread_muted?(author, activity)
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 read_notifications =
906 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
907
908 assert Enum.any?(read_notifications, fn n ->
909 n.type == "favourite" && n.activity_id == favorite_activity.id
910 end)
911
912 assert Enum.any?(read_notifications, fn n ->
913 n.type == "reblog" && n.activity_id == repeat_activity.id
914 end)
915
916 assert Enum.any?(read_notifications, fn n ->
917 n.type == "mention" && n.activity_id == reply_activity.id
918 end)
919 end
920
921 test "add mute", %{user: user, activity: activity} do
922 {:ok, _} = CommonAPI.add_mute(user, activity)
923 assert CommonAPI.thread_muted?(user, activity)
924 end
925
926 test "add expiring mute", %{user: user, activity: activity} do
927 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
928 assert CommonAPI.thread_muted?(user, activity)
929
930 worker = Pleroma.Workers.MuteExpireWorker
931 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
932
933 assert_enqueued(
934 worker: worker,
935 args: args
936 )
937
938 assert :ok = perform_job(worker, args)
939 refute CommonAPI.thread_muted?(user, activity)
940 end
941
942 test "remove mute", %{user: user, activity: activity} do
943 CommonAPI.add_mute(user, activity)
944 {:ok, _} = CommonAPI.remove_mute(user, activity)
945 refute CommonAPI.thread_muted?(user, activity)
946 end
947
948 test "remove mute by ids", %{user: user, activity: activity} do
949 CommonAPI.add_mute(user, activity)
950 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
951 refute CommonAPI.thread_muted?(user, activity)
952 end
953
954 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
955 CommonAPI.add_mute(user, activity)
956 {:error, _} = CommonAPI.add_mute(user, activity)
957 end
958 end
959
960 describe "reports" do
961 test "creates a report" do
962 reporter = insert(:user)
963 target_user = insert(:user)
964
965 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
966
967 reporter_ap_id = reporter.ap_id
968 target_ap_id = target_user.ap_id
969 activity_ap_id = activity.data["id"]
970 comment = "foobar"
971
972 report_data = %{
973 account_id: target_user.id,
974 comment: comment,
975 status_ids: [activity.id]
976 }
977
978 note_obj = %{
979 "type" => "Note",
980 "id" => activity_ap_id,
981 "content" => "foobar",
982 "published" => activity.object.data["published"],
983 "actor" => AccountView.render("show.json", %{user: target_user})
984 }
985
986 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
987
988 assert %Activity{
989 actor: ^reporter_ap_id,
990 data: %{
991 "type" => "Flag",
992 "content" => ^comment,
993 "object" => [^target_ap_id, ^note_obj],
994 "state" => "open"
995 }
996 } = flag_activity
997 end
998
999 test "updates report state" do
1000 [reporter, target_user] = insert_pair(:user)
1001 activity = insert(:note_activity, user: target_user)
1002
1003 {:ok, %Activity{id: report_id}} =
1004 CommonAPI.report(reporter, %{
1005 account_id: target_user.id,
1006 comment: "I feel offended",
1007 status_ids: [activity.id]
1008 })
1009
1010 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1011
1012 assert report.data["state"] == "resolved"
1013
1014 [reported_user, activity_id] = report.data["object"]
1015
1016 assert reported_user == target_user.ap_id
1017 assert activity_id == activity.data["id"]
1018 end
1019
1020 test "does not update report state when state is unsupported" do
1021 [reporter, target_user] = insert_pair(:user)
1022 activity = insert(:note_activity, user: target_user)
1023
1024 {:ok, %Activity{id: report_id}} =
1025 CommonAPI.report(reporter, %{
1026 account_id: target_user.id,
1027 comment: "I feel offended",
1028 status_ids: [activity.id]
1029 })
1030
1031 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1032 end
1033
1034 test "updates state of multiple reports" do
1035 [reporter, target_user] = insert_pair(:user)
1036 activity = insert(:note_activity, user: target_user)
1037
1038 {:ok, %Activity{id: first_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, %Activity{id: second_report_id}} =
1046 CommonAPI.report(reporter, %{
1047 account_id: target_user.id,
1048 comment: "I feel very offended!",
1049 status_ids: [activity.id]
1050 })
1051
1052 {:ok, report_ids} =
1053 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1054
1055 first_report = Activity.get_by_id(first_report_id)
1056 second_report = Activity.get_by_id(second_report_id)
1057
1058 assert report_ids -- [first_report_id, second_report_id] == []
1059 assert first_report.data["state"] == "resolved"
1060 assert second_report.data["state"] == "resolved"
1061 end
1062 end
1063
1064 describe "reblog muting" do
1065 setup do
1066 muter = insert(:user)
1067
1068 muted = insert(:user)
1069
1070 [muter: muter, muted: muted]
1071 end
1072
1073 test "add a reblog mute", %{muter: muter, muted: muted} do
1074 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1075
1076 assert User.showing_reblogs?(muter, muted) == false
1077 end
1078
1079 test "remove a reblog mute", %{muter: muter, muted: muted} do
1080 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1081 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1082
1083 assert User.showing_reblogs?(muter, muted) == true
1084 end
1085 end
1086
1087 describe "follow/2" do
1088 test "directly follows a non-locked local user" do
1089 [follower, followed] = insert_pair(:user)
1090 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1091
1092 assert User.following?(follower, followed)
1093 end
1094 end
1095
1096 describe "unfollow/2" do
1097 test "also unsubscribes a user" do
1098 [follower, followed] = insert_pair(:user)
1099 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1100 {:ok, _subscription} = User.subscribe(follower, followed)
1101
1102 assert User.subscribed_to?(follower, followed)
1103
1104 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1105
1106 refute User.subscribed_to?(follower, followed)
1107 end
1108
1109 test "cancels a pending follow for a local user" do
1110 follower = insert(:user)
1111 followed = insert(:user, is_locked: true)
1112
1113 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1114 CommonAPI.follow(follower, followed)
1115
1116 assert User.get_follow_state(follower, followed) == :follow_pending
1117 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1118 assert User.get_follow_state(follower, followed) == nil
1119
1120 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1121 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1122
1123 assert %{
1124 data: %{
1125 "type" => "Undo",
1126 "object" => %{"type" => "Follow", "state" => "cancelled"}
1127 }
1128 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1129 end
1130
1131 test "cancels a pending follow for a remote user" do
1132 follower = insert(:user)
1133 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1134
1135 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1136 CommonAPI.follow(follower, followed)
1137
1138 assert User.get_follow_state(follower, followed) == :follow_pending
1139 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1140 assert User.get_follow_state(follower, followed) == nil
1141
1142 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1143 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1144
1145 assert %{
1146 data: %{
1147 "type" => "Undo",
1148 "object" => %{"type" => "Follow", "state" => "cancelled"}
1149 }
1150 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1151 end
1152 end
1153
1154 describe "accept_follow_request/2" do
1155 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1156 user = insert(:user, is_locked: true)
1157 follower = insert(:user)
1158 follower_two = insert(:user)
1159
1160 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1161 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1162 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1163
1164 assert follow_activity.data["state"] == "pending"
1165 assert follow_activity_two.data["state"] == "pending"
1166 assert follow_activity_three.data["state"] == "pending"
1167
1168 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1169
1170 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1171 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1172 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1173 end
1174
1175 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1176 user = insert(:user, is_locked: true)
1177 follower = insert(:user)
1178 follower_two = insert(:user)
1179
1180 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1181 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1182 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1183
1184 assert follow_activity.data["state"] == "pending"
1185 assert follow_activity_two.data["state"] == "pending"
1186 assert follow_activity_three.data["state"] == "pending"
1187
1188 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1189
1190 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1191 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1192 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1193 end
1194
1195 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1196 user = insert(:user, is_locked: true)
1197 not_follower = insert(:user)
1198 CommonAPI.accept_follow_request(not_follower, user)
1199
1200 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1201 end
1202 end
1203
1204 describe "vote/3" do
1205 test "does not allow to vote twice" do
1206 user = insert(:user)
1207 other_user = insert(:user)
1208
1209 {:ok, activity} =
1210 CommonAPI.post(user, %{
1211 status: "Am I cute?",
1212 poll: %{options: ["Yes", "No"], expires_in: 20}
1213 })
1214
1215 object = Object.normalize(activity)
1216
1217 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1218
1219 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1220 end
1221 end
1222
1223 describe "listen/2" do
1224 test "returns a valid activity" do
1225 user = insert(:user)
1226
1227 {:ok, activity} =
1228 CommonAPI.listen(user, %{
1229 title: "lain radio episode 1",
1230 album: "lain radio",
1231 artist: "lain",
1232 length: 180_000
1233 })
1234
1235 object = Object.normalize(activity)
1236
1237 assert object.data["title"] == "lain radio episode 1"
1238
1239 assert Visibility.get_visibility(activity) == "public"
1240 end
1241
1242 test "respects visibility=private" do
1243 user = insert(:user)
1244
1245 {:ok, activity} =
1246 CommonAPI.listen(user, %{
1247 title: "lain radio episode 1",
1248 album: "lain radio",
1249 artist: "lain",
1250 length: 180_000,
1251 visibility: "private"
1252 })
1253
1254 object = Object.normalize(activity)
1255
1256 assert object.data["title"] == "lain radio episode 1"
1257
1258 assert Visibility.get_visibility(activity) == "private"
1259 end
1260 end
1261
1262 describe "get_user/1" do
1263 test "gets user by ap_id" do
1264 user = insert(:user)
1265 assert CommonAPI.get_user(user.ap_id) == user
1266 end
1267
1268 test "gets user by guessed nickname" do
1269 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1270 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1271 end
1272
1273 test "fallback" do
1274 assert %User{
1275 name: "",
1276 ap_id: "",
1277 nickname: "erroruser@example.com"
1278 } = CommonAPI.get_user("")
1279 end
1280 end
1281
1282 describe "with `local` visibility" do
1283 setup do: clear_config([:instance, :federating], true)
1284
1285 test "post" do
1286 user = insert(:user)
1287
1288 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1289 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1290
1291 assert Visibility.is_local_public?(activity)
1292 assert_not_called(Pleroma.Web.Federator.publish(activity))
1293 end
1294 end
1295
1296 test "delete" do
1297 user = insert(:user)
1298
1299 {:ok, %Activity{id: activity_id}} =
1300 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1301
1302 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1303 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1304 CommonAPI.delete(activity_id, user)
1305
1306 assert Visibility.is_local_public?(activity)
1307 assert_not_called(Pleroma.Web.Federator.publish(activity))
1308 end
1309 end
1310
1311 test "repeat" do
1312 user = insert(:user)
1313 other_user = insert(:user)
1314
1315 {:ok, %Activity{id: activity_id}} =
1316 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1317
1318 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1319 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1320 CommonAPI.repeat(activity_id, user)
1321
1322 assert Visibility.is_local_public?(activity)
1323 refute called(Pleroma.Web.Federator.publish(activity))
1324 end
1325 end
1326
1327 test "unrepeat" do
1328 user = insert(:user)
1329 other_user = insert(:user)
1330
1331 {:ok, %Activity{id: activity_id}} =
1332 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1333
1334 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1335
1336 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1337 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1338 CommonAPI.unrepeat(activity_id, user)
1339
1340 assert Visibility.is_local_public?(activity)
1341 refute called(Pleroma.Web.Federator.publish(activity))
1342 end
1343 end
1344
1345 test "favorite" do
1346 user = insert(:user)
1347 other_user = insert(:user)
1348
1349 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1350
1351 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1352 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1353 CommonAPI.favorite(user, activity.id)
1354
1355 assert Visibility.is_local_public?(activity)
1356 refute called(Pleroma.Web.Federator.publish(activity))
1357 end
1358 end
1359
1360 test "unfavorite" do
1361 user = insert(:user)
1362 other_user = insert(:user)
1363
1364 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1365
1366 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1367
1368 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1369 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1370 assert Visibility.is_local_public?(activity)
1371 refute called(Pleroma.Web.Federator.publish(activity))
1372 end
1373 end
1374
1375 test "react_with_emoji" do
1376 user = insert(:user)
1377 other_user = insert(:user)
1378 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1379
1380 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1381 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1382 CommonAPI.react_with_emoji(activity.id, user, "👍")
1383
1384 assert Visibility.is_local_public?(activity)
1385 refute called(Pleroma.Web.Federator.publish(activity))
1386 end
1387 end
1388
1389 test "unreact_with_emoji" do
1390 user = insert(:user)
1391 other_user = insert(:user)
1392 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1393
1394 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1395
1396 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1397 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1398 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1399
1400 assert Visibility.is_local_public?(activity)
1401 refute called(Pleroma.Web.Federator.publish(activity))
1402 end
1403 end
1404 end
1405 end