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