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