1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.Notification
13 alias Pleroma.ScheduledActivity
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.MastodonAPI.FilterView
18 alias Pleroma.Web.OAuth.App
19 alias Pleroma.Web.OAuth.Token
20 alias Pleroma.Web.OStatus
21 alias Pleroma.Web.Push
22 alias Pleroma.Web.TwitterAPI.TwitterAPI
23 import Pleroma.Factory
24 import ExUnit.CaptureLog
27 @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
30 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
34 test "the home timeline", %{conn: conn} do
36 following = insert(:user)
38 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
42 |> assign(:user, user)
43 |> get("/api/v1/timelines/home")
45 assert Enum.empty?(json_response(conn, 200))
47 {:ok, user} = User.follow(user, following)
51 |> assign(:user, user)
52 |> get("/api/v1/timelines/home")
54 assert [%{"content" => "test"}] = json_response(conn, 200)
57 test "the public timeline", %{conn: conn} do
58 following = insert(:user)
61 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
64 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
68 |> get("/api/v1/timelines/public", %{"local" => "False"})
70 assert length(json_response(conn, 200)) == 2
74 |> get("/api/v1/timelines/public", %{"local" => "True"})
76 assert [%{"content" => "test"}] = json_response(conn, 200)
80 |> get("/api/v1/timelines/public", %{"local" => "1"})
82 assert [%{"content" => "test"}] = json_response(conn, 200)
86 test "the public timeline when public is set to false", %{conn: conn} do
87 public = Pleroma.Config.get([:instance, :public])
88 Pleroma.Config.put([:instance, :public], false)
91 Pleroma.Config.put([:instance, :public], public)
95 |> get("/api/v1/timelines/public", %{"local" => "False"})
96 |> json_response(403) == %{"error" => "This resource requires authentication."}
99 test "posting a status", %{conn: conn} do
102 idempotency_key = "Pikachu rocks!"
106 |> assign(:user, user)
107 |> put_req_header("idempotency-key", idempotency_key)
108 |> post("/api/v1/statuses", %{
110 "spoiler_text" => "2hu",
111 "sensitive" => "false"
114 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
116 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
118 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
119 json_response(conn_one, 200)
121 assert Activity.get_by_id(id)
125 |> assign(:user, user)
126 |> put_req_header("idempotency-key", idempotency_key)
127 |> post("/api/v1/statuses", %{
129 "spoiler_text" => "2hu",
130 "sensitive" => "false"
133 assert %{"id" => second_id} = json_response(conn_two, 200)
135 assert id == second_id
139 |> assign(:user, user)
140 |> post("/api/v1/statuses", %{
142 "spoiler_text" => "2hu",
143 "sensitive" => "false"
146 assert %{"id" => third_id} = json_response(conn_three, 200)
148 refute id == third_id
151 describe "posting polls" do
152 test "posting a poll", %{conn: conn} do
154 time = NaiveDateTime.utc_now()
158 |> assign(:user, user)
159 |> post("/api/v1/statuses", %{
160 "status" => "Who is the #bestgrill?",
161 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
164 response = json_response(conn, 200)
166 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
167 title in ["Rei", "Asuka", "Misato"]
170 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
171 refute response["poll"]["expred"]
174 test "option limit is enforced", %{conn: conn} do
176 limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
180 |> assign(:user, user)
181 |> post("/api/v1/statuses", %{
183 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
186 %{"error" => error} = json_response(conn, 422)
187 assert error == "Poll can't contain more than #{limit} options"
190 test "option character limit is enforced", %{conn: conn} do
192 limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
196 |> assign(:user, user)
197 |> post("/api/v1/statuses", %{
200 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
205 %{"error" => error} = json_response(conn, 422)
206 assert error == "Poll options cannot be longer than #{limit} characters each"
209 test "minimal date limit is enforced", %{conn: conn} do
211 limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
215 |> assign(:user, user)
216 |> post("/api/v1/statuses", %{
217 "status" => "imagine arbitrary limits",
219 "options" => ["this post was made by pleroma gang"],
220 "expires_in" => limit - 1
224 %{"error" => error} = json_response(conn, 422)
225 assert error == "Expiration date is too soon"
228 test "maximum date limit is enforced", %{conn: conn} do
230 limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
234 |> assign(:user, user)
235 |> post("/api/v1/statuses", %{
236 "status" => "imagine arbitrary limits",
238 "options" => ["this post was made by pleroma gang"],
239 "expires_in" => limit + 1
243 %{"error" => error} = json_response(conn, 422)
244 assert error == "Expiration date is too far in the future"
248 test "posting a sensitive status", %{conn: conn} do
253 |> assign(:user, user)
254 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
256 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
257 assert Activity.get_by_id(id)
260 test "posting a fake status", %{conn: conn} do
265 |> assign(:user, user)
266 |> post("/api/v1/statuses", %{
268 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
271 real_status = json_response(real_conn, 200)
274 assert Object.get_by_ap_id(real_status["uri"])
278 |> Map.put("id", nil)
279 |> Map.put("url", nil)
280 |> Map.put("uri", nil)
281 |> Map.put("created_at", nil)
282 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
286 |> assign(:user, user)
287 |> post("/api/v1/statuses", %{
289 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
293 fake_status = json_response(fake_conn, 200)
296 refute Object.get_by_ap_id(fake_status["uri"])
300 |> Map.put("id", nil)
301 |> Map.put("url", nil)
302 |> Map.put("uri", nil)
303 |> Map.put("created_at", nil)
304 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
306 assert real_status == fake_status
309 test "posting a status with OGP link preview", %{conn: conn} do
310 Pleroma.Config.put([:rich_media, :enabled], true)
315 |> assign(:user, user)
316 |> post("/api/v1/statuses", %{
317 "status" => "http://example.com/ogp"
320 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
321 assert Activity.get_by_id(id)
322 Pleroma.Config.put([:rich_media, :enabled], false)
325 test "posting a direct status", %{conn: conn} do
326 user1 = insert(:user)
327 user2 = insert(:user)
328 content = "direct cofe @#{user2.nickname}"
332 |> assign(:user, user1)
333 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
335 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
336 assert activity = Activity.get_by_id(id)
337 assert activity.recipients == [user2.ap_id, user1.ap_id]
338 assert activity.data["to"] == [user2.ap_id]
339 assert activity.data["cc"] == []
342 test "direct timeline", %{conn: conn} do
343 user_one = insert(:user)
344 user_two = insert(:user)
346 {:ok, user_two} = User.follow(user_two, user_one)
349 CommonAPI.post(user_one, %{
350 "status" => "Hi @#{user_two.nickname}!",
351 "visibility" => "direct"
354 {:ok, _follower_only} =
355 CommonAPI.post(user_one, %{
356 "status" => "Hi @#{user_two.nickname}!",
357 "visibility" => "private"
360 # Only direct should be visible here
363 |> assign(:user, user_two)
364 |> get("api/v1/timelines/direct")
366 [status] = json_response(res_conn, 200)
368 assert %{"visibility" => "direct"} = status
369 assert status["url"] != direct.data["id"]
371 # User should be able to see his own direct message
374 |> assign(:user, user_one)
375 |> get("api/v1/timelines/direct")
377 [status] = json_response(res_conn, 200)
379 assert %{"visibility" => "direct"} = status
381 # Both should be visible here
384 |> assign(:user, user_two)
385 |> get("api/v1/timelines/home")
387 [_s1, _s2] = json_response(res_conn, 200)
390 Enum.each(1..20, fn _ ->
392 CommonAPI.post(user_one, %{
393 "status" => "Hi @#{user_two.nickname}!",
394 "visibility" => "direct"
400 |> assign(:user, user_two)
401 |> get("api/v1/timelines/direct")
403 statuses = json_response(res_conn, 200)
404 assert length(statuses) == 20
408 |> assign(:user, user_two)
409 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
411 [status] = json_response(res_conn, 200)
413 assert status["url"] != direct.data["id"]
416 test "Conversations", %{conn: conn} do
417 user_one = insert(:user)
418 user_two = insert(:user)
419 user_three = insert(:user)
421 {:ok, user_two} = User.follow(user_two, user_one)
424 CommonAPI.post(user_one, %{
425 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
426 "visibility" => "direct"
429 {:ok, _follower_only} =
430 CommonAPI.post(user_one, %{
431 "status" => "Hi @#{user_two.nickname}!",
432 "visibility" => "private"
437 |> assign(:user, user_one)
438 |> get("/api/v1/conversations")
440 assert response = json_response(res_conn, 200)
445 "accounts" => res_accounts,
446 "last_status" => res_last_status,
451 account_ids = Enum.map(res_accounts, & &1["id"])
452 assert length(res_accounts) == 2
453 assert user_two.id in account_ids
454 assert user_three.id in account_ids
455 assert is_binary(res_id)
456 assert unread == true
457 assert res_last_status["id"] == direct.id
459 # Apparently undocumented API endpoint
462 |> assign(:user, user_one)
463 |> post("/api/v1/conversations/#{res_id}/read")
465 assert response = json_response(res_conn, 200)
466 assert length(response["accounts"]) == 2
467 assert response["last_status"]["id"] == direct.id
468 assert response["unread"] == false
470 # (vanilla) Mastodon frontend behaviour
473 |> assign(:user, user_one)
474 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
476 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
479 test "doesn't include DMs from blocked users", %{conn: conn} do
480 blocker = insert(:user)
481 blocked = insert(:user)
483 {:ok, blocker} = User.block(blocker, blocked)
485 {:ok, _blocked_direct} =
486 CommonAPI.post(blocked, %{
487 "status" => "Hi @#{blocker.nickname}!",
488 "visibility" => "direct"
492 CommonAPI.post(user, %{
493 "status" => "Hi @#{blocker.nickname}!",
494 "visibility" => "direct"
499 |> assign(:user, user)
500 |> get("api/v1/timelines/direct")
502 [status] = json_response(res_conn, 200)
503 assert status["id"] == direct.id
506 test "replying to a status", %{conn: conn} do
509 {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
513 |> assign(:user, user)
514 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
516 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
518 activity = Activity.get_by_id(id)
520 assert activity.data["context"] == replied_to.data["context"]
521 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
524 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
529 |> assign(:user, user)
530 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
532 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
534 activity = Activity.get_by_id(id)
539 test "verify_credentials", %{conn: conn} do
544 |> assign(:user, user)
545 |> get("/api/v1/accounts/verify_credentials")
547 response = json_response(conn, 200)
549 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
550 assert response["pleroma"]["chat_token"]
551 assert id == to_string(user.id)
554 test "verify_credentials default scope unlisted", %{conn: conn} do
555 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
559 |> assign(:user, user)
560 |> get("/api/v1/accounts/verify_credentials")
562 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
563 assert id == to_string(user.id)
566 test "apps/verify_credentials", %{conn: conn} do
567 token = insert(:oauth_token)
571 |> assign(:user, token.user)
572 |> assign(:token, token)
573 |> get("/api/v1/apps/verify_credentials")
575 app = Repo.preload(token, :app).app
578 "name" => app.client_name,
579 "website" => app.website,
580 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
583 assert expected == json_response(conn, 200)
586 test "user avatar can be set", %{conn: conn} do
588 avatar_image = File.read!("test/fixtures/avatar_data_uri")
592 |> assign(:user, user)
593 |> patch("/api/v1/accounts/update_avatar", %{img: avatar_image})
595 user = refresh_record(user)
609 assert %{"url" => _} = json_response(conn, 200)
612 test "user avatar can be reset", %{conn: conn} do
617 |> assign(:user, user)
618 |> patch("/api/v1/accounts/update_avatar", %{img: ""})
620 user = User.get_cached_by_id(user.id)
622 assert user.avatar == nil
624 assert %{"url" => nil} = json_response(conn, 200)
627 test "can set profile banner", %{conn: conn} do
632 |> assign(:user, user)
633 |> patch("/api/v1/accounts/update_banner", %{"banner" => @image})
635 user = refresh_record(user)
636 assert user.info.banner["type"] == "Image"
638 assert %{"url" => _} = json_response(conn, 200)
641 test "can reset profile banner", %{conn: conn} do
646 |> assign(:user, user)
647 |> patch("/api/v1/accounts/update_banner", %{"banner" => ""})
649 user = refresh_record(user)
650 assert user.info.banner == %{}
652 assert %{"url" => nil} = json_response(conn, 200)
655 test "background image can be set", %{conn: conn} do
660 |> assign(:user, user)
661 |> patch("/api/v1/accounts/update_background", %{"img" => @image})
663 user = refresh_record(user)
664 assert user.info.background["type"] == "Image"
665 assert %{"url" => _} = json_response(conn, 200)
668 test "background image can be reset", %{conn: conn} do
673 |> assign(:user, user)
674 |> patch("/api/v1/accounts/update_background", %{"img" => ""})
676 user = refresh_record(user)
677 assert user.info.background == %{}
678 assert %{"url" => nil} = json_response(conn, 200)
681 test "creates an oauth app", %{conn: conn} do
683 app_attrs = build(:oauth_app)
687 |> assign(:user, user)
688 |> post("/api/v1/apps", %{
689 client_name: app_attrs.client_name,
690 redirect_uris: app_attrs.redirect_uris
693 [app] = Repo.all(App)
696 "name" => app.client_name,
697 "website" => app.website,
698 "client_id" => app.client_id,
699 "client_secret" => app.client_secret,
700 "id" => app.id |> to_string(),
701 "redirect_uri" => app.redirect_uris,
702 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
705 assert expected == json_response(conn, 200)
708 test "get a status", %{conn: conn} do
709 activity = insert(:note_activity)
713 |> get("/api/v1/statuses/#{activity.id}")
715 assert %{"id" => id} = json_response(conn, 200)
716 assert id == to_string(activity.id)
719 describe "deleting a status" do
720 test "when you created it", %{conn: conn} do
721 activity = insert(:note_activity)
722 author = User.get_cached_by_ap_id(activity.data["actor"])
726 |> assign(:user, author)
727 |> delete("/api/v1/statuses/#{activity.id}")
729 assert %{} = json_response(conn, 200)
731 refute Activity.get_by_id(activity.id)
734 test "when you didn't create it", %{conn: conn} do
735 activity = insert(:note_activity)
740 |> assign(:user, user)
741 |> delete("/api/v1/statuses/#{activity.id}")
743 assert %{"error" => _} = json_response(conn, 403)
745 assert Activity.get_by_id(activity.id) == activity
748 test "when you're an admin or moderator", %{conn: conn} do
749 activity1 = insert(:note_activity)
750 activity2 = insert(:note_activity)
751 admin = insert(:user, info: %{is_admin: true})
752 moderator = insert(:user, info: %{is_moderator: true})
756 |> assign(:user, admin)
757 |> delete("/api/v1/statuses/#{activity1.id}")
759 assert %{} = json_response(res_conn, 200)
763 |> assign(:user, moderator)
764 |> delete("/api/v1/statuses/#{activity2.id}")
766 assert %{} = json_response(res_conn, 200)
768 refute Activity.get_by_id(activity1.id)
769 refute Activity.get_by_id(activity2.id)
773 describe "filters" do
774 test "creating a filter", %{conn: conn} do
777 filter = %Pleroma.Filter{
784 |> assign(:user, user)
785 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
787 assert response = json_response(conn, 200)
788 assert response["phrase"] == filter.phrase
789 assert response["context"] == filter.context
790 assert response["irreversible"] == false
791 assert response["id"] != nil
792 assert response["id"] != ""
795 test "fetching a list of filters", %{conn: conn} do
798 query_one = %Pleroma.Filter{
805 query_two = %Pleroma.Filter{
812 {:ok, filter_one} = Pleroma.Filter.create(query_one)
813 {:ok, filter_two} = Pleroma.Filter.create(query_two)
817 |> assign(:user, user)
818 |> get("/api/v1/filters")
819 |> json_response(200)
825 filters: [filter_two, filter_one]
829 test "get a filter", %{conn: conn} do
832 query = %Pleroma.Filter{
839 {:ok, filter} = Pleroma.Filter.create(query)
843 |> assign(:user, user)
844 |> get("/api/v1/filters/#{filter.filter_id}")
846 assert _response = json_response(conn, 200)
849 test "update a filter", %{conn: conn} do
852 query = %Pleroma.Filter{
859 {:ok, _filter} = Pleroma.Filter.create(query)
861 new = %Pleroma.Filter{
868 |> assign(:user, user)
869 |> put("/api/v1/filters/#{query.filter_id}", %{
874 assert response = json_response(conn, 200)
875 assert response["phrase"] == new.phrase
876 assert response["context"] == new.context
879 test "delete a filter", %{conn: conn} do
882 query = %Pleroma.Filter{
889 {:ok, filter} = Pleroma.Filter.create(query)
893 |> assign(:user, user)
894 |> delete("/api/v1/filters/#{filter.filter_id}")
896 assert response = json_response(conn, 200)
897 assert response == %{}
902 test "creating a list", %{conn: conn} do
907 |> assign(:user, user)
908 |> post("/api/v1/lists", %{"title" => "cuties"})
910 assert %{"title" => title} = json_response(conn, 200)
911 assert title == "cuties"
914 test "adding users to a list", %{conn: conn} do
916 other_user = insert(:user)
917 {:ok, list} = Pleroma.List.create("name", user)
921 |> assign(:user, user)
922 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
924 assert %{} == json_response(conn, 200)
925 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
926 assert following == [other_user.follower_address]
929 test "removing users from a list", %{conn: conn} do
931 other_user = insert(:user)
932 third_user = insert(:user)
933 {:ok, list} = Pleroma.List.create("name", user)
934 {:ok, list} = Pleroma.List.follow(list, other_user)
935 {:ok, list} = Pleroma.List.follow(list, third_user)
939 |> assign(:user, user)
940 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
942 assert %{} == json_response(conn, 200)
943 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
944 assert following == [third_user.follower_address]
947 test "listing users in a list", %{conn: conn} do
949 other_user = insert(:user)
950 {:ok, list} = Pleroma.List.create("name", user)
951 {:ok, list} = Pleroma.List.follow(list, other_user)
955 |> assign(:user, user)
956 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
958 assert [%{"id" => id}] = json_response(conn, 200)
959 assert id == to_string(other_user.id)
962 test "retrieving a list", %{conn: conn} do
964 {:ok, list} = Pleroma.List.create("name", user)
968 |> assign(:user, user)
969 |> get("/api/v1/lists/#{list.id}")
971 assert %{"id" => id} = json_response(conn, 200)
972 assert id == to_string(list.id)
975 test "renaming a list", %{conn: conn} do
977 {:ok, list} = Pleroma.List.create("name", user)
981 |> assign(:user, user)
982 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
984 assert %{"title" => name} = json_response(conn, 200)
985 assert name == "newname"
988 test "deleting a list", %{conn: conn} do
990 {:ok, list} = Pleroma.List.create("name", user)
994 |> assign(:user, user)
995 |> delete("/api/v1/lists/#{list.id}")
997 assert %{} = json_response(conn, 200)
998 assert is_nil(Repo.get(Pleroma.List, list.id))
1001 test "list timeline", %{conn: conn} do
1002 user = insert(:user)
1003 other_user = insert(:user)
1004 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
1005 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
1006 {:ok, list} = Pleroma.List.create("name", user)
1007 {:ok, list} = Pleroma.List.follow(list, other_user)
1011 |> assign(:user, user)
1012 |> get("/api/v1/timelines/list/#{list.id}")
1014 assert [%{"id" => id}] = json_response(conn, 200)
1016 assert id == to_string(activity_two.id)
1019 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
1020 user = insert(:user)
1021 other_user = insert(:user)
1022 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
1024 {:ok, _activity_two} =
1025 TwitterAPI.create_status(other_user, %{
1026 "status" => "Marisa is cute.",
1027 "visibility" => "private"
1030 {:ok, list} = Pleroma.List.create("name", user)
1031 {:ok, list} = Pleroma.List.follow(list, other_user)
1035 |> assign(:user, user)
1036 |> get("/api/v1/timelines/list/#{list.id}")
1038 assert [%{"id" => id}] = json_response(conn, 200)
1040 assert id == to_string(activity_one.id)
1044 describe "notifications" do
1045 test "list of notifications", %{conn: conn} do
1046 user = insert(:user)
1047 other_user = insert(:user)
1050 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1052 {:ok, [_notification]} = Notification.create_notifications(activity)
1056 |> assign(:user, user)
1057 |> get("/api/v1/notifications")
1060 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
1062 }\">@<span>#{user.nickname}</span></a></span>"
1064 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
1065 assert response == expected_response
1068 test "getting a single notification", %{conn: conn} do
1069 user = insert(:user)
1070 other_user = insert(:user)
1073 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1075 {:ok, [notification]} = Notification.create_notifications(activity)
1079 |> assign(:user, user)
1080 |> get("/api/v1/notifications/#{notification.id}")
1083 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
1085 }\">@<span>#{user.nickname}</span></a></span>"
1087 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
1088 assert response == expected_response
1091 test "dismissing a single notification", %{conn: conn} do
1092 user = insert(:user)
1093 other_user = insert(:user)
1096 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1098 {:ok, [notification]} = Notification.create_notifications(activity)
1102 |> assign(:user, user)
1103 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
1105 assert %{} = json_response(conn, 200)
1108 test "clearing all notifications", %{conn: conn} do
1109 user = insert(:user)
1110 other_user = insert(:user)
1113 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1115 {:ok, [_notification]} = Notification.create_notifications(activity)
1119 |> assign(:user, user)
1120 |> post("/api/v1/notifications/clear")
1122 assert %{} = json_response(conn, 200)
1126 |> assign(:user, user)
1127 |> get("/api/v1/notifications")
1129 assert all = json_response(conn, 200)
1133 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
1134 user = insert(:user)
1135 other_user = insert(:user)
1137 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1138 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1139 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1140 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1142 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1143 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1144 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1145 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1149 |> assign(:user, user)
1154 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
1156 result = json_response(conn_res, 200)
1157 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1162 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
1164 result = json_response(conn_res, 200)
1165 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1170 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
1172 result = json_response(conn_res, 200)
1173 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1176 test "filters notifications using exclude_types", %{conn: conn} do
1177 user = insert(:user)
1178 other_user = insert(:user)
1180 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
1181 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
1182 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
1183 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
1184 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
1186 mention_notification_id =
1187 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
1189 favorite_notification_id =
1190 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
1192 reblog_notification_id =
1193 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
1195 follow_notification_id =
1196 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1200 |> assign(:user, user)
1203 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1205 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1208 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1210 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1213 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1215 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1218 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1220 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1223 test "destroy multiple", %{conn: conn} do
1224 user = insert(:user)
1225 other_user = insert(:user)
1227 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1228 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1229 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1230 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1232 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1233 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1234 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1235 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1239 |> assign(:user, user)
1243 |> get("/api/v1/notifications")
1245 result = json_response(conn_res, 200)
1246 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1250 |> assign(:user, other_user)
1254 |> get("/api/v1/notifications")
1256 result = json_response(conn_res, 200)
1257 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1261 |> delete("/api/v1/notifications/destroy_multiple", %{
1262 "ids" => [notification1_id, notification2_id]
1265 assert json_response(conn_destroy, 200) == %{}
1269 |> get("/api/v1/notifications")
1271 result = json_response(conn_res, 200)
1272 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1276 describe "reblogging" do
1277 test "reblogs and returns the reblogged status", %{conn: conn} do
1278 activity = insert(:note_activity)
1279 user = insert(:user)
1283 |> assign(:user, user)
1284 |> post("/api/v1/statuses/#{activity.id}/reblog")
1287 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1289 } = json_response(conn, 200)
1291 assert to_string(activity.id) == id
1294 test "reblogged status for another user", %{conn: conn} do
1295 activity = insert(:note_activity)
1296 user1 = insert(:user)
1297 user2 = insert(:user)
1298 user3 = insert(:user)
1299 CommonAPI.favorite(activity.id, user2)
1300 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1301 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1302 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1306 |> assign(:user, user3)
1307 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1310 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1311 "reblogged" => false,
1312 "favourited" => false,
1313 "bookmarked" => false
1314 } = json_response(conn_res, 200)
1318 |> assign(:user, user2)
1319 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1322 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1323 "reblogged" => true,
1324 "favourited" => true,
1325 "bookmarked" => true
1326 } = json_response(conn_res, 200)
1328 assert to_string(activity.id) == id
1332 describe "unreblogging" do
1333 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1334 activity = insert(:note_activity)
1335 user = insert(:user)
1337 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1341 |> assign(:user, user)
1342 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1344 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1346 assert to_string(activity.id) == id
1350 describe "favoriting" do
1351 test "favs a status and returns it", %{conn: conn} do
1352 activity = insert(:note_activity)
1353 user = insert(:user)
1357 |> assign(:user, user)
1358 |> post("/api/v1/statuses/#{activity.id}/favourite")
1360 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1361 json_response(conn, 200)
1363 assert to_string(activity.id) == id
1366 test "returns 500 for a wrong id", %{conn: conn} do
1367 user = insert(:user)
1371 |> assign(:user, user)
1372 |> post("/api/v1/statuses/1/favourite")
1373 |> json_response(500)
1375 assert resp == "Something went wrong"
1379 describe "unfavoriting" do
1380 test "unfavorites a status and returns it", %{conn: conn} do
1381 activity = insert(:note_activity)
1382 user = insert(:user)
1384 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1388 |> assign(:user, user)
1389 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1391 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1392 json_response(conn, 200)
1394 assert to_string(activity.id) == id
1398 describe "user timelines" do
1399 test "gets a users statuses", %{conn: conn} do
1400 user_one = insert(:user)
1401 user_two = insert(:user)
1402 user_three = insert(:user)
1404 {:ok, user_three} = User.follow(user_three, user_one)
1406 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1408 {:ok, direct_activity} =
1409 CommonAPI.post(user_one, %{
1410 "status" => "Hi, @#{user_two.nickname}.",
1411 "visibility" => "direct"
1414 {:ok, private_activity} =
1415 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1419 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1421 assert [%{"id" => id}] = json_response(resp, 200)
1422 assert id == to_string(activity.id)
1426 |> assign(:user, user_two)
1427 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1429 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1430 assert id_one == to_string(direct_activity.id)
1431 assert id_two == to_string(activity.id)
1435 |> assign(:user, user_three)
1436 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1438 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1439 assert id_one == to_string(private_activity.id)
1440 assert id_two == to_string(activity.id)
1443 test "unimplemented pinned statuses feature", %{conn: conn} do
1444 note = insert(:note_activity)
1445 user = User.get_cached_by_ap_id(note.data["actor"])
1449 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1451 assert json_response(conn, 200) == []
1454 test "gets an users media", %{conn: conn} do
1455 note = insert(:note_activity)
1456 user = User.get_cached_by_ap_id(note.data["actor"])
1458 file = %Plug.Upload{
1459 content_type: "image/jpg",
1460 path: Path.absname("test/fixtures/image.jpg"),
1461 filename: "an_image.jpg"
1465 TwitterAPI.upload(file, user, "json")
1469 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1473 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1475 assert [%{"id" => id}] = json_response(conn, 200)
1476 assert id == to_string(image_post.id)
1480 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1482 assert [%{"id" => id}] = json_response(conn, 200)
1483 assert id == to_string(image_post.id)
1486 test "gets a user's statuses without reblogs", %{conn: conn} do
1487 user = insert(:user)
1488 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1489 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1493 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1495 assert [%{"id" => id}] = json_response(conn, 200)
1496 assert id == to_string(post.id)
1500 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1502 assert [%{"id" => id}] = json_response(conn, 200)
1503 assert id == to_string(post.id)
1507 describe "user relationships" do
1508 test "returns the relationships for the current user", %{conn: conn} do
1509 user = insert(:user)
1510 other_user = insert(:user)
1511 {:ok, user} = User.follow(user, other_user)
1515 |> assign(:user, user)
1516 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1518 assert [relationship] = json_response(conn, 200)
1520 assert to_string(other_user.id) == relationship["id"]
1524 describe "media upload" do
1526 upload_config = Pleroma.Config.get([Pleroma.Upload])
1527 proxy_config = Pleroma.Config.get([:media_proxy])
1530 Pleroma.Config.put([Pleroma.Upload], upload_config)
1531 Pleroma.Config.put([:media_proxy], proxy_config)
1534 user = insert(:user)
1538 |> assign(:user, user)
1540 image = %Plug.Upload{
1541 content_type: "image/jpg",
1542 path: Path.absname("test/fixtures/image.jpg"),
1543 filename: "an_image.jpg"
1546 [conn: conn, image: image]
1549 test "returns uploaded image", %{conn: conn, image: image} do
1550 desc = "Description of the image"
1554 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1555 |> json_response(:ok)
1557 assert media["type"] == "image"
1558 assert media["description"] == desc
1561 object = Repo.get(Object, media["id"])
1562 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1565 test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
1566 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
1568 proxy_url = "https://cache.pleroma.social"
1569 Pleroma.Config.put([:media_proxy, :enabled], true)
1570 Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
1574 |> post("/api/v1/media", %{"file" => image})
1575 |> json_response(:ok)
1577 assert String.starts_with?(media["url"], proxy_url)
1580 test "returns media url when proxy is enabled but media url is whitelisted", %{
1584 media_url = "https://media.pleroma.social"
1585 Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
1587 Pleroma.Config.put([:media_proxy, :enabled], true)
1588 Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
1589 Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
1593 |> post("/api/v1/media", %{"file" => image})
1594 |> json_response(:ok)
1596 assert String.starts_with?(media["url"], media_url)
1600 describe "locked accounts" do
1601 test "/api/v1/follow_requests works" do
1602 user = insert(:user, %{info: %User.Info{locked: true}})
1603 other_user = insert(:user)
1605 {:ok, _activity} = ActivityPub.follow(other_user, user)
1607 user = User.get_cached_by_id(user.id)
1608 other_user = User.get_cached_by_id(other_user.id)
1610 assert User.following?(other_user, user) == false
1614 |> assign(:user, user)
1615 |> get("/api/v1/follow_requests")
1617 assert [relationship] = json_response(conn, 200)
1618 assert to_string(other_user.id) == relationship["id"]
1621 test "/api/v1/follow_requests/:id/authorize works" do
1622 user = insert(:user, %{info: %User.Info{locked: true}})
1623 other_user = insert(:user)
1625 {:ok, _activity} = ActivityPub.follow(other_user, user)
1627 user = User.get_cached_by_id(user.id)
1628 other_user = User.get_cached_by_id(other_user.id)
1630 assert User.following?(other_user, user) == false
1634 |> assign(:user, user)
1635 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1637 assert relationship = json_response(conn, 200)
1638 assert to_string(other_user.id) == relationship["id"]
1640 user = User.get_cached_by_id(user.id)
1641 other_user = User.get_cached_by_id(other_user.id)
1643 assert User.following?(other_user, user) == true
1646 test "verify_credentials", %{conn: conn} do
1647 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1651 |> assign(:user, user)
1652 |> get("/api/v1/accounts/verify_credentials")
1654 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1655 assert id == to_string(user.id)
1658 test "/api/v1/follow_requests/:id/reject works" do
1659 user = insert(:user, %{info: %User.Info{locked: true}})
1660 other_user = insert(:user)
1662 {:ok, _activity} = ActivityPub.follow(other_user, user)
1664 user = User.get_cached_by_id(user.id)
1668 |> assign(:user, user)
1669 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1671 assert relationship = json_response(conn, 200)
1672 assert to_string(other_user.id) == relationship["id"]
1674 user = User.get_cached_by_id(user.id)
1675 other_user = User.get_cached_by_id(other_user.id)
1677 assert User.following?(other_user, user) == false
1681 test "account fetching", %{conn: conn} do
1682 user = insert(:user)
1686 |> get("/api/v1/accounts/#{user.id}")
1688 assert %{"id" => id} = json_response(conn, 200)
1689 assert id == to_string(user.id)
1693 |> get("/api/v1/accounts/-1")
1695 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1698 test "account fetching also works nickname", %{conn: conn} do
1699 user = insert(:user)
1703 |> get("/api/v1/accounts/#{user.nickname}")
1705 assert %{"id" => id} = json_response(conn, 200)
1706 assert id == user.id
1709 test "mascot upload", %{conn: conn} do
1710 user = insert(:user)
1712 non_image_file = %Plug.Upload{
1713 content_type: "audio/mpeg",
1714 path: Path.absname("test/fixtures/sound.mp3"),
1715 filename: "sound.mp3"
1720 |> assign(:user, user)
1721 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1723 assert json_response(conn, 415)
1725 file = %Plug.Upload{
1726 content_type: "image/jpg",
1727 path: Path.absname("test/fixtures/image.jpg"),
1728 filename: "an_image.jpg"
1733 |> assign(:user, user)
1734 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1736 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1739 test "mascot retrieving", %{conn: conn} do
1740 user = insert(:user)
1741 # When user hasn't set a mascot, we should just get pleroma tan back
1744 |> assign(:user, user)
1745 |> get("/api/v1/pleroma/mascot")
1747 assert %{"url" => url} = json_response(conn, 200)
1748 assert url =~ "pleroma-fox-tan-smol"
1750 # When a user sets their mascot, we should get that back
1751 file = %Plug.Upload{
1752 content_type: "image/jpg",
1753 path: Path.absname("test/fixtures/image.jpg"),
1754 filename: "an_image.jpg"
1759 |> assign(:user, user)
1760 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1762 assert json_response(conn, 200)
1764 user = User.get_cached_by_id(user.id)
1768 |> assign(:user, user)
1769 |> get("/api/v1/pleroma/mascot")
1771 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1772 assert url =~ "an_image"
1775 test "hashtag timeline", %{conn: conn} do
1776 following = insert(:user)
1779 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1781 {:ok, [_activity]} =
1782 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1786 |> get("/api/v1/timelines/tag/2hu")
1788 assert [%{"id" => id}] = json_response(nconn, 200)
1790 assert id == to_string(activity.id)
1792 # works for different capitalization too
1795 |> get("/api/v1/timelines/tag/2HU")
1797 assert [%{"id" => id}] = json_response(nconn, 200)
1799 assert id == to_string(activity.id)
1803 test "multi-hashtag timeline", %{conn: conn} do
1804 user = insert(:user)
1806 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1807 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1808 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1812 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1814 [status_none, status_test1, status_test] = json_response(any_test, 200)
1816 assert to_string(activity_test.id) == status_test["id"]
1817 assert to_string(activity_test1.id) == status_test1["id"]
1818 assert to_string(activity_none.id) == status_none["id"]
1822 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1824 assert [status_test1] == json_response(restricted_test, 200)
1826 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1828 assert [status_none] == json_response(all_test, 200)
1831 test "getting followers", %{conn: conn} do
1832 user = insert(:user)
1833 other_user = insert(:user)
1834 {:ok, user} = User.follow(user, other_user)
1838 |> get("/api/v1/accounts/#{other_user.id}/followers")
1840 assert [%{"id" => id}] = json_response(conn, 200)
1841 assert id == to_string(user.id)
1844 test "getting followers, hide_followers", %{conn: conn} do
1845 user = insert(:user)
1846 other_user = insert(:user, %{info: %{hide_followers: true}})
1847 {:ok, _user} = User.follow(user, other_user)
1851 |> get("/api/v1/accounts/#{other_user.id}/followers")
1853 assert [] == json_response(conn, 200)
1856 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1857 user = insert(:user)
1858 other_user = insert(:user, %{info: %{hide_followers: true}})
1859 {:ok, _user} = User.follow(user, other_user)
1863 |> assign(:user, other_user)
1864 |> get("/api/v1/accounts/#{other_user.id}/followers")
1866 refute [] == json_response(conn, 200)
1869 test "getting followers, pagination", %{conn: conn} do
1870 user = insert(:user)
1871 follower1 = insert(:user)
1872 follower2 = insert(:user)
1873 follower3 = insert(:user)
1874 {:ok, _} = User.follow(follower1, user)
1875 {:ok, _} = User.follow(follower2, user)
1876 {:ok, _} = User.follow(follower3, user)
1880 |> assign(:user, user)
1884 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1886 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1887 assert id3 == follower3.id
1888 assert id2 == follower2.id
1892 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1894 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1895 assert id2 == follower2.id
1896 assert id1 == follower1.id
1900 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1902 assert [%{"id" => id2}] = json_response(res_conn, 200)
1903 assert id2 == follower2.id
1905 assert [link_header] = get_resp_header(res_conn, "link")
1906 assert link_header =~ ~r/min_id=#{follower2.id}/
1907 assert link_header =~ ~r/max_id=#{follower2.id}/
1910 test "getting following", %{conn: conn} do
1911 user = insert(:user)
1912 other_user = insert(:user)
1913 {:ok, user} = User.follow(user, other_user)
1917 |> get("/api/v1/accounts/#{user.id}/following")
1919 assert [%{"id" => id}] = json_response(conn, 200)
1920 assert id == to_string(other_user.id)
1923 test "getting following, hide_follows", %{conn: conn} do
1924 user = insert(:user, %{info: %{hide_follows: true}})
1925 other_user = insert(:user)
1926 {:ok, user} = User.follow(user, other_user)
1930 |> get("/api/v1/accounts/#{user.id}/following")
1932 assert [] == json_response(conn, 200)
1935 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1936 user = insert(:user, %{info: %{hide_follows: true}})
1937 other_user = insert(:user)
1938 {:ok, user} = User.follow(user, other_user)
1942 |> assign(:user, user)
1943 |> get("/api/v1/accounts/#{user.id}/following")
1945 refute [] == json_response(conn, 200)
1948 test "getting following, pagination", %{conn: conn} do
1949 user = insert(:user)
1950 following1 = insert(:user)
1951 following2 = insert(:user)
1952 following3 = insert(:user)
1953 {:ok, _} = User.follow(user, following1)
1954 {:ok, _} = User.follow(user, following2)
1955 {:ok, _} = User.follow(user, following3)
1959 |> assign(:user, user)
1963 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1965 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1966 assert id3 == following3.id
1967 assert id2 == following2.id
1971 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1973 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1974 assert id2 == following2.id
1975 assert id1 == following1.id
1979 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1981 assert [%{"id" => id2}] = json_response(res_conn, 200)
1982 assert id2 == following2.id
1984 assert [link_header] = get_resp_header(res_conn, "link")
1985 assert link_header =~ ~r/min_id=#{following2.id}/
1986 assert link_header =~ ~r/max_id=#{following2.id}/
1989 test "following / unfollowing a user", %{conn: conn} do
1990 user = insert(:user)
1991 other_user = insert(:user)
1995 |> assign(:user, user)
1996 |> post("/api/v1/accounts/#{other_user.id}/follow")
1998 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
2000 user = User.get_cached_by_id(user.id)
2004 |> assign(:user, user)
2005 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
2007 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
2009 user = User.get_cached_by_id(user.id)
2013 |> assign(:user, user)
2014 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
2016 assert %{"id" => id} = json_response(conn, 200)
2017 assert id == to_string(other_user.id)
2020 test "following without reblogs" do
2021 follower = insert(:user)
2022 followed = insert(:user)
2023 other_user = insert(:user)
2027 |> assign(:user, follower)
2028 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
2030 assert %{"showing_reblogs" => false} = json_response(conn, 200)
2032 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
2033 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
2037 |> assign(:user, User.get_cached_by_id(follower.id))
2038 |> get("/api/v1/timelines/home")
2040 assert [] == json_response(conn, 200)
2044 |> assign(:user, follower)
2045 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
2047 assert %{"showing_reblogs" => true} = json_response(conn, 200)
2051 |> assign(:user, User.get_cached_by_id(follower.id))
2052 |> get("/api/v1/timelines/home")
2054 expected_activity_id = reblog.id
2055 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
2058 test "following / unfollowing errors" do
2059 user = insert(:user)
2063 |> assign(:user, user)
2066 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
2067 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2070 user = User.get_cached_by_id(user.id)
2071 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
2072 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2074 # self follow via uri
2075 user = User.get_cached_by_id(user.id)
2076 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
2077 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2079 # follow non existing user
2080 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
2081 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2083 # follow non existing user via uri
2084 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
2085 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2087 # unfollow non existing user
2088 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
2089 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2092 test "muting / unmuting a user", %{conn: conn} do
2093 user = insert(:user)
2094 other_user = insert(:user)
2098 |> assign(:user, user)
2099 |> post("/api/v1/accounts/#{other_user.id}/mute")
2101 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
2103 user = User.get_cached_by_id(user.id)
2107 |> assign(:user, user)
2108 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2110 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
2113 test "subscribing / unsubscribing to a user", %{conn: conn} do
2114 user = insert(:user)
2115 subscription_target = insert(:user)
2119 |> assign(:user, user)
2120 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2122 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2126 |> assign(:user, user)
2127 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2129 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2132 test "getting a list of mutes", %{conn: conn} do
2133 user = insert(:user)
2134 other_user = insert(:user)
2136 {:ok, user} = User.mute(user, other_user)
2140 |> assign(:user, user)
2141 |> get("/api/v1/mutes")
2143 other_user_id = to_string(other_user.id)
2144 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2147 test "blocking / unblocking a user", %{conn: conn} do
2148 user = insert(:user)
2149 other_user = insert(:user)
2153 |> assign(:user, user)
2154 |> post("/api/v1/accounts/#{other_user.id}/block")
2156 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2158 user = User.get_cached_by_id(user.id)
2162 |> assign(:user, user)
2163 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2165 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2168 test "getting a list of blocks", %{conn: conn} do
2169 user = insert(:user)
2170 other_user = insert(:user)
2172 {:ok, user} = User.block(user, other_user)
2176 |> assign(:user, user)
2177 |> get("/api/v1/blocks")
2179 other_user_id = to_string(other_user.id)
2180 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2183 test "blocking / unblocking a domain", %{conn: conn} do
2184 user = insert(:user)
2185 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2189 |> assign(:user, user)
2190 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2192 assert %{} = json_response(conn, 200)
2193 user = User.get_cached_by_ap_id(user.ap_id)
2194 assert User.blocks?(user, other_user)
2198 |> assign(:user, user)
2199 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2201 assert %{} = json_response(conn, 200)
2202 user = User.get_cached_by_ap_id(user.ap_id)
2203 refute User.blocks?(user, other_user)
2206 test "getting a list of domain blocks", %{conn: conn} do
2207 user = insert(:user)
2209 {:ok, user} = User.block_domain(user, "bad.site")
2210 {:ok, user} = User.block_domain(user, "even.worse.site")
2214 |> assign(:user, user)
2215 |> get("/api/v1/domain_blocks")
2217 domain_blocks = json_response(conn, 200)
2219 assert "bad.site" in domain_blocks
2220 assert "even.worse.site" in domain_blocks
2223 test "unimplemented follow_requests, blocks, domain blocks" do
2224 user = insert(:user)
2226 ["blocks", "domain_blocks", "follow_requests"]
2227 |> Enum.each(fn endpoint ->
2230 |> assign(:user, user)
2231 |> get("/api/v1/#{endpoint}")
2233 assert [] = json_response(conn, 200)
2237 test "returns the favorites of a user", %{conn: conn} do
2238 user = insert(:user)
2239 other_user = insert(:user)
2241 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2242 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2244 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2248 |> assign(:user, user)
2249 |> get("/api/v1/favourites")
2251 assert [status] = json_response(first_conn, 200)
2252 assert status["id"] == to_string(activity.id)
2254 assert [{"link", _link_header}] =
2255 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2257 # Honours query params
2258 {:ok, second_activity} =
2259 CommonAPI.post(other_user, %{
2261 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2264 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2266 last_like = status["id"]
2270 |> assign(:user, user)
2271 |> get("/api/v1/favourites?since_id=#{last_like}")
2273 assert [second_status] = json_response(second_conn, 200)
2274 assert second_status["id"] == to_string(second_activity.id)
2278 |> assign(:user, user)
2279 |> get("/api/v1/favourites?limit=0")
2281 assert [] = json_response(third_conn, 200)
2284 describe "getting favorites timeline of specified user" do
2286 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2287 [current_user: current_user, user: user]
2290 test "returns list of statuses favorited by specified user", %{
2292 current_user: current_user,
2295 [activity | _] = insert_pair(:note_activity)
2296 CommonAPI.favorite(activity.id, user)
2300 |> assign(:user, current_user)
2301 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2302 |> json_response(:ok)
2306 assert length(response) == 1
2307 assert like["id"] == activity.id
2310 test "returns favorites for specified user_id when user is not logged in", %{
2314 activity = insert(:note_activity)
2315 CommonAPI.favorite(activity.id, user)
2319 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2320 |> json_response(:ok)
2322 assert length(response) == 1
2325 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2327 current_user: current_user,
2331 CommonAPI.post(current_user, %{
2332 "status" => "Hi @#{user.nickname}!",
2333 "visibility" => "direct"
2336 CommonAPI.favorite(direct.id, user)
2340 |> assign(:user, current_user)
2341 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2342 |> json_response(:ok)
2344 assert length(response) == 1
2346 anonymous_response =
2348 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2349 |> json_response(:ok)
2351 assert Enum.empty?(anonymous_response)
2354 test "does not return others' favorited DM when user is not one of recipients", %{
2356 current_user: current_user,
2359 user_two = insert(:user)
2362 CommonAPI.post(user_two, %{
2363 "status" => "Hi @#{user.nickname}!",
2364 "visibility" => "direct"
2367 CommonAPI.favorite(direct.id, user)
2371 |> assign(:user, current_user)
2372 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2373 |> json_response(:ok)
2375 assert Enum.empty?(response)
2378 test "paginates favorites using since_id and max_id", %{
2380 current_user: current_user,
2383 activities = insert_list(10, :note_activity)
2385 Enum.each(activities, fn activity ->
2386 CommonAPI.favorite(activity.id, user)
2389 third_activity = Enum.at(activities, 2)
2390 seventh_activity = Enum.at(activities, 6)
2394 |> assign(:user, current_user)
2395 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2396 since_id: third_activity.id,
2397 max_id: seventh_activity.id
2399 |> json_response(:ok)
2401 assert length(response) == 3
2402 refute third_activity in response
2403 refute seventh_activity in response
2406 test "limits favorites using limit parameter", %{
2408 current_user: current_user,
2412 |> insert_list(:note_activity)
2413 |> Enum.each(fn activity ->
2414 CommonAPI.favorite(activity.id, user)
2419 |> assign(:user, current_user)
2420 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2421 |> json_response(:ok)
2423 assert length(response) == 3
2426 test "returns empty response when user does not have any favorited statuses", %{
2428 current_user: current_user,
2433 |> assign(:user, current_user)
2434 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2435 |> json_response(:ok)
2437 assert Enum.empty?(response)
2440 test "returns 404 error when specified user is not exist", %{conn: conn} do
2441 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2443 assert json_response(conn, 404) == %{"error" => "Record not found"}
2446 test "returns 403 error when user has hidden own favorites", %{
2448 current_user: current_user
2450 user = insert(:user, %{info: %{hide_favorites: true}})
2451 activity = insert(:note_activity)
2452 CommonAPI.favorite(activity.id, user)
2456 |> assign(:user, current_user)
2457 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2459 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2462 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2463 user = insert(:user)
2464 activity = insert(:note_activity)
2465 CommonAPI.favorite(activity.id, user)
2469 |> assign(:user, current_user)
2470 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2472 assert user.info.hide_favorites
2473 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2477 test "get instance information", %{conn: conn} do
2478 conn = get(conn, "/api/v1/instance")
2479 assert result = json_response(conn, 200)
2481 email = Pleroma.Config.get([:instance, :email])
2482 # Note: not checking for "max_toot_chars" since it's optional
2488 "email" => from_config_email,
2490 "streaming_api" => _
2495 "registrations" => _,
2499 assert email == from_config_email
2502 test "get instance stats", %{conn: conn} do
2503 user = insert(:user, %{local: true})
2505 user2 = insert(:user, %{local: true})
2506 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2508 insert(:user, %{local: false, nickname: "u@peer1.com"})
2509 insert(:user, %{local: false, nickname: "u@peer2.com"})
2511 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2513 # Stats should count users with missing or nil `info.deactivated` value
2514 user = User.get_cached_by_id(user.id)
2515 info_change = Changeset.change(user.info, %{deactivated: nil})
2519 |> Changeset.change()
2520 |> Changeset.put_embed(:info, info_change)
2521 |> User.update_and_set_cache()
2523 Pleroma.Stats.update_stats()
2525 conn = get(conn, "/api/v1/instance")
2527 assert result = json_response(conn, 200)
2529 stats = result["stats"]
2532 assert stats["user_count"] == 1
2533 assert stats["status_count"] == 1
2534 assert stats["domain_count"] == 2
2537 test "get peers", %{conn: conn} do
2538 insert(:user, %{local: false, nickname: "u@peer1.com"})
2539 insert(:user, %{local: false, nickname: "u@peer2.com"})
2541 Pleroma.Stats.update_stats()
2543 conn = get(conn, "/api/v1/instance/peers")
2545 assert result = json_response(conn, 200)
2547 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2550 test "put settings", %{conn: conn} do
2551 user = insert(:user)
2555 |> assign(:user, user)
2556 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2558 assert _result = json_response(conn, 200)
2560 user = User.get_cached_by_ap_id(user.ap_id)
2561 assert user.info.settings == %{"programming" => "socks"}
2564 describe "pinned statuses" do
2566 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2568 user = insert(:user)
2569 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2571 [user: user, activity: activity]
2574 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2575 {:ok, _} = CommonAPI.pin(activity.id, user)
2579 |> assign(:user, user)
2580 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2581 |> json_response(200)
2583 id_str = to_string(activity.id)
2585 assert [%{"id" => ^id_str, "pinned" => true}] = result
2588 test "pin status", %{conn: conn, user: user, activity: activity} do
2589 id_str = to_string(activity.id)
2591 assert %{"id" => ^id_str, "pinned" => true} =
2593 |> assign(:user, user)
2594 |> post("/api/v1/statuses/#{activity.id}/pin")
2595 |> json_response(200)
2597 assert [%{"id" => ^id_str, "pinned" => true}] =
2599 |> assign(:user, user)
2600 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2601 |> json_response(200)
2604 test "unpin status", %{conn: conn, user: user, activity: activity} do
2605 {:ok, _} = CommonAPI.pin(activity.id, user)
2607 id_str = to_string(activity.id)
2608 user = refresh_record(user)
2610 assert %{"id" => ^id_str, "pinned" => false} =
2612 |> assign(:user, user)
2613 |> post("/api/v1/statuses/#{activity.id}/unpin")
2614 |> json_response(200)
2618 |> assign(:user, user)
2619 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2620 |> json_response(200)
2623 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2624 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2626 id_str_one = to_string(activity_one.id)
2628 assert %{"id" => ^id_str_one, "pinned" => true} =
2630 |> assign(:user, user)
2631 |> post("/api/v1/statuses/#{id_str_one}/pin")
2632 |> json_response(200)
2634 user = refresh_record(user)
2636 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2638 |> assign(:user, user)
2639 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2640 |> json_response(400)
2646 Pleroma.Config.put([:rich_media, :enabled], true)
2649 Pleroma.Config.put([:rich_media, :enabled], false)
2652 user = insert(:user)
2656 test "returns rich-media card", %{conn: conn, user: user} do
2657 {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
2660 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2661 "provider_name" => "www.imdb.com",
2662 "provider_url" => "http://www.imdb.com",
2663 "title" => "The Rock",
2665 "url" => "http://www.imdb.com/title/tt0117500/",
2667 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2670 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2671 "title" => "The Rock",
2672 "type" => "video.movie",
2673 "url" => "http://www.imdb.com/title/tt0117500/",
2675 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2682 |> get("/api/v1/statuses/#{activity.id}/card")
2683 |> json_response(200)
2685 assert response == card_data
2687 # works with private posts
2689 CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})
2693 |> assign(:user, user)
2694 |> get("/api/v1/statuses/#{activity.id}/card")
2695 |> json_response(200)
2697 assert response_two == card_data
2700 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2701 {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"})
2705 |> get("/api/v1/statuses/#{activity.id}/card")
2706 |> json_response(:ok)
2708 assert response == %{
2710 "title" => "Pleroma",
2711 "description" => "",
2713 "provider_name" => "pleroma.social",
2714 "provider_url" => "https://pleroma.social",
2715 "url" => "https://pleroma.social/",
2718 "title" => "Pleroma",
2719 "type" => "website",
2720 "url" => "https://pleroma.social/"
2728 user = insert(:user)
2729 for_user = insert(:user)
2732 CommonAPI.post(user, %{
2733 "status" => "heweoo?"
2737 CommonAPI.post(user, %{
2738 "status" => "heweoo!"
2743 |> assign(:user, for_user)
2744 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2746 assert json_response(response1, 200)["bookmarked"] == true
2750 |> assign(:user, for_user)
2751 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2753 assert json_response(response2, 200)["bookmarked"] == true
2757 |> assign(:user, for_user)
2758 |> get("/api/v1/bookmarks")
2760 assert [json_response(response2, 200), json_response(response1, 200)] ==
2761 json_response(bookmarks, 200)
2765 |> assign(:user, for_user)
2766 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2768 assert json_response(response1, 200)["bookmarked"] == false
2772 |> assign(:user, for_user)
2773 |> get("/api/v1/bookmarks")
2775 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2778 describe "conversation muting" do
2780 user = insert(:user)
2781 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2783 [user: user, activity: activity]
2786 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2787 id_str = to_string(activity.id)
2789 assert %{"id" => ^id_str, "muted" => true} =
2791 |> assign(:user, user)
2792 |> post("/api/v1/statuses/#{activity.id}/mute")
2793 |> json_response(200)
2796 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2797 {:ok, _} = CommonAPI.add_mute(user, activity)
2799 id_str = to_string(activity.id)
2800 user = refresh_record(user)
2802 assert %{"id" => ^id_str, "muted" => false} =
2804 |> assign(:user, user)
2805 |> post("/api/v1/statuses/#{activity.id}/unmute")
2806 |> json_response(200)
2810 describe "reports" do
2812 reporter = insert(:user)
2813 target_user = insert(:user)
2815 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2817 [reporter: reporter, target_user: target_user, activity: activity]
2820 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2821 assert %{"action_taken" => false, "id" => _} =
2823 |> assign(:user, reporter)
2824 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2825 |> json_response(200)
2828 test "submit a report with statuses and comment", %{
2831 target_user: target_user,
2834 assert %{"action_taken" => false, "id" => _} =
2836 |> assign(:user, reporter)
2837 |> post("/api/v1/reports", %{
2838 "account_id" => target_user.id,
2839 "status_ids" => [activity.id],
2840 "comment" => "bad status!"
2842 |> json_response(200)
2845 test "account_id is required", %{
2850 assert %{"error" => "Valid `account_id` required"} =
2852 |> assign(:user, reporter)
2853 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2854 |> json_response(400)
2857 test "comment must be up to the size specified in the config", %{
2860 target_user: target_user
2862 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2863 comment = String.pad_trailing("a", max_size + 1, "a")
2865 error = %{"error" => "Comment must be up to #{max_size} characters"}
2869 |> assign(:user, reporter)
2870 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2871 |> json_response(400)
2875 describe "link headers" do
2876 test "preserves parameters in link headers", %{conn: conn} do
2877 user = insert(:user)
2878 other_user = insert(:user)
2881 CommonAPI.post(other_user, %{
2882 "status" => "hi @#{user.nickname}",
2883 "visibility" => "public"
2887 CommonAPI.post(other_user, %{
2888 "status" => "hi @#{user.nickname}",
2889 "visibility" => "public"
2892 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2893 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2897 |> assign(:user, user)
2898 |> get("/api/v1/notifications", %{media_only: true})
2900 assert [link_header] = get_resp_header(conn, "link")
2901 assert link_header =~ ~r/media_only=true/
2902 assert link_header =~ ~r/min_id=#{notification2.id}/
2903 assert link_header =~ ~r/max_id=#{notification1.id}/
2907 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2908 # Need to set an old-style integer ID to reproduce the problem
2909 # (these are no longer assigned to new accounts but were preserved
2910 # for existing accounts during the migration to flakeIDs)
2911 user_one = insert(:user, %{id: 1212})
2912 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2916 |> get("/api/v1/accounts/#{user_one.id}")
2920 |> get("/api/v1/accounts/#{user_two.nickname}")
2924 |> get("/api/v1/accounts/#{user_two.id}")
2926 acc_one = json_response(resp_one, 200)
2927 acc_two = json_response(resp_two, 200)
2928 acc_three = json_response(resp_three, 200)
2929 refute acc_one == acc_two
2930 assert acc_two == acc_three
2933 describe "custom emoji" do
2934 test "with tags", %{conn: conn} do
2937 |> get("/api/v1/custom_emojis")
2938 |> json_response(200)
2940 assert Map.has_key?(emoji, "shortcode")
2941 assert Map.has_key?(emoji, "static_url")
2942 assert Map.has_key?(emoji, "tags")
2943 assert is_list(emoji["tags"])
2944 assert Map.has_key?(emoji, "url")
2945 assert Map.has_key?(emoji, "visible_in_picker")
2949 describe "index/2 redirections" do
2950 setup %{conn: conn} do
2954 signing_salt: "cooldude"
2959 |> Plug.Session.call(Plug.Session.init(session_opts))
2962 test_path = "/web/statuses/test"
2963 %{conn: conn, path: test_path}
2966 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
2967 conn = get(conn, path)
2969 assert conn.status == 302
2970 assert redirected_to(conn) == "/web/login"
2973 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
2974 token = insert(:oauth_token)
2978 |> assign(:user, token.user)
2979 |> put_session(:oauth_token, token.token)
2982 assert conn.status == 200
2985 test "saves referer path to session", %{conn: conn, path: path} do
2986 conn = get(conn, path)
2987 return_to = Plug.Conn.get_session(conn, :return_to)
2989 assert return_to == path
2992 test "redirects to the saved path after log in", %{conn: conn, path: path} do
2993 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2994 auth = insert(:oauth_authorization, app: app)
2998 |> put_session(:return_to, path)
2999 |> get("/web/login", %{code: auth.token})
3001 assert conn.status == 302
3002 assert redirected_to(conn) == path
3005 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
3006 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3007 auth = insert(:oauth_authorization, app: app)
3009 conn = get(conn, "/web/login", %{code: auth.token})
3011 assert conn.status == 302
3012 assert redirected_to(conn) == "/web/getting-started"
3016 describe "scheduled activities" do
3017 test "creates a scheduled activity", %{conn: conn} do
3018 user = insert(:user)
3019 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3023 |> assign(:user, user)
3024 |> post("/api/v1/statuses", %{
3025 "status" => "scheduled",
3026 "scheduled_at" => scheduled_at
3029 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
3030 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
3031 assert [] == Repo.all(Activity)
3034 test "creates a scheduled activity with a media attachment", %{conn: conn} do
3035 user = insert(:user)
3036 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3038 file = %Plug.Upload{
3039 content_type: "image/jpg",
3040 path: Path.absname("test/fixtures/image.jpg"),
3041 filename: "an_image.jpg"
3044 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
3048 |> assign(:user, user)
3049 |> post("/api/v1/statuses", %{
3050 "media_ids" => [to_string(upload.id)],
3051 "status" => "scheduled",
3052 "scheduled_at" => scheduled_at
3055 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
3056 assert %{"type" => "image"} = media_attachment
3059 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
3061 user = insert(:user)
3064 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
3068 |> assign(:user, user)
3069 |> post("/api/v1/statuses", %{
3070 "status" => "not scheduled",
3071 "scheduled_at" => scheduled_at
3074 assert %{"content" => "not scheduled"} = json_response(conn, 200)
3075 assert [] == Repo.all(ScheduledActivity)
3078 test "returns error when daily user limit is exceeded", %{conn: conn} do
3079 user = insert(:user)
3082 NaiveDateTime.utc_now()
3083 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3084 |> NaiveDateTime.to_iso8601()
3086 attrs = %{params: %{}, scheduled_at: today}
3087 {:ok, _} = ScheduledActivity.create(user, attrs)
3088 {:ok, _} = ScheduledActivity.create(user, attrs)
3092 |> assign(:user, user)
3093 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3095 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3098 test "returns error when total user limit is exceeded", %{conn: conn} do
3099 user = insert(:user)
3102 NaiveDateTime.utc_now()
3103 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3104 |> NaiveDateTime.to_iso8601()
3107 NaiveDateTime.utc_now()
3108 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3109 |> NaiveDateTime.to_iso8601()
3111 attrs = %{params: %{}, scheduled_at: today}
3112 {:ok, _} = ScheduledActivity.create(user, attrs)
3113 {:ok, _} = ScheduledActivity.create(user, attrs)
3114 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3118 |> assign(:user, user)
3119 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3121 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3124 test "shows scheduled activities", %{conn: conn} do
3125 user = insert(:user)
3126 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3127 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3128 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3129 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3133 |> assign(:user, user)
3138 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3140 result = json_response(conn_res, 200)
3141 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3146 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3148 result = json_response(conn_res, 200)
3149 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3154 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3156 result = json_response(conn_res, 200)
3157 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3160 test "shows a scheduled activity", %{conn: conn} do
3161 user = insert(:user)
3162 scheduled_activity = insert(:scheduled_activity, user: user)
3166 |> assign(:user, user)
3167 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3169 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3170 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3174 |> assign(:user, user)
3175 |> get("/api/v1/scheduled_statuses/404")
3177 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3180 test "updates a scheduled activity", %{conn: conn} do
3181 user = insert(:user)
3182 scheduled_activity = insert(:scheduled_activity, user: user)
3185 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3189 |> assign(:user, user)
3190 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3191 scheduled_at: new_scheduled_at
3194 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3195 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3199 |> assign(:user, user)
3200 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3202 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3205 test "deletes a scheduled activity", %{conn: conn} do
3206 user = insert(:user)
3207 scheduled_activity = insert(:scheduled_activity, user: user)
3211 |> assign(:user, user)
3212 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3214 assert %{} = json_response(res_conn, 200)
3215 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3219 |> assign(:user, user)
3220 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3222 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3226 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3227 user1 = insert(:user)
3228 user2 = insert(:user)
3229 user3 = insert(:user)
3231 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3233 # Reply to status from another user
3236 |> assign(:user, user2)
3237 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3239 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3241 activity = Activity.get_by_id_with_object(id)
3243 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3244 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3246 # Reblog from the third user
3249 |> assign(:user, user3)
3250 |> post("/api/v1/statuses/#{activity.id}/reblog")
3252 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3253 json_response(conn2, 200)
3255 assert to_string(activity.id) == id
3257 # Getting third user status
3260 |> assign(:user, user3)
3261 |> get("api/v1/timelines/home")
3263 [reblogged_activity] = json_response(conn3, 200)
3265 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3267 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3268 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3271 describe "create account by app" do
3272 test "Account registration via Application", %{conn: conn} do
3275 |> post("/api/v1/apps", %{
3276 client_name: "client_name",
3277 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3278 scopes: "read, write, follow"
3282 "client_id" => client_id,
3283 "client_secret" => client_secret,
3285 "name" => "client_name",
3286 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3289 } = json_response(conn, 200)
3293 |> post("/oauth/token", %{
3294 grant_type: "client_credentials",
3295 client_id: client_id,
3296 client_secret: client_secret
3299 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3300 json_response(conn, 200)
3303 token_from_db = Repo.get_by(Token, token: token)
3304 assert token_from_db
3306 assert scope == "read write follow"
3310 |> put_req_header("authorization", "Bearer " <> token)
3311 |> post("/api/v1/accounts", %{
3313 email: "lain@example.org",
3314 password: "PlzDontHackLain",
3319 "access_token" => token,
3320 "created_at" => _created_at,
3322 "token_type" => "Bearer"
3323 } = json_response(conn, 200)
3325 token_from_db = Repo.get_by(Token, token: token)
3326 assert token_from_db
3327 token_from_db = Repo.preload(token_from_db, :user)
3328 assert token_from_db.user
3330 assert token_from_db.user.info.confirmation_pending
3333 test "rate limit", %{conn: conn} do
3334 app_token = insert(:oauth_token, user: nil)
3337 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3338 |> Map.put(:remote_ip, {15, 15, 15, 15})
3343 |> post("/api/v1/accounts", %{
3344 username: "#{i}lain",
3345 email: "#{i}lain@example.org",
3346 password: "PlzDontHackLain",
3351 "access_token" => token,
3352 "created_at" => _created_at,
3354 "token_type" => "Bearer"
3355 } = json_response(conn, 200)
3357 token_from_db = Repo.get_by(Token, token: token)
3358 assert token_from_db
3359 token_from_db = Repo.preload(token_from_db, :user)
3360 assert token_from_db.user
3362 assert token_from_db.user.info.confirmation_pending
3367 |> post("/api/v1/accounts", %{
3369 email: "6lain@example.org",
3370 password: "PlzDontHackLain",
3374 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3378 describe "GET /api/v1/polls/:id" do
3379 test "returns poll entity for object id", %{conn: conn} do
3380 user = insert(:user)
3383 CommonAPI.post(user, %{
3384 "status" => "Pleroma does",
3385 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3388 object = Object.normalize(activity)
3392 |> assign(:user, user)
3393 |> get("/api/v1/polls/#{object.id}")
3395 response = json_response(conn, 200)
3397 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3400 test "does not expose polls for private statuses", %{conn: conn} do
3401 user = insert(:user)
3402 other_user = insert(:user)
3405 CommonAPI.post(user, %{
3406 "status" => "Pleroma does",
3407 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3408 "visibility" => "private"
3411 object = Object.normalize(activity)
3415 |> assign(:user, other_user)
3416 |> get("/api/v1/polls/#{object.id}")
3418 assert json_response(conn, 404)
3422 describe "POST /api/v1/polls/:id/votes" do
3423 test "votes are added to the poll", %{conn: conn} do
3424 user = insert(:user)
3425 other_user = insert(:user)
3428 CommonAPI.post(user, %{
3429 "status" => "A very delicious sandwich",
3431 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3437 object = Object.normalize(activity)
3441 |> assign(:user, other_user)
3442 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3444 assert json_response(conn, 200)
3445 object = Object.get_by_id(object.id)
3447 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3452 test "author can't vote", %{conn: conn} do
3453 user = insert(:user)
3456 CommonAPI.post(user, %{
3457 "status" => "Am I cute?",
3458 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3461 object = Object.normalize(activity)
3464 |> assign(:user, user)
3465 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3466 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3468 object = Object.get_by_id(object.id)
3470 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3473 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3474 user = insert(:user)
3475 other_user = insert(:user)
3478 CommonAPI.post(user, %{
3479 "status" => "The glass is",
3480 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3483 object = Object.normalize(activity)
3486 |> assign(:user, other_user)
3487 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3488 |> json_response(422) == %{"error" => "Too many choices"}
3490 object = Object.get_by_id(object.id)
3492 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->