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