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