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.ActivityExpiration
12 alias Pleroma.Notification
15 alias Pleroma.ScheduledActivity
16 alias Pleroma.Tests.ObanHelpers
18 alias Pleroma.Web.ActivityPub.ActivityPub
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.MastodonAPI.FilterView
21 alias Pleroma.Web.OAuth.App
22 alias Pleroma.Web.OAuth.Token
23 alias Pleroma.Web.OStatus
24 alias Pleroma.Web.Push
25 import Pleroma.Factory
26 import ExUnit.CaptureLog
28 import Swoosh.TestAssertions
30 @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
33 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
37 clear_config([:instance, :public])
38 clear_config([:rich_media, :enabled])
40 test "the home timeline", %{conn: conn} do
42 following = insert(:user)
44 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
48 |> assign(:user, user)
49 |> get("/api/v1/timelines/home")
51 assert Enum.empty?(json_response(conn, 200))
53 {:ok, user} = User.follow(user, following)
57 |> assign(:user, user)
58 |> get("/api/v1/timelines/home")
60 assert [%{"content" => "test"}] = json_response(conn, 200)
63 test "the public timeline", %{conn: conn} do
64 following = insert(:user)
67 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
70 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
74 |> get("/api/v1/timelines/public", %{"local" => "False"})
76 assert length(json_response(conn, 200)) == 2
80 |> get("/api/v1/timelines/public", %{"local" => "True"})
82 assert [%{"content" => "test"}] = json_response(conn, 200)
86 |> get("/api/v1/timelines/public", %{"local" => "1"})
88 assert [%{"content" => "test"}] = json_response(conn, 200)
92 test "the public timeline when public is set to false", %{conn: conn} do
93 Config.put([:instance, :public], false)
96 |> get("/api/v1/timelines/public", %{"local" => "False"})
97 |> json_response(403) == %{"error" => "This resource requires authentication."}
100 test "the public timeline includes only public statuses for an authenticated user" do
105 |> assign(:user, user)
107 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
108 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
109 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
110 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
112 res_conn = get(conn, "/api/v1/timelines/public")
113 assert length(json_response(res_conn, 200)) == 1
116 describe "posting statuses" do
122 |> assign(:user, user)
127 test "posting a status", %{conn: conn} do
128 idempotency_key = "Pikachu rocks!"
132 |> put_req_header("idempotency-key", idempotency_key)
133 |> post("/api/v1/statuses", %{
135 "spoiler_text" => "2hu",
136 "sensitive" => "false"
139 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
141 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
143 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
144 json_response(conn_one, 200)
146 assert Activity.get_by_id(id)
150 |> put_req_header("idempotency-key", idempotency_key)
151 |> post("/api/v1/statuses", %{
153 "spoiler_text" => "2hu",
154 "sensitive" => "false"
157 assert %{"id" => second_id} = json_response(conn_two, 200)
158 assert id == second_id
162 |> post("/api/v1/statuses", %{
164 "spoiler_text" => "2hu",
165 "sensitive" => "false"
168 assert %{"id" => third_id} = json_response(conn_three, 200)
169 refute id == third_id
171 # An activity that will expire:
173 expires_in = 120 * 60
177 |> post("api/v1/statuses", %{
178 "status" => "oolong",
179 "expires_in" => expires_in
182 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
183 assert activity = Activity.get_by_id(fourth_id)
184 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
186 estimated_expires_at =
187 NaiveDateTime.utc_now()
188 |> NaiveDateTime.add(expires_in)
189 |> NaiveDateTime.truncate(:second)
191 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
192 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
194 assert fourth_response["pleroma"]["expires_at"] ==
195 NaiveDateTime.to_iso8601(expiration.scheduled_at)
198 test "replying to a status", %{conn: conn} do
200 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
204 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
206 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
208 activity = Activity.get_by_id(id)
210 assert activity.data["context"] == replied_to.data["context"]
211 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
214 test "replying to a direct message with visibility other than direct", %{conn: conn} do
216 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
218 Enum.each(["public", "private", "unlisted"], fn visibility ->
221 |> post("/api/v1/statuses", %{
222 "status" => "@#{user.nickname} hey",
223 "in_reply_to_id" => replied_to.id,
224 "visibility" => visibility
227 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
231 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
234 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
236 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
237 assert Activity.get_by_id(id)
240 test "posting a sensitive status", %{conn: conn} do
243 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
245 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
246 assert Activity.get_by_id(id)
249 test "posting a fake status", %{conn: conn} do
252 |> post("/api/v1/statuses", %{
254 "\"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"
257 real_status = json_response(real_conn, 200)
260 assert Object.get_by_ap_id(real_status["uri"])
264 |> Map.put("id", nil)
265 |> Map.put("url", nil)
266 |> Map.put("uri", nil)
267 |> Map.put("created_at", nil)
268 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
272 |> post("/api/v1/statuses", %{
274 "\"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",
278 fake_status = json_response(fake_conn, 200)
281 refute Object.get_by_ap_id(fake_status["uri"])
285 |> Map.put("id", nil)
286 |> Map.put("url", nil)
287 |> Map.put("uri", nil)
288 |> Map.put("created_at", nil)
289 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
291 assert real_status == fake_status
294 test "posting a status with OGP link preview", %{conn: conn} do
295 Config.put([:rich_media, :enabled], true)
299 |> post("/api/v1/statuses", %{
300 "status" => "https://example.com/ogp"
303 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
304 assert Activity.get_by_id(id)
307 test "posting a direct status", %{conn: conn} do
308 user2 = insert(:user)
309 content = "direct cofe @#{user2.nickname}"
313 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
315 assert %{"id" => id} = response = json_response(conn, 200)
316 assert response["visibility"] == "direct"
317 assert response["pleroma"]["direct_conversation_id"]
318 assert activity = Activity.get_by_id(id)
319 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
320 assert activity.data["to"] == [user2.ap_id]
321 assert activity.data["cc"] == []
325 describe "posting polls" do
326 test "posting a poll", %{conn: conn} do
328 time = NaiveDateTime.utc_now()
332 |> assign(:user, user)
333 |> post("/api/v1/statuses", %{
334 "status" => "Who is the #bestgrill?",
335 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
338 response = json_response(conn, 200)
340 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
341 title in ["Rei", "Asuka", "Misato"]
344 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
345 refute response["poll"]["expred"]
348 test "option limit is enforced", %{conn: conn} do
350 limit = Config.get([:instance, :poll_limits, :max_options])
354 |> assign(:user, user)
355 |> post("/api/v1/statuses", %{
357 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
360 %{"error" => error} = json_response(conn, 422)
361 assert error == "Poll can't contain more than #{limit} options"
364 test "option character limit is enforced", %{conn: conn} do
366 limit = Config.get([:instance, :poll_limits, :max_option_chars])
370 |> assign(:user, user)
371 |> post("/api/v1/statuses", %{
374 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
379 %{"error" => error} = json_response(conn, 422)
380 assert error == "Poll options cannot be longer than #{limit} characters each"
383 test "minimal date limit is enforced", %{conn: conn} do
385 limit = Config.get([:instance, :poll_limits, :min_expiration])
389 |> assign(:user, user)
390 |> post("/api/v1/statuses", %{
391 "status" => "imagine arbitrary limits",
393 "options" => ["this post was made by pleroma gang"],
394 "expires_in" => limit - 1
398 %{"error" => error} = json_response(conn, 422)
399 assert error == "Expiration date is too soon"
402 test "maximum date limit is enforced", %{conn: conn} do
404 limit = Config.get([:instance, :poll_limits, :max_expiration])
408 |> assign(:user, user)
409 |> post("/api/v1/statuses", %{
410 "status" => "imagine arbitrary limits",
412 "options" => ["this post was made by pleroma gang"],
413 "expires_in" => limit + 1
417 %{"error" => error} = json_response(conn, 422)
418 assert error == "Expiration date is too far in the future"
422 test "direct timeline", %{conn: conn} do
423 user_one = insert(:user)
424 user_two = insert(:user)
426 {:ok, user_two} = User.follow(user_two, user_one)
429 CommonAPI.post(user_one, %{
430 "status" => "Hi @#{user_two.nickname}!",
431 "visibility" => "direct"
434 {:ok, _follower_only} =
435 CommonAPI.post(user_one, %{
436 "status" => "Hi @#{user_two.nickname}!",
437 "visibility" => "private"
440 # Only direct should be visible here
443 |> assign(:user, user_two)
444 |> get("api/v1/timelines/direct")
446 [status] = json_response(res_conn, 200)
448 assert %{"visibility" => "direct"} = status
449 assert status["url"] != direct.data["id"]
451 # User should be able to see their own direct message
454 |> assign(:user, user_one)
455 |> get("api/v1/timelines/direct")
457 [status] = json_response(res_conn, 200)
459 assert %{"visibility" => "direct"} = status
461 # Both should be visible here
464 |> assign(:user, user_two)
465 |> get("api/v1/timelines/home")
467 [_s1, _s2] = json_response(res_conn, 200)
470 Enum.each(1..20, fn _ ->
472 CommonAPI.post(user_one, %{
473 "status" => "Hi @#{user_two.nickname}!",
474 "visibility" => "direct"
480 |> assign(:user, user_two)
481 |> get("api/v1/timelines/direct")
483 statuses = json_response(res_conn, 200)
484 assert length(statuses) == 20
488 |> assign(:user, user_two)
489 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
491 [status] = json_response(res_conn, 200)
493 assert status["url"] != direct.data["id"]
496 test "Conversations", %{conn: conn} do
497 user_one = insert(:user)
498 user_two = insert(:user)
499 user_three = insert(:user)
501 {:ok, user_two} = User.follow(user_two, user_one)
504 CommonAPI.post(user_one, %{
505 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
506 "visibility" => "direct"
509 {:ok, _follower_only} =
510 CommonAPI.post(user_one, %{
511 "status" => "Hi @#{user_two.nickname}!",
512 "visibility" => "private"
517 |> assign(:user, user_one)
518 |> get("/api/v1/conversations")
520 assert response = json_response(res_conn, 200)
525 "accounts" => res_accounts,
526 "last_status" => res_last_status,
531 account_ids = Enum.map(res_accounts, & &1["id"])
532 assert length(res_accounts) == 2
533 assert user_two.id in account_ids
534 assert user_three.id in account_ids
535 assert is_binary(res_id)
536 assert unread == true
537 assert res_last_status["id"] == direct.id
539 # Apparently undocumented API endpoint
542 |> assign(:user, user_one)
543 |> post("/api/v1/conversations/#{res_id}/read")
545 assert response = json_response(res_conn, 200)
546 assert length(response["accounts"]) == 2
547 assert response["last_status"]["id"] == direct.id
548 assert response["unread"] == false
550 # (vanilla) Mastodon frontend behaviour
553 |> assign(:user, user_one)
554 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
556 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
559 test "doesn't include DMs from blocked users", %{conn: conn} do
560 blocker = insert(:user)
561 blocked = insert(:user)
563 {:ok, blocker} = User.block(blocker, blocked)
565 {:ok, _blocked_direct} =
566 CommonAPI.post(blocked, %{
567 "status" => "Hi @#{blocker.nickname}!",
568 "visibility" => "direct"
572 CommonAPI.post(user, %{
573 "status" => "Hi @#{blocker.nickname}!",
574 "visibility" => "direct"
579 |> assign(:user, user)
580 |> get("api/v1/timelines/direct")
582 [status] = json_response(res_conn, 200)
583 assert status["id"] == direct.id
586 test "verify_credentials", %{conn: conn} do
591 |> assign(:user, user)
592 |> get("/api/v1/accounts/verify_credentials")
594 response = json_response(conn, 200)
596 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
597 assert response["pleroma"]["chat_token"]
598 assert id == to_string(user.id)
601 test "verify_credentials default scope unlisted", %{conn: conn} do
602 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
606 |> assign(:user, user)
607 |> get("/api/v1/accounts/verify_credentials")
609 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
610 assert id == to_string(user.id)
613 test "apps/verify_credentials", %{conn: conn} do
614 token = insert(:oauth_token)
618 |> assign(:user, token.user)
619 |> assign(:token, token)
620 |> get("/api/v1/apps/verify_credentials")
622 app = Repo.preload(token, :app).app
625 "name" => app.client_name,
626 "website" => app.website,
627 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
630 assert expected == json_response(conn, 200)
633 test "user avatar can be set", %{conn: conn} do
635 avatar_image = File.read!("test/fixtures/avatar_data_uri")
639 |> assign(:user, user)
640 |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
642 user = refresh_record(user)
656 assert %{"url" => _} = json_response(conn, 200)
659 test "user avatar can be reset", %{conn: conn} do
664 |> assign(:user, user)
665 |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
667 user = User.get_cached_by_id(user.id)
669 assert user.avatar == nil
671 assert %{"url" => nil} = json_response(conn, 200)
674 test "can set profile banner", %{conn: conn} do
679 |> assign(:user, user)
680 |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
682 user = refresh_record(user)
683 assert user.info.banner["type"] == "Image"
685 assert %{"url" => _} = json_response(conn, 200)
688 test "can reset profile banner", %{conn: conn} do
693 |> assign(:user, user)
694 |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
696 user = refresh_record(user)
697 assert user.info.banner == %{}
699 assert %{"url" => nil} = json_response(conn, 200)
702 test "background image can be set", %{conn: conn} do
707 |> assign(:user, user)
708 |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
710 user = refresh_record(user)
711 assert user.info.background["type"] == "Image"
712 assert %{"url" => _} = json_response(conn, 200)
715 test "background image can be reset", %{conn: conn} do
720 |> assign(:user, user)
721 |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
723 user = refresh_record(user)
724 assert user.info.background == %{}
725 assert %{"url" => nil} = json_response(conn, 200)
728 test "creates an oauth app", %{conn: conn} do
730 app_attrs = build(:oauth_app)
734 |> assign(:user, user)
735 |> post("/api/v1/apps", %{
736 client_name: app_attrs.client_name,
737 redirect_uris: app_attrs.redirect_uris
740 [app] = Repo.all(App)
743 "name" => app.client_name,
744 "website" => app.website,
745 "client_id" => app.client_id,
746 "client_secret" => app.client_secret,
747 "id" => app.id |> to_string(),
748 "redirect_uri" => app.redirect_uris,
749 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
752 assert expected == json_response(conn, 200)
755 test "get a status", %{conn: conn} do
756 activity = insert(:note_activity)
760 |> get("/api/v1/statuses/#{activity.id}")
762 assert %{"id" => id} = json_response(conn, 200)
763 assert id == to_string(activity.id)
766 test "get statuses by IDs", %{conn: conn} do
767 %{id: id1} = insert(:note_activity)
768 %{id: id2} = insert(:note_activity)
770 query_string = "ids[]=#{id1}&ids[]=#{id2}"
771 conn = get(conn, "/api/v1/statuses/?#{query_string}")
773 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
776 describe "deleting a status" do
777 test "when you created it", %{conn: conn} do
778 activity = insert(:note_activity)
779 author = User.get_cached_by_ap_id(activity.data["actor"])
783 |> assign(:user, author)
784 |> delete("/api/v1/statuses/#{activity.id}")
786 assert %{} = json_response(conn, 200)
788 refute Activity.get_by_id(activity.id)
791 test "when you didn't create it", %{conn: conn} do
792 activity = insert(:note_activity)
797 |> assign(:user, user)
798 |> delete("/api/v1/statuses/#{activity.id}")
800 assert %{"error" => _} = json_response(conn, 403)
802 assert Activity.get_by_id(activity.id) == activity
805 test "when you're an admin or moderator", %{conn: conn} do
806 activity1 = insert(:note_activity)
807 activity2 = insert(:note_activity)
808 admin = insert(:user, info: %{is_admin: true})
809 moderator = insert(:user, info: %{is_moderator: true})
813 |> assign(:user, admin)
814 |> delete("/api/v1/statuses/#{activity1.id}")
816 assert %{} = json_response(res_conn, 200)
820 |> assign(:user, moderator)
821 |> delete("/api/v1/statuses/#{activity2.id}")
823 assert %{} = json_response(res_conn, 200)
825 refute Activity.get_by_id(activity1.id)
826 refute Activity.get_by_id(activity2.id)
830 describe "filters" do
831 test "creating a filter", %{conn: conn} do
834 filter = %Pleroma.Filter{
841 |> assign(:user, user)
842 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
844 assert response = json_response(conn, 200)
845 assert response["phrase"] == filter.phrase
846 assert response["context"] == filter.context
847 assert response["irreversible"] == false
848 assert response["id"] != nil
849 assert response["id"] != ""
852 test "fetching a list of filters", %{conn: conn} do
855 query_one = %Pleroma.Filter{
862 query_two = %Pleroma.Filter{
869 {:ok, filter_one} = Pleroma.Filter.create(query_one)
870 {:ok, filter_two} = Pleroma.Filter.create(query_two)
874 |> assign(:user, user)
875 |> get("/api/v1/filters")
876 |> json_response(200)
882 filters: [filter_two, filter_one]
886 test "get a filter", %{conn: conn} do
889 query = %Pleroma.Filter{
896 {:ok, filter} = Pleroma.Filter.create(query)
900 |> assign(:user, user)
901 |> get("/api/v1/filters/#{filter.filter_id}")
903 assert _response = json_response(conn, 200)
906 test "update a filter", %{conn: conn} do
909 query = %Pleroma.Filter{
916 {:ok, _filter} = Pleroma.Filter.create(query)
918 new = %Pleroma.Filter{
925 |> assign(:user, user)
926 |> put("/api/v1/filters/#{query.filter_id}", %{
931 assert response = json_response(conn, 200)
932 assert response["phrase"] == new.phrase
933 assert response["context"] == new.context
936 test "delete a filter", %{conn: conn} do
939 query = %Pleroma.Filter{
946 {:ok, filter} = Pleroma.Filter.create(query)
950 |> assign(:user, user)
951 |> delete("/api/v1/filters/#{filter.filter_id}")
953 assert response = json_response(conn, 200)
954 assert response == %{}
958 describe "list timelines" do
959 test "list timeline", %{conn: conn} do
961 other_user = insert(:user)
962 {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
963 {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
964 {:ok, list} = Pleroma.List.create("name", user)
965 {:ok, list} = Pleroma.List.follow(list, other_user)
969 |> assign(:user, user)
970 |> get("/api/v1/timelines/list/#{list.id}")
972 assert [%{"id" => id}] = json_response(conn, 200)
974 assert id == to_string(activity_two.id)
977 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
979 other_user = insert(:user)
980 {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
982 {:ok, _activity_two} =
983 CommonAPI.post(other_user, %{
984 "status" => "Marisa is cute.",
985 "visibility" => "private"
988 {:ok, list} = Pleroma.List.create("name", user)
989 {:ok, list} = Pleroma.List.follow(list, other_user)
993 |> assign(:user, user)
994 |> get("/api/v1/timelines/list/#{list.id}")
996 assert [%{"id" => id}] = json_response(conn, 200)
998 assert id == to_string(activity_one.id)
1002 describe "notifications" do
1003 test "list of notifications", %{conn: conn} do
1004 user = insert(:user)
1005 other_user = insert(:user)
1007 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1009 {:ok, [_notification]} = Notification.create_notifications(activity)
1013 |> assign(:user, user)
1014 |> get("/api/v1/notifications")
1017 ~s(hi <span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
1019 }" rel="ugc">@<span>#{user.nickname}</span></a></span>)
1021 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
1022 assert response == expected_response
1025 test "getting a single notification", %{conn: conn} do
1026 user = insert(:user)
1027 other_user = insert(:user)
1029 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1031 {:ok, [notification]} = Notification.create_notifications(activity)
1035 |> assign(:user, user)
1036 |> get("/api/v1/notifications/#{notification.id}")
1039 ~s(hi <span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
1041 }" rel="ugc">@<span>#{user.nickname}</span></a></span>)
1043 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
1044 assert response == expected_response
1047 test "dismissing a single notification", %{conn: conn} do
1048 user = insert(:user)
1049 other_user = insert(:user)
1051 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1053 {:ok, [notification]} = Notification.create_notifications(activity)
1057 |> assign(:user, user)
1058 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
1060 assert %{} = json_response(conn, 200)
1063 test "clearing all notifications", %{conn: conn} do
1064 user = insert(:user)
1065 other_user = insert(:user)
1067 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1069 {:ok, [_notification]} = Notification.create_notifications(activity)
1073 |> assign(:user, user)
1074 |> post("/api/v1/notifications/clear")
1076 assert %{} = json_response(conn, 200)
1080 |> assign(:user, user)
1081 |> get("/api/v1/notifications")
1083 assert all = json_response(conn, 200)
1087 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
1088 user = insert(:user)
1089 other_user = insert(:user)
1091 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1092 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1093 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1094 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1096 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1097 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1098 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1099 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1103 |> assign(:user, user)
1108 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
1110 result = json_response(conn_res, 200)
1111 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1116 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
1118 result = json_response(conn_res, 200)
1119 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1124 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
1126 result = json_response(conn_res, 200)
1127 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1130 test "filters notifications using exclude_types", %{conn: conn} do
1131 user = insert(:user)
1132 other_user = insert(:user)
1134 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
1135 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
1136 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
1137 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
1138 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
1140 mention_notification_id =
1141 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
1143 favorite_notification_id =
1144 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
1146 reblog_notification_id =
1147 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
1149 follow_notification_id =
1150 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1154 |> assign(:user, user)
1157 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1159 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1162 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1164 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1167 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1169 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1172 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1174 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1177 test "destroy multiple", %{conn: conn} do
1178 user = insert(:user)
1179 other_user = insert(:user)
1181 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1182 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1183 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1184 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1186 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1187 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1188 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1189 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1193 |> assign(:user, user)
1197 |> get("/api/v1/notifications")
1199 result = json_response(conn_res, 200)
1200 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1204 |> assign(:user, other_user)
1208 |> get("/api/v1/notifications")
1210 result = json_response(conn_res, 200)
1211 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1215 |> delete("/api/v1/notifications/destroy_multiple", %{
1216 "ids" => [notification1_id, notification2_id]
1219 assert json_response(conn_destroy, 200) == %{}
1223 |> get("/api/v1/notifications")
1225 result = json_response(conn_res, 200)
1226 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1229 test "doesn't see notifications after muting user with notifications", %{conn: conn} do
1230 user = insert(:user)
1231 user2 = insert(:user)
1233 {:ok, _, _, _} = CommonAPI.follow(user, user2)
1234 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
1236 conn = assign(conn, :user, user)
1238 conn = get(conn, "/api/v1/notifications")
1240 assert length(json_response(conn, 200)) == 1
1242 {:ok, user} = User.mute(user, user2)
1244 conn = assign(build_conn(), :user, user)
1245 conn = get(conn, "/api/v1/notifications")
1247 assert json_response(conn, 200) == []
1250 test "see notifications after muting user without notifications", %{conn: conn} do
1251 user = insert(:user)
1252 user2 = insert(:user)
1254 {:ok, _, _, _} = CommonAPI.follow(user, user2)
1255 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
1257 conn = assign(conn, :user, user)
1259 conn = get(conn, "/api/v1/notifications")
1261 assert length(json_response(conn, 200)) == 1
1263 {:ok, user} = User.mute(user, user2, false)
1265 conn = assign(build_conn(), :user, user)
1266 conn = get(conn, "/api/v1/notifications")
1268 assert length(json_response(conn, 200)) == 1
1271 test "see notifications after muting user with notifications and with_muted parameter", %{
1274 user = insert(:user)
1275 user2 = insert(:user)
1277 {:ok, _, _, _} = CommonAPI.follow(user, user2)
1278 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
1280 conn = assign(conn, :user, user)
1282 conn = get(conn, "/api/v1/notifications")
1284 assert length(json_response(conn, 200)) == 1
1286 {:ok, user} = User.mute(user, user2)
1288 conn = assign(build_conn(), :user, user)
1289 conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
1291 assert length(json_response(conn, 200)) == 1
1295 describe "reblogging" do
1296 test "reblogs and returns the reblogged status", %{conn: conn} do
1297 activity = insert(:note_activity)
1298 user = insert(:user)
1302 |> assign(:user, user)
1303 |> post("/api/v1/statuses/#{activity.id}/reblog")
1306 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1308 } = json_response(conn, 200)
1310 assert to_string(activity.id) == id
1313 test "reblogged status for another user", %{conn: conn} do
1314 activity = insert(:note_activity)
1315 user1 = insert(:user)
1316 user2 = insert(:user)
1317 user3 = insert(:user)
1318 CommonAPI.favorite(activity.id, user2)
1319 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1320 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1321 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1325 |> assign(:user, user3)
1326 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1329 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1330 "reblogged" => false,
1331 "favourited" => false,
1332 "bookmarked" => false
1333 } = json_response(conn_res, 200)
1337 |> assign(:user, user2)
1338 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1341 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1342 "reblogged" => true,
1343 "favourited" => true,
1344 "bookmarked" => true
1345 } = json_response(conn_res, 200)
1347 assert to_string(activity.id) == id
1350 test "returns 400 error when activity is not exist", %{conn: conn} do
1351 user = insert(:user)
1355 |> assign(:user, user)
1356 |> post("/api/v1/statuses/foo/reblog")
1358 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
1362 describe "unreblogging" do
1363 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1364 activity = insert(:note_activity)
1365 user = insert(:user)
1367 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1371 |> assign(:user, user)
1372 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1374 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1376 assert to_string(activity.id) == id
1379 test "returns 400 error when activity is not exist", %{conn: conn} do
1380 user = insert(:user)
1384 |> assign(:user, user)
1385 |> post("/api/v1/statuses/foo/unreblog")
1387 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
1391 describe "favoriting" do
1392 test "favs a status and returns it", %{conn: conn} do
1393 activity = insert(:note_activity)
1394 user = insert(:user)
1398 |> assign(:user, user)
1399 |> post("/api/v1/statuses/#{activity.id}/favourite")
1401 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1402 json_response(conn, 200)
1404 assert to_string(activity.id) == id
1407 test "returns 400 error for a wrong id", %{conn: conn} do
1408 user = insert(:user)
1412 |> assign(:user, user)
1413 |> post("/api/v1/statuses/1/favourite")
1415 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
1419 describe "unfavoriting" do
1420 test "unfavorites a status and returns it", %{conn: conn} do
1421 activity = insert(:note_activity)
1422 user = insert(:user)
1424 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1428 |> assign(:user, user)
1429 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1431 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1432 json_response(conn, 200)
1434 assert to_string(activity.id) == id
1437 test "returns 400 error for a wrong id", %{conn: conn} do
1438 user = insert(:user)
1442 |> assign(:user, user)
1443 |> post("/api/v1/statuses/1/unfavourite")
1445 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
1449 describe "user timelines" do
1450 test "gets a users statuses", %{conn: conn} do
1451 user_one = insert(:user)
1452 user_two = insert(:user)
1453 user_three = insert(:user)
1455 {:ok, user_three} = User.follow(user_three, user_one)
1457 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1459 {:ok, direct_activity} =
1460 CommonAPI.post(user_one, %{
1461 "status" => "Hi, @#{user_two.nickname}.",
1462 "visibility" => "direct"
1465 {:ok, private_activity} =
1466 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1470 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1472 assert [%{"id" => id}] = json_response(resp, 200)
1473 assert id == to_string(activity.id)
1477 |> assign(:user, user_two)
1478 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1480 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1481 assert id_one == to_string(direct_activity.id)
1482 assert id_two == to_string(activity.id)
1486 |> assign(:user, user_three)
1487 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1489 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1490 assert id_one == to_string(private_activity.id)
1491 assert id_two == to_string(activity.id)
1494 test "unimplemented pinned statuses feature", %{conn: conn} do
1495 note = insert(:note_activity)
1496 user = User.get_cached_by_ap_id(note.data["actor"])
1500 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1502 assert json_response(conn, 200) == []
1505 test "gets an users media", %{conn: conn} do
1506 note = insert(:note_activity)
1507 user = User.get_cached_by_ap_id(note.data["actor"])
1509 file = %Plug.Upload{
1510 content_type: "image/jpg",
1511 path: Path.absname("test/fixtures/image.jpg"),
1512 filename: "an_image.jpg"
1515 {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
1517 {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
1521 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1523 assert [%{"id" => id}] = json_response(conn, 200)
1524 assert id == to_string(image_post.id)
1528 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1530 assert [%{"id" => id}] = json_response(conn, 200)
1531 assert id == to_string(image_post.id)
1534 test "gets a user's statuses without reblogs", %{conn: conn} do
1535 user = insert(:user)
1536 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1537 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1541 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1543 assert [%{"id" => id}] = json_response(conn, 200)
1544 assert id == to_string(post.id)
1548 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1550 assert [%{"id" => id}] = json_response(conn, 200)
1551 assert id == to_string(post.id)
1554 test "filters user's statuses by a hashtag", %{conn: conn} do
1555 user = insert(:user)
1556 {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
1557 {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
1561 |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
1563 assert [%{"id" => id}] = json_response(conn, 200)
1564 assert id == to_string(post.id)
1568 describe "user relationships" do
1569 test "returns the relationships for the current user", %{conn: conn} do
1570 user = insert(:user)
1571 other_user = insert(:user)
1572 {:ok, user} = User.follow(user, other_user)
1576 |> assign(:user, user)
1577 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1579 assert [relationship] = json_response(conn, 200)
1581 assert to_string(other_user.id) == relationship["id"]
1584 test "returns an empty list on a bad request", %{conn: conn} do
1585 user = insert(:user)
1589 |> assign(:user, user)
1590 |> get("/api/v1/accounts/relationships", %{})
1592 assert [] = json_response(conn, 200)
1596 describe "media upload" do
1598 user = insert(:user)
1602 |> assign(:user, user)
1604 image = %Plug.Upload{
1605 content_type: "image/jpg",
1606 path: Path.absname("test/fixtures/image.jpg"),
1607 filename: "an_image.jpg"
1610 [conn: conn, image: image]
1613 clear_config([:media_proxy])
1614 clear_config([Pleroma.Upload])
1616 test "returns uploaded image", %{conn: conn, image: image} do
1617 desc = "Description of the image"
1621 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1622 |> json_response(:ok)
1624 assert media["type"] == "image"
1625 assert media["description"] == desc
1628 object = Repo.get(Object, media["id"])
1629 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1633 describe "locked accounts" do
1634 test "/api/v1/follow_requests works" do
1635 user = insert(:user, %{info: %User.Info{locked: true}})
1636 other_user = insert(:user)
1638 {:ok, _activity} = ActivityPub.follow(other_user, user)
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) == false
1647 |> assign(:user, user)
1648 |> get("/api/v1/follow_requests")
1650 assert [relationship] = json_response(conn, 200)
1651 assert to_string(other_user.id) == relationship["id"]
1654 test "/api/v1/follow_requests/:id/authorize works" do
1655 user = insert(:user, %{info: %User.Info{locked: true}})
1656 other_user = insert(:user)
1658 {:ok, _activity} = ActivityPub.follow(other_user, user)
1660 user = User.get_cached_by_id(user.id)
1661 other_user = User.get_cached_by_id(other_user.id)
1663 assert User.following?(other_user, user) == false
1667 |> assign(:user, user)
1668 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1670 assert relationship = json_response(conn, 200)
1671 assert to_string(other_user.id) == relationship["id"]
1673 user = User.get_cached_by_id(user.id)
1674 other_user = User.get_cached_by_id(other_user.id)
1676 assert User.following?(other_user, user) == true
1679 test "verify_credentials", %{conn: conn} do
1680 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1684 |> assign(:user, user)
1685 |> get("/api/v1/accounts/verify_credentials")
1687 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1688 assert id == to_string(user.id)
1691 test "/api/v1/follow_requests/:id/reject works" do
1692 user = insert(:user, %{info: %User.Info{locked: true}})
1693 other_user = insert(:user)
1695 {:ok, _activity} = ActivityPub.follow(other_user, user)
1697 user = User.get_cached_by_id(user.id)
1701 |> assign(:user, user)
1702 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1704 assert relationship = json_response(conn, 200)
1705 assert to_string(other_user.id) == relationship["id"]
1707 user = User.get_cached_by_id(user.id)
1708 other_user = User.get_cached_by_id(other_user.id)
1710 assert User.following?(other_user, user) == false
1714 describe "account fetching" do
1715 test "works by id" do
1716 user = insert(:user)
1720 |> get("/api/v1/accounts/#{user.id}")
1722 assert %{"id" => id} = json_response(conn, 200)
1723 assert id == to_string(user.id)
1727 |> get("/api/v1/accounts/-1")
1729 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1732 test "works by nickname" do
1733 user = insert(:user)
1737 |> get("/api/v1/accounts/#{user.nickname}")
1739 assert %{"id" => id} = json_response(conn, 200)
1740 assert id == user.id
1743 test "works by nickname for remote users" do
1744 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
1745 Pleroma.Config.put([:instance, :limit_to_local_content], false)
1746 user = insert(:user, nickname: "user@example.com", local: false)
1750 |> get("/api/v1/accounts/#{user.nickname}")
1752 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
1753 assert %{"id" => id} = json_response(conn, 200)
1754 assert id == user.id
1757 test "respects limit_to_local_content == :all for remote user nicknames" do
1758 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
1759 Pleroma.Config.put([:instance, :limit_to_local_content], :all)
1761 user = insert(:user, nickname: "user@example.com", local: false)
1765 |> get("/api/v1/accounts/#{user.nickname}")
1767 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
1768 assert json_response(conn, 404)
1771 test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do
1772 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
1773 Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
1775 user = insert(:user, nickname: "user@example.com", local: false)
1776 reading_user = insert(:user)
1780 |> get("/api/v1/accounts/#{user.nickname}")
1782 assert json_response(conn, 404)
1786 |> assign(:user, reading_user)
1787 |> get("/api/v1/accounts/#{user.nickname}")
1789 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
1790 assert %{"id" => id} = json_response(conn, 200)
1791 assert id == user.id
1795 describe "/api/v1/pleroma/mascot" do
1796 test "mascot upload", %{conn: conn} do
1797 user = insert(:user)
1799 non_image_file = %Plug.Upload{
1800 content_type: "audio/mpeg",
1801 path: Path.absname("test/fixtures/sound.mp3"),
1802 filename: "sound.mp3"
1807 |> assign(:user, user)
1808 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1810 assert json_response(conn, 415)
1812 file = %Plug.Upload{
1813 content_type: "image/jpg",
1814 path: Path.absname("test/fixtures/image.jpg"),
1815 filename: "an_image.jpg"
1820 |> assign(:user, user)
1821 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1823 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1826 test "mascot retrieving", %{conn: conn} do
1827 user = insert(:user)
1828 # When user hasn't set a mascot, we should just get pleroma tan back
1831 |> assign(:user, user)
1832 |> get("/api/v1/pleroma/mascot")
1834 assert %{"url" => url} = json_response(conn, 200)
1835 assert url =~ "pleroma-fox-tan-smol"
1837 # When a user sets their mascot, we should get that back
1838 file = %Plug.Upload{
1839 content_type: "image/jpg",
1840 path: Path.absname("test/fixtures/image.jpg"),
1841 filename: "an_image.jpg"
1846 |> assign(:user, user)
1847 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1849 assert json_response(conn, 200)
1851 user = User.get_cached_by_id(user.id)
1855 |> assign(:user, user)
1856 |> get("/api/v1/pleroma/mascot")
1858 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1859 assert url =~ "an_image"
1863 test "hashtag timeline", %{conn: conn} do
1864 following = insert(:user)
1867 {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
1869 {:ok, [_activity]} =
1870 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1874 |> get("/api/v1/timelines/tag/2hu")
1876 assert [%{"id" => id}] = json_response(nconn, 200)
1878 assert id == to_string(activity.id)
1880 # works for different capitalization too
1883 |> get("/api/v1/timelines/tag/2HU")
1885 assert [%{"id" => id}] = json_response(nconn, 200)
1887 assert id == to_string(activity.id)
1891 test "multi-hashtag timeline", %{conn: conn} do
1892 user = insert(:user)
1894 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1895 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1896 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1900 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1902 [status_none, status_test1, status_test] = json_response(any_test, 200)
1904 assert to_string(activity_test.id) == status_test["id"]
1905 assert to_string(activity_test1.id) == status_test1["id"]
1906 assert to_string(activity_none.id) == status_none["id"]
1910 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1912 assert [status_test1] == json_response(restricted_test, 200)
1914 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1916 assert [status_none] == json_response(all_test, 200)
1919 test "getting followers", %{conn: conn} do
1920 user = insert(:user)
1921 other_user = insert(:user)
1922 {:ok, user} = User.follow(user, other_user)
1926 |> get("/api/v1/accounts/#{other_user.id}/followers")
1928 assert [%{"id" => id}] = json_response(conn, 200)
1929 assert id == to_string(user.id)
1932 test "getting followers, hide_followers", %{conn: conn} do
1933 user = insert(:user)
1934 other_user = insert(:user, %{info: %{hide_followers: true}})
1935 {:ok, _user} = User.follow(user, other_user)
1939 |> get("/api/v1/accounts/#{other_user.id}/followers")
1941 assert [] == json_response(conn, 200)
1944 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1945 user = insert(:user)
1946 other_user = insert(:user, %{info: %{hide_followers: true}})
1947 {:ok, _user} = User.follow(user, other_user)
1951 |> assign(:user, other_user)
1952 |> get("/api/v1/accounts/#{other_user.id}/followers")
1954 refute [] == json_response(conn, 200)
1957 test "getting followers, pagination", %{conn: conn} do
1958 user = insert(:user)
1959 follower1 = insert(:user)
1960 follower2 = insert(:user)
1961 follower3 = insert(:user)
1962 {:ok, _} = User.follow(follower1, user)
1963 {:ok, _} = User.follow(follower2, user)
1964 {:ok, _} = User.follow(follower3, user)
1968 |> assign(:user, user)
1972 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1974 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1975 assert id3 == follower3.id
1976 assert id2 == follower2.id
1980 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1982 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1983 assert id2 == follower2.id
1984 assert id1 == follower1.id
1988 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1990 assert [%{"id" => id2}] = json_response(res_conn, 200)
1991 assert id2 == follower2.id
1993 assert [link_header] = get_resp_header(res_conn, "link")
1994 assert link_header =~ ~r/min_id=#{follower2.id}/
1995 assert link_header =~ ~r/max_id=#{follower2.id}/
1998 test "getting following", %{conn: conn} do
1999 user = insert(:user)
2000 other_user = insert(:user)
2001 {:ok, user} = User.follow(user, other_user)
2005 |> get("/api/v1/accounts/#{user.id}/following")
2007 assert [%{"id" => id}] = json_response(conn, 200)
2008 assert id == to_string(other_user.id)
2011 test "getting following, hide_follows", %{conn: conn} do
2012 user = insert(:user, %{info: %{hide_follows: true}})
2013 other_user = insert(:user)
2014 {:ok, user} = User.follow(user, other_user)
2018 |> get("/api/v1/accounts/#{user.id}/following")
2020 assert [] == json_response(conn, 200)
2023 test "getting following, hide_follows, same user requesting", %{conn: conn} do
2024 user = insert(:user, %{info: %{hide_follows: true}})
2025 other_user = insert(:user)
2026 {:ok, user} = User.follow(user, other_user)
2030 |> assign(:user, user)
2031 |> get("/api/v1/accounts/#{user.id}/following")
2033 refute [] == json_response(conn, 200)
2036 test "getting following, pagination", %{conn: conn} do
2037 user = insert(:user)
2038 following1 = insert(:user)
2039 following2 = insert(:user)
2040 following3 = insert(:user)
2041 {:ok, _} = User.follow(user, following1)
2042 {:ok, _} = User.follow(user, following2)
2043 {:ok, _} = User.follow(user, following3)
2047 |> assign(:user, user)
2051 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
2053 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
2054 assert id3 == following3.id
2055 assert id2 == following2.id
2059 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
2061 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
2062 assert id2 == following2.id
2063 assert id1 == following1.id
2067 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
2069 assert [%{"id" => id2}] = json_response(res_conn, 200)
2070 assert id2 == following2.id
2072 assert [link_header] = get_resp_header(res_conn, "link")
2073 assert link_header =~ ~r/min_id=#{following2.id}/
2074 assert link_header =~ ~r/max_id=#{following2.id}/
2077 test "following / unfollowing a user", %{conn: conn} do
2078 user = insert(:user)
2079 other_user = insert(:user)
2083 |> assign(:user, user)
2084 |> post("/api/v1/accounts/#{other_user.id}/follow")
2086 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
2088 user = User.get_cached_by_id(user.id)
2092 |> assign(:user, user)
2093 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
2095 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
2097 user = User.get_cached_by_id(user.id)
2101 |> assign(:user, user)
2102 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
2104 assert %{"id" => id} = json_response(conn, 200)
2105 assert id == to_string(other_user.id)
2108 test "following without reblogs" do
2109 follower = insert(:user)
2110 followed = insert(:user)
2111 other_user = insert(:user)
2115 |> assign(:user, follower)
2116 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
2118 assert %{"showing_reblogs" => false} = json_response(conn, 200)
2120 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
2121 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
2125 |> assign(:user, User.get_cached_by_id(follower.id))
2126 |> get("/api/v1/timelines/home")
2128 assert [] == json_response(conn, 200)
2132 |> assign(:user, follower)
2133 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
2135 assert %{"showing_reblogs" => true} = json_response(conn, 200)
2139 |> assign(:user, User.get_cached_by_id(follower.id))
2140 |> get("/api/v1/timelines/home")
2142 expected_activity_id = reblog.id
2143 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
2146 test "following / unfollowing errors" do
2147 user = insert(:user)
2151 |> assign(:user, user)
2154 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
2155 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2158 user = User.get_cached_by_id(user.id)
2159 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
2160 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2162 # self follow via uri
2163 user = User.get_cached_by_id(user.id)
2164 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
2165 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2167 # follow non existing user
2168 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
2169 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2171 # follow non existing user via uri
2172 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
2173 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2175 # unfollow non existing user
2176 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
2177 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2180 describe "mute/unmute" do
2181 test "with notifications", %{conn: conn} do
2182 user = insert(:user)
2183 other_user = insert(:user)
2187 |> assign(:user, user)
2188 |> post("/api/v1/accounts/#{other_user.id}/mute")
2190 response = json_response(conn, 200)
2192 assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
2193 user = User.get_cached_by_id(user.id)
2197 |> assign(:user, user)
2198 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2200 response = json_response(conn, 200)
2201 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
2204 test "without notifications", %{conn: conn} do
2205 user = insert(:user)
2206 other_user = insert(:user)
2210 |> assign(:user, user)
2211 |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
2213 response = json_response(conn, 200)
2215 assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
2216 user = User.get_cached_by_id(user.id)
2220 |> assign(:user, user)
2221 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2223 response = json_response(conn, 200)
2224 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
2228 describe "subscribing / unsubscribing" do
2229 test "subscribing / unsubscribing to a user", %{conn: conn} do
2230 user = insert(:user)
2231 subscription_target = insert(:user)
2235 |> assign(:user, user)
2236 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2238 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2242 |> assign(:user, user)
2243 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2245 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2249 describe "subscribing" do
2250 test "returns 404 when subscription_target not found", %{conn: conn} do
2251 user = insert(:user)
2255 |> assign(:user, user)
2256 |> post("/api/v1/pleroma/accounts/target_id/subscribe")
2258 assert %{"error" => "Record not found"} = json_response(conn, 404)
2262 describe "unsubscribing" do
2263 test "returns 404 when subscription_target not found", %{conn: conn} do
2264 user = insert(:user)
2268 |> assign(:user, user)
2269 |> post("/api/v1/pleroma/accounts/target_id/unsubscribe")
2271 assert %{"error" => "Record not found"} = json_response(conn, 404)
2275 test "getting a list of mutes", %{conn: conn} do
2276 user = insert(:user)
2277 other_user = insert(:user)
2279 {:ok, user} = User.mute(user, other_user)
2283 |> assign(:user, user)
2284 |> get("/api/v1/mutes")
2286 other_user_id = to_string(other_user.id)
2287 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2290 test "blocking / unblocking a user", %{conn: conn} do
2291 user = insert(:user)
2292 other_user = insert(:user)
2296 |> assign(:user, user)
2297 |> post("/api/v1/accounts/#{other_user.id}/block")
2299 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2301 user = User.get_cached_by_id(user.id)
2305 |> assign(:user, user)
2306 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2308 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2311 test "getting a list of blocks", %{conn: conn} do
2312 user = insert(:user)
2313 other_user = insert(:user)
2315 {:ok, user} = User.block(user, other_user)
2319 |> assign(:user, user)
2320 |> get("/api/v1/blocks")
2322 other_user_id = to_string(other_user.id)
2323 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2326 test "blocking / unblocking a domain", %{conn: conn} do
2327 user = insert(:user)
2328 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2332 |> assign(:user, user)
2333 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2335 assert %{} = json_response(conn, 200)
2336 user = User.get_cached_by_ap_id(user.ap_id)
2337 assert User.blocks?(user, other_user)
2341 |> assign(:user, user)
2342 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2344 assert %{} = json_response(conn, 200)
2345 user = User.get_cached_by_ap_id(user.ap_id)
2346 refute User.blocks?(user, other_user)
2349 test "getting a list of domain blocks", %{conn: conn} do
2350 user = insert(:user)
2352 {:ok, user} = User.block_domain(user, "bad.site")
2353 {:ok, user} = User.block_domain(user, "even.worse.site")
2357 |> assign(:user, user)
2358 |> get("/api/v1/domain_blocks")
2360 domain_blocks = json_response(conn, 200)
2362 assert "bad.site" in domain_blocks
2363 assert "even.worse.site" in domain_blocks
2366 test "unimplemented follow_requests, blocks, domain blocks" do
2367 user = insert(:user)
2369 ["blocks", "domain_blocks", "follow_requests"]
2370 |> Enum.each(fn endpoint ->
2373 |> assign(:user, user)
2374 |> get("/api/v1/#{endpoint}")
2376 assert [] = json_response(conn, 200)
2380 test "returns the favorites of a user", %{conn: conn} do
2381 user = insert(:user)
2382 other_user = insert(:user)
2384 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2385 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2387 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2391 |> assign(:user, user)
2392 |> get("/api/v1/favourites")
2394 assert [status] = json_response(first_conn, 200)
2395 assert status["id"] == to_string(activity.id)
2397 assert [{"link", _link_header}] =
2398 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2400 # Honours query params
2401 {:ok, second_activity} =
2402 CommonAPI.post(other_user, %{
2404 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2407 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2409 last_like = status["id"]
2413 |> assign(:user, user)
2414 |> get("/api/v1/favourites?since_id=#{last_like}")
2416 assert [second_status] = json_response(second_conn, 200)
2417 assert second_status["id"] == to_string(second_activity.id)
2421 |> assign(:user, user)
2422 |> get("/api/v1/favourites?limit=0")
2424 assert [] = json_response(third_conn, 200)
2427 describe "getting favorites timeline of specified user" do
2429 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2430 [current_user: current_user, user: user]
2433 test "returns list of statuses favorited by specified user", %{
2435 current_user: current_user,
2438 [activity | _] = insert_pair(:note_activity)
2439 CommonAPI.favorite(activity.id, user)
2443 |> assign(:user, current_user)
2444 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2445 |> json_response(:ok)
2449 assert length(response) == 1
2450 assert like["id"] == activity.id
2453 test "returns favorites for specified user_id when user is not logged in", %{
2457 activity = insert(:note_activity)
2458 CommonAPI.favorite(activity.id, user)
2462 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2463 |> json_response(:ok)
2465 assert length(response) == 1
2468 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2470 current_user: current_user,
2474 CommonAPI.post(current_user, %{
2475 "status" => "Hi @#{user.nickname}!",
2476 "visibility" => "direct"
2479 CommonAPI.favorite(direct.id, user)
2483 |> assign(:user, current_user)
2484 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2485 |> json_response(:ok)
2487 assert length(response) == 1
2489 anonymous_response =
2491 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2492 |> json_response(:ok)
2494 assert Enum.empty?(anonymous_response)
2497 test "does not return others' favorited DM when user is not one of recipients", %{
2499 current_user: current_user,
2502 user_two = insert(:user)
2505 CommonAPI.post(user_two, %{
2506 "status" => "Hi @#{user.nickname}!",
2507 "visibility" => "direct"
2510 CommonAPI.favorite(direct.id, user)
2514 |> assign(:user, current_user)
2515 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2516 |> json_response(:ok)
2518 assert Enum.empty?(response)
2521 test "paginates favorites using since_id and max_id", %{
2523 current_user: current_user,
2526 activities = insert_list(10, :note_activity)
2528 Enum.each(activities, fn activity ->
2529 CommonAPI.favorite(activity.id, user)
2532 third_activity = Enum.at(activities, 2)
2533 seventh_activity = Enum.at(activities, 6)
2537 |> assign(:user, current_user)
2538 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2539 since_id: third_activity.id,
2540 max_id: seventh_activity.id
2542 |> json_response(:ok)
2544 assert length(response) == 3
2545 refute third_activity in response
2546 refute seventh_activity in response
2549 test "limits favorites using limit parameter", %{
2551 current_user: current_user,
2555 |> insert_list(:note_activity)
2556 |> Enum.each(fn activity ->
2557 CommonAPI.favorite(activity.id, user)
2562 |> assign(:user, current_user)
2563 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2564 |> json_response(:ok)
2566 assert length(response) == 3
2569 test "returns empty response when user does not have any favorited statuses", %{
2571 current_user: current_user,
2576 |> assign(:user, current_user)
2577 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2578 |> json_response(:ok)
2580 assert Enum.empty?(response)
2583 test "returns 404 error when specified user is not exist", %{conn: conn} do
2584 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2586 assert json_response(conn, 404) == %{"error" => "Record not found"}
2589 test "returns 403 error when user has hidden own favorites", %{
2591 current_user: current_user
2593 user = insert(:user, %{info: %{hide_favorites: true}})
2594 activity = insert(:note_activity)
2595 CommonAPI.favorite(activity.id, user)
2599 |> assign(:user, current_user)
2600 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2602 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2605 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2606 user = insert(:user)
2607 activity = insert(:note_activity)
2608 CommonAPI.favorite(activity.id, user)
2612 |> assign(:user, current_user)
2613 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2615 assert user.info.hide_favorites
2616 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2620 test "get instance information", %{conn: conn} do
2621 conn = get(conn, "/api/v1/instance")
2622 assert result = json_response(conn, 200)
2624 email = Config.get([:instance, :email])
2625 # Note: not checking for "max_toot_chars" since it's optional
2631 "email" => from_config_email,
2633 "streaming_api" => _
2638 "registrations" => _,
2642 assert email == from_config_email
2645 test "get instance stats", %{conn: conn} do
2646 user = insert(:user, %{local: true})
2648 user2 = insert(:user, %{local: true})
2649 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2651 insert(:user, %{local: false, nickname: "u@peer1.com"})
2652 insert(:user, %{local: false, nickname: "u@peer2.com"})
2654 {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"})
2656 # Stats should count users with missing or nil `info.deactivated` value
2657 user = User.get_cached_by_id(user.id)
2658 info_change = Changeset.change(user.info, %{deactivated: nil})
2662 |> Changeset.change()
2663 |> Changeset.put_embed(:info, info_change)
2664 |> User.update_and_set_cache()
2666 Pleroma.Stats.force_update()
2668 conn = get(conn, "/api/v1/instance")
2670 assert result = json_response(conn, 200)
2672 stats = result["stats"]
2675 assert stats["user_count"] == 1
2676 assert stats["status_count"] == 1
2677 assert stats["domain_count"] == 2
2680 test "get peers", %{conn: conn} do
2681 insert(:user, %{local: false, nickname: "u@peer1.com"})
2682 insert(:user, %{local: false, nickname: "u@peer2.com"})
2684 Pleroma.Stats.force_update()
2686 conn = get(conn, "/api/v1/instance/peers")
2688 assert result = json_response(conn, 200)
2690 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2693 test "put settings", %{conn: conn} do
2694 user = insert(:user)
2698 |> assign(:user, user)
2699 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2701 assert _result = json_response(conn, 200)
2703 user = User.get_cached_by_ap_id(user.ap_id)
2704 assert user.info.settings == %{"programming" => "socks"}
2707 describe "pinned statuses" do
2709 user = insert(:user)
2710 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2712 [user: user, activity: activity]
2715 clear_config([:instance, :max_pinned_statuses]) do
2716 Config.put([:instance, :max_pinned_statuses], 1)
2719 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2720 {:ok, _} = CommonAPI.pin(activity.id, user)
2724 |> assign(:user, user)
2725 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2726 |> json_response(200)
2728 id_str = to_string(activity.id)
2730 assert [%{"id" => ^id_str, "pinned" => true}] = result
2733 test "pin status", %{conn: conn, user: user, activity: activity} do
2734 id_str = to_string(activity.id)
2736 assert %{"id" => ^id_str, "pinned" => true} =
2738 |> assign(:user, user)
2739 |> post("/api/v1/statuses/#{activity.id}/pin")
2740 |> json_response(200)
2742 assert [%{"id" => ^id_str, "pinned" => true}] =
2744 |> assign(:user, user)
2745 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2746 |> json_response(200)
2749 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
2750 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
2754 |> assign(:user, user)
2755 |> post("/api/v1/statuses/#{dm.id}/pin")
2757 assert json_response(conn, 400) == %{"error" => "Could not pin"}
2760 test "unpin status", %{conn: conn, user: user, activity: activity} do
2761 {:ok, _} = CommonAPI.pin(activity.id, user)
2763 id_str = to_string(activity.id)
2764 user = refresh_record(user)
2766 assert %{"id" => ^id_str, "pinned" => false} =
2768 |> assign(:user, user)
2769 |> post("/api/v1/statuses/#{activity.id}/unpin")
2770 |> json_response(200)
2774 |> assign(:user, user)
2775 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2776 |> json_response(200)
2779 test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
2782 |> assign(:user, user)
2783 |> post("/api/v1/statuses/1/unpin")
2785 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
2788 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2789 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2791 id_str_one = to_string(activity_one.id)
2793 assert %{"id" => ^id_str_one, "pinned" => true} =
2795 |> assign(:user, user)
2796 |> post("/api/v1/statuses/#{id_str_one}/pin")
2797 |> json_response(200)
2799 user = refresh_record(user)
2801 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2803 |> assign(:user, user)
2804 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2805 |> json_response(400)
2811 Config.put([:rich_media, :enabled], true)
2813 user = insert(:user)
2817 test "returns empty result when rich_media disabled", %{conn: conn, user: user} do
2818 Config.put([:rich_media, :enabled], false)
2819 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2823 |> get("/api/v1/statuses/#{activity.id}/card")
2824 |> json_response(200)
2826 assert response == nil
2829 test "returns rich-media card", %{conn: conn, user: user} do
2830 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2833 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2834 "provider_name" => "example.com",
2835 "provider_url" => "https://example.com",
2836 "title" => "The Rock",
2838 "url" => "https://example.com/ogp",
2840 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2843 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2844 "title" => "The Rock",
2845 "type" => "video.movie",
2846 "url" => "https://example.com/ogp",
2848 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2855 |> get("/api/v1/statuses/#{activity.id}/card")
2856 |> json_response(200)
2858 assert response == card_data
2860 # works with private posts
2862 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2866 |> assign(:user, user)
2867 |> get("/api/v1/statuses/#{activity.id}/card")
2868 |> json_response(200)
2870 assert response_two == card_data
2873 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2875 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2879 |> get("/api/v1/statuses/#{activity.id}/card")
2880 |> json_response(:ok)
2882 assert response == %{
2884 "title" => "Pleroma",
2885 "description" => "",
2887 "provider_name" => "example.com",
2888 "provider_url" => "https://example.com",
2889 "url" => "https://example.com/ogp-missing-data",
2892 "title" => "Pleroma",
2893 "type" => "website",
2894 "url" => "https://example.com/ogp-missing-data"
2900 test "returns 404 response when id invalid", %{conn: conn} do
2901 assert %{"error" => "Record not found"} =
2903 |> get("/api/v1/statuses/9eoozpwTul5mjSEDRI/card")
2904 |> json_response(404)
2907 test "returns 404 response when id isn't FlakeID", %{conn: conn} do
2908 assert %{"error" => "Record not found"} =
2910 |> get("/api/v1/statuses/3ebbadd1-eb14-4e20-8118/card")
2911 |> json_response(404)
2913 assert %{"error" => "Record not found"} =
2915 |> get("/api/v1/statuses/8118/card")
2916 |> json_response(404)
2921 user = insert(:user)
2922 for_user = insert(:user)
2925 CommonAPI.post(user, %{
2926 "status" => "heweoo?"
2930 CommonAPI.post(user, %{
2931 "status" => "heweoo!"
2936 |> assign(:user, for_user)
2937 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2939 assert json_response(response1, 200)["bookmarked"] == true
2943 |> assign(:user, for_user)
2944 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2946 assert json_response(response2, 200)["bookmarked"] == true
2950 |> assign(:user, for_user)
2951 |> get("/api/v1/bookmarks")
2953 assert [json_response(response2, 200), json_response(response1, 200)] ==
2954 json_response(bookmarks, 200)
2958 |> assign(:user, for_user)
2959 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2961 assert json_response(response1, 200)["bookmarked"] == false
2965 |> assign(:user, for_user)
2966 |> get("/api/v1/bookmarks")
2968 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2971 describe "conversation muting" do
2973 post_user = insert(:user)
2974 user = insert(:user)
2976 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
2978 [user: user, activity: activity]
2981 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2982 id_str = to_string(activity.id)
2984 assert %{"id" => ^id_str, "muted" => true} =
2986 |> assign(:user, user)
2987 |> post("/api/v1/statuses/#{activity.id}/mute")
2988 |> json_response(200)
2991 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
2992 {:ok, _} = CommonAPI.add_mute(user, activity)
2996 |> assign(:user, user)
2997 |> post("/api/v1/statuses/#{activity.id}/mute")
2999 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
3002 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
3003 {:ok, _} = CommonAPI.add_mute(user, activity)
3005 id_str = to_string(activity.id)
3006 user = refresh_record(user)
3008 assert %{"id" => ^id_str, "muted" => false} =
3010 |> assign(:user, user)
3011 |> post("/api/v1/statuses/#{activity.id}/unmute")
3012 |> json_response(200)
3016 describe "reports" do
3018 reporter = insert(:user)
3019 target_user = insert(:user)
3021 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
3023 [reporter: reporter, target_user: target_user, activity: activity]
3026 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
3027 assert %{"action_taken" => false, "id" => _} =
3029 |> assign(:user, reporter)
3030 |> post("/api/v1/reports", %{"account_id" => target_user.id})
3031 |> json_response(200)
3034 test "submit a report with statuses and comment", %{
3037 target_user: target_user,
3040 assert %{"action_taken" => false, "id" => _} =
3042 |> assign(:user, reporter)
3043 |> post("/api/v1/reports", %{
3044 "account_id" => target_user.id,
3045 "status_ids" => [activity.id],
3046 "comment" => "bad status!",
3047 "forward" => "false"
3049 |> json_response(200)
3052 test "account_id is required", %{
3057 assert %{"error" => "Valid `account_id` required"} =
3059 |> assign(:user, reporter)
3060 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
3061 |> json_response(400)
3064 test "comment must be up to the size specified in the config", %{
3067 target_user: target_user
3069 max_size = Config.get([:instance, :max_report_comment_size], 1000)
3070 comment = String.pad_trailing("a", max_size + 1, "a")
3072 error = %{"error" => "Comment must be up to #{max_size} characters"}
3076 |> assign(:user, reporter)
3077 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
3078 |> json_response(400)
3081 test "returns error when account is not exist", %{
3088 |> assign(:user, reporter)
3089 |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"})
3091 assert json_response(conn, 400) == %{"error" => "Account not found"}
3095 describe "link headers" do
3096 test "preserves parameters in link headers", %{conn: conn} do
3097 user = insert(:user)
3098 other_user = insert(:user)
3101 CommonAPI.post(other_user, %{
3102 "status" => "hi @#{user.nickname}",
3103 "visibility" => "public"
3107 CommonAPI.post(other_user, %{
3108 "status" => "hi @#{user.nickname}",
3109 "visibility" => "public"
3112 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
3113 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
3117 |> assign(:user, user)
3118 |> get("/api/v1/notifications", %{media_only: true})
3120 assert [link_header] = get_resp_header(conn, "link")
3121 assert link_header =~ ~r/media_only=true/
3122 assert link_header =~ ~r/min_id=#{notification2.id}/
3123 assert link_header =~ ~r/max_id=#{notification1.id}/
3127 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
3128 # Need to set an old-style integer ID to reproduce the problem
3129 # (these are no longer assigned to new accounts but were preserved
3130 # for existing accounts during the migration to flakeIDs)
3131 user_one = insert(:user, %{id: 1212})
3132 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
3136 |> get("/api/v1/accounts/#{user_one.id}")
3140 |> get("/api/v1/accounts/#{user_two.nickname}")
3144 |> get("/api/v1/accounts/#{user_two.id}")
3146 acc_one = json_response(resp_one, 200)
3147 acc_two = json_response(resp_two, 200)
3148 acc_three = json_response(resp_three, 200)
3149 refute acc_one == acc_two
3150 assert acc_two == acc_three
3153 describe "custom emoji" do
3154 test "with tags", %{conn: conn} do
3157 |> get("/api/v1/custom_emojis")
3158 |> json_response(200)
3160 assert Map.has_key?(emoji, "shortcode")
3161 assert Map.has_key?(emoji, "static_url")
3162 assert Map.has_key?(emoji, "tags")
3163 assert is_list(emoji["tags"])
3164 assert Map.has_key?(emoji, "category")
3165 assert Map.has_key?(emoji, "url")
3166 assert Map.has_key?(emoji, "visible_in_picker")
3170 describe "index/2 redirections" do
3171 setup %{conn: conn} do
3175 signing_salt: "cooldude"
3180 |> Plug.Session.call(Plug.Session.init(session_opts))
3183 test_path = "/web/statuses/test"
3184 %{conn: conn, path: test_path}
3187 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
3188 conn = get(conn, path)
3190 assert conn.status == 302
3191 assert redirected_to(conn) == "/web/login"
3194 test "redirects not logged-in users to the login page on private instances", %{
3198 Config.put([:instance, :public], false)
3200 conn = get(conn, path)
3202 assert conn.status == 302
3203 assert redirected_to(conn) == "/web/login"
3206 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
3207 token = insert(:oauth_token)
3211 |> assign(:user, token.user)
3212 |> put_session(:oauth_token, token.token)
3215 assert conn.status == 200
3218 test "saves referer path to session", %{conn: conn, path: path} do
3219 conn = get(conn, path)
3220 return_to = Plug.Conn.get_session(conn, :return_to)
3222 assert return_to == path
3225 test "redirects to the saved path after log in", %{conn: conn, path: path} do
3226 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3227 auth = insert(:oauth_authorization, app: app)
3231 |> put_session(:return_to, path)
3232 |> get("/web/login", %{code: auth.token})
3234 assert conn.status == 302
3235 assert redirected_to(conn) == path
3239 describe "GET /web/login" do
3240 test "redirects to /oauth/authorize", %{conn: conn} do
3241 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3242 conn = get(conn, "/web/login", %{})
3244 assert conn.status == 302
3246 assert redirected_to(conn) ==
3247 "/oauth/authorize?response_type=code&client_id=#{app.client_id}&redirect_uri=.&scope=read+write+follow+push"
3250 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
3251 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3252 auth = insert(:oauth_authorization, app: app)
3254 conn = get(conn, "/web/login", %{code: auth.token})
3256 assert conn.status == 302
3257 assert redirected_to(conn) == "/web/getting-started"
3260 test "redirects to the getting-started page when user assigned", %{conn: conn} do
3261 user = insert(:user)
3265 |> assign(:user, user)
3266 |> get("/web/login", %{})
3268 assert conn.status == 302
3269 assert redirected_to(conn) == "/web/getting-started"
3273 describe "scheduled activities" do
3274 test "creates a scheduled activity", %{conn: conn} do
3275 user = insert(:user)
3276 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3280 |> assign(:user, user)
3281 |> post("/api/v1/statuses", %{
3282 "status" => "scheduled",
3283 "scheduled_at" => scheduled_at
3286 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
3287 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
3288 assert [] == Repo.all(Activity)
3291 test "creates a scheduled activity with a media attachment", %{conn: conn} do
3292 user = insert(:user)
3293 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3295 file = %Plug.Upload{
3296 content_type: "image/jpg",
3297 path: Path.absname("test/fixtures/image.jpg"),
3298 filename: "an_image.jpg"
3301 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
3305 |> assign(:user, user)
3306 |> post("/api/v1/statuses", %{
3307 "media_ids" => [to_string(upload.id)],
3308 "status" => "scheduled",
3309 "scheduled_at" => scheduled_at
3312 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
3313 assert %{"type" => "image"} = media_attachment
3316 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
3318 user = insert(:user)
3321 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
3325 |> assign(:user, user)
3326 |> post("/api/v1/statuses", %{
3327 "status" => "not scheduled",
3328 "scheduled_at" => scheduled_at
3331 assert %{"content" => "not scheduled"} = json_response(conn, 200)
3332 assert [] == Repo.all(ScheduledActivity)
3335 test "returns error when daily user limit is exceeded", %{conn: conn} do
3336 user = insert(:user)
3339 NaiveDateTime.utc_now()
3340 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3341 |> NaiveDateTime.to_iso8601()
3343 attrs = %{params: %{}, scheduled_at: today}
3344 {:ok, _} = ScheduledActivity.create(user, attrs)
3345 {:ok, _} = ScheduledActivity.create(user, attrs)
3349 |> assign(:user, user)
3350 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3352 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3355 test "returns error when total user limit is exceeded", %{conn: conn} do
3356 user = insert(:user)
3359 NaiveDateTime.utc_now()
3360 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3361 |> NaiveDateTime.to_iso8601()
3364 NaiveDateTime.utc_now()
3365 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3366 |> NaiveDateTime.to_iso8601()
3368 attrs = %{params: %{}, scheduled_at: today}
3369 {:ok, _} = ScheduledActivity.create(user, attrs)
3370 {:ok, _} = ScheduledActivity.create(user, attrs)
3371 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3375 |> assign(:user, user)
3376 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3378 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3381 test "shows scheduled activities", %{conn: conn} do
3382 user = insert(:user)
3383 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3384 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3385 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3386 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3390 |> assign(:user, user)
3395 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3397 result = json_response(conn_res, 200)
3398 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3403 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3405 result = json_response(conn_res, 200)
3406 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3411 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3413 result = json_response(conn_res, 200)
3414 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3417 test "shows a scheduled activity", %{conn: conn} do
3418 user = insert(:user)
3419 scheduled_activity = insert(:scheduled_activity, user: user)
3423 |> assign(:user, user)
3424 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3426 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3427 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3431 |> assign(:user, user)
3432 |> get("/api/v1/scheduled_statuses/404")
3434 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3437 test "updates a scheduled activity", %{conn: conn} do
3438 user = insert(:user)
3439 scheduled_activity = insert(:scheduled_activity, user: user)
3442 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3446 |> assign(:user, user)
3447 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3448 scheduled_at: new_scheduled_at
3451 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3452 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3456 |> assign(:user, user)
3457 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3459 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3462 test "deletes a scheduled activity", %{conn: conn} do
3463 user = insert(:user)
3464 scheduled_activity = insert(:scheduled_activity, user: user)
3468 |> assign(:user, user)
3469 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3471 assert %{} = json_response(res_conn, 200)
3472 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3476 |> assign(:user, user)
3477 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3479 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3483 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3484 user1 = insert(:user)
3485 user2 = insert(:user)
3486 user3 = insert(:user)
3488 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
3490 # Reply to status from another user
3493 |> assign(:user, user2)
3494 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3496 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3498 activity = Activity.get_by_id_with_object(id)
3500 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3501 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3503 # Reblog from the third user
3506 |> assign(:user, user3)
3507 |> post("/api/v1/statuses/#{activity.id}/reblog")
3509 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3510 json_response(conn2, 200)
3512 assert to_string(activity.id) == id
3514 # Getting third user status
3517 |> assign(:user, user3)
3518 |> get("api/v1/timelines/home")
3520 [reblogged_activity] = json_response(conn3, 200)
3522 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3524 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3525 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3528 describe "create account by app" do
3532 email: "lain@example.org",
3533 password: "PlzDontHackLain",
3537 [valid_params: valid_params]
3540 test "Account registration via Application", %{conn: conn} do
3543 |> post("/api/v1/apps", %{
3544 client_name: "client_name",
3545 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3546 scopes: "read, write, follow"
3550 "client_id" => client_id,
3551 "client_secret" => client_secret,
3553 "name" => "client_name",
3554 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3557 } = json_response(conn, 200)
3561 |> post("/oauth/token", %{
3562 grant_type: "client_credentials",
3563 client_id: client_id,
3564 client_secret: client_secret
3567 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3568 json_response(conn, 200)
3571 token_from_db = Repo.get_by(Token, token: token)
3572 assert token_from_db
3574 assert scope == "read write follow"
3578 |> put_req_header("authorization", "Bearer " <> token)
3579 |> post("/api/v1/accounts", %{
3581 email: "lain@example.org",
3582 password: "PlzDontHackLain",
3588 "access_token" => token,
3589 "created_at" => _created_at,
3591 "token_type" => "Bearer"
3592 } = json_response(conn, 200)
3594 token_from_db = Repo.get_by(Token, token: token)
3595 assert token_from_db
3596 token_from_db = Repo.preload(token_from_db, :user)
3597 assert token_from_db.user
3599 assert token_from_db.user.info.confirmation_pending
3602 test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
3603 _user = insert(:user, email: "lain@example.org")
3604 app_token = insert(:oauth_token, user: nil)
3608 |> put_req_header("authorization", "Bearer " <> app_token.token)
3610 res = post(conn, "/api/v1/accounts", valid_params)
3611 assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
3614 test "rate limit", %{conn: conn} do
3615 app_token = insert(:oauth_token, user: nil)
3618 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3619 |> Map.put(:remote_ip, {15, 15, 15, 15})
3624 |> post("/api/v1/accounts", %{
3625 username: "#{i}lain",
3626 email: "#{i}lain@example.org",
3627 password: "PlzDontHackLain",
3632 "access_token" => token,
3633 "created_at" => _created_at,
3635 "token_type" => "Bearer"
3636 } = json_response(conn, 200)
3638 token_from_db = Repo.get_by(Token, token: token)
3639 assert token_from_db
3640 token_from_db = Repo.preload(token_from_db, :user)
3641 assert token_from_db.user
3643 assert token_from_db.user.info.confirmation_pending
3648 |> post("/api/v1/accounts", %{
3650 email: "6lain@example.org",
3651 password: "PlzDontHackLain",
3655 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3658 test "returns bad_request if missing required params", %{
3660 valid_params: valid_params
3662 app_token = insert(:oauth_token, user: nil)
3666 |> put_req_header("authorization", "Bearer " <> app_token.token)
3668 res = post(conn, "/api/v1/accounts", valid_params)
3669 assert json_response(res, 200)
3671 [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
3672 |> Stream.zip(valid_params)
3673 |> Enum.each(fn {ip, {attr, _}} ->
3676 |> Map.put(:remote_ip, ip)
3677 |> post("/api/v1/accounts", Map.delete(valid_params, attr))
3678 |> json_response(400)
3680 assert res == %{"error" => "Missing parameters"}
3684 test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
3687 |> put_req_header("authorization", "Bearer " <> "invalid-token")
3689 res = post(conn, "/api/v1/accounts", valid_params)
3690 assert json_response(res, 403) == %{"error" => "Invalid credentials"}
3694 describe "GET /api/v1/polls/:id" do
3695 test "returns poll entity for object id", %{conn: conn} do
3696 user = insert(:user)
3699 CommonAPI.post(user, %{
3700 "status" => "Pleroma does",
3701 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3704 object = Object.normalize(activity)
3708 |> assign(:user, user)
3709 |> get("/api/v1/polls/#{object.id}")
3711 response = json_response(conn, 200)
3712 id = to_string(object.id)
3713 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3716 test "does not expose polls for private statuses", %{conn: conn} do
3717 user = insert(:user)
3718 other_user = insert(:user)
3721 CommonAPI.post(user, %{
3722 "status" => "Pleroma does",
3723 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3724 "visibility" => "private"
3727 object = Object.normalize(activity)
3731 |> assign(:user, other_user)
3732 |> get("/api/v1/polls/#{object.id}")
3734 assert json_response(conn, 404)
3738 describe "POST /api/v1/polls/:id/votes" do
3739 test "votes are added to the poll", %{conn: conn} do
3740 user = insert(:user)
3741 other_user = insert(:user)
3744 CommonAPI.post(user, %{
3745 "status" => "A very delicious sandwich",
3747 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3753 object = Object.normalize(activity)
3757 |> assign(:user, other_user)
3758 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3760 assert json_response(conn, 200)
3761 object = Object.get_by_id(object.id)
3763 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3768 test "author can't vote", %{conn: conn} do
3769 user = insert(:user)
3772 CommonAPI.post(user, %{
3773 "status" => "Am I cute?",
3774 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3777 object = Object.normalize(activity)
3780 |> assign(:user, user)
3781 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3782 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3784 object = Object.get_by_id(object.id)
3786 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3789 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3790 user = insert(:user)
3791 other_user = insert(:user)
3794 CommonAPI.post(user, %{
3795 "status" => "The glass is",
3796 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3799 object = Object.normalize(activity)
3802 |> assign(:user, other_user)
3803 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3804 |> json_response(422) == %{"error" => "Too many choices"}
3806 object = Object.get_by_id(object.id)
3808 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3813 test "does not allow choice index to be greater than options count", %{conn: conn} do
3814 user = insert(:user)
3815 other_user = insert(:user)
3818 CommonAPI.post(user, %{
3819 "status" => "Am I cute?",
3820 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3823 object = Object.normalize(activity)
3827 |> assign(:user, other_user)
3828 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
3830 assert json_response(conn, 422) == %{"error" => "Invalid indices"}
3833 test "returns 404 error when object is not exist", %{conn: conn} do
3834 user = insert(:user)
3838 |> assign(:user, user)
3839 |> post("/api/v1/polls/1/votes", %{"choices" => [0]})
3841 assert json_response(conn, 404) == %{"error" => "Record not found"}
3844 test "returns 404 when poll is private and not available for user", %{conn: conn} do
3845 user = insert(:user)
3846 other_user = insert(:user)
3849 CommonAPI.post(user, %{
3850 "status" => "Am I cute?",
3851 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20},
3852 "visibility" => "private"
3855 object = Object.normalize(activity)
3859 |> assign(:user, other_user)
3860 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
3862 assert json_response(conn, 404) == %{"error" => "Record not found"}
3866 describe "GET /api/v1/statuses/:id/favourited_by" do
3868 user = insert(:user)
3869 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
3873 |> assign(:user, user)
3875 [conn: conn, activity: activity, user: user]
3878 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
3879 other_user = insert(:user)
3880 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3884 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3885 |> json_response(:ok)
3887 [%{"id" => id}] = response
3889 assert id == other_user.id
3892 test "returns empty array when status has not been favorited yet", %{
3898 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3899 |> json_response(:ok)
3901 assert Enum.empty?(response)
3904 test "does not return users who have favorited the status but are blocked", %{
3905 conn: %{assigns: %{user: user}} = conn,
3908 other_user = insert(:user)
3909 {:ok, user} = User.block(user, other_user)
3911 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3915 |> assign(:user, user)
3916 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3917 |> json_response(:ok)
3919 assert Enum.empty?(response)
3922 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
3923 other_user = insert(:user)
3924 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3928 |> assign(:user, nil)
3929 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3930 |> json_response(:ok)
3932 [%{"id" => id}] = response
3933 assert id == other_user.id
3936 test "requires authentification for private posts", %{conn: conn, user: user} do
3937 other_user = insert(:user)
3940 CommonAPI.post(user, %{
3941 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
3942 "visibility" => "direct"
3945 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3948 |> assign(:user, nil)
3949 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3950 |> json_response(404)
3954 |> assign(:user, other_user)
3955 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3956 |> json_response(200)
3958 [%{"id" => id}] = response
3959 assert id == other_user.id
3963 describe "GET /api/v1/statuses/:id/reblogged_by" do
3965 user = insert(:user)
3966 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
3970 |> assign(:user, user)
3972 [conn: conn, activity: activity, user: user]
3975 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
3976 other_user = insert(:user)
3977 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
3981 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3982 |> json_response(:ok)
3984 [%{"id" => id}] = response
3986 assert id == other_user.id
3989 test "returns empty array when status has not been reblogged yet", %{
3995 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3996 |> json_response(:ok)
3998 assert Enum.empty?(response)
4001 test "does not return users who have reblogged the status but are blocked", %{
4002 conn: %{assigns: %{user: user}} = conn,
4005 other_user = insert(:user)
4006 {:ok, user} = User.block(user, other_user)
4008 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
4012 |> assign(:user, user)
4013 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
4014 |> json_response(:ok)
4016 assert Enum.empty?(response)
4019 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
4020 other_user = insert(:user)
4021 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
4025 |> assign(:user, nil)
4026 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
4027 |> json_response(:ok)
4029 [%{"id" => id}] = response
4030 assert id == other_user.id
4033 test "requires authentification for private posts", %{conn: conn, user: user} do
4034 other_user = insert(:user)
4037 CommonAPI.post(user, %{
4038 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
4039 "visibility" => "direct"
4043 |> assign(:user, nil)
4044 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
4045 |> json_response(404)
4049 |> assign(:user, other_user)
4050 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
4051 |> json_response(200)
4053 assert [] == response
4057 describe "POST /auth/password, with valid parameters" do
4058 setup %{conn: conn} do
4059 user = insert(:user)
4060 conn = post(conn, "/auth/password?email=#{user.email}")
4061 %{conn: conn, user: user}
4064 test "it returns 204", %{conn: conn} do
4065 assert json_response(conn, :no_content)
4068 test "it creates a PasswordResetToken record for user", %{user: user} do
4069 token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
4073 test "it sends an email to user", %{user: user} do
4074 ObanHelpers.perform_all()
4075 token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
4077 email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
4078 notify_email = Config.get([:instance, :notify_email])
4079 instance_name = Config.get([:instance, :name])
4082 from: {instance_name, notify_email},
4083 to: {user.name, user.email},
4084 html_body: email.html_body
4089 describe "POST /auth/password, with invalid parameters" do
4091 user = insert(:user)
4095 test "it returns 404 when user is not found", %{conn: conn, user: user} do
4096 conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
4097 assert conn.status == 404
4098 assert conn.resp_body == ""
4101 test "it returns 400 when user is not local", %{conn: conn, user: user} do
4102 {:ok, user} = Repo.update(Changeset.change(user, local: false))
4103 conn = post(conn, "/auth/password?email=#{user.email}")
4104 assert conn.status == 400
4105 assert conn.resp_body == ""
4109 describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
4111 user = insert(:user)
4112 info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
4116 |> Changeset.change()
4117 |> Changeset.put_embed(:info, info_change)
4120 assert user.info.confirmation_pending
4125 clear_config([:instance, :account_activation_required]) do
4126 Config.put([:instance, :account_activation_required], true)
4129 test "resend account confirmation email", %{conn: conn, user: user} do
4131 |> assign(:user, user)
4132 |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}")
4133 |> json_response(:no_content)
4135 ObanHelpers.perform_all()
4137 email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
4138 notify_email = Config.get([:instance, :notify_email])
4139 instance_name = Config.get([:instance, :name])
4142 from: {instance_name, notify_email},
4143 to: {user.name, user.email},
4144 html_body: email.html_body
4149 describe "GET /api/v1/suggestions" do
4151 user = insert(:user)
4152 other_user = insert(:user)
4153 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
4154 url500 = "http://test500?#{host}&#{user.nickname}"
4155 url200 = "http://test200?#{host}&#{user.nickname}"
4158 %{method: :get, url: ^url500} ->
4159 %Tesla.Env{status: 500, body: "bad request"}
4161 %{method: :get, url: ^url200} ->
4165 ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{
4167 }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}])
4171 [user: user, other_user: other_user]
4174 clear_config(:suggestions)
4176 test "returns empty result when suggestions disabled", %{conn: conn, user: user} do
4177 Config.put([:suggestions, :enabled], false)
4181 |> assign(:user, user)
4182 |> get("/api/v1/suggestions")
4183 |> json_response(200)
4188 test "returns error", %{conn: conn, user: user} do
4189 Config.put([:suggestions, :enabled], true)
4190 Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}")
4192 assert capture_log(fn ->
4195 |> assign(:user, user)
4196 |> get("/api/v1/suggestions")
4197 |> json_response(500)
4199 assert res == "Something went wrong"
4200 end) =~ "Could not retrieve suggestions"
4203 test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do
4204 Config.put([:suggestions, :enabled], true)
4205 Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}")
4209 |> assign(:user, user)
4210 |> get("/api/v1/suggestions")
4211 |> json_response(200)
4216 "avatar" => "https://social.heldscal.la/avatar/201.jpeg",
4217 "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg",
4221 "acct" => other_user.ap_id,
4222 "avatar" => "https://social.heldscal.la/avatar/202.jpeg",
4223 "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg",
4224 "id" => other_user.id
4230 describe "PUT /api/v1/media/:id" do
4232 actor = insert(:user)
4234 file = %Plug.Upload{
4235 content_type: "image/jpg",
4236 path: Path.absname("test/fixtures/image.jpg"),
4237 filename: "an_image.jpg"
4240 {:ok, %Object{} = object} =
4243 actor: User.ap_id(actor),
4244 description: "test-m"
4247 [actor: actor, object: object]
4250 test "updates name of media", %{conn: conn, actor: actor, object: object} do
4253 |> assign(:user, actor)
4254 |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"})
4255 |> json_response(:ok)
4257 assert media["description"] == "test-media"
4258 assert refresh_record(object).data["name"] == "test-media"
4261 test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do
4264 |> assign(:user, actor)
4265 |> put("/api/v1/media/#{object.id}", %{})
4266 |> json_response(400)
4268 assert media == %{"error" => "bad_request"}
4272 describe "DELETE /auth/sign_out" do
4273 test "redirect to root page", %{conn: conn} do
4274 user = insert(:user)
4278 |> assign(:user, user)
4279 |> delete("/auth/sign_out")
4281 assert conn.status == 302
4282 assert redirected_to(conn) == "/"
4286 describe "GET /api/v1/accounts/:id/lists - account_lists" do
4287 test "returns lists to which the account belongs", %{conn: conn} do
4288 user = insert(:user)
4289 other_user = insert(:user)
4290 assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user)
4291 {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
4295 |> assign(:user, user)
4296 |> get("/api/v1/accounts/#{other_user.id}/lists")
4297 |> json_response(200)
4299 assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
4303 describe "empty_array, stubs for mastodon api" do
4304 test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do
4305 user = insert(:user)
4309 |> assign(:user, user)
4310 |> get("/api/v1/accounts/#{user.id}/identity_proofs")
4311 |> json_response(200)
4316 test "GET /api/v1/endorsements", %{conn: conn} do
4317 user = insert(:user)
4321 |> assign(:user, user)
4322 |> get("/api/v1/endorsements")
4323 |> json_response(200)
4328 test "GET /api/v1/trends", %{conn: conn} do
4329 user = insert(:user)
4333 |> assign(:user, user)
4334 |> get("/api/v1/trends")
4335 |> json_response(200)