Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/expire...
[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"] == ["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 "add expiring mute", %{user: user, activity: activity} do
926 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
927 assert CommonAPI.thread_muted?(user, activity)
928
929 worker = Pleroma.Workers.MuteExpireWorker
930 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
931
932 assert_enqueued(
933 worker: worker,
934 args: args
935 )
936
937 assert :ok = perform_job(worker, args)
938 refute CommonAPI.thread_muted?(user, activity)
939 end
940
941 test "remove mute", %{user: user, activity: activity} do
942 CommonAPI.add_mute(user, activity)
943 {:ok, _} = CommonAPI.remove_mute(user, activity)
944 refute CommonAPI.thread_muted?(user, activity)
945 end
946
947 test "remove mute by ids", %{user: user, activity: activity} do
948 CommonAPI.add_mute(user, activity)
949 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
950 refute CommonAPI.thread_muted?(user, activity)
951 end
952
953 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
954 CommonAPI.add_mute(user, activity)
955 {:error, _} = CommonAPI.add_mute(user, activity)
956 end
957 end
958
959 describe "reports" do
960 test "creates a report" do
961 reporter = insert(:user)
962 target_user = insert(:user)
963
964 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
965
966 reporter_ap_id = reporter.ap_id
967 target_ap_id = target_user.ap_id
968 activity_ap_id = activity.data["id"]
969 comment = "foobar"
970
971 report_data = %{
972 account_id: target_user.id,
973 comment: comment,
974 status_ids: [activity.id]
975 }
976
977 note_obj = %{
978 "type" => "Note",
979 "id" => activity_ap_id,
980 "content" => "foobar",
981 "published" => activity.object.data["published"],
982 "actor" => AccountView.render("show.json", %{user: target_user})
983 }
984
985 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
986
987 assert %Activity{
988 actor: ^reporter_ap_id,
989 data: %{
990 "type" => "Flag",
991 "content" => ^comment,
992 "object" => [^target_ap_id, ^note_obj],
993 "state" => "open"
994 }
995 } = flag_activity
996 end
997
998 test "updates report state" do
999 [reporter, target_user] = insert_pair(:user)
1000 activity = insert(:note_activity, user: target_user)
1001
1002 {:ok, %Activity{id: report_id}} =
1003 CommonAPI.report(reporter, %{
1004 account_id: target_user.id,
1005 comment: "I feel offended",
1006 status_ids: [activity.id]
1007 })
1008
1009 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1010
1011 assert report.data["state"] == "resolved"
1012
1013 [reported_user, activity_id] = report.data["object"]
1014
1015 assert reported_user == target_user.ap_id
1016 assert activity_id == activity.data["id"]
1017 end
1018
1019 test "does not update report state when state is unsupported" do
1020 [reporter, target_user] = insert_pair(:user)
1021 activity = insert(:note_activity, user: target_user)
1022
1023 {:ok, %Activity{id: report_id}} =
1024 CommonAPI.report(reporter, %{
1025 account_id: target_user.id,
1026 comment: "I feel offended",
1027 status_ids: [activity.id]
1028 })
1029
1030 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1031 end
1032
1033 test "updates state of multiple reports" do
1034 [reporter, target_user] = insert_pair(:user)
1035 activity = insert(:note_activity, user: target_user)
1036
1037 {:ok, %Activity{id: first_report_id}} =
1038 CommonAPI.report(reporter, %{
1039 account_id: target_user.id,
1040 comment: "I feel offended",
1041 status_ids: [activity.id]
1042 })
1043
1044 {:ok, %Activity{id: second_report_id}} =
1045 CommonAPI.report(reporter, %{
1046 account_id: target_user.id,
1047 comment: "I feel very offended!",
1048 status_ids: [activity.id]
1049 })
1050
1051 {:ok, report_ids} =
1052 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1053
1054 first_report = Activity.get_by_id(first_report_id)
1055 second_report = Activity.get_by_id(second_report_id)
1056
1057 assert report_ids -- [first_report_id, second_report_id] == []
1058 assert first_report.data["state"] == "resolved"
1059 assert second_report.data["state"] == "resolved"
1060 end
1061 end
1062
1063 describe "reblog muting" do
1064 setup do
1065 muter = insert(:user)
1066
1067 muted = insert(:user)
1068
1069 [muter: muter, muted: muted]
1070 end
1071
1072 test "add a reblog mute", %{muter: muter, muted: muted} do
1073 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1074
1075 assert User.showing_reblogs?(muter, muted) == false
1076 end
1077
1078 test "remove a reblog mute", %{muter: muter, muted: muted} do
1079 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1080 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1081
1082 assert User.showing_reblogs?(muter, muted) == true
1083 end
1084 end
1085
1086 describe "follow/2" do
1087 test "directly follows a non-locked local user" do
1088 [follower, followed] = insert_pair(:user)
1089 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1090
1091 assert User.following?(follower, followed)
1092 end
1093 end
1094
1095 describe "unfollow/2" do
1096 test "also unsubscribes a user" do
1097 [follower, followed] = insert_pair(:user)
1098 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1099 {:ok, _subscription} = User.subscribe(follower, followed)
1100
1101 assert User.subscribed_to?(follower, followed)
1102
1103 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1104
1105 refute User.subscribed_to?(follower, followed)
1106 end
1107
1108 test "cancels a pending follow for a local user" do
1109 follower = insert(:user)
1110 followed = insert(:user, is_locked: 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
1130 test "cancels a pending follow for a remote user" do
1131 follower = insert(:user)
1132 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1133
1134 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1135 CommonAPI.follow(follower, followed)
1136
1137 assert User.get_follow_state(follower, followed) == :follow_pending
1138 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1139 assert User.get_follow_state(follower, followed) == nil
1140
1141 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1142 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1143
1144 assert %{
1145 data: %{
1146 "type" => "Undo",
1147 "object" => %{"type" => "Follow", "state" => "cancelled"}
1148 }
1149 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1150 end
1151 end
1152
1153 describe "accept_follow_request/2" do
1154 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1155 user = insert(:user, is_locked: true)
1156 follower = insert(:user)
1157 follower_two = insert(:user)
1158
1159 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1160 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1161 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1162
1163 assert follow_activity.data["state"] == "pending"
1164 assert follow_activity_two.data["state"] == "pending"
1165 assert follow_activity_three.data["state"] == "pending"
1166
1167 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1168
1169 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1170 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1171 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1172 end
1173
1174 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1175 user = insert(:user, is_locked: true)
1176 follower = insert(:user)
1177 follower_two = insert(:user)
1178
1179 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1180 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1181 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1182
1183 assert follow_activity.data["state"] == "pending"
1184 assert follow_activity_two.data["state"] == "pending"
1185 assert follow_activity_three.data["state"] == "pending"
1186
1187 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1188
1189 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1190 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1191 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1192 end
1193
1194 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1195 user = insert(:user, is_locked: true)
1196 not_follower = insert(:user)
1197 CommonAPI.accept_follow_request(not_follower, user)
1198
1199 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1200 end
1201 end
1202
1203 describe "vote/3" do
1204 test "does not allow to vote twice" do
1205 user = insert(:user)
1206 other_user = insert(:user)
1207
1208 {:ok, activity} =
1209 CommonAPI.post(user, %{
1210 status: "Am I cute?",
1211 poll: %{options: ["Yes", "No"], expires_in: 20}
1212 })
1213
1214 object = Object.normalize(activity)
1215
1216 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1217
1218 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1219 end
1220 end
1221
1222 describe "listen/2" do
1223 test "returns a valid activity" do
1224 user = insert(:user)
1225
1226 {:ok, activity} =
1227 CommonAPI.listen(user, %{
1228 title: "lain radio episode 1",
1229 album: "lain radio",
1230 artist: "lain",
1231 length: 180_000
1232 })
1233
1234 object = Object.normalize(activity)
1235
1236 assert object.data["title"] == "lain radio episode 1"
1237
1238 assert Visibility.get_visibility(activity) == "public"
1239 end
1240
1241 test "respects visibility=private" do
1242 user = insert(:user)
1243
1244 {:ok, activity} =
1245 CommonAPI.listen(user, %{
1246 title: "lain radio episode 1",
1247 album: "lain radio",
1248 artist: "lain",
1249 length: 180_000,
1250 visibility: "private"
1251 })
1252
1253 object = Object.normalize(activity)
1254
1255 assert object.data["title"] == "lain radio episode 1"
1256
1257 assert Visibility.get_visibility(activity) == "private"
1258 end
1259 end
1260
1261 describe "get_user/1" do
1262 test "gets user by ap_id" do
1263 user = insert(:user)
1264 assert CommonAPI.get_user(user.ap_id) == user
1265 end
1266
1267 test "gets user by guessed nickname" do
1268 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1269 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1270 end
1271
1272 test "fallback" do
1273 assert %User{
1274 name: "",
1275 ap_id: "",
1276 nickname: "erroruser@example.com"
1277 } = CommonAPI.get_user("")
1278 end
1279 end
1280 end