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