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.Push
25 import ExUnit.CaptureLog
26 import Pleroma.Factory
27 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 describe "posting statuses" do
46 |> assign(:user, user)
51 test "posting a status", %{conn: conn} do
52 idempotency_key = "Pikachu rocks!"
56 |> put_req_header("idempotency-key", idempotency_key)
57 |> post("/api/v1/statuses", %{
59 "spoiler_text" => "2hu",
60 "sensitive" => "false"
63 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
65 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
67 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
68 json_response(conn_one, 200)
70 assert Activity.get_by_id(id)
74 |> put_req_header("idempotency-key", idempotency_key)
75 |> post("/api/v1/statuses", %{
77 "spoiler_text" => "2hu",
78 "sensitive" => "false"
81 assert %{"id" => second_id} = json_response(conn_two, 200)
82 assert id == second_id
86 |> post("/api/v1/statuses", %{
88 "spoiler_text" => "2hu",
89 "sensitive" => "false"
92 assert %{"id" => third_id} = json_response(conn_three, 200)
95 # An activity that will expire:
101 |> post("api/v1/statuses", %{
102 "status" => "oolong",
103 "expires_in" => expires_in
106 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
107 assert activity = Activity.get_by_id(fourth_id)
108 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
110 estimated_expires_at =
111 NaiveDateTime.utc_now()
112 |> NaiveDateTime.add(expires_in)
113 |> NaiveDateTime.truncate(:second)
115 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
116 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
118 assert fourth_response["pleroma"]["expires_at"] ==
119 NaiveDateTime.to_iso8601(expiration.scheduled_at)
122 test "replying to a status", %{conn: conn} do
124 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
128 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
130 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
132 activity = Activity.get_by_id(id)
134 assert activity.data["context"] == replied_to.data["context"]
135 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
138 test "replying to a direct message with visibility other than direct", %{conn: conn} do
140 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
142 Enum.each(["public", "private", "unlisted"], fn visibility ->
145 |> post("/api/v1/statuses", %{
146 "status" => "@#{user.nickname} hey",
147 "in_reply_to_id" => replied_to.id,
148 "visibility" => visibility
151 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
155 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
158 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
160 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
161 assert Activity.get_by_id(id)
164 test "posting a sensitive status", %{conn: conn} do
167 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
169 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
170 assert Activity.get_by_id(id)
173 test "posting a fake status", %{conn: conn} do
176 |> post("/api/v1/statuses", %{
178 "\"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"
181 real_status = json_response(real_conn, 200)
184 assert Object.get_by_ap_id(real_status["uri"])
188 |> Map.put("id", nil)
189 |> Map.put("url", nil)
190 |> Map.put("uri", nil)
191 |> Map.put("created_at", nil)
192 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
196 |> post("/api/v1/statuses", %{
198 "\"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",
202 fake_status = json_response(fake_conn, 200)
205 refute Object.get_by_ap_id(fake_status["uri"])
209 |> Map.put("id", nil)
210 |> Map.put("url", nil)
211 |> Map.put("uri", nil)
212 |> Map.put("created_at", nil)
213 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
215 assert real_status == fake_status
218 test "posting a status with OGP link preview", %{conn: conn} do
219 Config.put([:rich_media, :enabled], true)
223 |> post("/api/v1/statuses", %{
224 "status" => "https://example.com/ogp"
227 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
228 assert Activity.get_by_id(id)
231 test "posting a direct status", %{conn: conn} do
232 user2 = insert(:user)
233 content = "direct cofe @#{user2.nickname}"
237 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
239 assert %{"id" => id} = response = json_response(conn, 200)
240 assert response["visibility"] == "direct"
241 assert response["pleroma"]["direct_conversation_id"]
242 assert activity = Activity.get_by_id(id)
243 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
244 assert activity.data["to"] == [user2.ap_id]
245 assert activity.data["cc"] == []
249 describe "posting polls" do
250 test "posting a poll", %{conn: conn} do
252 time = NaiveDateTime.utc_now()
256 |> assign(:user, user)
257 |> post("/api/v1/statuses", %{
258 "status" => "Who is the #bestgrill?",
259 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
262 response = json_response(conn, 200)
264 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
265 title in ["Rei", "Asuka", "Misato"]
268 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
269 refute response["poll"]["expred"]
272 test "option limit is enforced", %{conn: conn} do
274 limit = Config.get([:instance, :poll_limits, :max_options])
278 |> assign(:user, user)
279 |> post("/api/v1/statuses", %{
281 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
284 %{"error" => error} = json_response(conn, 422)
285 assert error == "Poll can't contain more than #{limit} options"
288 test "option character limit is enforced", %{conn: conn} do
290 limit = Config.get([:instance, :poll_limits, :max_option_chars])
294 |> assign(:user, user)
295 |> post("/api/v1/statuses", %{
298 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
303 %{"error" => error} = json_response(conn, 422)
304 assert error == "Poll options cannot be longer than #{limit} characters each"
307 test "minimal date limit is enforced", %{conn: conn} do
309 limit = Config.get([:instance, :poll_limits, :min_expiration])
313 |> assign(:user, user)
314 |> post("/api/v1/statuses", %{
315 "status" => "imagine arbitrary limits",
317 "options" => ["this post was made by pleroma gang"],
318 "expires_in" => limit - 1
322 %{"error" => error} = json_response(conn, 422)
323 assert error == "Expiration date is too soon"
326 test "maximum date limit is enforced", %{conn: conn} do
328 limit = Config.get([:instance, :poll_limits, :max_expiration])
332 |> assign(:user, user)
333 |> post("/api/v1/statuses", %{
334 "status" => "imagine arbitrary limits",
336 "options" => ["this post was made by pleroma gang"],
337 "expires_in" => limit + 1
341 %{"error" => error} = json_response(conn, 422)
342 assert error == "Expiration date is too far in the future"
346 test "Conversations", %{conn: conn} do
347 user_one = insert(:user)
348 user_two = insert(:user)
349 user_three = insert(:user)
351 {:ok, user_two} = User.follow(user_two, user_one)
354 CommonAPI.post(user_one, %{
355 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
356 "visibility" => "direct"
359 {:ok, _follower_only} =
360 CommonAPI.post(user_one, %{
361 "status" => "Hi @#{user_two.nickname}!",
362 "visibility" => "private"
367 |> assign(:user, user_one)
368 |> get("/api/v1/conversations")
370 assert response = json_response(res_conn, 200)
375 "accounts" => res_accounts,
376 "last_status" => res_last_status,
381 account_ids = Enum.map(res_accounts, & &1["id"])
382 assert length(res_accounts) == 2
383 assert user_two.id in account_ids
384 assert user_three.id in account_ids
385 assert is_binary(res_id)
386 assert unread == true
387 assert res_last_status["id"] == direct.id
389 # Apparently undocumented API endpoint
392 |> assign(:user, user_one)
393 |> post("/api/v1/conversations/#{res_id}/read")
395 assert response = json_response(res_conn, 200)
396 assert length(response["accounts"]) == 2
397 assert response["last_status"]["id"] == direct.id
398 assert response["unread"] == false
400 # (vanilla) Mastodon frontend behaviour
403 |> assign(:user, user_one)
404 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
406 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
409 test "verify_credentials", %{conn: conn} do
414 |> assign(:user, user)
415 |> get("/api/v1/accounts/verify_credentials")
417 response = json_response(conn, 200)
419 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
420 assert response["pleroma"]["chat_token"]
421 assert id == to_string(user.id)
424 test "verify_credentials default scope unlisted", %{conn: conn} do
425 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
429 |> assign(:user, user)
430 |> get("/api/v1/accounts/verify_credentials")
432 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
433 assert id == to_string(user.id)
436 test "apps/verify_credentials", %{conn: conn} do
437 token = insert(:oauth_token)
441 |> assign(:user, token.user)
442 |> assign(:token, token)
443 |> get("/api/v1/apps/verify_credentials")
445 app = Repo.preload(token, :app).app
448 "name" => app.client_name,
449 "website" => app.website,
450 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
453 assert expected == json_response(conn, 200)
456 test "user avatar can be set", %{conn: conn} do
458 avatar_image = File.read!("test/fixtures/avatar_data_uri")
462 |> assign(:user, user)
463 |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
465 user = refresh_record(user)
479 assert %{"url" => _} = json_response(conn, 200)
482 test "user avatar can be reset", %{conn: conn} do
487 |> assign(:user, user)
488 |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
490 user = User.get_cached_by_id(user.id)
492 assert user.avatar == nil
494 assert %{"url" => nil} = json_response(conn, 200)
497 test "can set profile banner", %{conn: conn} do
502 |> assign(:user, user)
503 |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
505 user = refresh_record(user)
506 assert user.info.banner["type"] == "Image"
508 assert %{"url" => _} = json_response(conn, 200)
511 test "can reset profile banner", %{conn: conn} do
516 |> assign(:user, user)
517 |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
519 user = refresh_record(user)
520 assert user.info.banner == %{}
522 assert %{"url" => nil} = json_response(conn, 200)
525 test "background image can be set", %{conn: conn} do
530 |> assign(:user, user)
531 |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
533 user = refresh_record(user)
534 assert user.info.background["type"] == "Image"
535 assert %{"url" => _} = json_response(conn, 200)
538 test "background image can be reset", %{conn: conn} do
543 |> assign(:user, user)
544 |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
546 user = refresh_record(user)
547 assert user.info.background == %{}
548 assert %{"url" => nil} = json_response(conn, 200)
551 test "creates an oauth app", %{conn: conn} do
553 app_attrs = build(:oauth_app)
557 |> assign(:user, user)
558 |> post("/api/v1/apps", %{
559 client_name: app_attrs.client_name,
560 redirect_uris: app_attrs.redirect_uris
563 [app] = Repo.all(App)
566 "name" => app.client_name,
567 "website" => app.website,
568 "client_id" => app.client_id,
569 "client_secret" => app.client_secret,
570 "id" => app.id |> to_string(),
571 "redirect_uri" => app.redirect_uris,
572 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
575 assert expected == json_response(conn, 200)
578 test "get a status", %{conn: conn} do
579 activity = insert(:note_activity)
583 |> get("/api/v1/statuses/#{activity.id}")
585 assert %{"id" => id} = json_response(conn, 200)
586 assert id == to_string(activity.id)
589 test "get statuses by IDs", %{conn: conn} do
590 %{id: id1} = insert(:note_activity)
591 %{id: id2} = insert(:note_activity)
593 query_string = "ids[]=#{id1}&ids[]=#{id2}"
594 conn = get(conn, "/api/v1/statuses/?#{query_string}")
596 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
599 describe "deleting a status" do
600 test "when you created it", %{conn: conn} do
601 activity = insert(:note_activity)
602 author = User.get_cached_by_ap_id(activity.data["actor"])
606 |> assign(:user, author)
607 |> delete("/api/v1/statuses/#{activity.id}")
609 assert %{} = json_response(conn, 200)
611 refute Activity.get_by_id(activity.id)
614 test "when you didn't create it", %{conn: conn} do
615 activity = insert(:note_activity)
620 |> assign(:user, user)
621 |> delete("/api/v1/statuses/#{activity.id}")
623 assert %{"error" => _} = json_response(conn, 403)
625 assert Activity.get_by_id(activity.id) == activity
628 test "when you're an admin or moderator", %{conn: conn} do
629 activity1 = insert(:note_activity)
630 activity2 = insert(:note_activity)
631 admin = insert(:user, info: %{is_admin: true})
632 moderator = insert(:user, info: %{is_moderator: true})
636 |> assign(:user, admin)
637 |> delete("/api/v1/statuses/#{activity1.id}")
639 assert %{} = json_response(res_conn, 200)
643 |> assign(:user, moderator)
644 |> delete("/api/v1/statuses/#{activity2.id}")
646 assert %{} = json_response(res_conn, 200)
648 refute Activity.get_by_id(activity1.id)
649 refute Activity.get_by_id(activity2.id)
653 describe "filters" do
654 test "creating a filter", %{conn: conn} do
657 filter = %Pleroma.Filter{
664 |> assign(:user, user)
665 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
667 assert response = json_response(conn, 200)
668 assert response["phrase"] == filter.phrase
669 assert response["context"] == filter.context
670 assert response["irreversible"] == false
671 assert response["id"] != nil
672 assert response["id"] != ""
675 test "fetching a list of filters", %{conn: conn} do
678 query_one = %Pleroma.Filter{
685 query_two = %Pleroma.Filter{
692 {:ok, filter_one} = Pleroma.Filter.create(query_one)
693 {:ok, filter_two} = Pleroma.Filter.create(query_two)
697 |> assign(:user, user)
698 |> get("/api/v1/filters")
699 |> json_response(200)
705 filters: [filter_two, filter_one]
709 test "get a filter", %{conn: conn} do
712 query = %Pleroma.Filter{
719 {:ok, filter} = Pleroma.Filter.create(query)
723 |> assign(:user, user)
724 |> get("/api/v1/filters/#{filter.filter_id}")
726 assert _response = json_response(conn, 200)
729 test "update a filter", %{conn: conn} do
732 query = %Pleroma.Filter{
739 {:ok, _filter} = Pleroma.Filter.create(query)
741 new = %Pleroma.Filter{
748 |> assign(:user, user)
749 |> put("/api/v1/filters/#{query.filter_id}", %{
754 assert response = json_response(conn, 200)
755 assert response["phrase"] == new.phrase
756 assert response["context"] == new.context
759 test "delete a filter", %{conn: conn} do
762 query = %Pleroma.Filter{
769 {:ok, filter} = Pleroma.Filter.create(query)
773 |> assign(:user, user)
774 |> delete("/api/v1/filters/#{filter.filter_id}")
776 assert response = json_response(conn, 200)
777 assert response == %{}
781 describe "reblogging" do
782 test "reblogs and returns the reblogged status", %{conn: conn} do
783 activity = insert(:note_activity)
788 |> assign(:user, user)
789 |> post("/api/v1/statuses/#{activity.id}/reblog")
792 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
794 } = json_response(conn, 200)
796 assert to_string(activity.id) == id
799 test "reblogged status for another user", %{conn: conn} do
800 activity = insert(:note_activity)
801 user1 = insert(:user)
802 user2 = insert(:user)
803 user3 = insert(:user)
804 CommonAPI.favorite(activity.id, user2)
805 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
806 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
807 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
811 |> assign(:user, user3)
812 |> get("/api/v1/statuses/#{reblog_activity1.id}")
815 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
816 "reblogged" => false,
817 "favourited" => false,
818 "bookmarked" => false
819 } = json_response(conn_res, 200)
823 |> assign(:user, user2)
824 |> get("/api/v1/statuses/#{reblog_activity1.id}")
827 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
829 "favourited" => true,
831 } = json_response(conn_res, 200)
833 assert to_string(activity.id) == id
836 test "returns 400 error when activity is not exist", %{conn: conn} do
841 |> assign(:user, user)
842 |> post("/api/v1/statuses/foo/reblog")
844 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
848 describe "unreblogging" do
849 test "unreblogs and returns the unreblogged status", %{conn: conn} do
850 activity = insert(:note_activity)
853 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
857 |> assign(:user, user)
858 |> post("/api/v1/statuses/#{activity.id}/unreblog")
860 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
862 assert to_string(activity.id) == id
865 test "returns 400 error when activity is not exist", %{conn: conn} do
870 |> assign(:user, user)
871 |> post("/api/v1/statuses/foo/unreblog")
873 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
877 describe "favoriting" do
878 test "favs a status and returns it", %{conn: conn} do
879 activity = insert(:note_activity)
884 |> assign(:user, user)
885 |> post("/api/v1/statuses/#{activity.id}/favourite")
887 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
888 json_response(conn, 200)
890 assert to_string(activity.id) == id
893 test "returns 400 error for a wrong id", %{conn: conn} do
898 |> assign(:user, user)
899 |> post("/api/v1/statuses/1/favourite")
901 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
905 describe "unfavoriting" do
906 test "unfavorites a status and returns it", %{conn: conn} do
907 activity = insert(:note_activity)
910 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
914 |> assign(:user, user)
915 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
917 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
918 json_response(conn, 200)
920 assert to_string(activity.id) == id
923 test "returns 400 error for a wrong id", %{conn: conn} do
928 |> assign(:user, user)
929 |> post("/api/v1/statuses/1/unfavourite")
931 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
935 describe "user timelines" do
936 test "gets a users statuses", %{conn: conn} do
937 user_one = insert(:user)
938 user_two = insert(:user)
939 user_three = insert(:user)
941 {:ok, user_three} = User.follow(user_three, user_one)
943 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
945 {:ok, direct_activity} =
946 CommonAPI.post(user_one, %{
947 "status" => "Hi, @#{user_two.nickname}.",
948 "visibility" => "direct"
951 {:ok, private_activity} =
952 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
956 |> get("/api/v1/accounts/#{user_one.id}/statuses")
958 assert [%{"id" => id}] = json_response(resp, 200)
959 assert id == to_string(activity.id)
963 |> assign(:user, user_two)
964 |> get("/api/v1/accounts/#{user_one.id}/statuses")
966 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
967 assert id_one == to_string(direct_activity.id)
968 assert id_two == to_string(activity.id)
972 |> assign(:user, user_three)
973 |> get("/api/v1/accounts/#{user_one.id}/statuses")
975 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
976 assert id_one == to_string(private_activity.id)
977 assert id_two == to_string(activity.id)
980 test "unimplemented pinned statuses feature", %{conn: conn} do
981 note = insert(:note_activity)
982 user = User.get_cached_by_ap_id(note.data["actor"])
986 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
988 assert json_response(conn, 200) == []
991 test "gets an users media", %{conn: conn} do
992 note = insert(:note_activity)
993 user = User.get_cached_by_ap_id(note.data["actor"])
996 content_type: "image/jpg",
997 path: Path.absname("test/fixtures/image.jpg"),
998 filename: "an_image.jpg"
1001 {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
1003 {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
1007 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1009 assert [%{"id" => id}] = json_response(conn, 200)
1010 assert id == to_string(image_post.id)
1014 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1016 assert [%{"id" => id}] = json_response(conn, 200)
1017 assert id == to_string(image_post.id)
1020 test "gets a user's statuses without reblogs", %{conn: conn} do
1021 user = insert(:user)
1022 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1023 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1027 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1029 assert [%{"id" => id}] = json_response(conn, 200)
1030 assert id == to_string(post.id)
1034 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1036 assert [%{"id" => id}] = json_response(conn, 200)
1037 assert id == to_string(post.id)
1040 test "filters user's statuses by a hashtag", %{conn: conn} do
1041 user = insert(:user)
1042 {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
1043 {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
1047 |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
1049 assert [%{"id" => id}] = json_response(conn, 200)
1050 assert id == to_string(post.id)
1054 describe "user relationships" do
1055 test "returns the relationships for the current user", %{conn: conn} do
1056 user = insert(:user)
1057 other_user = insert(:user)
1058 {:ok, user} = User.follow(user, other_user)
1062 |> assign(:user, user)
1063 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1065 assert [relationship] = json_response(conn, 200)
1067 assert to_string(other_user.id) == relationship["id"]
1071 describe "media upload" do
1073 user = insert(:user)
1077 |> assign(:user, user)
1079 image = %Plug.Upload{
1080 content_type: "image/jpg",
1081 path: Path.absname("test/fixtures/image.jpg"),
1082 filename: "an_image.jpg"
1085 [conn: conn, image: image]
1088 clear_config([:media_proxy])
1089 clear_config([Pleroma.Upload])
1091 test "returns uploaded image", %{conn: conn, image: image} do
1092 desc = "Description of the image"
1096 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1097 |> json_response(:ok)
1099 assert media["type"] == "image"
1100 assert media["description"] == desc
1103 object = Repo.get(Object, media["id"])
1104 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1108 describe "locked accounts" do
1109 test "/api/v1/follow_requests works" do
1110 user = insert(:user, %{info: %User.Info{locked: true}})
1111 other_user = insert(:user)
1113 {:ok, _activity} = ActivityPub.follow(other_user, user)
1115 user = User.get_cached_by_id(user.id)
1116 other_user = User.get_cached_by_id(other_user.id)
1118 assert User.following?(other_user, user) == false
1122 |> assign(:user, user)
1123 |> get("/api/v1/follow_requests")
1125 assert [relationship] = json_response(conn, 200)
1126 assert to_string(other_user.id) == relationship["id"]
1129 test "/api/v1/follow_requests/:id/authorize works" do
1130 user = insert(:user, %{info: %User.Info{locked: true}})
1131 other_user = insert(:user)
1133 {:ok, _activity} = ActivityPub.follow(other_user, user)
1135 user = User.get_cached_by_id(user.id)
1136 other_user = User.get_cached_by_id(other_user.id)
1138 assert User.following?(other_user, user) == false
1142 |> assign(:user, user)
1143 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1145 assert relationship = json_response(conn, 200)
1146 assert to_string(other_user.id) == relationship["id"]
1148 user = User.get_cached_by_id(user.id)
1149 other_user = User.get_cached_by_id(other_user.id)
1151 assert User.following?(other_user, user) == true
1154 test "verify_credentials", %{conn: conn} do
1155 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1159 |> assign(:user, user)
1160 |> get("/api/v1/accounts/verify_credentials")
1162 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1163 assert id == to_string(user.id)
1166 test "/api/v1/follow_requests/:id/reject works" do
1167 user = insert(:user, %{info: %User.Info{locked: true}})
1168 other_user = insert(:user)
1170 {:ok, _activity} = ActivityPub.follow(other_user, user)
1172 user = User.get_cached_by_id(user.id)
1176 |> assign(:user, user)
1177 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1179 assert relationship = json_response(conn, 200)
1180 assert to_string(other_user.id) == relationship["id"]
1182 user = User.get_cached_by_id(user.id)
1183 other_user = User.get_cached_by_id(other_user.id)
1185 assert User.following?(other_user, user) == false
1189 describe "account fetching" do
1190 test "works by id" do
1191 user = insert(:user)
1195 |> get("/api/v1/accounts/#{user.id}")
1197 assert %{"id" => id} = json_response(conn, 200)
1198 assert id == to_string(user.id)
1202 |> get("/api/v1/accounts/-1")
1204 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1207 test "works by nickname" do
1208 user = insert(:user)
1212 |> get("/api/v1/accounts/#{user.nickname}")
1214 assert %{"id" => id} = json_response(conn, 200)
1215 assert id == user.id
1218 test "works by nickname for remote users" do
1219 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
1220 Pleroma.Config.put([:instance, :limit_to_local_content], false)
1221 user = insert(:user, nickname: "user@example.com", local: false)
1225 |> get("/api/v1/accounts/#{user.nickname}")
1227 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
1228 assert %{"id" => id} = json_response(conn, 200)
1229 assert id == user.id
1232 test "respects limit_to_local_content == :all for remote user nicknames" do
1233 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
1234 Pleroma.Config.put([:instance, :limit_to_local_content], :all)
1236 user = insert(:user, nickname: "user@example.com", local: false)
1240 |> get("/api/v1/accounts/#{user.nickname}")
1242 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
1243 assert json_response(conn, 404)
1246 test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do
1247 limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
1248 Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
1250 user = insert(:user, nickname: "user@example.com", local: false)
1251 reading_user = insert(:user)
1255 |> get("/api/v1/accounts/#{user.nickname}")
1257 assert json_response(conn, 404)
1261 |> assign(:user, reading_user)
1262 |> get("/api/v1/accounts/#{user.nickname}")
1264 Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local)
1265 assert %{"id" => id} = json_response(conn, 200)
1266 assert id == user.id
1270 test "mascot upload", %{conn: conn} do
1271 user = insert(:user)
1273 non_image_file = %Plug.Upload{
1274 content_type: "audio/mpeg",
1275 path: Path.absname("test/fixtures/sound.mp3"),
1276 filename: "sound.mp3"
1281 |> assign(:user, user)
1282 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1284 assert json_response(conn, 415)
1286 file = %Plug.Upload{
1287 content_type: "image/jpg",
1288 path: Path.absname("test/fixtures/image.jpg"),
1289 filename: "an_image.jpg"
1294 |> assign(:user, user)
1295 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1297 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1300 test "mascot retrieving", %{conn: conn} do
1301 user = insert(:user)
1302 # When user hasn't set a mascot, we should just get pleroma tan back
1305 |> assign(:user, user)
1306 |> get("/api/v1/pleroma/mascot")
1308 assert %{"url" => url} = json_response(conn, 200)
1309 assert url =~ "pleroma-fox-tan-smol"
1311 # When a user sets their mascot, we should get that back
1312 file = %Plug.Upload{
1313 content_type: "image/jpg",
1314 path: Path.absname("test/fixtures/image.jpg"),
1315 filename: "an_image.jpg"
1320 |> assign(:user, user)
1321 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1323 assert json_response(conn, 200)
1325 user = User.get_cached_by_id(user.id)
1329 |> assign(:user, user)
1330 |> get("/api/v1/pleroma/mascot")
1332 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1333 assert url =~ "an_image"
1336 test "getting followers", %{conn: conn} do
1337 user = insert(:user)
1338 other_user = insert(:user)
1339 {:ok, user} = User.follow(user, other_user)
1343 |> get("/api/v1/accounts/#{other_user.id}/followers")
1345 assert [%{"id" => id}] = json_response(conn, 200)
1346 assert id == to_string(user.id)
1349 test "getting followers, hide_followers", %{conn: conn} do
1350 user = insert(:user)
1351 other_user = insert(:user, %{info: %{hide_followers: true}})
1352 {:ok, _user} = User.follow(user, other_user)
1356 |> get("/api/v1/accounts/#{other_user.id}/followers")
1358 assert [] == json_response(conn, 200)
1361 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1362 user = insert(:user)
1363 other_user = insert(:user, %{info: %{hide_followers: true}})
1364 {:ok, _user} = User.follow(user, other_user)
1368 |> assign(:user, other_user)
1369 |> get("/api/v1/accounts/#{other_user.id}/followers")
1371 refute [] == json_response(conn, 200)
1374 test "getting followers, pagination", %{conn: conn} do
1375 user = insert(:user)
1376 follower1 = insert(:user)
1377 follower2 = insert(:user)
1378 follower3 = insert(:user)
1379 {:ok, _} = User.follow(follower1, user)
1380 {:ok, _} = User.follow(follower2, user)
1381 {:ok, _} = User.follow(follower3, user)
1385 |> assign(:user, user)
1389 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1391 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1392 assert id3 == follower3.id
1393 assert id2 == follower2.id
1397 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1399 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1400 assert id2 == follower2.id
1401 assert id1 == follower1.id
1405 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1407 assert [%{"id" => id2}] = json_response(res_conn, 200)
1408 assert id2 == follower2.id
1410 assert [link_header] = get_resp_header(res_conn, "link")
1411 assert link_header =~ ~r/min_id=#{follower2.id}/
1412 assert link_header =~ ~r/max_id=#{follower2.id}/
1415 test "getting following", %{conn: conn} do
1416 user = insert(:user)
1417 other_user = insert(:user)
1418 {:ok, user} = User.follow(user, other_user)
1422 |> get("/api/v1/accounts/#{user.id}/following")
1424 assert [%{"id" => id}] = json_response(conn, 200)
1425 assert id == to_string(other_user.id)
1428 test "getting following, hide_follows", %{conn: conn} do
1429 user = insert(:user, %{info: %{hide_follows: true}})
1430 other_user = insert(:user)
1431 {:ok, user} = User.follow(user, other_user)
1435 |> get("/api/v1/accounts/#{user.id}/following")
1437 assert [] == json_response(conn, 200)
1440 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1441 user = insert(:user, %{info: %{hide_follows: true}})
1442 other_user = insert(:user)
1443 {:ok, user} = User.follow(user, other_user)
1447 |> assign(:user, user)
1448 |> get("/api/v1/accounts/#{user.id}/following")
1450 refute [] == json_response(conn, 200)
1453 test "getting following, pagination", %{conn: conn} do
1454 user = insert(:user)
1455 following1 = insert(:user)
1456 following2 = insert(:user)
1457 following3 = insert(:user)
1458 {:ok, _} = User.follow(user, following1)
1459 {:ok, _} = User.follow(user, following2)
1460 {:ok, _} = User.follow(user, following3)
1464 |> assign(:user, user)
1468 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1470 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1471 assert id3 == following3.id
1472 assert id2 == following2.id
1476 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1478 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1479 assert id2 == following2.id
1480 assert id1 == following1.id
1484 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1486 assert [%{"id" => id2}] = json_response(res_conn, 200)
1487 assert id2 == following2.id
1489 assert [link_header] = get_resp_header(res_conn, "link")
1490 assert link_header =~ ~r/min_id=#{following2.id}/
1491 assert link_header =~ ~r/max_id=#{following2.id}/
1494 test "following / unfollowing a user", %{conn: conn} do
1495 user = insert(:user)
1496 other_user = insert(:user)
1500 |> assign(:user, user)
1501 |> post("/api/v1/accounts/#{other_user.id}/follow")
1503 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
1505 user = User.get_cached_by_id(user.id)
1509 |> assign(:user, user)
1510 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
1512 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
1514 user = User.get_cached_by_id(user.id)
1518 |> assign(:user, user)
1519 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
1521 assert %{"id" => id} = json_response(conn, 200)
1522 assert id == to_string(other_user.id)
1525 test "following without reblogs" do
1526 follower = insert(:user)
1527 followed = insert(:user)
1528 other_user = insert(:user)
1532 |> assign(:user, follower)
1533 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
1535 assert %{"showing_reblogs" => false} = json_response(conn, 200)
1537 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
1538 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
1542 |> assign(:user, User.get_cached_by_id(follower.id))
1543 |> get("/api/v1/timelines/home")
1545 assert [] == json_response(conn, 200)
1549 |> assign(:user, follower)
1550 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
1552 assert %{"showing_reblogs" => true} = json_response(conn, 200)
1556 |> assign(:user, User.get_cached_by_id(follower.id))
1557 |> get("/api/v1/timelines/home")
1559 expected_activity_id = reblog.id
1560 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
1563 test "following / unfollowing errors" do
1564 user = insert(:user)
1568 |> assign(:user, user)
1571 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
1572 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1575 user = User.get_cached_by_id(user.id)
1576 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
1577 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1579 # self follow via uri
1580 user = User.get_cached_by_id(user.id)
1581 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
1582 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1584 # follow non existing user
1585 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
1586 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1588 # follow non existing user via uri
1589 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
1590 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1592 # unfollow non existing user
1593 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
1594 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1597 describe "mute/unmute" do
1598 test "with notifications", %{conn: conn} do
1599 user = insert(:user)
1600 other_user = insert(:user)
1604 |> assign(:user, user)
1605 |> post("/api/v1/accounts/#{other_user.id}/mute")
1607 response = json_response(conn, 200)
1609 assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
1610 user = User.get_cached_by_id(user.id)
1614 |> assign(:user, user)
1615 |> post("/api/v1/accounts/#{other_user.id}/unmute")
1617 response = json_response(conn, 200)
1618 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
1621 test "without notifications", %{conn: conn} do
1622 user = insert(:user)
1623 other_user = insert(:user)
1627 |> assign(:user, user)
1628 |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
1630 response = json_response(conn, 200)
1632 assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
1633 user = User.get_cached_by_id(user.id)
1637 |> assign(:user, user)
1638 |> post("/api/v1/accounts/#{other_user.id}/unmute")
1640 response = json_response(conn, 200)
1641 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
1645 test "subscribing / unsubscribing to a user", %{conn: conn} do
1646 user = insert(:user)
1647 subscription_target = insert(:user)
1651 |> assign(:user, user)
1652 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
1654 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
1658 |> assign(:user, user)
1659 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
1661 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
1664 test "getting a list of mutes", %{conn: conn} do
1665 user = insert(:user)
1666 other_user = insert(:user)
1668 {:ok, user} = User.mute(user, other_user)
1672 |> assign(:user, user)
1673 |> get("/api/v1/mutes")
1675 other_user_id = to_string(other_user.id)
1676 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
1679 test "blocking / unblocking a user", %{conn: conn} do
1680 user = insert(:user)
1681 other_user = insert(:user)
1685 |> assign(:user, user)
1686 |> post("/api/v1/accounts/#{other_user.id}/block")
1688 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
1690 user = User.get_cached_by_id(user.id)
1694 |> assign(:user, user)
1695 |> post("/api/v1/accounts/#{other_user.id}/unblock")
1697 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
1700 test "getting a list of blocks", %{conn: conn} do
1701 user = insert(:user)
1702 other_user = insert(:user)
1704 {:ok, user} = User.block(user, other_user)
1708 |> assign(:user, user)
1709 |> get("/api/v1/blocks")
1711 other_user_id = to_string(other_user.id)
1712 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
1715 test "blocking / unblocking a domain", %{conn: conn} do
1716 user = insert(:user)
1717 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
1721 |> assign(:user, user)
1722 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
1724 assert %{} = json_response(conn, 200)
1725 user = User.get_cached_by_ap_id(user.ap_id)
1726 assert User.blocks?(user, other_user)
1730 |> assign(:user, user)
1731 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
1733 assert %{} = json_response(conn, 200)
1734 user = User.get_cached_by_ap_id(user.ap_id)
1735 refute User.blocks?(user, other_user)
1738 test "getting a list of domain blocks", %{conn: conn} do
1739 user = insert(:user)
1741 {:ok, user} = User.block_domain(user, "bad.site")
1742 {:ok, user} = User.block_domain(user, "even.worse.site")
1746 |> assign(:user, user)
1747 |> get("/api/v1/domain_blocks")
1749 domain_blocks = json_response(conn, 200)
1751 assert "bad.site" in domain_blocks
1752 assert "even.worse.site" in domain_blocks
1755 test "unimplemented follow_requests, blocks, domain blocks" do
1756 user = insert(:user)
1758 ["blocks", "domain_blocks", "follow_requests"]
1759 |> Enum.each(fn endpoint ->
1762 |> assign(:user, user)
1763 |> get("/api/v1/#{endpoint}")
1765 assert [] = json_response(conn, 200)
1769 test "returns the favorites of a user", %{conn: conn} do
1770 user = insert(:user)
1771 other_user = insert(:user)
1773 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1774 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1776 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1780 |> assign(:user, user)
1781 |> get("/api/v1/favourites")
1783 assert [status] = json_response(first_conn, 200)
1784 assert status["id"] == to_string(activity.id)
1786 assert [{"link", _link_header}] =
1787 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1789 # Honours query params
1790 {:ok, second_activity} =
1791 CommonAPI.post(other_user, %{
1793 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1796 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
1798 last_like = status["id"]
1802 |> assign(:user, user)
1803 |> get("/api/v1/favourites?since_id=#{last_like}")
1805 assert [second_status] = json_response(second_conn, 200)
1806 assert second_status["id"] == to_string(second_activity.id)
1810 |> assign(:user, user)
1811 |> get("/api/v1/favourites?limit=0")
1813 assert [] = json_response(third_conn, 200)
1816 describe "getting favorites timeline of specified user" do
1818 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
1819 [current_user: current_user, user: user]
1822 test "returns list of statuses favorited by specified user", %{
1824 current_user: current_user,
1827 [activity | _] = insert_pair(:note_activity)
1828 CommonAPI.favorite(activity.id, user)
1832 |> assign(:user, current_user)
1833 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1834 |> json_response(:ok)
1838 assert length(response) == 1
1839 assert like["id"] == activity.id
1842 test "returns favorites for specified user_id when user is not logged in", %{
1846 activity = insert(:note_activity)
1847 CommonAPI.favorite(activity.id, user)
1851 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1852 |> json_response(:ok)
1854 assert length(response) == 1
1857 test "returns favorited DM only when user is logged in and he is one of recipients", %{
1859 current_user: current_user,
1863 CommonAPI.post(current_user, %{
1864 "status" => "Hi @#{user.nickname}!",
1865 "visibility" => "direct"
1868 CommonAPI.favorite(direct.id, user)
1872 |> assign(:user, current_user)
1873 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1874 |> json_response(:ok)
1876 assert length(response) == 1
1878 anonymous_response =
1880 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1881 |> json_response(:ok)
1883 assert Enum.empty?(anonymous_response)
1886 test "does not return others' favorited DM when user is not one of recipients", %{
1888 current_user: current_user,
1891 user_two = insert(:user)
1894 CommonAPI.post(user_two, %{
1895 "status" => "Hi @#{user.nickname}!",
1896 "visibility" => "direct"
1899 CommonAPI.favorite(direct.id, user)
1903 |> assign(:user, current_user)
1904 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1905 |> json_response(:ok)
1907 assert Enum.empty?(response)
1910 test "paginates favorites using since_id and max_id", %{
1912 current_user: current_user,
1915 activities = insert_list(10, :note_activity)
1917 Enum.each(activities, fn activity ->
1918 CommonAPI.favorite(activity.id, user)
1921 third_activity = Enum.at(activities, 2)
1922 seventh_activity = Enum.at(activities, 6)
1926 |> assign(:user, current_user)
1927 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
1928 since_id: third_activity.id,
1929 max_id: seventh_activity.id
1931 |> json_response(:ok)
1933 assert length(response) == 3
1934 refute third_activity in response
1935 refute seventh_activity in response
1938 test "limits favorites using limit parameter", %{
1940 current_user: current_user,
1944 |> insert_list(:note_activity)
1945 |> Enum.each(fn activity ->
1946 CommonAPI.favorite(activity.id, user)
1951 |> assign(:user, current_user)
1952 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
1953 |> json_response(:ok)
1955 assert length(response) == 3
1958 test "returns empty response when user does not have any favorited statuses", %{
1960 current_user: current_user,
1965 |> assign(:user, current_user)
1966 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1967 |> json_response(:ok)
1969 assert Enum.empty?(response)
1972 test "returns 404 error when specified user is not exist", %{conn: conn} do
1973 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
1975 assert json_response(conn, 404) == %{"error" => "Record not found"}
1978 test "returns 403 error when user has hidden own favorites", %{
1980 current_user: current_user
1982 user = insert(:user, %{info: %{hide_favorites: true}})
1983 activity = insert(:note_activity)
1984 CommonAPI.favorite(activity.id, user)
1988 |> assign(:user, current_user)
1989 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
1991 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
1994 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
1995 user = insert(:user)
1996 activity = insert(:note_activity)
1997 CommonAPI.favorite(activity.id, user)
2001 |> assign(:user, current_user)
2002 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2004 assert user.info.hide_favorites
2005 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2009 test "get instance information", %{conn: conn} do
2010 conn = get(conn, "/api/v1/instance")
2011 assert result = json_response(conn, 200)
2013 email = Config.get([:instance, :email])
2014 # Note: not checking for "max_toot_chars" since it's optional
2020 "email" => from_config_email,
2022 "streaming_api" => _
2027 "registrations" => _,
2031 assert email == from_config_email
2034 test "get instance stats", %{conn: conn} do
2035 user = insert(:user, %{local: true})
2037 user2 = insert(:user, %{local: true})
2038 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2040 insert(:user, %{local: false, nickname: "u@peer1.com"})
2041 insert(:user, %{local: false, nickname: "u@peer2.com"})
2043 {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"})
2045 # Stats should count users with missing or nil `info.deactivated` value
2049 |> User.get_cached_by_id()
2050 |> User.update_info(&Changeset.change(&1, %{deactivated: nil}))
2052 Pleroma.Stats.force_update()
2054 conn = get(conn, "/api/v1/instance")
2056 assert result = json_response(conn, 200)
2058 stats = result["stats"]
2061 assert stats["user_count"] == 1
2062 assert stats["status_count"] == 1
2063 assert stats["domain_count"] == 2
2066 test "get peers", %{conn: conn} do
2067 insert(:user, %{local: false, nickname: "u@peer1.com"})
2068 insert(:user, %{local: false, nickname: "u@peer2.com"})
2070 Pleroma.Stats.force_update()
2072 conn = get(conn, "/api/v1/instance/peers")
2074 assert result = json_response(conn, 200)
2076 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2079 test "put settings", %{conn: conn} do
2080 user = insert(:user)
2084 |> assign(:user, user)
2085 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2087 assert _result = json_response(conn, 200)
2089 user = User.get_cached_by_ap_id(user.ap_id)
2090 assert user.info.settings == %{"programming" => "socks"}
2093 describe "pinned statuses" do
2095 user = insert(:user)
2096 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2098 [user: user, activity: activity]
2101 clear_config([:instance, :max_pinned_statuses]) do
2102 Config.put([:instance, :max_pinned_statuses], 1)
2105 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2106 {:ok, _} = CommonAPI.pin(activity.id, user)
2110 |> assign(:user, user)
2111 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2112 |> json_response(200)
2114 id_str = to_string(activity.id)
2116 assert [%{"id" => ^id_str, "pinned" => true}] = result
2119 test "pin status", %{conn: conn, user: user, activity: activity} do
2120 id_str = to_string(activity.id)
2122 assert %{"id" => ^id_str, "pinned" => true} =
2124 |> assign(:user, user)
2125 |> post("/api/v1/statuses/#{activity.id}/pin")
2126 |> json_response(200)
2128 assert [%{"id" => ^id_str, "pinned" => true}] =
2130 |> assign(:user, user)
2131 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2132 |> json_response(200)
2135 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
2136 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
2140 |> assign(:user, user)
2141 |> post("/api/v1/statuses/#{dm.id}/pin")
2143 assert json_response(conn, 400) == %{"error" => "Could not pin"}
2146 test "unpin status", %{conn: conn, user: user, activity: activity} do
2147 {:ok, _} = CommonAPI.pin(activity.id, user)
2149 id_str = to_string(activity.id)
2150 user = refresh_record(user)
2152 assert %{"id" => ^id_str, "pinned" => false} =
2154 |> assign(:user, user)
2155 |> post("/api/v1/statuses/#{activity.id}/unpin")
2156 |> json_response(200)
2160 |> assign(:user, user)
2161 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2162 |> json_response(200)
2165 test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
2168 |> assign(:user, user)
2169 |> post("/api/v1/statuses/1/unpin")
2171 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
2174 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2175 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2177 id_str_one = to_string(activity_one.id)
2179 assert %{"id" => ^id_str_one, "pinned" => true} =
2181 |> assign(:user, user)
2182 |> post("/api/v1/statuses/#{id_str_one}/pin")
2183 |> json_response(200)
2185 user = refresh_record(user)
2187 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2189 |> assign(:user, user)
2190 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2191 |> json_response(400)
2197 Config.put([:rich_media, :enabled], true)
2199 user = insert(:user)
2203 test "returns rich-media card", %{conn: conn, user: user} do
2204 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2207 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2208 "provider_name" => "example.com",
2209 "provider_url" => "https://example.com",
2210 "title" => "The Rock",
2212 "url" => "https://example.com/ogp",
2214 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2217 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2218 "title" => "The Rock",
2219 "type" => "video.movie",
2220 "url" => "https://example.com/ogp",
2222 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2229 |> get("/api/v1/statuses/#{activity.id}/card")
2230 |> json_response(200)
2232 assert response == card_data
2234 # works with private posts
2236 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2240 |> assign(:user, user)
2241 |> get("/api/v1/statuses/#{activity.id}/card")
2242 |> json_response(200)
2244 assert response_two == card_data
2247 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2249 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2253 |> get("/api/v1/statuses/#{activity.id}/card")
2254 |> json_response(:ok)
2256 assert response == %{
2258 "title" => "Pleroma",
2259 "description" => "",
2261 "provider_name" => "example.com",
2262 "provider_url" => "https://example.com",
2263 "url" => "https://example.com/ogp-missing-data",
2266 "title" => "Pleroma",
2267 "type" => "website",
2268 "url" => "https://example.com/ogp-missing-data"
2276 user = insert(:user)
2277 for_user = insert(:user)
2280 CommonAPI.post(user, %{
2281 "status" => "heweoo?"
2285 CommonAPI.post(user, %{
2286 "status" => "heweoo!"
2291 |> assign(:user, for_user)
2292 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2294 assert json_response(response1, 200)["bookmarked"] == true
2298 |> assign(:user, for_user)
2299 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2301 assert json_response(response2, 200)["bookmarked"] == true
2305 |> assign(:user, for_user)
2306 |> get("/api/v1/bookmarks")
2308 assert [json_response(response2, 200), json_response(response1, 200)] ==
2309 json_response(bookmarks, 200)
2313 |> assign(:user, for_user)
2314 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2316 assert json_response(response1, 200)["bookmarked"] == false
2320 |> assign(:user, for_user)
2321 |> get("/api/v1/bookmarks")
2323 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2326 describe "conversation muting" do
2328 post_user = insert(:user)
2329 user = insert(:user)
2331 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
2333 [user: user, activity: activity]
2336 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2337 id_str = to_string(activity.id)
2339 assert %{"id" => ^id_str, "muted" => true} =
2341 |> assign(:user, user)
2342 |> post("/api/v1/statuses/#{activity.id}/mute")
2343 |> json_response(200)
2346 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
2347 {:ok, _} = CommonAPI.add_mute(user, activity)
2351 |> assign(:user, user)
2352 |> post("/api/v1/statuses/#{activity.id}/mute")
2354 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
2357 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2358 {:ok, _} = CommonAPI.add_mute(user, activity)
2360 id_str = to_string(activity.id)
2361 user = refresh_record(user)
2363 assert %{"id" => ^id_str, "muted" => false} =
2365 |> assign(:user, user)
2366 |> post("/api/v1/statuses/#{activity.id}/unmute")
2367 |> json_response(200)
2371 describe "reports" do
2373 reporter = insert(:user)
2374 target_user = insert(:user)
2376 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2378 [reporter: reporter, target_user: target_user, activity: activity]
2381 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2382 assert %{"action_taken" => false, "id" => _} =
2384 |> assign(:user, reporter)
2385 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2386 |> json_response(200)
2389 test "submit a report with statuses and comment", %{
2392 target_user: target_user,
2395 assert %{"action_taken" => false, "id" => _} =
2397 |> assign(:user, reporter)
2398 |> post("/api/v1/reports", %{
2399 "account_id" => target_user.id,
2400 "status_ids" => [activity.id],
2401 "comment" => "bad status!",
2402 "forward" => "false"
2404 |> json_response(200)
2407 test "account_id is required", %{
2412 assert %{"error" => "Valid `account_id` required"} =
2414 |> assign(:user, reporter)
2415 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2416 |> json_response(400)
2419 test "comment must be up to the size specified in the config", %{
2422 target_user: target_user
2424 max_size = Config.get([:instance, :max_report_comment_size], 1000)
2425 comment = String.pad_trailing("a", max_size + 1, "a")
2427 error = %{"error" => "Comment must be up to #{max_size} characters"}
2431 |> assign(:user, reporter)
2432 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2433 |> json_response(400)
2436 test "returns error when account is not exist", %{
2443 |> assign(:user, reporter)
2444 |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"})
2446 assert json_response(conn, 400) == %{"error" => "Account not found"}
2450 describe "link headers" do
2451 test "preserves parameters in link headers", %{conn: conn} do
2452 user = insert(:user)
2453 other_user = insert(:user)
2456 CommonAPI.post(other_user, %{
2457 "status" => "hi @#{user.nickname}",
2458 "visibility" => "public"
2462 CommonAPI.post(other_user, %{
2463 "status" => "hi @#{user.nickname}",
2464 "visibility" => "public"
2467 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2468 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2472 |> assign(:user, user)
2473 |> get("/api/v1/notifications", %{media_only: true})
2475 assert [link_header] = get_resp_header(conn, "link")
2476 assert link_header =~ ~r/media_only=true/
2477 assert link_header =~ ~r/min_id=#{notification2.id}/
2478 assert link_header =~ ~r/max_id=#{notification1.id}/
2482 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2483 # Need to set an old-style integer ID to reproduce the problem
2484 # (these are no longer assigned to new accounts but were preserved
2485 # for existing accounts during the migration to flakeIDs)
2486 user_one = insert(:user, %{id: 1212})
2487 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2491 |> get("/api/v1/accounts/#{user_one.id}")
2495 |> get("/api/v1/accounts/#{user_two.nickname}")
2499 |> get("/api/v1/accounts/#{user_two.id}")
2501 acc_one = json_response(resp_one, 200)
2502 acc_two = json_response(resp_two, 200)
2503 acc_three = json_response(resp_three, 200)
2504 refute acc_one == acc_two
2505 assert acc_two == acc_three
2508 describe "custom emoji" do
2509 test "with tags", %{conn: conn} do
2512 |> get("/api/v1/custom_emojis")
2513 |> json_response(200)
2515 assert Map.has_key?(emoji, "shortcode")
2516 assert Map.has_key?(emoji, "static_url")
2517 assert Map.has_key?(emoji, "tags")
2518 assert is_list(emoji["tags"])
2519 assert Map.has_key?(emoji, "category")
2520 assert Map.has_key?(emoji, "url")
2521 assert Map.has_key?(emoji, "visible_in_picker")
2525 describe "index/2 redirections" do
2526 setup %{conn: conn} do
2530 signing_salt: "cooldude"
2535 |> Plug.Session.call(Plug.Session.init(session_opts))
2538 test_path = "/web/statuses/test"
2539 %{conn: conn, path: test_path}
2542 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
2543 conn = get(conn, path)
2545 assert conn.status == 302
2546 assert redirected_to(conn) == "/web/login"
2549 test "redirects not logged-in users to the login page on private instances", %{
2553 Config.put([:instance, :public], false)
2555 conn = get(conn, path)
2557 assert conn.status == 302
2558 assert redirected_to(conn) == "/web/login"
2561 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
2562 token = insert(:oauth_token)
2566 |> assign(:user, token.user)
2567 |> put_session(:oauth_token, token.token)
2570 assert conn.status == 200
2573 test "saves referer path to session", %{conn: conn, path: path} do
2574 conn = get(conn, path)
2575 return_to = Plug.Conn.get_session(conn, :return_to)
2577 assert return_to == path
2580 test "redirects to the saved path after log in", %{conn: conn, path: path} do
2581 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2582 auth = insert(:oauth_authorization, app: app)
2586 |> put_session(:return_to, path)
2587 |> get("/web/login", %{code: auth.token})
2589 assert conn.status == 302
2590 assert redirected_to(conn) == path
2593 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
2594 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2595 auth = insert(:oauth_authorization, app: app)
2597 conn = get(conn, "/web/login", %{code: auth.token})
2599 assert conn.status == 302
2600 assert redirected_to(conn) == "/web/getting-started"
2604 describe "scheduled activities" do
2605 test "creates a scheduled activity", %{conn: conn} do
2606 user = insert(:user)
2607 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2611 |> assign(:user, user)
2612 |> post("/api/v1/statuses", %{
2613 "status" => "scheduled",
2614 "scheduled_at" => scheduled_at
2617 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
2618 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
2619 assert [] == Repo.all(Activity)
2622 test "creates a scheduled activity with a media attachment", %{conn: conn} do
2623 user = insert(:user)
2624 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2626 file = %Plug.Upload{
2627 content_type: "image/jpg",
2628 path: Path.absname("test/fixtures/image.jpg"),
2629 filename: "an_image.jpg"
2632 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
2636 |> assign(:user, user)
2637 |> post("/api/v1/statuses", %{
2638 "media_ids" => [to_string(upload.id)],
2639 "status" => "scheduled",
2640 "scheduled_at" => scheduled_at
2643 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
2644 assert %{"type" => "image"} = media_attachment
2647 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
2649 user = insert(:user)
2652 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
2656 |> assign(:user, user)
2657 |> post("/api/v1/statuses", %{
2658 "status" => "not scheduled",
2659 "scheduled_at" => scheduled_at
2662 assert %{"content" => "not scheduled"} = json_response(conn, 200)
2663 assert [] == Repo.all(ScheduledActivity)
2666 test "returns error when daily user limit is exceeded", %{conn: conn} do
2667 user = insert(:user)
2670 NaiveDateTime.utc_now()
2671 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
2672 |> NaiveDateTime.to_iso8601()
2674 attrs = %{params: %{}, scheduled_at: today}
2675 {:ok, _} = ScheduledActivity.create(user, attrs)
2676 {:ok, _} = ScheduledActivity.create(user, attrs)
2680 |> assign(:user, user)
2681 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
2683 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
2686 test "returns error when total user limit is exceeded", %{conn: conn} do
2687 user = insert(:user)
2690 NaiveDateTime.utc_now()
2691 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
2692 |> NaiveDateTime.to_iso8601()
2695 NaiveDateTime.utc_now()
2696 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
2697 |> NaiveDateTime.to_iso8601()
2699 attrs = %{params: %{}, scheduled_at: today}
2700 {:ok, _} = ScheduledActivity.create(user, attrs)
2701 {:ok, _} = ScheduledActivity.create(user, attrs)
2702 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
2706 |> assign(:user, user)
2707 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
2709 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
2712 test "shows scheduled activities", %{conn: conn} do
2713 user = insert(:user)
2714 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
2715 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
2716 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
2717 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
2721 |> assign(:user, user)
2726 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
2728 result = json_response(conn_res, 200)
2729 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
2734 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
2736 result = json_response(conn_res, 200)
2737 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
2742 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
2744 result = json_response(conn_res, 200)
2745 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
2748 test "shows a scheduled activity", %{conn: conn} do
2749 user = insert(:user)
2750 scheduled_activity = insert(:scheduled_activity, user: user)
2754 |> assign(:user, user)
2755 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
2757 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
2758 assert scheduled_activity_id == scheduled_activity.id |> to_string()
2762 |> assign(:user, user)
2763 |> get("/api/v1/scheduled_statuses/404")
2765 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
2768 test "updates a scheduled activity", %{conn: conn} do
2769 user = insert(:user)
2770 scheduled_activity = insert(:scheduled_activity, user: user)
2773 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2777 |> assign(:user, user)
2778 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
2779 scheduled_at: new_scheduled_at
2782 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
2783 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
2787 |> assign(:user, user)
2788 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
2790 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
2793 test "deletes a scheduled activity", %{conn: conn} do
2794 user = insert(:user)
2795 scheduled_activity = insert(:scheduled_activity, user: user)
2799 |> assign(:user, user)
2800 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
2802 assert %{} = json_response(res_conn, 200)
2803 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
2807 |> assign(:user, user)
2808 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
2810 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
2814 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
2815 user1 = insert(:user)
2816 user2 = insert(:user)
2817 user3 = insert(:user)
2819 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
2821 # Reply to status from another user
2824 |> assign(:user, user2)
2825 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
2827 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
2829 activity = Activity.get_by_id_with_object(id)
2831 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
2832 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
2834 # Reblog from the third user
2837 |> assign(:user, user3)
2838 |> post("/api/v1/statuses/#{activity.id}/reblog")
2840 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
2841 json_response(conn2, 200)
2843 assert to_string(activity.id) == id
2845 # Getting third user status
2848 |> assign(:user, user3)
2849 |> get("api/v1/timelines/home")
2851 [reblogged_activity] = json_response(conn3, 200)
2853 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
2855 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
2856 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
2859 describe "create account by app" do
2860 test "Account registration via Application", %{conn: conn} do
2863 |> post("/api/v1/apps", %{
2864 client_name: "client_name",
2865 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
2866 scopes: "read, write, follow"
2870 "client_id" => client_id,
2871 "client_secret" => client_secret,
2873 "name" => "client_name",
2874 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
2877 } = json_response(conn, 200)
2881 |> post("/oauth/token", %{
2882 grant_type: "client_credentials",
2883 client_id: client_id,
2884 client_secret: client_secret
2887 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
2888 json_response(conn, 200)
2891 token_from_db = Repo.get_by(Token, token: token)
2892 assert token_from_db
2894 assert scope == "read write follow"
2898 |> put_req_header("authorization", "Bearer " <> token)
2899 |> post("/api/v1/accounts", %{
2901 email: "lain@example.org",
2902 password: "PlzDontHackLain",
2907 "access_token" => token,
2908 "created_at" => _created_at,
2910 "token_type" => "Bearer"
2911 } = json_response(conn, 200)
2913 token_from_db = Repo.get_by(Token, token: token)
2914 assert token_from_db
2915 token_from_db = Repo.preload(token_from_db, :user)
2916 assert token_from_db.user
2918 assert token_from_db.user.info.confirmation_pending
2921 test "rate limit", %{conn: conn} do
2922 app_token = insert(:oauth_token, user: nil)
2925 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
2926 |> Map.put(:remote_ip, {15, 15, 15, 15})
2931 |> post("/api/v1/accounts", %{
2932 username: "#{i}lain",
2933 email: "#{i}lain@example.org",
2934 password: "PlzDontHackLain",
2939 "access_token" => token,
2940 "created_at" => _created_at,
2942 "token_type" => "Bearer"
2943 } = json_response(conn, 200)
2945 token_from_db = Repo.get_by(Token, token: token)
2946 assert token_from_db
2947 token_from_db = Repo.preload(token_from_db, :user)
2948 assert token_from_db.user
2950 assert token_from_db.user.info.confirmation_pending
2955 |> post("/api/v1/accounts", %{
2957 email: "6lain@example.org",
2958 password: "PlzDontHackLain",
2962 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
2966 describe "GET /api/v1/polls/:id" do
2967 test "returns poll entity for object id", %{conn: conn} do
2968 user = insert(:user)
2971 CommonAPI.post(user, %{
2972 "status" => "Pleroma does",
2973 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
2976 object = Object.normalize(activity)
2980 |> assign(:user, user)
2981 |> get("/api/v1/polls/#{object.id}")
2983 response = json_response(conn, 200)
2984 id = to_string(object.id)
2985 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
2988 test "does not expose polls for private statuses", %{conn: conn} do
2989 user = insert(:user)
2990 other_user = insert(:user)
2993 CommonAPI.post(user, %{
2994 "status" => "Pleroma does",
2995 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
2996 "visibility" => "private"
2999 object = Object.normalize(activity)
3003 |> assign(:user, other_user)
3004 |> get("/api/v1/polls/#{object.id}")
3006 assert json_response(conn, 404)
3010 describe "POST /api/v1/polls/:id/votes" do
3011 test "votes are added to the poll", %{conn: conn} do
3012 user = insert(:user)
3013 other_user = insert(:user)
3016 CommonAPI.post(user, %{
3017 "status" => "A very delicious sandwich",
3019 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3025 object = Object.normalize(activity)
3029 |> assign(:user, other_user)
3030 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3032 assert json_response(conn, 200)
3033 object = Object.get_by_id(object.id)
3035 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3040 test "author can't vote", %{conn: conn} do
3041 user = insert(:user)
3044 CommonAPI.post(user, %{
3045 "status" => "Am I cute?",
3046 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3049 object = Object.normalize(activity)
3052 |> assign(:user, user)
3053 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3054 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3056 object = Object.get_by_id(object.id)
3058 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3061 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3062 user = insert(:user)
3063 other_user = insert(:user)
3066 CommonAPI.post(user, %{
3067 "status" => "The glass is",
3068 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3071 object = Object.normalize(activity)
3074 |> assign(:user, other_user)
3075 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3076 |> json_response(422) == %{"error" => "Too many choices"}
3078 object = Object.get_by_id(object.id)
3080 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3085 test "does not allow choice index to be greater than options count", %{conn: conn} do
3086 user = insert(:user)
3087 other_user = insert(:user)
3090 CommonAPI.post(user, %{
3091 "status" => "Am I cute?",
3092 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3095 object = Object.normalize(activity)
3099 |> assign(:user, other_user)
3100 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
3102 assert json_response(conn, 422) == %{"error" => "Invalid indices"}
3105 test "returns 404 error when object is not exist", %{conn: conn} do
3106 user = insert(:user)
3110 |> assign(:user, user)
3111 |> post("/api/v1/polls/1/votes", %{"choices" => [0]})
3113 assert json_response(conn, 404) == %{"error" => "Record not found"}
3116 test "returns 404 when poll is private and not available for user", %{conn: conn} do
3117 user = insert(:user)
3118 other_user = insert(:user)
3121 CommonAPI.post(user, %{
3122 "status" => "Am I cute?",
3123 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20},
3124 "visibility" => "private"
3127 object = Object.normalize(activity)
3131 |> assign(:user, other_user)
3132 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
3134 assert json_response(conn, 404) == %{"error" => "Record not found"}
3138 describe "GET /api/v1/statuses/:id/favourited_by" do
3140 user = insert(:user)
3141 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
3145 |> assign(:user, user)
3147 [conn: conn, activity: activity, user: user]
3150 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
3151 other_user = insert(:user)
3152 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3156 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3157 |> json_response(:ok)
3159 [%{"id" => id}] = response
3161 assert id == other_user.id
3164 test "returns empty array when status has not been favorited yet", %{
3170 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3171 |> json_response(:ok)
3173 assert Enum.empty?(response)
3176 test "does not return users who have favorited the status but are blocked", %{
3177 conn: %{assigns: %{user: user}} = conn,
3180 other_user = insert(:user)
3181 {:ok, user} = User.block(user, other_user)
3183 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3187 |> assign(:user, user)
3188 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3189 |> json_response(:ok)
3191 assert Enum.empty?(response)
3194 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
3195 other_user = insert(:user)
3196 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3200 |> assign(:user, nil)
3201 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3202 |> json_response(:ok)
3204 [%{"id" => id}] = response
3205 assert id == other_user.id
3208 test "requires authentification for private posts", %{conn: conn, user: user} do
3209 other_user = insert(:user)
3212 CommonAPI.post(user, %{
3213 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
3214 "visibility" => "direct"
3217 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
3220 |> assign(:user, nil)
3221 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3222 |> json_response(404)
3226 |> assign(:user, other_user)
3227 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
3228 |> json_response(200)
3230 [%{"id" => id}] = response
3231 assert id == other_user.id
3235 describe "GET /api/v1/statuses/:id/reblogged_by" do
3237 user = insert(:user)
3238 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
3242 |> assign(:user, user)
3244 [conn: conn, activity: activity, user: user]
3247 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
3248 other_user = insert(:user)
3249 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
3253 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3254 |> json_response(:ok)
3256 [%{"id" => id}] = response
3258 assert id == other_user.id
3261 test "returns empty array when status has not been reblogged yet", %{
3267 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3268 |> json_response(:ok)
3270 assert Enum.empty?(response)
3273 test "does not return users who have reblogged the status but are blocked", %{
3274 conn: %{assigns: %{user: user}} = conn,
3277 other_user = insert(:user)
3278 {:ok, user} = User.block(user, other_user)
3280 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
3284 |> assign(:user, user)
3285 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3286 |> json_response(:ok)
3288 assert Enum.empty?(response)
3291 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
3292 other_user = insert(:user)
3293 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
3297 |> assign(:user, nil)
3298 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3299 |> json_response(:ok)
3301 [%{"id" => id}] = response
3302 assert id == other_user.id
3305 test "requires authentification for private posts", %{conn: conn, user: user} do
3306 other_user = insert(:user)
3309 CommonAPI.post(user, %{
3310 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
3311 "visibility" => "direct"
3315 |> assign(:user, nil)
3316 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3317 |> json_response(404)
3321 |> assign(:user, other_user)
3322 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
3323 |> json_response(200)
3325 assert [] == response
3329 describe "POST /auth/password, with valid parameters" do
3330 setup %{conn: conn} do
3331 user = insert(:user)
3332 conn = post(conn, "/auth/password?email=#{user.email}")
3333 %{conn: conn, user: user}
3336 test "it returns 204", %{conn: conn} do
3337 assert json_response(conn, :no_content)
3340 test "it creates a PasswordResetToken record for user", %{user: user} do
3341 token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
3345 test "it sends an email to user", %{user: user} do
3346 ObanHelpers.perform_all()
3347 token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
3349 email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
3350 notify_email = Config.get([:instance, :notify_email])
3351 instance_name = Config.get([:instance, :name])
3354 from: {instance_name, notify_email},
3355 to: {user.name, user.email},
3356 html_body: email.html_body
3361 describe "POST /auth/password, with invalid parameters" do
3363 user = insert(:user)
3367 test "it returns 404 when user is not found", %{conn: conn, user: user} do
3368 conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
3369 assert conn.status == 404
3370 assert conn.resp_body == ""
3373 test "it returns 400 when user is not local", %{conn: conn, user: user} do
3374 {:ok, user} = Repo.update(Changeset.change(user, local: false))
3375 conn = post(conn, "/auth/password?email=#{user.email}")
3376 assert conn.status == 400
3377 assert conn.resp_body == ""
3381 describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
3385 |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
3388 assert user.info.confirmation_pending
3393 clear_config([:instance, :account_activation_required]) do
3394 Config.put([:instance, :account_activation_required], true)
3397 test "resend account confirmation email", %{conn: conn, user: user} do
3399 |> assign(:user, user)
3400 |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}")
3401 |> json_response(:no_content)
3403 ObanHelpers.perform_all()
3405 email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
3406 notify_email = Config.get([:instance, :notify_email])
3407 instance_name = Config.get([:instance, :name])
3410 from: {instance_name, notify_email},
3411 to: {user.name, user.email},
3412 html_body: email.html_body
3417 describe "GET /api/v1/suggestions" do
3419 user = insert(:user)
3420 other_user = insert(:user)
3421 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
3422 url500 = "http://test500?#{host}&#{user.nickname}"
3423 url200 = "http://test200?#{host}&#{user.nickname}"
3426 %{method: :get, url: ^url500} ->
3427 %Tesla.Env{status: 500, body: "bad request"}
3429 %{method: :get, url: ^url200} ->
3433 ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{
3435 }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}])
3439 [user: user, other_user: other_user]
3442 clear_config(:suggestions)
3444 test "returns empty result when suggestions disabled", %{conn: conn, user: user} do
3445 Config.put([:suggestions, :enabled], false)
3449 |> assign(:user, user)
3450 |> get("/api/v1/suggestions")
3451 |> json_response(200)
3456 test "returns error", %{conn: conn, user: user} do
3457 Config.put([:suggestions, :enabled], true)
3458 Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}")
3460 assert capture_log(fn ->
3463 |> assign(:user, user)
3464 |> get("/api/v1/suggestions")
3465 |> json_response(500)
3467 assert res == "Something went wrong"
3468 end) =~ "Could not retrieve suggestions"
3471 test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do
3472 Config.put([:suggestions, :enabled], true)
3473 Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}")
3477 |> assign(:user, user)
3478 |> get("/api/v1/suggestions")
3479 |> json_response(200)
3484 "avatar" => "https://social.heldscal.la/avatar/201.jpeg",
3485 "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg",
3489 "acct" => other_user.ap_id,
3490 "avatar" => "https://social.heldscal.la/avatar/202.jpeg",
3491 "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg",
3492 "id" => other_user.id