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