Merge branch 'develop' into feature/gen-magic
[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 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 "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 "remove mute", %{user: user, activity: activity} do
884 CommonAPI.add_mute(user, activity)
885 {:ok, _} = CommonAPI.remove_mute(user, activity)
886 refute CommonAPI.thread_muted?(user, activity)
887 end
888
889 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
890 CommonAPI.add_mute(user, activity)
891 {:error, _} = CommonAPI.add_mute(user, activity)
892 end
893 end
894
895 describe "reports" do
896 test "creates a report" do
897 reporter = insert(:user)
898 target_user = insert(:user)
899
900 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
901
902 reporter_ap_id = reporter.ap_id
903 target_ap_id = target_user.ap_id
904 activity_ap_id = activity.data["id"]
905 comment = "foobar"
906
907 report_data = %{
908 account_id: target_user.id,
909 comment: comment,
910 status_ids: [activity.id]
911 }
912
913 note_obj = %{
914 "type" => "Note",
915 "id" => activity_ap_id,
916 "content" => "foobar",
917 "published" => activity.object.data["published"],
918 "actor" => AccountView.render("show.json", %{user: target_user})
919 }
920
921 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
922
923 assert %Activity{
924 actor: ^reporter_ap_id,
925 data: %{
926 "type" => "Flag",
927 "content" => ^comment,
928 "object" => [^target_ap_id, ^note_obj],
929 "state" => "open"
930 }
931 } = flag_activity
932 end
933
934 test "updates report state" do
935 [reporter, target_user] = insert_pair(:user)
936 activity = insert(:note_activity, user: target_user)
937
938 {:ok, %Activity{id: report_id}} =
939 CommonAPI.report(reporter, %{
940 account_id: target_user.id,
941 comment: "I feel offended",
942 status_ids: [activity.id]
943 })
944
945 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
946
947 assert report.data["state"] == "resolved"
948
949 [reported_user, activity_id] = report.data["object"]
950
951 assert reported_user == target_user.ap_id
952 assert activity_id == activity.data["id"]
953 end
954
955 test "does not update report state when state is unsupported" do
956 [reporter, target_user] = insert_pair(:user)
957 activity = insert(:note_activity, user: target_user)
958
959 {:ok, %Activity{id: report_id}} =
960 CommonAPI.report(reporter, %{
961 account_id: target_user.id,
962 comment: "I feel offended",
963 status_ids: [activity.id]
964 })
965
966 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
967 end
968
969 test "updates state of multiple reports" do
970 [reporter, target_user] = insert_pair(:user)
971 activity = insert(:note_activity, user: target_user)
972
973 {:ok, %Activity{id: first_report_id}} =
974 CommonAPI.report(reporter, %{
975 account_id: target_user.id,
976 comment: "I feel offended",
977 status_ids: [activity.id]
978 })
979
980 {:ok, %Activity{id: second_report_id}} =
981 CommonAPI.report(reporter, %{
982 account_id: target_user.id,
983 comment: "I feel very offended!",
984 status_ids: [activity.id]
985 })
986
987 {:ok, report_ids} =
988 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
989
990 first_report = Activity.get_by_id(first_report_id)
991 second_report = Activity.get_by_id(second_report_id)
992
993 assert report_ids -- [first_report_id, second_report_id] == []
994 assert first_report.data["state"] == "resolved"
995 assert second_report.data["state"] == "resolved"
996 end
997 end
998
999 describe "reblog muting" do
1000 setup do
1001 muter = insert(:user)
1002
1003 muted = insert(:user)
1004
1005 [muter: muter, muted: muted]
1006 end
1007
1008 test "add a reblog mute", %{muter: muter, muted: muted} do
1009 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1010
1011 assert User.showing_reblogs?(muter, muted) == false
1012 end
1013
1014 test "remove a reblog mute", %{muter: muter, muted: muted} do
1015 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1016 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1017
1018 assert User.showing_reblogs?(muter, muted) == true
1019 end
1020 end
1021
1022 describe "follow/2" do
1023 test "directly follows a non-locked local user" do
1024 [follower, followed] = insert_pair(:user)
1025 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1026
1027 assert User.following?(follower, followed)
1028 end
1029 end
1030
1031 describe "unfollow/2" do
1032 test "also unsubscribes a user" do
1033 [follower, followed] = insert_pair(:user)
1034 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1035 {:ok, _subscription} = User.subscribe(follower, followed)
1036
1037 assert User.subscribed_to?(follower, followed)
1038
1039 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1040
1041 refute User.subscribed_to?(follower, followed)
1042 end
1043
1044 test "cancels a pending follow for a local user" do
1045 follower = insert(:user)
1046 followed = insert(:user, locked: true)
1047
1048 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1049 CommonAPI.follow(follower, followed)
1050
1051 assert User.get_follow_state(follower, followed) == :follow_pending
1052 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1053 assert User.get_follow_state(follower, followed) == nil
1054
1055 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1056 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1057
1058 assert %{
1059 data: %{
1060 "type" => "Undo",
1061 "object" => %{"type" => "Follow", "state" => "cancelled"}
1062 }
1063 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1064 end
1065
1066 test "cancels a pending follow for a remote user" do
1067 follower = insert(:user)
1068 followed = insert(:user, locked: true, local: false, ap_enabled: true)
1069
1070 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1071 CommonAPI.follow(follower, followed)
1072
1073 assert User.get_follow_state(follower, followed) == :follow_pending
1074 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1075 assert User.get_follow_state(follower, followed) == nil
1076
1077 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1078 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1079
1080 assert %{
1081 data: %{
1082 "type" => "Undo",
1083 "object" => %{"type" => "Follow", "state" => "cancelled"}
1084 }
1085 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1086 end
1087 end
1088
1089 describe "accept_follow_request/2" do
1090 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1091 user = insert(:user, locked: true)
1092 follower = insert(:user)
1093 follower_two = insert(:user)
1094
1095 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1096 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1097 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1098
1099 assert follow_activity.data["state"] == "pending"
1100 assert follow_activity_two.data["state"] == "pending"
1101 assert follow_activity_three.data["state"] == "pending"
1102
1103 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1104
1105 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1106 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1107 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1108 end
1109
1110 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1111 user = insert(:user, locked: true)
1112 follower = insert(:user)
1113 follower_two = insert(:user)
1114
1115 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1116 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1117 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1118
1119 assert follow_activity.data["state"] == "pending"
1120 assert follow_activity_two.data["state"] == "pending"
1121 assert follow_activity_three.data["state"] == "pending"
1122
1123 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1124
1125 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1126 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1127 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1128 end
1129
1130 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1131 user = insert(:user, locked: true)
1132 not_follower = insert(:user)
1133 CommonAPI.accept_follow_request(not_follower, user)
1134
1135 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1136 end
1137 end
1138
1139 describe "vote/3" do
1140 test "does not allow to vote twice" do
1141 user = insert(:user)
1142 other_user = insert(:user)
1143
1144 {:ok, activity} =
1145 CommonAPI.post(user, %{
1146 status: "Am I cute?",
1147 poll: %{options: ["Yes", "No"], expires_in: 20}
1148 })
1149
1150 object = Object.normalize(activity)
1151
1152 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1153
1154 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1155 end
1156 end
1157
1158 describe "listen/2" do
1159 test "returns a valid activity" do
1160 user = insert(:user)
1161
1162 {:ok, activity} =
1163 CommonAPI.listen(user, %{
1164 title: "lain radio episode 1",
1165 album: "lain radio",
1166 artist: "lain",
1167 length: 180_000
1168 })
1169
1170 object = Object.normalize(activity)
1171
1172 assert object.data["title"] == "lain radio episode 1"
1173
1174 assert Visibility.get_visibility(activity) == "public"
1175 end
1176
1177 test "respects visibility=private" do
1178 user = insert(:user)
1179
1180 {:ok, activity} =
1181 CommonAPI.listen(user, %{
1182 title: "lain radio episode 1",
1183 album: "lain radio",
1184 artist: "lain",
1185 length: 180_000,
1186 visibility: "private"
1187 })
1188
1189 object = Object.normalize(activity)
1190
1191 assert object.data["title"] == "lain radio episode 1"
1192
1193 assert Visibility.get_visibility(activity) == "private"
1194 end
1195 end
1196 end