1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.Notification
13 alias Pleroma.ScheduledActivity
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.MastodonAPI.FilterView
18 alias Pleroma.Web.OAuth.App
19 alias Pleroma.Web.OAuth.Token
20 alias Pleroma.Web.OStatus
21 alias Pleroma.Web.Push
22 alias Pleroma.Web.TwitterAPI.TwitterAPI
23 import Pleroma.Factory
24 import ExUnit.CaptureLog
27 @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
30 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
34 test "the home timeline", %{conn: conn} do
36 following = insert(:user)
38 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
42 |> assign(:user, user)
43 |> get("/api/v1/timelines/home")
45 assert Enum.empty?(json_response(conn, 200))
47 {:ok, user} = User.follow(user, following)
51 |> assign(:user, user)
52 |> get("/api/v1/timelines/home")
54 assert [%{"content" => "test"}] = json_response(conn, 200)
57 test "the public timeline", %{conn: conn} do
58 following = insert(:user)
61 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
64 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
68 |> get("/api/v1/timelines/public", %{"local" => "False"})
70 assert length(json_response(conn, 200)) == 2
74 |> get("/api/v1/timelines/public", %{"local" => "True"})
76 assert [%{"content" => "test"}] = json_response(conn, 200)
80 |> get("/api/v1/timelines/public", %{"local" => "1"})
82 assert [%{"content" => "test"}] = json_response(conn, 200)
86 test "the public timeline when public is set to false", %{conn: conn} do
87 public = Pleroma.Config.get([:instance, :public])
88 Pleroma.Config.put([:instance, :public], false)
91 Pleroma.Config.put([:instance, :public], public)
95 |> get("/api/v1/timelines/public", %{"local" => "False"})
96 |> json_response(403) == %{"error" => "This resource requires authentication."}
99 describe "posting statuses" do
105 |> assign(:user, user)
110 test "posting a status", %{conn: conn} do
111 idempotency_key = "Pikachu rocks!"
115 |> put_req_header("idempotency-key", idempotency_key)
116 |> post("/api/v1/statuses", %{
118 "spoiler_text" => "2hu",
119 "sensitive" => "false"
122 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
124 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
126 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
127 json_response(conn_one, 200)
129 assert Activity.get_by_id(id)
133 |> put_req_header("idempotency-key", idempotency_key)
134 |> post("/api/v1/statuses", %{
136 "spoiler_text" => "2hu",
137 "sensitive" => "false"
140 assert %{"id" => second_id} = json_response(conn_two, 200)
141 assert id == second_id
145 |> post("/api/v1/statuses", %{
147 "spoiler_text" => "2hu",
148 "sensitive" => "false"
151 assert %{"id" => third_id} = json_response(conn_three, 200)
152 refute id == third_id
155 test "replying to a status", %{conn: conn} do
157 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
161 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
163 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
165 activity = Activity.get_by_id(id)
167 assert activity.data["context"] == replied_to.data["context"]
168 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
171 test "replying to a direct message with visibility other than direct", %{conn: conn} do
173 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
175 Enum.each(["public", "private", "unlisted"], fn visibility ->
178 |> post("/api/v1/statuses", %{
179 "status" => "@#{user.nickname} hey",
180 "in_reply_to_id" => replied_to.id,
181 "visibility" => visibility
184 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
188 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
191 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
193 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
194 assert Activity.get_by_id(id)
197 test "posting a sensitive status", %{conn: conn} do
200 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
202 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
203 assert Activity.get_by_id(id)
206 test "posting a fake status", %{conn: conn} do
209 |> post("/api/v1/statuses", %{
211 "\"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"
214 real_status = json_response(real_conn, 200)
217 assert Object.get_by_ap_id(real_status["uri"])
221 |> Map.put("id", nil)
222 |> Map.put("url", nil)
223 |> Map.put("uri", nil)
224 |> Map.put("created_at", nil)
225 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
229 |> post("/api/v1/statuses", %{
231 "\"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",
235 fake_status = json_response(fake_conn, 200)
238 refute Object.get_by_ap_id(fake_status["uri"])
242 |> Map.put("id", nil)
243 |> Map.put("url", nil)
244 |> Map.put("uri", nil)
245 |> Map.put("created_at", nil)
246 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
248 assert real_status == fake_status
251 test "posting a status with OGP link preview", %{conn: conn} do
252 Pleroma.Config.put([:rich_media, :enabled], true)
256 |> post("/api/v1/statuses", %{
257 "status" => "https://example.com/ogp"
260 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
261 assert Activity.get_by_id(id)
262 Pleroma.Config.put([:rich_media, :enabled], false)
265 test "posting a direct status", %{conn: conn} do
266 user2 = insert(:user)
267 content = "direct cofe @#{user2.nickname}"
271 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
273 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
274 assert activity = Activity.get_by_id(id)
275 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
276 assert activity.data["to"] == [user2.ap_id]
277 assert activity.data["cc"] == []
281 describe "posting polls" do
282 test "posting a poll", %{conn: conn} do
284 time = NaiveDateTime.utc_now()
288 |> assign(:user, user)
289 |> post("/api/v1/statuses", %{
290 "status" => "Who is the #bestgrill?",
291 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
294 response = json_response(conn, 200)
296 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
297 title in ["Rei", "Asuka", "Misato"]
300 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
301 refute response["poll"]["expred"]
304 test "option limit is enforced", %{conn: conn} do
306 limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
310 |> assign(:user, user)
311 |> post("/api/v1/statuses", %{
313 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
316 %{"error" => error} = json_response(conn, 422)
317 assert error == "Poll can't contain more than #{limit} options"
320 test "option character limit is enforced", %{conn: conn} do
322 limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
326 |> assign(:user, user)
327 |> post("/api/v1/statuses", %{
330 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
335 %{"error" => error} = json_response(conn, 422)
336 assert error == "Poll options cannot be longer than #{limit} characters each"
339 test "minimal date limit is enforced", %{conn: conn} do
341 limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
345 |> assign(:user, user)
346 |> post("/api/v1/statuses", %{
347 "status" => "imagine arbitrary limits",
349 "options" => ["this post was made by pleroma gang"],
350 "expires_in" => limit - 1
354 %{"error" => error} = json_response(conn, 422)
355 assert error == "Expiration date is too soon"
358 test "maximum date limit is enforced", %{conn: conn} do
360 limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
364 |> assign(:user, user)
365 |> post("/api/v1/statuses", %{
366 "status" => "imagine arbitrary limits",
368 "options" => ["this post was made by pleroma gang"],
369 "expires_in" => limit + 1
373 %{"error" => error} = json_response(conn, 422)
374 assert error == "Expiration date is too far in the future"
378 test "direct timeline", %{conn: conn} do
379 user_one = insert(:user)
380 user_two = insert(:user)
382 {:ok, user_two} = User.follow(user_two, user_one)
385 CommonAPI.post(user_one, %{
386 "status" => "Hi @#{user_two.nickname}!",
387 "visibility" => "direct"
390 {:ok, _follower_only} =
391 CommonAPI.post(user_one, %{
392 "status" => "Hi @#{user_two.nickname}!",
393 "visibility" => "private"
396 # Only direct should be visible here
399 |> assign(:user, user_two)
400 |> get("api/v1/timelines/direct")
402 [status] = json_response(res_conn, 200)
404 assert %{"visibility" => "direct"} = status
405 assert status["url"] != direct.data["id"]
407 # User should be able to see his own direct message
410 |> assign(:user, user_one)
411 |> get("api/v1/timelines/direct")
413 [status] = json_response(res_conn, 200)
415 assert %{"visibility" => "direct"} = status
417 # Both should be visible here
420 |> assign(:user, user_two)
421 |> get("api/v1/timelines/home")
423 [_s1, _s2] = json_response(res_conn, 200)
426 Enum.each(1..20, fn _ ->
428 CommonAPI.post(user_one, %{
429 "status" => "Hi @#{user_two.nickname}!",
430 "visibility" => "direct"
436 |> assign(:user, user_two)
437 |> get("api/v1/timelines/direct")
439 statuses = json_response(res_conn, 200)
440 assert length(statuses) == 20
444 |> assign(:user, user_two)
445 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
447 [status] = json_response(res_conn, 200)
449 assert status["url"] != direct.data["id"]
452 test "Conversations", %{conn: conn} do
453 user_one = insert(:user)
454 user_two = insert(:user)
455 user_three = insert(:user)
457 {:ok, user_two} = User.follow(user_two, user_one)
460 CommonAPI.post(user_one, %{
461 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
462 "visibility" => "direct"
465 {:ok, _follower_only} =
466 CommonAPI.post(user_one, %{
467 "status" => "Hi @#{user_two.nickname}!",
468 "visibility" => "private"
473 |> assign(:user, user_one)
474 |> get("/api/v1/conversations")
476 assert response = json_response(res_conn, 200)
481 "accounts" => res_accounts,
482 "last_status" => res_last_status,
487 account_ids = Enum.map(res_accounts, & &1["id"])
488 assert length(res_accounts) == 2
489 assert user_two.id in account_ids
490 assert user_three.id in account_ids
491 assert is_binary(res_id)
492 assert unread == true
493 assert res_last_status["id"] == direct.id
495 # Apparently undocumented API endpoint
498 |> assign(:user, user_one)
499 |> post("/api/v1/conversations/#{res_id}/read")
501 assert response = json_response(res_conn, 200)
502 assert length(response["accounts"]) == 2
503 assert response["last_status"]["id"] == direct.id
504 assert response["unread"] == false
506 # (vanilla) Mastodon frontend behaviour
509 |> assign(:user, user_one)
510 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
512 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
515 test "doesn't include DMs from blocked users", %{conn: conn} do
516 blocker = insert(:user)
517 blocked = insert(:user)
519 {:ok, blocker} = User.block(blocker, blocked)
521 {:ok, _blocked_direct} =
522 CommonAPI.post(blocked, %{
523 "status" => "Hi @#{blocker.nickname}!",
524 "visibility" => "direct"
528 CommonAPI.post(user, %{
529 "status" => "Hi @#{blocker.nickname}!",
530 "visibility" => "direct"
535 |> assign(:user, user)
536 |> get("api/v1/timelines/direct")
538 [status] = json_response(res_conn, 200)
539 assert status["id"] == direct.id
542 test "verify_credentials", %{conn: conn} do
547 |> assign(:user, user)
548 |> get("/api/v1/accounts/verify_credentials")
550 response = json_response(conn, 200)
552 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
553 assert response["pleroma"]["chat_token"]
554 assert id == to_string(user.id)
557 test "verify_credentials default scope unlisted", %{conn: conn} do
558 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
562 |> assign(:user, user)
563 |> get("/api/v1/accounts/verify_credentials")
565 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
566 assert id == to_string(user.id)
569 test "apps/verify_credentials", %{conn: conn} do
570 token = insert(:oauth_token)
574 |> assign(:user, token.user)
575 |> assign(:token, token)
576 |> get("/api/v1/apps/verify_credentials")
578 app = Repo.preload(token, :app).app
581 "name" => app.client_name,
582 "website" => app.website,
583 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
586 assert expected == json_response(conn, 200)
589 test "user avatar can be set", %{conn: conn} do
591 avatar_image = File.read!("test/fixtures/avatar_data_uri")
595 |> assign(:user, user)
596 |> patch("/api/v1/accounts/update_avatar", %{img: avatar_image})
598 user = refresh_record(user)
612 assert %{"url" => _} = json_response(conn, 200)
615 test "user avatar can be reset", %{conn: conn} do
620 |> assign(:user, user)
621 |> patch("/api/v1/accounts/update_avatar", %{img: ""})
623 user = User.get_cached_by_id(user.id)
625 assert user.avatar == nil
627 assert %{"url" => nil} = json_response(conn, 200)
630 test "can set profile banner", %{conn: conn} do
635 |> assign(:user, user)
636 |> patch("/api/v1/accounts/update_banner", %{"banner" => @image})
638 user = refresh_record(user)
639 assert user.info.banner["type"] == "Image"
641 assert %{"url" => _} = json_response(conn, 200)
644 test "can reset profile banner", %{conn: conn} do
649 |> assign(:user, user)
650 |> patch("/api/v1/accounts/update_banner", %{"banner" => ""})
652 user = refresh_record(user)
653 assert user.info.banner == %{}
655 assert %{"url" => nil} = json_response(conn, 200)
658 test "background image can be set", %{conn: conn} do
663 |> assign(:user, user)
664 |> patch("/api/v1/accounts/update_background", %{"img" => @image})
666 user = refresh_record(user)
667 assert user.info.background["type"] == "Image"
668 assert %{"url" => _} = json_response(conn, 200)
671 test "background image can be reset", %{conn: conn} do
676 |> assign(:user, user)
677 |> patch("/api/v1/accounts/update_background", %{"img" => ""})
679 user = refresh_record(user)
680 assert user.info.background == %{}
681 assert %{"url" => nil} = json_response(conn, 200)
684 test "creates an oauth app", %{conn: conn} do
686 app_attrs = build(:oauth_app)
690 |> assign(:user, user)
691 |> post("/api/v1/apps", %{
692 client_name: app_attrs.client_name,
693 redirect_uris: app_attrs.redirect_uris
696 [app] = Repo.all(App)
699 "name" => app.client_name,
700 "website" => app.website,
701 "client_id" => app.client_id,
702 "client_secret" => app.client_secret,
703 "id" => app.id |> to_string(),
704 "redirect_uri" => app.redirect_uris,
705 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
708 assert expected == json_response(conn, 200)
711 test "get a status", %{conn: conn} do
712 activity = insert(:note_activity)
716 |> get("/api/v1/statuses/#{activity.id}")
718 assert %{"id" => id} = json_response(conn, 200)
719 assert id == to_string(activity.id)
722 describe "deleting a status" do
723 test "when you created it", %{conn: conn} do
724 activity = insert(:note_activity)
725 author = User.get_cached_by_ap_id(activity.data["actor"])
729 |> assign(:user, author)
730 |> delete("/api/v1/statuses/#{activity.id}")
732 assert %{} = json_response(conn, 200)
734 refute Activity.get_by_id(activity.id)
737 test "when you didn't create it", %{conn: conn} do
738 activity = insert(:note_activity)
743 |> assign(:user, user)
744 |> delete("/api/v1/statuses/#{activity.id}")
746 assert %{"error" => _} = json_response(conn, 403)
748 assert Activity.get_by_id(activity.id) == activity
751 test "when you're an admin or moderator", %{conn: conn} do
752 activity1 = insert(:note_activity)
753 activity2 = insert(:note_activity)
754 admin = insert(:user, info: %{is_admin: true})
755 moderator = insert(:user, info: %{is_moderator: true})
759 |> assign(:user, admin)
760 |> delete("/api/v1/statuses/#{activity1.id}")
762 assert %{} = json_response(res_conn, 200)
766 |> assign(:user, moderator)
767 |> delete("/api/v1/statuses/#{activity2.id}")
769 assert %{} = json_response(res_conn, 200)
771 refute Activity.get_by_id(activity1.id)
772 refute Activity.get_by_id(activity2.id)
776 describe "filters" do
777 test "creating a filter", %{conn: conn} do
780 filter = %Pleroma.Filter{
787 |> assign(:user, user)
788 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
790 assert response = json_response(conn, 200)
791 assert response["phrase"] == filter.phrase
792 assert response["context"] == filter.context
793 assert response["irreversible"] == false
794 assert response["id"] != nil
795 assert response["id"] != ""
798 test "fetching a list of filters", %{conn: conn} do
801 query_one = %Pleroma.Filter{
808 query_two = %Pleroma.Filter{
815 {:ok, filter_one} = Pleroma.Filter.create(query_one)
816 {:ok, filter_two} = Pleroma.Filter.create(query_two)
820 |> assign(:user, user)
821 |> get("/api/v1/filters")
822 |> json_response(200)
828 filters: [filter_two, filter_one]
832 test "get a filter", %{conn: conn} do
835 query = %Pleroma.Filter{
842 {:ok, filter} = Pleroma.Filter.create(query)
846 |> assign(:user, user)
847 |> get("/api/v1/filters/#{filter.filter_id}")
849 assert _response = json_response(conn, 200)
852 test "update a filter", %{conn: conn} do
855 query = %Pleroma.Filter{
862 {:ok, _filter} = Pleroma.Filter.create(query)
864 new = %Pleroma.Filter{
871 |> assign(:user, user)
872 |> put("/api/v1/filters/#{query.filter_id}", %{
877 assert response = json_response(conn, 200)
878 assert response["phrase"] == new.phrase
879 assert response["context"] == new.context
882 test "delete a filter", %{conn: conn} do
885 query = %Pleroma.Filter{
892 {:ok, filter} = Pleroma.Filter.create(query)
896 |> assign(:user, user)
897 |> delete("/api/v1/filters/#{filter.filter_id}")
899 assert response = json_response(conn, 200)
900 assert response == %{}
905 test "creating a list", %{conn: conn} do
910 |> assign(:user, user)
911 |> post("/api/v1/lists", %{"title" => "cuties"})
913 assert %{"title" => title} = json_response(conn, 200)
914 assert title == "cuties"
917 test "adding users to a list", %{conn: conn} do
919 other_user = insert(:user)
920 {:ok, list} = Pleroma.List.create("name", user)
924 |> assign(:user, user)
925 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
927 assert %{} == json_response(conn, 200)
928 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
929 assert following == [other_user.follower_address]
932 test "removing users from a list", %{conn: conn} do
934 other_user = insert(:user)
935 third_user = insert(:user)
936 {:ok, list} = Pleroma.List.create("name", user)
937 {:ok, list} = Pleroma.List.follow(list, other_user)
938 {:ok, list} = Pleroma.List.follow(list, third_user)
942 |> assign(:user, user)
943 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
945 assert %{} == json_response(conn, 200)
946 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
947 assert following == [third_user.follower_address]
950 test "listing users in a list", %{conn: conn} do
952 other_user = insert(:user)
953 {:ok, list} = Pleroma.List.create("name", user)
954 {:ok, list} = Pleroma.List.follow(list, other_user)
958 |> assign(:user, user)
959 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
961 assert [%{"id" => id}] = json_response(conn, 200)
962 assert id == to_string(other_user.id)
965 test "retrieving a list", %{conn: conn} do
967 {:ok, list} = Pleroma.List.create("name", user)
971 |> assign(:user, user)
972 |> get("/api/v1/lists/#{list.id}")
974 assert %{"id" => id} = json_response(conn, 200)
975 assert id == to_string(list.id)
978 test "renaming a list", %{conn: conn} do
980 {:ok, list} = Pleroma.List.create("name", user)
984 |> assign(:user, user)
985 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
987 assert %{"title" => name} = json_response(conn, 200)
988 assert name == "newname"
991 test "deleting a list", %{conn: conn} do
993 {:ok, list} = Pleroma.List.create("name", user)
997 |> assign(:user, user)
998 |> delete("/api/v1/lists/#{list.id}")
1000 assert %{} = json_response(conn, 200)
1001 assert is_nil(Repo.get(Pleroma.List, list.id))
1004 test "list timeline", %{conn: conn} do
1005 user = insert(:user)
1006 other_user = insert(:user)
1007 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
1008 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
1009 {:ok, list} = Pleroma.List.create("name", user)
1010 {:ok, list} = Pleroma.List.follow(list, other_user)
1014 |> assign(:user, user)
1015 |> get("/api/v1/timelines/list/#{list.id}")
1017 assert [%{"id" => id}] = json_response(conn, 200)
1019 assert id == to_string(activity_two.id)
1022 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
1023 user = insert(:user)
1024 other_user = insert(:user)
1025 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
1027 {:ok, _activity_two} =
1028 TwitterAPI.create_status(other_user, %{
1029 "status" => "Marisa is cute.",
1030 "visibility" => "private"
1033 {:ok, list} = Pleroma.List.create("name", user)
1034 {:ok, list} = Pleroma.List.follow(list, other_user)
1038 |> assign(:user, user)
1039 |> get("/api/v1/timelines/list/#{list.id}")
1041 assert [%{"id" => id}] = json_response(conn, 200)
1043 assert id == to_string(activity_one.id)
1047 describe "notifications" do
1048 test "list of notifications", %{conn: conn} do
1049 user = insert(:user)
1050 other_user = insert(:user)
1053 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1055 {:ok, [_notification]} = Notification.create_notifications(activity)
1059 |> assign(:user, user)
1060 |> get("/api/v1/notifications")
1063 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
1065 }\">@<span>#{user.nickname}</span></a></span>"
1067 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
1068 assert response == expected_response
1071 test "getting a single notification", %{conn: conn} do
1072 user = insert(:user)
1073 other_user = insert(:user)
1076 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1078 {:ok, [notification]} = Notification.create_notifications(activity)
1082 |> assign(:user, user)
1083 |> get("/api/v1/notifications/#{notification.id}")
1086 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
1088 }\">@<span>#{user.nickname}</span></a></span>"
1090 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
1091 assert response == expected_response
1094 test "dismissing a single notification", %{conn: conn} do
1095 user = insert(:user)
1096 other_user = insert(:user)
1099 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1101 {:ok, [notification]} = Notification.create_notifications(activity)
1105 |> assign(:user, user)
1106 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
1108 assert %{} = json_response(conn, 200)
1111 test "clearing all notifications", %{conn: conn} do
1112 user = insert(:user)
1113 other_user = insert(:user)
1116 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1118 {:ok, [_notification]} = Notification.create_notifications(activity)
1122 |> assign(:user, user)
1123 |> post("/api/v1/notifications/clear")
1125 assert %{} = json_response(conn, 200)
1129 |> assign(:user, user)
1130 |> get("/api/v1/notifications")
1132 assert all = json_response(conn, 200)
1136 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
1137 user = insert(:user)
1138 other_user = insert(:user)
1140 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1141 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1142 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1143 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1145 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1146 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1147 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1148 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1152 |> assign(:user, user)
1157 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
1159 result = json_response(conn_res, 200)
1160 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1165 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
1167 result = json_response(conn_res, 200)
1168 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1173 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
1175 result = json_response(conn_res, 200)
1176 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1179 test "filters notifications using exclude_types", %{conn: conn} do
1180 user = insert(:user)
1181 other_user = insert(:user)
1183 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
1184 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
1185 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
1186 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
1187 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
1189 mention_notification_id =
1190 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
1192 favorite_notification_id =
1193 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
1195 reblog_notification_id =
1196 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
1198 follow_notification_id =
1199 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1203 |> assign(:user, user)
1206 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1208 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1211 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1213 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1216 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1218 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1221 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1223 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1226 test "destroy multiple", %{conn: conn} do
1227 user = insert(:user)
1228 other_user = insert(:user)
1230 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1231 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1232 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1233 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1235 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1236 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1237 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1238 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1242 |> assign(:user, user)
1246 |> get("/api/v1/notifications")
1248 result = json_response(conn_res, 200)
1249 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1253 |> assign(:user, other_user)
1257 |> get("/api/v1/notifications")
1259 result = json_response(conn_res, 200)
1260 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1264 |> delete("/api/v1/notifications/destroy_multiple", %{
1265 "ids" => [notification1_id, notification2_id]
1268 assert json_response(conn_destroy, 200) == %{}
1272 |> get("/api/v1/notifications")
1274 result = json_response(conn_res, 200)
1275 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1279 describe "reblogging" do
1280 test "reblogs and returns the reblogged status", %{conn: conn} do
1281 activity = insert(:note_activity)
1282 user = insert(:user)
1286 |> assign(:user, user)
1287 |> post("/api/v1/statuses/#{activity.id}/reblog")
1290 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1292 } = json_response(conn, 200)
1294 assert to_string(activity.id) == id
1297 test "reblogged status for another user", %{conn: conn} do
1298 activity = insert(:note_activity)
1299 user1 = insert(:user)
1300 user2 = insert(:user)
1301 user3 = insert(:user)
1302 CommonAPI.favorite(activity.id, user2)
1303 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1304 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1305 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1309 |> assign(:user, user3)
1310 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1313 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1314 "reblogged" => false,
1315 "favourited" => false,
1316 "bookmarked" => false
1317 } = json_response(conn_res, 200)
1321 |> assign(:user, user2)
1322 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1325 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1326 "reblogged" => true,
1327 "favourited" => true,
1328 "bookmarked" => true
1329 } = json_response(conn_res, 200)
1331 assert to_string(activity.id) == id
1335 describe "unreblogging" do
1336 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1337 activity = insert(:note_activity)
1338 user = insert(:user)
1340 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1344 |> assign(:user, user)
1345 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1347 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1349 assert to_string(activity.id) == id
1353 describe "favoriting" do
1354 test "favs a status and returns it", %{conn: conn} do
1355 activity = insert(:note_activity)
1356 user = insert(:user)
1360 |> assign(:user, user)
1361 |> post("/api/v1/statuses/#{activity.id}/favourite")
1363 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1364 json_response(conn, 200)
1366 assert to_string(activity.id) == id
1369 test "returns 500 for a wrong id", %{conn: conn} do
1370 user = insert(:user)
1374 |> assign(:user, user)
1375 |> post("/api/v1/statuses/1/favourite")
1376 |> json_response(500)
1378 assert resp == "Something went wrong"
1382 describe "unfavoriting" do
1383 test "unfavorites a status and returns it", %{conn: conn} do
1384 activity = insert(:note_activity)
1385 user = insert(:user)
1387 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1391 |> assign(:user, user)
1392 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1394 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1395 json_response(conn, 200)
1397 assert to_string(activity.id) == id
1401 describe "user timelines" do
1402 test "gets a users statuses", %{conn: conn} do
1403 user_one = insert(:user)
1404 user_two = insert(:user)
1405 user_three = insert(:user)
1407 {:ok, user_three} = User.follow(user_three, user_one)
1409 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1411 {:ok, direct_activity} =
1412 CommonAPI.post(user_one, %{
1413 "status" => "Hi, @#{user_two.nickname}.",
1414 "visibility" => "direct"
1417 {:ok, private_activity} =
1418 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1422 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1424 assert [%{"id" => id}] = json_response(resp, 200)
1425 assert id == to_string(activity.id)
1429 |> assign(:user, user_two)
1430 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1432 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1433 assert id_one == to_string(direct_activity.id)
1434 assert id_two == to_string(activity.id)
1438 |> assign(:user, user_three)
1439 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1441 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1442 assert id_one == to_string(private_activity.id)
1443 assert id_two == to_string(activity.id)
1446 test "unimplemented pinned statuses feature", %{conn: conn} do
1447 note = insert(:note_activity)
1448 user = User.get_cached_by_ap_id(note.data["actor"])
1452 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1454 assert json_response(conn, 200) == []
1457 test "gets an users media", %{conn: conn} do
1458 note = insert(:note_activity)
1459 user = User.get_cached_by_ap_id(note.data["actor"])
1461 file = %Plug.Upload{
1462 content_type: "image/jpg",
1463 path: Path.absname("test/fixtures/image.jpg"),
1464 filename: "an_image.jpg"
1468 TwitterAPI.upload(file, user, "json")
1472 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1476 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1478 assert [%{"id" => id}] = json_response(conn, 200)
1479 assert id == to_string(image_post.id)
1483 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1485 assert [%{"id" => id}] = json_response(conn, 200)
1486 assert id == to_string(image_post.id)
1489 test "gets a user's statuses without reblogs", %{conn: conn} do
1490 user = insert(:user)
1491 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1492 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1496 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1498 assert [%{"id" => id}] = json_response(conn, 200)
1499 assert id == to_string(post.id)
1503 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1505 assert [%{"id" => id}] = json_response(conn, 200)
1506 assert id == to_string(post.id)
1509 test "filters user's statuses by a hashtag", %{conn: conn} do
1510 user = insert(:user)
1511 {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
1512 {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
1516 |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
1518 assert [%{"id" => id}] = json_response(conn, 200)
1519 assert id == to_string(post.id)
1523 describe "user relationships" do
1524 test "returns the relationships for the current user", %{conn: conn} do
1525 user = insert(:user)
1526 other_user = insert(:user)
1527 {:ok, user} = User.follow(user, other_user)
1531 |> assign(:user, user)
1532 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1534 assert [relationship] = json_response(conn, 200)
1536 assert to_string(other_user.id) == relationship["id"]
1540 describe "media upload" do
1542 upload_config = Pleroma.Config.get([Pleroma.Upload])
1543 proxy_config = Pleroma.Config.get([:media_proxy])
1546 Pleroma.Config.put([Pleroma.Upload], upload_config)
1547 Pleroma.Config.put([:media_proxy], proxy_config)
1550 user = insert(:user)
1554 |> assign(:user, user)
1556 image = %Plug.Upload{
1557 content_type: "image/jpg",
1558 path: Path.absname("test/fixtures/image.jpg"),
1559 filename: "an_image.jpg"
1562 [conn: conn, image: image]
1565 test "returns uploaded image", %{conn: conn, image: image} do
1566 desc = "Description of the image"
1570 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1571 |> json_response(:ok)
1573 assert media["type"] == "image"
1574 assert media["description"] == desc
1577 object = Repo.get(Object, media["id"])
1578 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1581 test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
1582 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
1584 proxy_url = "https://cache.pleroma.social"
1585 Pleroma.Config.put([:media_proxy, :enabled], true)
1586 Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
1590 |> post("/api/v1/media", %{"file" => image})
1591 |> json_response(:ok)
1593 assert String.starts_with?(media["url"], proxy_url)
1596 test "returns media url when proxy is enabled but media url is whitelisted", %{
1600 media_url = "https://media.pleroma.social"
1601 Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
1603 Pleroma.Config.put([:media_proxy, :enabled], true)
1604 Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
1605 Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
1609 |> post("/api/v1/media", %{"file" => image})
1610 |> json_response(:ok)
1612 assert String.starts_with?(media["url"], media_url)
1616 describe "locked accounts" do
1617 test "/api/v1/follow_requests works" do
1618 user = insert(:user, %{info: %User.Info{locked: true}})
1619 other_user = insert(:user)
1621 {:ok, _activity} = ActivityPub.follow(other_user, user)
1623 user = User.get_cached_by_id(user.id)
1624 other_user = User.get_cached_by_id(other_user.id)
1626 assert User.following?(other_user, user) == false
1630 |> assign(:user, user)
1631 |> get("/api/v1/follow_requests")
1633 assert [relationship] = json_response(conn, 200)
1634 assert to_string(other_user.id) == relationship["id"]
1637 test "/api/v1/follow_requests/:id/authorize works" do
1638 user = insert(:user, %{info: %User.Info{locked: true}})
1639 other_user = insert(:user)
1641 {:ok, _activity} = ActivityPub.follow(other_user, user)
1643 user = User.get_cached_by_id(user.id)
1644 other_user = User.get_cached_by_id(other_user.id)
1646 assert User.following?(other_user, user) == false
1650 |> assign(:user, user)
1651 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1653 assert relationship = json_response(conn, 200)
1654 assert to_string(other_user.id) == relationship["id"]
1656 user = User.get_cached_by_id(user.id)
1657 other_user = User.get_cached_by_id(other_user.id)
1659 assert User.following?(other_user, user) == true
1662 test "verify_credentials", %{conn: conn} do
1663 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1667 |> assign(:user, user)
1668 |> get("/api/v1/accounts/verify_credentials")
1670 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1671 assert id == to_string(user.id)
1674 test "/api/v1/follow_requests/:id/reject works" do
1675 user = insert(:user, %{info: %User.Info{locked: true}})
1676 other_user = insert(:user)
1678 {:ok, _activity} = ActivityPub.follow(other_user, user)
1680 user = User.get_cached_by_id(user.id)
1684 |> assign(:user, user)
1685 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1687 assert relationship = json_response(conn, 200)
1688 assert to_string(other_user.id) == relationship["id"]
1690 user = User.get_cached_by_id(user.id)
1691 other_user = User.get_cached_by_id(other_user.id)
1693 assert User.following?(other_user, user) == false
1697 test "account fetching", %{conn: conn} do
1698 user = insert(:user)
1702 |> get("/api/v1/accounts/#{user.id}")
1704 assert %{"id" => id} = json_response(conn, 200)
1705 assert id == to_string(user.id)
1709 |> get("/api/v1/accounts/-1")
1711 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1714 test "account fetching also works nickname", %{conn: conn} do
1715 user = insert(:user)
1719 |> get("/api/v1/accounts/#{user.nickname}")
1721 assert %{"id" => id} = json_response(conn, 200)
1722 assert id == user.id
1725 test "mascot upload", %{conn: conn} do
1726 user = insert(:user)
1728 non_image_file = %Plug.Upload{
1729 content_type: "audio/mpeg",
1730 path: Path.absname("test/fixtures/sound.mp3"),
1731 filename: "sound.mp3"
1736 |> assign(:user, user)
1737 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1739 assert json_response(conn, 415)
1741 file = %Plug.Upload{
1742 content_type: "image/jpg",
1743 path: Path.absname("test/fixtures/image.jpg"),
1744 filename: "an_image.jpg"
1749 |> assign(:user, user)
1750 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1752 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1755 test "mascot retrieving", %{conn: conn} do
1756 user = insert(:user)
1757 # When user hasn't set a mascot, we should just get pleroma tan back
1760 |> assign(:user, user)
1761 |> get("/api/v1/pleroma/mascot")
1763 assert %{"url" => url} = json_response(conn, 200)
1764 assert url =~ "pleroma-fox-tan-smol"
1766 # When a user sets their mascot, we should get that back
1767 file = %Plug.Upload{
1768 content_type: "image/jpg",
1769 path: Path.absname("test/fixtures/image.jpg"),
1770 filename: "an_image.jpg"
1775 |> assign(:user, user)
1776 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1778 assert json_response(conn, 200)
1780 user = User.get_cached_by_id(user.id)
1784 |> assign(:user, user)
1785 |> get("/api/v1/pleroma/mascot")
1787 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1788 assert url =~ "an_image"
1791 test "hashtag timeline", %{conn: conn} do
1792 following = insert(:user)
1795 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1797 {:ok, [_activity]} =
1798 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1802 |> get("/api/v1/timelines/tag/2hu")
1804 assert [%{"id" => id}] = json_response(nconn, 200)
1806 assert id == to_string(activity.id)
1808 # works for different capitalization too
1811 |> get("/api/v1/timelines/tag/2HU")
1813 assert [%{"id" => id}] = json_response(nconn, 200)
1815 assert id == to_string(activity.id)
1819 test "multi-hashtag timeline", %{conn: conn} do
1820 user = insert(:user)
1822 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1823 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1824 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1828 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1830 [status_none, status_test1, status_test] = json_response(any_test, 200)
1832 assert to_string(activity_test.id) == status_test["id"]
1833 assert to_string(activity_test1.id) == status_test1["id"]
1834 assert to_string(activity_none.id) == status_none["id"]
1838 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1840 assert [status_test1] == json_response(restricted_test, 200)
1842 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1844 assert [status_none] == json_response(all_test, 200)
1847 test "getting followers", %{conn: conn} do
1848 user = insert(:user)
1849 other_user = insert(:user)
1850 {:ok, user} = User.follow(user, other_user)
1854 |> get("/api/v1/accounts/#{other_user.id}/followers")
1856 assert [%{"id" => id}] = json_response(conn, 200)
1857 assert id == to_string(user.id)
1860 test "getting followers, hide_followers", %{conn: conn} do
1861 user = insert(:user)
1862 other_user = insert(:user, %{info: %{hide_followers: true}})
1863 {:ok, _user} = User.follow(user, other_user)
1867 |> get("/api/v1/accounts/#{other_user.id}/followers")
1869 assert [] == json_response(conn, 200)
1872 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1873 user = insert(:user)
1874 other_user = insert(:user, %{info: %{hide_followers: true}})
1875 {:ok, _user} = User.follow(user, other_user)
1879 |> assign(:user, other_user)
1880 |> get("/api/v1/accounts/#{other_user.id}/followers")
1882 refute [] == json_response(conn, 200)
1885 test "getting followers, pagination", %{conn: conn} do
1886 user = insert(:user)
1887 follower1 = insert(:user)
1888 follower2 = insert(:user)
1889 follower3 = insert(:user)
1890 {:ok, _} = User.follow(follower1, user)
1891 {:ok, _} = User.follow(follower2, user)
1892 {:ok, _} = User.follow(follower3, user)
1896 |> assign(:user, user)
1900 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1902 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1903 assert id3 == follower3.id
1904 assert id2 == follower2.id
1908 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1910 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1911 assert id2 == follower2.id
1912 assert id1 == follower1.id
1916 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1918 assert [%{"id" => id2}] = json_response(res_conn, 200)
1919 assert id2 == follower2.id
1921 assert [link_header] = get_resp_header(res_conn, "link")
1922 assert link_header =~ ~r/min_id=#{follower2.id}/
1923 assert link_header =~ ~r/max_id=#{follower2.id}/
1926 test "getting following", %{conn: conn} do
1927 user = insert(:user)
1928 other_user = insert(:user)
1929 {:ok, user} = User.follow(user, other_user)
1933 |> get("/api/v1/accounts/#{user.id}/following")
1935 assert [%{"id" => id}] = json_response(conn, 200)
1936 assert id == to_string(other_user.id)
1939 test "getting following, hide_follows", %{conn: conn} do
1940 user = insert(:user, %{info: %{hide_follows: true}})
1941 other_user = insert(:user)
1942 {:ok, user} = User.follow(user, other_user)
1946 |> get("/api/v1/accounts/#{user.id}/following")
1948 assert [] == json_response(conn, 200)
1951 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1952 user = insert(:user, %{info: %{hide_follows: true}})
1953 other_user = insert(:user)
1954 {:ok, user} = User.follow(user, other_user)
1958 |> assign(:user, user)
1959 |> get("/api/v1/accounts/#{user.id}/following")
1961 refute [] == json_response(conn, 200)
1964 test "getting following, pagination", %{conn: conn} do
1965 user = insert(:user)
1966 following1 = insert(:user)
1967 following2 = insert(:user)
1968 following3 = insert(:user)
1969 {:ok, _} = User.follow(user, following1)
1970 {:ok, _} = User.follow(user, following2)
1971 {:ok, _} = User.follow(user, following3)
1975 |> assign(:user, user)
1979 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1981 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1982 assert id3 == following3.id
1983 assert id2 == following2.id
1987 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1989 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1990 assert id2 == following2.id
1991 assert id1 == following1.id
1995 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1997 assert [%{"id" => id2}] = json_response(res_conn, 200)
1998 assert id2 == following2.id
2000 assert [link_header] = get_resp_header(res_conn, "link")
2001 assert link_header =~ ~r/min_id=#{following2.id}/
2002 assert link_header =~ ~r/max_id=#{following2.id}/
2005 test "following / unfollowing a user", %{conn: conn} do
2006 user = insert(:user)
2007 other_user = insert(:user)
2011 |> assign(:user, user)
2012 |> post("/api/v1/accounts/#{other_user.id}/follow")
2014 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
2016 user = User.get_cached_by_id(user.id)
2020 |> assign(:user, user)
2021 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
2023 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
2025 user = User.get_cached_by_id(user.id)
2029 |> assign(:user, user)
2030 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
2032 assert %{"id" => id} = json_response(conn, 200)
2033 assert id == to_string(other_user.id)
2036 test "following without reblogs" do
2037 follower = insert(:user)
2038 followed = insert(:user)
2039 other_user = insert(:user)
2043 |> assign(:user, follower)
2044 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
2046 assert %{"showing_reblogs" => false} = json_response(conn, 200)
2048 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
2049 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
2053 |> assign(:user, User.get_cached_by_id(follower.id))
2054 |> get("/api/v1/timelines/home")
2056 assert [] == json_response(conn, 200)
2060 |> assign(:user, follower)
2061 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
2063 assert %{"showing_reblogs" => true} = json_response(conn, 200)
2067 |> assign(:user, User.get_cached_by_id(follower.id))
2068 |> get("/api/v1/timelines/home")
2070 expected_activity_id = reblog.id
2071 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
2074 test "following / unfollowing errors" do
2075 user = insert(:user)
2079 |> assign(:user, user)
2082 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
2083 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2086 user = User.get_cached_by_id(user.id)
2087 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
2088 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2090 # self follow via uri
2091 user = User.get_cached_by_id(user.id)
2092 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
2093 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2095 # follow non existing user
2096 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
2097 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2099 # follow non existing user via uri
2100 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
2101 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2103 # unfollow non existing user
2104 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
2105 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2108 test "muting / unmuting a user", %{conn: conn} do
2109 user = insert(:user)
2110 other_user = insert(:user)
2114 |> assign(:user, user)
2115 |> post("/api/v1/accounts/#{other_user.id}/mute")
2117 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
2119 user = User.get_cached_by_id(user.id)
2123 |> assign(:user, user)
2124 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2126 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
2129 test "subscribing / unsubscribing to a user", %{conn: conn} do
2130 user = insert(:user)
2131 subscription_target = insert(:user)
2135 |> assign(:user, user)
2136 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2138 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2142 |> assign(:user, user)
2143 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2145 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2148 test "getting a list of mutes", %{conn: conn} do
2149 user = insert(:user)
2150 other_user = insert(:user)
2152 {:ok, user} = User.mute(user, other_user)
2156 |> assign(:user, user)
2157 |> get("/api/v1/mutes")
2159 other_user_id = to_string(other_user.id)
2160 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2163 test "blocking / unblocking a user", %{conn: conn} do
2164 user = insert(:user)
2165 other_user = insert(:user)
2169 |> assign(:user, user)
2170 |> post("/api/v1/accounts/#{other_user.id}/block")
2172 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2174 user = User.get_cached_by_id(user.id)
2178 |> assign(:user, user)
2179 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2181 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2184 test "getting a list of blocks", %{conn: conn} do
2185 user = insert(:user)
2186 other_user = insert(:user)
2188 {:ok, user} = User.block(user, other_user)
2192 |> assign(:user, user)
2193 |> get("/api/v1/blocks")
2195 other_user_id = to_string(other_user.id)
2196 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2199 test "blocking / unblocking a domain", %{conn: conn} do
2200 user = insert(:user)
2201 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2205 |> assign(:user, user)
2206 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2208 assert %{} = json_response(conn, 200)
2209 user = User.get_cached_by_ap_id(user.ap_id)
2210 assert User.blocks?(user, other_user)
2214 |> assign(:user, user)
2215 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2217 assert %{} = json_response(conn, 200)
2218 user = User.get_cached_by_ap_id(user.ap_id)
2219 refute User.blocks?(user, other_user)
2222 test "getting a list of domain blocks", %{conn: conn} do
2223 user = insert(:user)
2225 {:ok, user} = User.block_domain(user, "bad.site")
2226 {:ok, user} = User.block_domain(user, "even.worse.site")
2230 |> assign(:user, user)
2231 |> get("/api/v1/domain_blocks")
2233 domain_blocks = json_response(conn, 200)
2235 assert "bad.site" in domain_blocks
2236 assert "even.worse.site" in domain_blocks
2239 test "unimplemented follow_requests, blocks, domain blocks" do
2240 user = insert(:user)
2242 ["blocks", "domain_blocks", "follow_requests"]
2243 |> Enum.each(fn endpoint ->
2246 |> assign(:user, user)
2247 |> get("/api/v1/#{endpoint}")
2249 assert [] = json_response(conn, 200)
2253 test "returns the favorites of a user", %{conn: conn} do
2254 user = insert(:user)
2255 other_user = insert(:user)
2257 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2258 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2260 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2264 |> assign(:user, user)
2265 |> get("/api/v1/favourites")
2267 assert [status] = json_response(first_conn, 200)
2268 assert status["id"] == to_string(activity.id)
2270 assert [{"link", _link_header}] =
2271 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2273 # Honours query params
2274 {:ok, second_activity} =
2275 CommonAPI.post(other_user, %{
2277 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2280 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2282 last_like = status["id"]
2286 |> assign(:user, user)
2287 |> get("/api/v1/favourites?since_id=#{last_like}")
2289 assert [second_status] = json_response(second_conn, 200)
2290 assert second_status["id"] == to_string(second_activity.id)
2294 |> assign(:user, user)
2295 |> get("/api/v1/favourites?limit=0")
2297 assert [] = json_response(third_conn, 200)
2300 describe "getting favorites timeline of specified user" do
2302 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2303 [current_user: current_user, user: user]
2306 test "returns list of statuses favorited by specified user", %{
2308 current_user: current_user,
2311 [activity | _] = insert_pair(:note_activity)
2312 CommonAPI.favorite(activity.id, user)
2316 |> assign(:user, current_user)
2317 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2318 |> json_response(:ok)
2322 assert length(response) == 1
2323 assert like["id"] == activity.id
2326 test "returns favorites for specified user_id when user is not logged in", %{
2330 activity = insert(:note_activity)
2331 CommonAPI.favorite(activity.id, user)
2335 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2336 |> json_response(:ok)
2338 assert length(response) == 1
2341 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2343 current_user: current_user,
2347 CommonAPI.post(current_user, %{
2348 "status" => "Hi @#{user.nickname}!",
2349 "visibility" => "direct"
2352 CommonAPI.favorite(direct.id, user)
2356 |> assign(:user, current_user)
2357 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2358 |> json_response(:ok)
2360 assert length(response) == 1
2362 anonymous_response =
2364 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2365 |> json_response(:ok)
2367 assert Enum.empty?(anonymous_response)
2370 test "does not return others' favorited DM when user is not one of recipients", %{
2372 current_user: current_user,
2375 user_two = insert(:user)
2378 CommonAPI.post(user_two, %{
2379 "status" => "Hi @#{user.nickname}!",
2380 "visibility" => "direct"
2383 CommonAPI.favorite(direct.id, user)
2387 |> assign(:user, current_user)
2388 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2389 |> json_response(:ok)
2391 assert Enum.empty?(response)
2394 test "paginates favorites using since_id and max_id", %{
2396 current_user: current_user,
2399 activities = insert_list(10, :note_activity)
2401 Enum.each(activities, fn activity ->
2402 CommonAPI.favorite(activity.id, user)
2405 third_activity = Enum.at(activities, 2)
2406 seventh_activity = Enum.at(activities, 6)
2410 |> assign(:user, current_user)
2411 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2412 since_id: third_activity.id,
2413 max_id: seventh_activity.id
2415 |> json_response(:ok)
2417 assert length(response) == 3
2418 refute third_activity in response
2419 refute seventh_activity in response
2422 test "limits favorites using limit parameter", %{
2424 current_user: current_user,
2428 |> insert_list(:note_activity)
2429 |> Enum.each(fn activity ->
2430 CommonAPI.favorite(activity.id, user)
2435 |> assign(:user, current_user)
2436 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2437 |> json_response(:ok)
2439 assert length(response) == 3
2442 test "returns empty response when user does not have any favorited statuses", %{
2444 current_user: current_user,
2449 |> assign(:user, current_user)
2450 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2451 |> json_response(:ok)
2453 assert Enum.empty?(response)
2456 test "returns 404 error when specified user is not exist", %{conn: conn} do
2457 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2459 assert json_response(conn, 404) == %{"error" => "Record not found"}
2462 test "returns 403 error when user has hidden own favorites", %{
2464 current_user: current_user
2466 user = insert(:user, %{info: %{hide_favorites: true}})
2467 activity = insert(:note_activity)
2468 CommonAPI.favorite(activity.id, user)
2472 |> assign(:user, current_user)
2473 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2475 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2478 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2479 user = insert(:user)
2480 activity = insert(:note_activity)
2481 CommonAPI.favorite(activity.id, user)
2485 |> assign(:user, current_user)
2486 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2488 assert user.info.hide_favorites
2489 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2493 test "get instance information", %{conn: conn} do
2494 conn = get(conn, "/api/v1/instance")
2495 assert result = json_response(conn, 200)
2497 email = Pleroma.Config.get([:instance, :email])
2498 # Note: not checking for "max_toot_chars" since it's optional
2504 "email" => from_config_email,
2506 "streaming_api" => _
2511 "registrations" => _,
2515 assert email == from_config_email
2518 test "get instance stats", %{conn: conn} do
2519 user = insert(:user, %{local: true})
2521 user2 = insert(:user, %{local: true})
2522 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2524 insert(:user, %{local: false, nickname: "u@peer1.com"})
2525 insert(:user, %{local: false, nickname: "u@peer2.com"})
2527 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2529 # Stats should count users with missing or nil `info.deactivated` value
2530 user = User.get_cached_by_id(user.id)
2531 info_change = Changeset.change(user.info, %{deactivated: nil})
2535 |> Changeset.change()
2536 |> Changeset.put_embed(:info, info_change)
2537 |> User.update_and_set_cache()
2539 Pleroma.Stats.update_stats()
2541 conn = get(conn, "/api/v1/instance")
2543 assert result = json_response(conn, 200)
2545 stats = result["stats"]
2548 assert stats["user_count"] == 1
2549 assert stats["status_count"] == 1
2550 assert stats["domain_count"] == 2
2553 test "get peers", %{conn: conn} do
2554 insert(:user, %{local: false, nickname: "u@peer1.com"})
2555 insert(:user, %{local: false, nickname: "u@peer2.com"})
2557 Pleroma.Stats.update_stats()
2559 conn = get(conn, "/api/v1/instance/peers")
2561 assert result = json_response(conn, 200)
2563 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2566 test "put settings", %{conn: conn} do
2567 user = insert(:user)
2571 |> assign(:user, user)
2572 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2574 assert _result = json_response(conn, 200)
2576 user = User.get_cached_by_ap_id(user.ap_id)
2577 assert user.info.settings == %{"programming" => "socks"}
2580 describe "pinned statuses" do
2582 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2584 user = insert(:user)
2585 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2587 [user: user, activity: activity]
2590 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2591 {:ok, _} = CommonAPI.pin(activity.id, user)
2595 |> assign(:user, user)
2596 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2597 |> json_response(200)
2599 id_str = to_string(activity.id)
2601 assert [%{"id" => ^id_str, "pinned" => true}] = result
2604 test "pin status", %{conn: conn, user: user, activity: activity} do
2605 id_str = to_string(activity.id)
2607 assert %{"id" => ^id_str, "pinned" => true} =
2609 |> assign(:user, user)
2610 |> post("/api/v1/statuses/#{activity.id}/pin")
2611 |> json_response(200)
2613 assert [%{"id" => ^id_str, "pinned" => true}] =
2615 |> assign(:user, user)
2616 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2617 |> json_response(200)
2620 test "unpin status", %{conn: conn, user: user, activity: activity} do
2621 {:ok, _} = CommonAPI.pin(activity.id, user)
2623 id_str = to_string(activity.id)
2624 user = refresh_record(user)
2626 assert %{"id" => ^id_str, "pinned" => false} =
2628 |> assign(:user, user)
2629 |> post("/api/v1/statuses/#{activity.id}/unpin")
2630 |> json_response(200)
2634 |> assign(:user, user)
2635 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2636 |> json_response(200)
2639 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2640 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2642 id_str_one = to_string(activity_one.id)
2644 assert %{"id" => ^id_str_one, "pinned" => true} =
2646 |> assign(:user, user)
2647 |> post("/api/v1/statuses/#{id_str_one}/pin")
2648 |> json_response(200)
2650 user = refresh_record(user)
2652 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2654 |> assign(:user, user)
2655 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2656 |> json_response(400)
2662 Pleroma.Config.put([:rich_media, :enabled], true)
2665 Pleroma.Config.put([:rich_media, :enabled], false)
2668 user = insert(:user)
2672 test "returns rich-media card", %{conn: conn, user: user} do
2673 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2676 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2677 "provider_name" => "www.imdb.com",
2678 "provider_url" => "http://www.imdb.com",
2679 "title" => "The Rock",
2681 "url" => "http://www.imdb.com/title/tt0117500/",
2683 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2686 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2687 "title" => "The Rock",
2688 "type" => "video.movie",
2689 "url" => "http://www.imdb.com/title/tt0117500/",
2691 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2698 |> get("/api/v1/statuses/#{activity.id}/card")
2699 |> json_response(200)
2701 assert response == card_data
2703 # works with private posts
2705 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2709 |> assign(:user, user)
2710 |> get("/api/v1/statuses/#{activity.id}/card")
2711 |> json_response(200)
2713 assert response_two == card_data
2716 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2718 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2722 |> get("/api/v1/statuses/#{activity.id}/card")
2723 |> json_response(:ok)
2725 assert response == %{
2727 "title" => "Pleroma",
2728 "description" => "",
2730 "provider_name" => "pleroma.social",
2731 "provider_url" => "https://pleroma.social",
2732 "url" => "https://pleroma.social/",
2735 "title" => "Pleroma",
2736 "type" => "website",
2737 "url" => "https://pleroma.social/"
2745 user = insert(:user)
2746 for_user = insert(:user)
2749 CommonAPI.post(user, %{
2750 "status" => "heweoo?"
2754 CommonAPI.post(user, %{
2755 "status" => "heweoo!"
2760 |> assign(:user, for_user)
2761 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2763 assert json_response(response1, 200)["bookmarked"] == true
2767 |> assign(:user, for_user)
2768 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2770 assert json_response(response2, 200)["bookmarked"] == true
2774 |> assign(:user, for_user)
2775 |> get("/api/v1/bookmarks")
2777 assert [json_response(response2, 200), json_response(response1, 200)] ==
2778 json_response(bookmarks, 200)
2782 |> assign(:user, for_user)
2783 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2785 assert json_response(response1, 200)["bookmarked"] == false
2789 |> assign(:user, for_user)
2790 |> get("/api/v1/bookmarks")
2792 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2795 describe "conversation muting" do
2797 user = insert(:user)
2798 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2800 [user: user, activity: activity]
2803 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2804 id_str = to_string(activity.id)
2806 assert %{"id" => ^id_str, "muted" => true} =
2808 |> assign(:user, user)
2809 |> post("/api/v1/statuses/#{activity.id}/mute")
2810 |> json_response(200)
2813 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2814 {:ok, _} = CommonAPI.add_mute(user, activity)
2816 id_str = to_string(activity.id)
2817 user = refresh_record(user)
2819 assert %{"id" => ^id_str, "muted" => false} =
2821 |> assign(:user, user)
2822 |> post("/api/v1/statuses/#{activity.id}/unmute")
2823 |> json_response(200)
2827 describe "reports" do
2829 reporter = insert(:user)
2830 target_user = insert(:user)
2832 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2834 [reporter: reporter, target_user: target_user, activity: activity]
2837 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2838 assert %{"action_taken" => false, "id" => _} =
2840 |> assign(:user, reporter)
2841 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2842 |> json_response(200)
2845 test "submit a report with statuses and comment", %{
2848 target_user: target_user,
2851 assert %{"action_taken" => false, "id" => _} =
2853 |> assign(:user, reporter)
2854 |> post("/api/v1/reports", %{
2855 "account_id" => target_user.id,
2856 "status_ids" => [activity.id],
2857 "comment" => "bad status!"
2859 |> json_response(200)
2862 test "account_id is required", %{
2867 assert %{"error" => "Valid `account_id` required"} =
2869 |> assign(:user, reporter)
2870 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2871 |> json_response(400)
2874 test "comment must be up to the size specified in the config", %{
2877 target_user: target_user
2879 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2880 comment = String.pad_trailing("a", max_size + 1, "a")
2882 error = %{"error" => "Comment must be up to #{max_size} characters"}
2886 |> assign(:user, reporter)
2887 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2888 |> json_response(400)
2892 describe "link headers" do
2893 test "preserves parameters in link headers", %{conn: conn} do
2894 user = insert(:user)
2895 other_user = insert(:user)
2898 CommonAPI.post(other_user, %{
2899 "status" => "hi @#{user.nickname}",
2900 "visibility" => "public"
2904 CommonAPI.post(other_user, %{
2905 "status" => "hi @#{user.nickname}",
2906 "visibility" => "public"
2909 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2910 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2914 |> assign(:user, user)
2915 |> get("/api/v1/notifications", %{media_only: true})
2917 assert [link_header] = get_resp_header(conn, "link")
2918 assert link_header =~ ~r/media_only=true/
2919 assert link_header =~ ~r/min_id=#{notification2.id}/
2920 assert link_header =~ ~r/max_id=#{notification1.id}/
2924 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2925 # Need to set an old-style integer ID to reproduce the problem
2926 # (these are no longer assigned to new accounts but were preserved
2927 # for existing accounts during the migration to flakeIDs)
2928 user_one = insert(:user, %{id: 1212})
2929 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2933 |> get("/api/v1/accounts/#{user_one.id}")
2937 |> get("/api/v1/accounts/#{user_two.nickname}")
2941 |> get("/api/v1/accounts/#{user_two.id}")
2943 acc_one = json_response(resp_one, 200)
2944 acc_two = json_response(resp_two, 200)
2945 acc_three = json_response(resp_three, 200)
2946 refute acc_one == acc_two
2947 assert acc_two == acc_three
2950 describe "custom emoji" do
2951 test "with tags", %{conn: conn} do
2954 |> get("/api/v1/custom_emojis")
2955 |> json_response(200)
2957 assert Map.has_key?(emoji, "shortcode")
2958 assert Map.has_key?(emoji, "static_url")
2959 assert Map.has_key?(emoji, "tags")
2960 assert is_list(emoji["tags"])
2961 assert Map.has_key?(emoji, "url")
2962 assert Map.has_key?(emoji, "visible_in_picker")
2966 describe "index/2 redirections" do
2967 setup %{conn: conn} do
2971 signing_salt: "cooldude"
2976 |> Plug.Session.call(Plug.Session.init(session_opts))
2979 test_path = "/web/statuses/test"
2980 %{conn: conn, path: test_path}
2983 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
2984 conn = get(conn, path)
2986 assert conn.status == 302
2987 assert redirected_to(conn) == "/web/login"
2990 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
2991 token = insert(:oauth_token)
2995 |> assign(:user, token.user)
2996 |> put_session(:oauth_token, token.token)
2999 assert conn.status == 200
3002 test "saves referer path to session", %{conn: conn, path: path} do
3003 conn = get(conn, path)
3004 return_to = Plug.Conn.get_session(conn, :return_to)
3006 assert return_to == path
3009 test "redirects to the saved path after log in", %{conn: conn, path: path} do
3010 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3011 auth = insert(:oauth_authorization, app: app)
3015 |> put_session(:return_to, path)
3016 |> get("/web/login", %{code: auth.token})
3018 assert conn.status == 302
3019 assert redirected_to(conn) == path
3022 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
3023 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3024 auth = insert(:oauth_authorization, app: app)
3026 conn = get(conn, "/web/login", %{code: auth.token})
3028 assert conn.status == 302
3029 assert redirected_to(conn) == "/web/getting-started"
3033 describe "scheduled activities" do
3034 test "creates a scheduled activity", %{conn: conn} do
3035 user = insert(:user)
3036 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3040 |> assign(:user, user)
3041 |> post("/api/v1/statuses", %{
3042 "status" => "scheduled",
3043 "scheduled_at" => scheduled_at
3046 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
3047 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
3048 assert [] == Repo.all(Activity)
3051 test "creates a scheduled activity with a media attachment", %{conn: conn} do
3052 user = insert(:user)
3053 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3055 file = %Plug.Upload{
3056 content_type: "image/jpg",
3057 path: Path.absname("test/fixtures/image.jpg"),
3058 filename: "an_image.jpg"
3061 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
3065 |> assign(:user, user)
3066 |> post("/api/v1/statuses", %{
3067 "media_ids" => [to_string(upload.id)],
3068 "status" => "scheduled",
3069 "scheduled_at" => scheduled_at
3072 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
3073 assert %{"type" => "image"} = media_attachment
3076 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
3078 user = insert(:user)
3081 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
3085 |> assign(:user, user)
3086 |> post("/api/v1/statuses", %{
3087 "status" => "not scheduled",
3088 "scheduled_at" => scheduled_at
3091 assert %{"content" => "not scheduled"} = json_response(conn, 200)
3092 assert [] == Repo.all(ScheduledActivity)
3095 test "returns error when daily user limit is exceeded", %{conn: conn} do
3096 user = insert(:user)
3099 NaiveDateTime.utc_now()
3100 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3101 |> NaiveDateTime.to_iso8601()
3103 attrs = %{params: %{}, scheduled_at: today}
3104 {:ok, _} = ScheduledActivity.create(user, attrs)
3105 {:ok, _} = ScheduledActivity.create(user, attrs)
3109 |> assign(:user, user)
3110 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3112 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3115 test "returns error when total user limit is exceeded", %{conn: conn} do
3116 user = insert(:user)
3119 NaiveDateTime.utc_now()
3120 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3121 |> NaiveDateTime.to_iso8601()
3124 NaiveDateTime.utc_now()
3125 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3126 |> NaiveDateTime.to_iso8601()
3128 attrs = %{params: %{}, scheduled_at: today}
3129 {:ok, _} = ScheduledActivity.create(user, attrs)
3130 {:ok, _} = ScheduledActivity.create(user, attrs)
3131 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3135 |> assign(:user, user)
3136 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3138 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3141 test "shows scheduled activities", %{conn: conn} do
3142 user = insert(:user)
3143 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3144 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3145 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3146 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3150 |> assign(:user, user)
3155 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3157 result = json_response(conn_res, 200)
3158 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3163 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3165 result = json_response(conn_res, 200)
3166 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3171 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3173 result = json_response(conn_res, 200)
3174 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3177 test "shows a scheduled activity", %{conn: conn} do
3178 user = insert(:user)
3179 scheduled_activity = insert(:scheduled_activity, user: user)
3183 |> assign(:user, user)
3184 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3186 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3187 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3191 |> assign(:user, user)
3192 |> get("/api/v1/scheduled_statuses/404")
3194 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3197 test "updates a scheduled activity", %{conn: conn} do
3198 user = insert(:user)
3199 scheduled_activity = insert(:scheduled_activity, user: user)
3202 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3206 |> assign(:user, user)
3207 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3208 scheduled_at: new_scheduled_at
3211 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3212 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3216 |> assign(:user, user)
3217 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3219 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3222 test "deletes a scheduled activity", %{conn: conn} do
3223 user = insert(:user)
3224 scheduled_activity = insert(:scheduled_activity, user: user)
3228 |> assign(:user, user)
3229 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3231 assert %{} = json_response(res_conn, 200)
3232 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3236 |> assign(:user, user)
3237 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3239 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3243 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3244 user1 = insert(:user)
3245 user2 = insert(:user)
3246 user3 = insert(:user)
3248 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3250 # Reply to status from another user
3253 |> assign(:user, user2)
3254 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3256 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3258 activity = Activity.get_by_id_with_object(id)
3260 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3261 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3263 # Reblog from the third user
3266 |> assign(:user, user3)
3267 |> post("/api/v1/statuses/#{activity.id}/reblog")
3269 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3270 json_response(conn2, 200)
3272 assert to_string(activity.id) == id
3274 # Getting third user status
3277 |> assign(:user, user3)
3278 |> get("api/v1/timelines/home")
3280 [reblogged_activity] = json_response(conn3, 200)
3282 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3284 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3285 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3288 describe "create account by app" do
3289 test "Account registration via Application", %{conn: conn} do
3292 |> post("/api/v1/apps", %{
3293 client_name: "client_name",
3294 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3295 scopes: "read, write, follow"
3299 "client_id" => client_id,
3300 "client_secret" => client_secret,
3302 "name" => "client_name",
3303 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3306 } = json_response(conn, 200)
3310 |> post("/oauth/token", %{
3311 grant_type: "client_credentials",
3312 client_id: client_id,
3313 client_secret: client_secret
3316 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3317 json_response(conn, 200)
3320 token_from_db = Repo.get_by(Token, token: token)
3321 assert token_from_db
3323 assert scope == "read write follow"
3327 |> put_req_header("authorization", "Bearer " <> token)
3328 |> post("/api/v1/accounts", %{
3330 email: "lain@example.org",
3331 password: "PlzDontHackLain",
3336 "access_token" => token,
3337 "created_at" => _created_at,
3339 "token_type" => "Bearer"
3340 } = json_response(conn, 200)
3342 token_from_db = Repo.get_by(Token, token: token)
3343 assert token_from_db
3344 token_from_db = Repo.preload(token_from_db, :user)
3345 assert token_from_db.user
3347 assert token_from_db.user.info.confirmation_pending
3350 test "rate limit", %{conn: conn} do
3351 app_token = insert(:oauth_token, user: nil)
3354 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3355 |> Map.put(:remote_ip, {15, 15, 15, 15})
3360 |> post("/api/v1/accounts", %{
3361 username: "#{i}lain",
3362 email: "#{i}lain@example.org",
3363 password: "PlzDontHackLain",
3368 "access_token" => token,
3369 "created_at" => _created_at,
3371 "token_type" => "Bearer"
3372 } = json_response(conn, 200)
3374 token_from_db = Repo.get_by(Token, token: token)
3375 assert token_from_db
3376 token_from_db = Repo.preload(token_from_db, :user)
3377 assert token_from_db.user
3379 assert token_from_db.user.info.confirmation_pending
3384 |> post("/api/v1/accounts", %{
3386 email: "6lain@example.org",
3387 password: "PlzDontHackLain",
3391 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3395 describe "GET /api/v1/polls/:id" do
3396 test "returns poll entity for object id", %{conn: conn} do
3397 user = insert(:user)
3400 CommonAPI.post(user, %{
3401 "status" => "Pleroma does",
3402 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3405 object = Object.normalize(activity)
3409 |> assign(:user, user)
3410 |> get("/api/v1/polls/#{object.id}")
3412 response = json_response(conn, 200)
3414 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3417 test "does not expose polls for private statuses", %{conn: conn} do
3418 user = insert(:user)
3419 other_user = insert(:user)
3422 CommonAPI.post(user, %{
3423 "status" => "Pleroma does",
3424 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3425 "visibility" => "private"
3428 object = Object.normalize(activity)
3432 |> assign(:user, other_user)
3433 |> get("/api/v1/polls/#{object.id}")
3435 assert json_response(conn, 404)
3439 describe "POST /api/v1/polls/:id/votes" do
3440 test "votes are added to the poll", %{conn: conn} do
3441 user = insert(:user)
3442 other_user = insert(:user)
3445 CommonAPI.post(user, %{
3446 "status" => "A very delicious sandwich",
3448 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3454 object = Object.normalize(activity)
3458 |> assign(:user, other_user)
3459 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3461 assert json_response(conn, 200)
3462 object = Object.get_by_id(object.id)
3464 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3469 test "author can't vote", %{conn: conn} do
3470 user = insert(:user)
3473 CommonAPI.post(user, %{
3474 "status" => "Am I cute?",
3475 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3478 object = Object.normalize(activity)
3481 |> assign(:user, user)
3482 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3483 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3485 object = Object.get_by_id(object.id)
3487 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3490 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3491 user = insert(:user)
3492 other_user = insert(:user)
3495 CommonAPI.post(user, %{
3496 "status" => "The glass is",
3497 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3500 object = Object.normalize(activity)
3503 |> assign(:user, other_user)
3504 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3505 |> json_response(422) == %{"error" => "Too many choices"}
3507 object = Object.get_by_id(object.id)
3509 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->