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