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