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
28 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
32 test "the home timeline", %{conn: conn} do
34 following = insert(:user)
36 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
40 |> assign(:user, user)
41 |> get("/api/v1/timelines/home")
43 assert Enum.empty?(json_response(conn, 200))
45 {:ok, user} = User.follow(user, following)
49 |> assign(:user, user)
50 |> get("/api/v1/timelines/home")
52 assert [%{"content" => "test"}] = json_response(conn, 200)
55 test "the public timeline", %{conn: conn} do
56 following = insert(:user)
59 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
62 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
66 |> get("/api/v1/timelines/public", %{"local" => "False"})
68 assert length(json_response(conn, 200)) == 2
72 |> get("/api/v1/timelines/public", %{"local" => "True"})
74 assert [%{"content" => "test"}] = json_response(conn, 200)
78 |> get("/api/v1/timelines/public", %{"local" => "1"})
80 assert [%{"content" => "test"}] = json_response(conn, 200)
84 test "the public timeline when public is set to false", %{conn: conn} do
85 public = Pleroma.Config.get([:instance, :public])
86 Pleroma.Config.put([:instance, :public], false)
89 Pleroma.Config.put([:instance, :public], public)
93 |> get("/api/v1/timelines/public", %{"local" => "False"})
94 |> json_response(403) == %{"error" => "This resource requires authentication."}
97 test "posting a status", %{conn: conn} do
100 idempotency_key = "Pikachu rocks!"
104 |> assign(:user, user)
105 |> put_req_header("idempotency-key", idempotency_key)
106 |> post("/api/v1/statuses", %{
108 "spoiler_text" => "2hu",
109 "sensitive" => "false"
112 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
114 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
116 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
117 json_response(conn_one, 200)
119 assert Activity.get_by_id(id)
123 |> assign(:user, user)
124 |> put_req_header("idempotency-key", idempotency_key)
125 |> post("/api/v1/statuses", %{
127 "spoiler_text" => "2hu",
128 "sensitive" => "false"
131 assert %{"id" => second_id} = json_response(conn_two, 200)
133 assert id == second_id
137 |> assign(:user, user)
138 |> post("/api/v1/statuses", %{
140 "spoiler_text" => "2hu",
141 "sensitive" => "false"
144 assert %{"id" => third_id} = json_response(conn_three, 200)
146 refute id == third_id
149 describe "posting polls" do
150 test "posting a poll", %{conn: conn} do
152 time = NaiveDateTime.utc_now()
156 |> assign(:user, user)
157 |> post("/api/v1/statuses", %{
158 "status" => "Who is the #bestgrill?",
159 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
162 response = json_response(conn, 200)
164 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
165 title in ["Rei", "Asuka", "Misato"]
168 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
169 refute response["poll"]["expred"]
172 test "option limit is enforced", %{conn: conn} do
174 limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
178 |> assign(:user, user)
179 |> post("/api/v1/statuses", %{
181 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
184 %{"error" => error} = json_response(conn, 422)
185 assert error == "Poll can't contain more than #{limit} options"
188 test "option character limit is enforced", %{conn: conn} do
190 limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
194 |> assign(:user, user)
195 |> post("/api/v1/statuses", %{
198 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
203 %{"error" => error} = json_response(conn, 422)
204 assert error == "Poll options cannot be longer than #{limit} characters each"
207 test "minimal date limit is enforced", %{conn: conn} do
209 limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
213 |> assign(:user, user)
214 |> post("/api/v1/statuses", %{
215 "status" => "imagine arbitrary limits",
217 "options" => ["this post was made by pleroma gang"],
218 "expires_in" => limit - 1
222 %{"error" => error} = json_response(conn, 422)
223 assert error == "Expiration date is too soon"
226 test "maximum date limit is enforced", %{conn: conn} do
228 limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
232 |> assign(:user, user)
233 |> post("/api/v1/statuses", %{
234 "status" => "imagine arbitrary limits",
236 "options" => ["this post was made by pleroma gang"],
237 "expires_in" => limit + 1
241 %{"error" => error} = json_response(conn, 422)
242 assert error == "Expiration date is too far in the future"
246 test "posting a sensitive status", %{conn: conn} do
251 |> assign(:user, user)
252 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
254 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
255 assert Activity.get_by_id(id)
258 test "posting a fake status", %{conn: conn} do
263 |> assign(:user, user)
264 |> post("/api/v1/statuses", %{
266 "\"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"
269 real_status = json_response(real_conn, 200)
272 assert Object.get_by_ap_id(real_status["uri"])
276 |> Map.put("id", nil)
277 |> Map.put("url", nil)
278 |> Map.put("uri", nil)
279 |> Map.put("created_at", nil)
280 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
284 |> assign(:user, user)
285 |> post("/api/v1/statuses", %{
287 "\"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",
291 fake_status = json_response(fake_conn, 200)
294 refute Object.get_by_ap_id(fake_status["uri"])
298 |> Map.put("id", nil)
299 |> Map.put("url", nil)
300 |> Map.put("uri", nil)
301 |> Map.put("created_at", nil)
302 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
304 assert real_status == fake_status
307 test "posting a status with OGP link preview", %{conn: conn} do
308 Pleroma.Config.put([:rich_media, :enabled], true)
313 |> assign(:user, user)
314 |> post("/api/v1/statuses", %{
315 "status" => "https://example.com/ogp"
318 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
319 assert Activity.get_by_id(id)
320 Pleroma.Config.put([:rich_media, :enabled], false)
323 test "posting a direct status", %{conn: conn} do
324 user1 = insert(:user)
325 user2 = insert(:user)
326 content = "direct cofe @#{user2.nickname}"
330 |> assign(:user, user1)
331 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
333 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
334 assert activity = Activity.get_by_id(id)
335 assert activity.recipients == [user2.ap_id, user1.ap_id]
336 assert activity.data["to"] == [user2.ap_id]
337 assert activity.data["cc"] == []
340 test "direct timeline", %{conn: conn} do
341 user_one = insert(:user)
342 user_two = insert(:user)
344 {:ok, user_two} = User.follow(user_two, user_one)
347 CommonAPI.post(user_one, %{
348 "status" => "Hi @#{user_two.nickname}!",
349 "visibility" => "direct"
352 {:ok, _follower_only} =
353 CommonAPI.post(user_one, %{
354 "status" => "Hi @#{user_two.nickname}!",
355 "visibility" => "private"
358 # Only direct should be visible here
361 |> assign(:user, user_two)
362 |> get("api/v1/timelines/direct")
364 [status] = json_response(res_conn, 200)
366 assert %{"visibility" => "direct"} = status
367 assert status["url"] != direct.data["id"]
369 # User should be able to see his own direct message
372 |> assign(:user, user_one)
373 |> get("api/v1/timelines/direct")
375 [status] = json_response(res_conn, 200)
377 assert %{"visibility" => "direct"} = status
379 # Both should be visible here
382 |> assign(:user, user_two)
383 |> get("api/v1/timelines/home")
385 [_s1, _s2] = json_response(res_conn, 200)
388 Enum.each(1..20, fn _ ->
390 CommonAPI.post(user_one, %{
391 "status" => "Hi @#{user_two.nickname}!",
392 "visibility" => "direct"
398 |> assign(:user, user_two)
399 |> get("api/v1/timelines/direct")
401 statuses = json_response(res_conn, 200)
402 assert length(statuses) == 20
406 |> assign(:user, user_two)
407 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
409 [status] = json_response(res_conn, 200)
411 assert status["url"] != direct.data["id"]
414 test "Conversations", %{conn: conn} do
415 user_one = insert(:user)
416 user_two = insert(:user)
417 user_three = insert(:user)
419 {:ok, user_two} = User.follow(user_two, user_one)
422 CommonAPI.post(user_one, %{
423 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
424 "visibility" => "direct"
427 {:ok, _follower_only} =
428 CommonAPI.post(user_one, %{
429 "status" => "Hi @#{user_two.nickname}!",
430 "visibility" => "private"
435 |> assign(:user, user_one)
436 |> get("/api/v1/conversations")
438 assert response = json_response(res_conn, 200)
443 "accounts" => res_accounts,
444 "last_status" => res_last_status,
449 account_ids = Enum.map(res_accounts, & &1["id"])
450 assert length(res_accounts) == 2
451 assert user_two.id in account_ids
452 assert user_three.id in account_ids
453 assert is_binary(res_id)
454 assert unread == true
455 assert res_last_status["id"] == direct.id
457 # Apparently undocumented API endpoint
460 |> assign(:user, user_one)
461 |> post("/api/v1/conversations/#{res_id}/read")
463 assert response = json_response(res_conn, 200)
464 assert length(response["accounts"]) == 2
465 assert response["last_status"]["id"] == direct.id
466 assert response["unread"] == false
468 # (vanilla) Mastodon frontend behaviour
471 |> assign(:user, user_one)
472 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
474 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
477 test "doesn't include DMs from blocked users", %{conn: conn} do
478 blocker = insert(:user)
479 blocked = insert(:user)
481 {:ok, blocker} = User.block(blocker, blocked)
483 {:ok, _blocked_direct} =
484 CommonAPI.post(blocked, %{
485 "status" => "Hi @#{blocker.nickname}!",
486 "visibility" => "direct"
490 CommonAPI.post(user, %{
491 "status" => "Hi @#{blocker.nickname}!",
492 "visibility" => "direct"
497 |> assign(:user, user)
498 |> get("api/v1/timelines/direct")
500 [status] = json_response(res_conn, 200)
501 assert status["id"] == direct.id
504 test "replying to a status", %{conn: conn} do
507 {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
511 |> assign(:user, user)
512 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
514 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
516 activity = Activity.get_by_id(id)
518 assert activity.data["context"] == replied_to.data["context"]
519 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
522 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
527 |> assign(:user, user)
528 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
530 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
532 activity = Activity.get_by_id(id)
537 test "verify_credentials", %{conn: conn} do
542 |> assign(:user, user)
543 |> get("/api/v1/accounts/verify_credentials")
545 response = json_response(conn, 200)
547 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
548 assert response["pleroma"]["chat_token"]
549 assert id == to_string(user.id)
552 test "verify_credentials default scope unlisted", %{conn: conn} do
553 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
557 |> assign(:user, user)
558 |> get("/api/v1/accounts/verify_credentials")
560 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
561 assert id == to_string(user.id)
564 test "apps/verify_credentials", %{conn: conn} do
565 token = insert(:oauth_token)
569 |> assign(:user, token.user)
570 |> assign(:token, token)
571 |> get("/api/v1/apps/verify_credentials")
573 app = Repo.preload(token, :app).app
576 "name" => app.client_name,
577 "website" => app.website,
578 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
581 assert expected == json_response(conn, 200)
584 test "creates an oauth app", %{conn: conn} do
586 app_attrs = build(:oauth_app)
590 |> assign(:user, user)
591 |> post("/api/v1/apps", %{
592 client_name: app_attrs.client_name,
593 redirect_uris: app_attrs.redirect_uris
596 [app] = Repo.all(App)
599 "name" => app.client_name,
600 "website" => app.website,
601 "client_id" => app.client_id,
602 "client_secret" => app.client_secret,
603 "id" => app.id |> to_string(),
604 "redirect_uri" => app.redirect_uris,
605 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
608 assert expected == json_response(conn, 200)
611 test "get a status", %{conn: conn} do
612 activity = insert(:note_activity)
616 |> get("/api/v1/statuses/#{activity.id}")
618 assert %{"id" => id} = json_response(conn, 200)
619 assert id == to_string(activity.id)
622 describe "deleting a status" do
623 test "when you created it", %{conn: conn} do
624 activity = insert(:note_activity)
625 author = User.get_cached_by_ap_id(activity.data["actor"])
629 |> assign(:user, author)
630 |> delete("/api/v1/statuses/#{activity.id}")
632 assert %{} = json_response(conn, 200)
634 refute Activity.get_by_id(activity.id)
637 test "when you didn't create it", %{conn: conn} do
638 activity = insert(:note_activity)
643 |> assign(:user, user)
644 |> delete("/api/v1/statuses/#{activity.id}")
646 assert %{"error" => _} = json_response(conn, 403)
648 assert Activity.get_by_id(activity.id) == activity
651 test "when you're an admin or moderator", %{conn: conn} do
652 activity1 = insert(:note_activity)
653 activity2 = insert(:note_activity)
654 admin = insert(:user, info: %{is_admin: true})
655 moderator = insert(:user, info: %{is_moderator: true})
659 |> assign(:user, admin)
660 |> delete("/api/v1/statuses/#{activity1.id}")
662 assert %{} = json_response(res_conn, 200)
666 |> assign(:user, moderator)
667 |> delete("/api/v1/statuses/#{activity2.id}")
669 assert %{} = json_response(res_conn, 200)
671 refute Activity.get_by_id(activity1.id)
672 refute Activity.get_by_id(activity2.id)
676 describe "filters" do
677 test "creating a filter", %{conn: conn} do
680 filter = %Pleroma.Filter{
687 |> assign(:user, user)
688 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
690 assert response = json_response(conn, 200)
691 assert response["phrase"] == filter.phrase
692 assert response["context"] == filter.context
693 assert response["irreversible"] == false
694 assert response["id"] != nil
695 assert response["id"] != ""
698 test "fetching a list of filters", %{conn: conn} do
701 query_one = %Pleroma.Filter{
708 query_two = %Pleroma.Filter{
715 {:ok, filter_one} = Pleroma.Filter.create(query_one)
716 {:ok, filter_two} = Pleroma.Filter.create(query_two)
720 |> assign(:user, user)
721 |> get("/api/v1/filters")
722 |> json_response(200)
728 filters: [filter_two, filter_one]
732 test "get a filter", %{conn: conn} do
735 query = %Pleroma.Filter{
742 {:ok, filter} = Pleroma.Filter.create(query)
746 |> assign(:user, user)
747 |> get("/api/v1/filters/#{filter.filter_id}")
749 assert _response = json_response(conn, 200)
752 test "update a filter", %{conn: conn} do
755 query = %Pleroma.Filter{
762 {:ok, _filter} = Pleroma.Filter.create(query)
764 new = %Pleroma.Filter{
771 |> assign(:user, user)
772 |> put("/api/v1/filters/#{query.filter_id}", %{
777 assert response = json_response(conn, 200)
778 assert response["phrase"] == new.phrase
779 assert response["context"] == new.context
782 test "delete a filter", %{conn: conn} do
785 query = %Pleroma.Filter{
792 {:ok, filter} = Pleroma.Filter.create(query)
796 |> assign(:user, user)
797 |> delete("/api/v1/filters/#{filter.filter_id}")
799 assert response = json_response(conn, 200)
800 assert response == %{}
805 test "creating a list", %{conn: conn} do
810 |> assign(:user, user)
811 |> post("/api/v1/lists", %{"title" => "cuties"})
813 assert %{"title" => title} = json_response(conn, 200)
814 assert title == "cuties"
817 test "adding users to a list", %{conn: conn} do
819 other_user = insert(:user)
820 {:ok, list} = Pleroma.List.create("name", user)
824 |> assign(:user, user)
825 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
827 assert %{} == json_response(conn, 200)
828 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
829 assert following == [other_user.follower_address]
832 test "removing users from a list", %{conn: conn} do
834 other_user = insert(:user)
835 third_user = insert(:user)
836 {:ok, list} = Pleroma.List.create("name", user)
837 {:ok, list} = Pleroma.List.follow(list, other_user)
838 {:ok, list} = Pleroma.List.follow(list, third_user)
842 |> assign(:user, user)
843 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
845 assert %{} == json_response(conn, 200)
846 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
847 assert following == [third_user.follower_address]
850 test "listing users in a list", %{conn: conn} do
852 other_user = insert(:user)
853 {:ok, list} = Pleroma.List.create("name", user)
854 {:ok, list} = Pleroma.List.follow(list, other_user)
858 |> assign(:user, user)
859 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
861 assert [%{"id" => id}] = json_response(conn, 200)
862 assert id == to_string(other_user.id)
865 test "retrieving a list", %{conn: conn} do
867 {:ok, list} = Pleroma.List.create("name", user)
871 |> assign(:user, user)
872 |> get("/api/v1/lists/#{list.id}")
874 assert %{"id" => id} = json_response(conn, 200)
875 assert id == to_string(list.id)
878 test "renaming a list", %{conn: conn} do
880 {:ok, list} = Pleroma.List.create("name", user)
884 |> assign(:user, user)
885 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
887 assert %{"title" => name} = json_response(conn, 200)
888 assert name == "newname"
891 test "deleting a list", %{conn: conn} do
893 {:ok, list} = Pleroma.List.create("name", user)
897 |> assign(:user, user)
898 |> delete("/api/v1/lists/#{list.id}")
900 assert %{} = json_response(conn, 200)
901 assert is_nil(Repo.get(Pleroma.List, list.id))
904 test "list timeline", %{conn: conn} do
906 other_user = insert(:user)
907 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
908 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
909 {:ok, list} = Pleroma.List.create("name", user)
910 {:ok, list} = Pleroma.List.follow(list, other_user)
914 |> assign(:user, user)
915 |> get("/api/v1/timelines/list/#{list.id}")
917 assert [%{"id" => id}] = json_response(conn, 200)
919 assert id == to_string(activity_two.id)
922 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
924 other_user = insert(:user)
925 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
927 {:ok, _activity_two} =
928 TwitterAPI.create_status(other_user, %{
929 "status" => "Marisa is cute.",
930 "visibility" => "private"
933 {:ok, list} = Pleroma.List.create("name", user)
934 {:ok, list} = Pleroma.List.follow(list, other_user)
938 |> assign(:user, user)
939 |> get("/api/v1/timelines/list/#{list.id}")
941 assert [%{"id" => id}] = json_response(conn, 200)
943 assert id == to_string(activity_one.id)
947 describe "notifications" do
948 test "list of notifications", %{conn: conn} do
950 other_user = insert(:user)
953 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
955 {:ok, [_notification]} = Notification.create_notifications(activity)
959 |> assign(:user, user)
960 |> get("/api/v1/notifications")
963 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
965 }\">@<span>#{user.nickname}</span></a></span>"
967 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
968 assert response == expected_response
971 test "getting a single notification", %{conn: conn} do
973 other_user = insert(:user)
976 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
978 {:ok, [notification]} = Notification.create_notifications(activity)
982 |> assign(:user, user)
983 |> get("/api/v1/notifications/#{notification.id}")
986 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
988 }\">@<span>#{user.nickname}</span></a></span>"
990 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
991 assert response == expected_response
994 test "dismissing a single notification", %{conn: conn} do
996 other_user = insert(:user)
999 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1001 {:ok, [notification]} = Notification.create_notifications(activity)
1005 |> assign(:user, user)
1006 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
1008 assert %{} = json_response(conn, 200)
1011 test "clearing all notifications", %{conn: conn} do
1012 user = insert(:user)
1013 other_user = insert(:user)
1016 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1018 {:ok, [_notification]} = Notification.create_notifications(activity)
1022 |> assign(:user, user)
1023 |> post("/api/v1/notifications/clear")
1025 assert %{} = json_response(conn, 200)
1029 |> assign(:user, user)
1030 |> get("/api/v1/notifications")
1032 assert all = json_response(conn, 200)
1036 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
1037 user = insert(:user)
1038 other_user = insert(:user)
1040 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1041 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1042 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1043 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1045 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1046 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1047 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1048 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1052 |> assign(:user, user)
1057 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
1059 result = json_response(conn_res, 200)
1060 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1065 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
1067 result = json_response(conn_res, 200)
1068 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1073 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
1075 result = json_response(conn_res, 200)
1076 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1079 test "filters notifications using exclude_types", %{conn: conn} do
1080 user = insert(:user)
1081 other_user = insert(:user)
1083 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
1084 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
1085 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
1086 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
1087 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
1089 mention_notification_id =
1090 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
1092 favorite_notification_id =
1093 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
1095 reblog_notification_id =
1096 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
1098 follow_notification_id =
1099 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1103 |> assign(:user, user)
1106 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1108 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1111 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1113 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1116 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1118 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1121 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1123 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1126 test "destroy multiple", %{conn: conn} do
1127 user = insert(:user)
1128 other_user = insert(:user)
1130 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1131 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1132 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1133 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1135 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1136 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1137 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1138 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1142 |> assign(:user, user)
1146 |> get("/api/v1/notifications")
1148 result = json_response(conn_res, 200)
1149 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1153 |> assign(:user, other_user)
1157 |> get("/api/v1/notifications")
1159 result = json_response(conn_res, 200)
1160 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1164 |> delete("/api/v1/notifications/destroy_multiple", %{
1165 "ids" => [notification1_id, notification2_id]
1168 assert json_response(conn_destroy, 200) == %{}
1172 |> get("/api/v1/notifications")
1174 result = json_response(conn_res, 200)
1175 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1179 describe "reblogging" do
1180 test "reblogs and returns the reblogged status", %{conn: conn} do
1181 activity = insert(:note_activity)
1182 user = insert(:user)
1186 |> assign(:user, user)
1187 |> post("/api/v1/statuses/#{activity.id}/reblog")
1190 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1192 } = json_response(conn, 200)
1194 assert to_string(activity.id) == id
1197 test "reblogged status for another user", %{conn: conn} do
1198 activity = insert(:note_activity)
1199 user1 = insert(:user)
1200 user2 = insert(:user)
1201 user3 = insert(:user)
1202 CommonAPI.favorite(activity.id, user2)
1203 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1204 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1205 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1209 |> assign(:user, user3)
1210 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1213 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1214 "reblogged" => false,
1215 "favourited" => false,
1216 "bookmarked" => false
1217 } = json_response(conn_res, 200)
1221 |> assign(:user, user2)
1222 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1225 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1226 "reblogged" => true,
1227 "favourited" => true,
1228 "bookmarked" => true
1229 } = json_response(conn_res, 200)
1231 assert to_string(activity.id) == id
1235 describe "unreblogging" do
1236 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1237 activity = insert(:note_activity)
1238 user = insert(:user)
1240 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1244 |> assign(:user, user)
1245 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1247 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1249 assert to_string(activity.id) == id
1253 describe "favoriting" do
1254 test "favs a status and returns it", %{conn: conn} do
1255 activity = insert(:note_activity)
1256 user = insert(:user)
1260 |> assign(:user, user)
1261 |> post("/api/v1/statuses/#{activity.id}/favourite")
1263 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1264 json_response(conn, 200)
1266 assert to_string(activity.id) == id
1269 test "returns 500 for a wrong id", %{conn: conn} do
1270 user = insert(:user)
1274 |> assign(:user, user)
1275 |> post("/api/v1/statuses/1/favourite")
1276 |> json_response(500)
1278 assert resp == "Something went wrong"
1282 describe "unfavoriting" do
1283 test "unfavorites a status and returns it", %{conn: conn} do
1284 activity = insert(:note_activity)
1285 user = insert(:user)
1287 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1291 |> assign(:user, user)
1292 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1294 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1295 json_response(conn, 200)
1297 assert to_string(activity.id) == id
1301 describe "user timelines" do
1302 test "gets a users statuses", %{conn: conn} do
1303 user_one = insert(:user)
1304 user_two = insert(:user)
1305 user_three = insert(:user)
1307 {:ok, user_three} = User.follow(user_three, user_one)
1309 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1311 {:ok, direct_activity} =
1312 CommonAPI.post(user_one, %{
1313 "status" => "Hi, @#{user_two.nickname}.",
1314 "visibility" => "direct"
1317 {:ok, private_activity} =
1318 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1322 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1324 assert [%{"id" => id}] = json_response(resp, 200)
1325 assert id == to_string(activity.id)
1329 |> assign(:user, user_two)
1330 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1332 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1333 assert id_one == to_string(direct_activity.id)
1334 assert id_two == to_string(activity.id)
1338 |> assign(:user, user_three)
1339 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1341 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1342 assert id_one == to_string(private_activity.id)
1343 assert id_two == to_string(activity.id)
1346 test "unimplemented pinned statuses feature", %{conn: conn} do
1347 note = insert(:note_activity)
1348 user = User.get_cached_by_ap_id(note.data["actor"])
1352 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1354 assert json_response(conn, 200) == []
1357 test "gets an users media", %{conn: conn} do
1358 note = insert(:note_activity)
1359 user = User.get_cached_by_ap_id(note.data["actor"])
1361 file = %Plug.Upload{
1362 content_type: "image/jpg",
1363 path: Path.absname("test/fixtures/image.jpg"),
1364 filename: "an_image.jpg"
1368 TwitterAPI.upload(file, user, "json")
1372 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1376 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1378 assert [%{"id" => id}] = json_response(conn, 200)
1379 assert id == to_string(image_post.id)
1383 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1385 assert [%{"id" => id}] = json_response(conn, 200)
1386 assert id == to_string(image_post.id)
1389 test "gets a user's statuses without reblogs", %{conn: conn} do
1390 user = insert(:user)
1391 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1392 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1396 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1398 assert [%{"id" => id}] = json_response(conn, 200)
1399 assert id == to_string(post.id)
1403 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1405 assert [%{"id" => id}] = json_response(conn, 200)
1406 assert id == to_string(post.id)
1410 describe "user relationships" do
1411 test "returns the relationships for the current user", %{conn: conn} do
1412 user = insert(:user)
1413 other_user = insert(:user)
1414 {:ok, user} = User.follow(user, other_user)
1418 |> assign(:user, user)
1419 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1421 assert [relationship] = json_response(conn, 200)
1423 assert to_string(other_user.id) == relationship["id"]
1427 describe "media upload" do
1429 upload_config = Pleroma.Config.get([Pleroma.Upload])
1430 proxy_config = Pleroma.Config.get([:media_proxy])
1433 Pleroma.Config.put([Pleroma.Upload], upload_config)
1434 Pleroma.Config.put([:media_proxy], proxy_config)
1437 user = insert(:user)
1441 |> assign(:user, user)
1443 image = %Plug.Upload{
1444 content_type: "image/jpg",
1445 path: Path.absname("test/fixtures/image.jpg"),
1446 filename: "an_image.jpg"
1449 [conn: conn, image: image]
1452 test "returns uploaded image", %{conn: conn, image: image} do
1453 desc = "Description of the image"
1457 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1458 |> json_response(:ok)
1460 assert media["type"] == "image"
1461 assert media["description"] == desc
1464 object = Repo.get(Object, media["id"])
1465 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1468 test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
1469 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
1471 proxy_url = "https://cache.pleroma.social"
1472 Pleroma.Config.put([:media_proxy, :enabled], true)
1473 Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
1477 |> post("/api/v1/media", %{"file" => image})
1478 |> json_response(:ok)
1480 assert String.starts_with?(media["url"], proxy_url)
1483 test "returns media url when proxy is enabled but media url is whitelisted", %{
1487 media_url = "https://media.pleroma.social"
1488 Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
1490 Pleroma.Config.put([:media_proxy, :enabled], true)
1491 Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
1492 Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
1496 |> post("/api/v1/media", %{"file" => image})
1497 |> json_response(:ok)
1499 assert String.starts_with?(media["url"], media_url)
1503 describe "locked accounts" do
1504 test "/api/v1/follow_requests works" do
1505 user = insert(:user, %{info: %User.Info{locked: true}})
1506 other_user = insert(:user)
1508 {:ok, _activity} = ActivityPub.follow(other_user, user)
1510 user = User.get_cached_by_id(user.id)
1511 other_user = User.get_cached_by_id(other_user.id)
1513 assert User.following?(other_user, user) == false
1517 |> assign(:user, user)
1518 |> get("/api/v1/follow_requests")
1520 assert [relationship] = json_response(conn, 200)
1521 assert to_string(other_user.id) == relationship["id"]
1524 test "/api/v1/follow_requests/:id/authorize works" do
1525 user = insert(:user, %{info: %User.Info{locked: true}})
1526 other_user = insert(:user)
1528 {:ok, _activity} = ActivityPub.follow(other_user, user)
1530 user = User.get_cached_by_id(user.id)
1531 other_user = User.get_cached_by_id(other_user.id)
1533 assert User.following?(other_user, user) == false
1537 |> assign(:user, user)
1538 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1540 assert relationship = json_response(conn, 200)
1541 assert to_string(other_user.id) == relationship["id"]
1543 user = User.get_cached_by_id(user.id)
1544 other_user = User.get_cached_by_id(other_user.id)
1546 assert User.following?(other_user, user) == true
1549 test "verify_credentials", %{conn: conn} do
1550 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1554 |> assign(:user, user)
1555 |> get("/api/v1/accounts/verify_credentials")
1557 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1558 assert id == to_string(user.id)
1561 test "/api/v1/follow_requests/:id/reject works" do
1562 user = insert(:user, %{info: %User.Info{locked: true}})
1563 other_user = insert(:user)
1565 {:ok, _activity} = ActivityPub.follow(other_user, user)
1567 user = User.get_cached_by_id(user.id)
1571 |> assign(:user, user)
1572 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1574 assert relationship = json_response(conn, 200)
1575 assert to_string(other_user.id) == relationship["id"]
1577 user = User.get_cached_by_id(user.id)
1578 other_user = User.get_cached_by_id(other_user.id)
1580 assert User.following?(other_user, user) == false
1584 test "account fetching", %{conn: conn} do
1585 user = insert(:user)
1589 |> get("/api/v1/accounts/#{user.id}")
1591 assert %{"id" => id} = json_response(conn, 200)
1592 assert id == to_string(user.id)
1596 |> get("/api/v1/accounts/-1")
1598 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1601 test "account fetching also works nickname", %{conn: conn} do
1602 user = insert(:user)
1606 |> get("/api/v1/accounts/#{user.nickname}")
1608 assert %{"id" => id} = json_response(conn, 200)
1609 assert id == user.id
1612 test "mascot upload", %{conn: conn} do
1613 user = insert(:user)
1615 non_image_file = %Plug.Upload{
1616 content_type: "audio/mpeg",
1617 path: Path.absname("test/fixtures/sound.mp3"),
1618 filename: "sound.mp3"
1623 |> assign(:user, user)
1624 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1626 assert json_response(conn, 415)
1628 file = %Plug.Upload{
1629 content_type: "image/jpg",
1630 path: Path.absname("test/fixtures/image.jpg"),
1631 filename: "an_image.jpg"
1636 |> assign(:user, user)
1637 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1639 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1642 test "mascot retrieving", %{conn: conn} do
1643 user = insert(:user)
1644 # When user hasn't set a mascot, we should just get pleroma tan back
1647 |> assign(:user, user)
1648 |> get("/api/v1/pleroma/mascot")
1650 assert %{"url" => url} = json_response(conn, 200)
1651 assert url =~ "pleroma-fox-tan-smol"
1653 # When a user sets their mascot, we should get that back
1654 file = %Plug.Upload{
1655 content_type: "image/jpg",
1656 path: Path.absname("test/fixtures/image.jpg"),
1657 filename: "an_image.jpg"
1662 |> assign(:user, user)
1663 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1665 assert json_response(conn, 200)
1667 user = User.get_cached_by_id(user.id)
1671 |> assign(:user, user)
1672 |> get("/api/v1/pleroma/mascot")
1674 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1675 assert url =~ "an_image"
1678 test "hashtag timeline", %{conn: conn} do
1679 following = insert(:user)
1682 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1684 {:ok, [_activity]} =
1685 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1689 |> get("/api/v1/timelines/tag/2hu")
1691 assert [%{"id" => id}] = json_response(nconn, 200)
1693 assert id == to_string(activity.id)
1695 # works for different capitalization too
1698 |> get("/api/v1/timelines/tag/2HU")
1700 assert [%{"id" => id}] = json_response(nconn, 200)
1702 assert id == to_string(activity.id)
1706 test "multi-hashtag timeline", %{conn: conn} do
1707 user = insert(:user)
1709 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1710 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1711 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1715 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1717 [status_none, status_test1, status_test] = json_response(any_test, 200)
1719 assert to_string(activity_test.id) == status_test["id"]
1720 assert to_string(activity_test1.id) == status_test1["id"]
1721 assert to_string(activity_none.id) == status_none["id"]
1725 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1727 assert [status_test1] == json_response(restricted_test, 200)
1729 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1731 assert [status_none] == json_response(all_test, 200)
1734 test "getting followers", %{conn: conn} do
1735 user = insert(:user)
1736 other_user = insert(:user)
1737 {:ok, user} = User.follow(user, other_user)
1741 |> get("/api/v1/accounts/#{other_user.id}/followers")
1743 assert [%{"id" => id}] = json_response(conn, 200)
1744 assert id == to_string(user.id)
1747 test "getting followers, hide_followers", %{conn: conn} do
1748 user = insert(:user)
1749 other_user = insert(:user, %{info: %{hide_followers: true}})
1750 {:ok, _user} = User.follow(user, other_user)
1754 |> get("/api/v1/accounts/#{other_user.id}/followers")
1756 assert [] == json_response(conn, 200)
1759 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1760 user = insert(:user)
1761 other_user = insert(:user, %{info: %{hide_followers: true}})
1762 {:ok, _user} = User.follow(user, other_user)
1766 |> assign(:user, other_user)
1767 |> get("/api/v1/accounts/#{other_user.id}/followers")
1769 refute [] == json_response(conn, 200)
1772 test "getting followers, pagination", %{conn: conn} do
1773 user = insert(:user)
1774 follower1 = insert(:user)
1775 follower2 = insert(:user)
1776 follower3 = insert(:user)
1777 {:ok, _} = User.follow(follower1, user)
1778 {:ok, _} = User.follow(follower2, user)
1779 {:ok, _} = User.follow(follower3, user)
1783 |> assign(:user, user)
1787 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1789 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1790 assert id3 == follower3.id
1791 assert id2 == follower2.id
1795 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1797 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1798 assert id2 == follower2.id
1799 assert id1 == follower1.id
1803 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1805 assert [%{"id" => id2}] = json_response(res_conn, 200)
1806 assert id2 == follower2.id
1808 assert [link_header] = get_resp_header(res_conn, "link")
1809 assert link_header =~ ~r/min_id=#{follower2.id}/
1810 assert link_header =~ ~r/max_id=#{follower2.id}/
1813 test "getting following", %{conn: conn} do
1814 user = insert(:user)
1815 other_user = insert(:user)
1816 {:ok, user} = User.follow(user, other_user)
1820 |> get("/api/v1/accounts/#{user.id}/following")
1822 assert [%{"id" => id}] = json_response(conn, 200)
1823 assert id == to_string(other_user.id)
1826 test "getting following, hide_follows", %{conn: conn} do
1827 user = insert(:user, %{info: %{hide_follows: true}})
1828 other_user = insert(:user)
1829 {:ok, user} = User.follow(user, other_user)
1833 |> get("/api/v1/accounts/#{user.id}/following")
1835 assert [] == json_response(conn, 200)
1838 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1839 user = insert(:user, %{info: %{hide_follows: true}})
1840 other_user = insert(:user)
1841 {:ok, user} = User.follow(user, other_user)
1845 |> assign(:user, user)
1846 |> get("/api/v1/accounts/#{user.id}/following")
1848 refute [] == json_response(conn, 200)
1851 test "getting following, pagination", %{conn: conn} do
1852 user = insert(:user)
1853 following1 = insert(:user)
1854 following2 = insert(:user)
1855 following3 = insert(:user)
1856 {:ok, _} = User.follow(user, following1)
1857 {:ok, _} = User.follow(user, following2)
1858 {:ok, _} = User.follow(user, following3)
1862 |> assign(:user, user)
1866 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1868 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1869 assert id3 == following3.id
1870 assert id2 == following2.id
1874 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1876 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1877 assert id2 == following2.id
1878 assert id1 == following1.id
1882 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1884 assert [%{"id" => id2}] = json_response(res_conn, 200)
1885 assert id2 == following2.id
1887 assert [link_header] = get_resp_header(res_conn, "link")
1888 assert link_header =~ ~r/min_id=#{following2.id}/
1889 assert link_header =~ ~r/max_id=#{following2.id}/
1892 test "following / unfollowing a user", %{conn: conn} do
1893 user = insert(:user)
1894 other_user = insert(:user)
1898 |> assign(:user, user)
1899 |> post("/api/v1/accounts/#{other_user.id}/follow")
1901 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
1903 user = User.get_cached_by_id(user.id)
1907 |> assign(:user, user)
1908 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
1910 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
1912 user = User.get_cached_by_id(user.id)
1916 |> assign(:user, user)
1917 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
1919 assert %{"id" => id} = json_response(conn, 200)
1920 assert id == to_string(other_user.id)
1923 test "following without reblogs" do
1924 follower = insert(:user)
1925 followed = insert(:user)
1926 other_user = insert(:user)
1930 |> assign(:user, follower)
1931 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
1933 assert %{"showing_reblogs" => false} = json_response(conn, 200)
1935 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
1936 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
1940 |> assign(:user, User.get_cached_by_id(follower.id))
1941 |> get("/api/v1/timelines/home")
1943 assert [] == json_response(conn, 200)
1947 |> assign(:user, follower)
1948 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
1950 assert %{"showing_reblogs" => true} = json_response(conn, 200)
1954 |> assign(:user, User.get_cached_by_id(follower.id))
1955 |> get("/api/v1/timelines/home")
1957 expected_activity_id = reblog.id
1958 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
1961 test "following / unfollowing errors" do
1962 user = insert(:user)
1966 |> assign(:user, user)
1969 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
1970 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1973 user = User.get_cached_by_id(user.id)
1974 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
1975 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1977 # self follow via uri
1978 user = User.get_cached_by_id(user.id)
1979 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
1980 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1982 # follow non existing user
1983 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
1984 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1986 # follow non existing user via uri
1987 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
1988 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1990 # unfollow non existing user
1991 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
1992 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1995 test "muting / unmuting a user", %{conn: conn} do
1996 user = insert(:user)
1997 other_user = insert(:user)
2001 |> assign(:user, user)
2002 |> post("/api/v1/accounts/#{other_user.id}/mute")
2004 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
2006 user = User.get_cached_by_id(user.id)
2010 |> assign(:user, user)
2011 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2013 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
2016 test "subscribing / unsubscribing to a user", %{conn: conn} do
2017 user = insert(:user)
2018 subscription_target = insert(:user)
2022 |> assign(:user, user)
2023 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2025 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2029 |> assign(:user, user)
2030 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2032 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2035 test "getting a list of mutes", %{conn: conn} do
2036 user = insert(:user)
2037 other_user = insert(:user)
2039 {:ok, user} = User.mute(user, other_user)
2043 |> assign(:user, user)
2044 |> get("/api/v1/mutes")
2046 other_user_id = to_string(other_user.id)
2047 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2050 test "blocking / unblocking a user", %{conn: conn} do
2051 user = insert(:user)
2052 other_user = insert(:user)
2056 |> assign(:user, user)
2057 |> post("/api/v1/accounts/#{other_user.id}/block")
2059 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2061 user = User.get_cached_by_id(user.id)
2065 |> assign(:user, user)
2066 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2068 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2071 test "getting a list of blocks", %{conn: conn} do
2072 user = insert(:user)
2073 other_user = insert(:user)
2075 {:ok, user} = User.block(user, other_user)
2079 |> assign(:user, user)
2080 |> get("/api/v1/blocks")
2082 other_user_id = to_string(other_user.id)
2083 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2086 test "blocking / unblocking a domain", %{conn: conn} do
2087 user = insert(:user)
2088 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2092 |> assign(:user, user)
2093 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2095 assert %{} = json_response(conn, 200)
2096 user = User.get_cached_by_ap_id(user.ap_id)
2097 assert User.blocks?(user, other_user)
2101 |> assign(:user, user)
2102 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2104 assert %{} = json_response(conn, 200)
2105 user = User.get_cached_by_ap_id(user.ap_id)
2106 refute User.blocks?(user, other_user)
2109 test "getting a list of domain blocks", %{conn: conn} do
2110 user = insert(:user)
2112 {:ok, user} = User.block_domain(user, "bad.site")
2113 {:ok, user} = User.block_domain(user, "even.worse.site")
2117 |> assign(:user, user)
2118 |> get("/api/v1/domain_blocks")
2120 domain_blocks = json_response(conn, 200)
2122 assert "bad.site" in domain_blocks
2123 assert "even.worse.site" in domain_blocks
2126 test "unimplemented follow_requests, blocks, domain blocks" do
2127 user = insert(:user)
2129 ["blocks", "domain_blocks", "follow_requests"]
2130 |> Enum.each(fn endpoint ->
2133 |> assign(:user, user)
2134 |> get("/api/v1/#{endpoint}")
2136 assert [] = json_response(conn, 200)
2140 test "returns the favorites of a user", %{conn: conn} do
2141 user = insert(:user)
2142 other_user = insert(:user)
2144 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2145 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2147 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2151 |> assign(:user, user)
2152 |> get("/api/v1/favourites")
2154 assert [status] = json_response(first_conn, 200)
2155 assert status["id"] == to_string(activity.id)
2157 assert [{"link", _link_header}] =
2158 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2160 # Honours query params
2161 {:ok, second_activity} =
2162 CommonAPI.post(other_user, %{
2164 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2167 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2169 last_like = status["id"]
2173 |> assign(:user, user)
2174 |> get("/api/v1/favourites?since_id=#{last_like}")
2176 assert [second_status] = json_response(second_conn, 200)
2177 assert second_status["id"] == to_string(second_activity.id)
2181 |> assign(:user, user)
2182 |> get("/api/v1/favourites?limit=0")
2184 assert [] = json_response(third_conn, 200)
2187 describe "getting favorites timeline of specified user" do
2189 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2190 [current_user: current_user, user: user]
2193 test "returns list of statuses favorited by specified user", %{
2195 current_user: current_user,
2198 [activity | _] = insert_pair(:note_activity)
2199 CommonAPI.favorite(activity.id, user)
2203 |> assign(:user, current_user)
2204 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2205 |> json_response(:ok)
2209 assert length(response) == 1
2210 assert like["id"] == activity.id
2213 test "returns favorites for specified user_id when user is not logged in", %{
2217 activity = insert(:note_activity)
2218 CommonAPI.favorite(activity.id, user)
2222 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2223 |> json_response(:ok)
2225 assert length(response) == 1
2228 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2230 current_user: current_user,
2234 CommonAPI.post(current_user, %{
2235 "status" => "Hi @#{user.nickname}!",
2236 "visibility" => "direct"
2239 CommonAPI.favorite(direct.id, user)
2243 |> assign(:user, current_user)
2244 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2245 |> json_response(:ok)
2247 assert length(response) == 1
2249 anonymous_response =
2251 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2252 |> json_response(:ok)
2254 assert Enum.empty?(anonymous_response)
2257 test "does not return others' favorited DM when user is not one of recipients", %{
2259 current_user: current_user,
2262 user_two = insert(:user)
2265 CommonAPI.post(user_two, %{
2266 "status" => "Hi @#{user.nickname}!",
2267 "visibility" => "direct"
2270 CommonAPI.favorite(direct.id, user)
2274 |> assign(:user, current_user)
2275 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2276 |> json_response(:ok)
2278 assert Enum.empty?(response)
2281 test "paginates favorites using since_id and max_id", %{
2283 current_user: current_user,
2286 activities = insert_list(10, :note_activity)
2288 Enum.each(activities, fn activity ->
2289 CommonAPI.favorite(activity.id, user)
2292 third_activity = Enum.at(activities, 2)
2293 seventh_activity = Enum.at(activities, 6)
2297 |> assign(:user, current_user)
2298 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2299 since_id: third_activity.id,
2300 max_id: seventh_activity.id
2302 |> json_response(:ok)
2304 assert length(response) == 3
2305 refute third_activity in response
2306 refute seventh_activity in response
2309 test "limits favorites using limit parameter", %{
2311 current_user: current_user,
2315 |> insert_list(:note_activity)
2316 |> Enum.each(fn activity ->
2317 CommonAPI.favorite(activity.id, user)
2322 |> assign(:user, current_user)
2323 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2324 |> json_response(:ok)
2326 assert length(response) == 3
2329 test "returns empty response when user does not have any favorited statuses", %{
2331 current_user: current_user,
2336 |> assign(:user, current_user)
2337 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2338 |> json_response(:ok)
2340 assert Enum.empty?(response)
2343 test "returns 404 error when specified user is not exist", %{conn: conn} do
2344 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2346 assert json_response(conn, 404) == %{"error" => "Record not found"}
2349 test "returns 403 error when user has hidden own favorites", %{
2351 current_user: current_user
2353 user = insert(:user, %{info: %{hide_favorites: true}})
2354 activity = insert(:note_activity)
2355 CommonAPI.favorite(activity.id, user)
2359 |> assign(:user, current_user)
2360 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2362 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2365 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2366 user = insert(:user)
2367 activity = insert(:note_activity)
2368 CommonAPI.favorite(activity.id, user)
2372 |> assign(:user, current_user)
2373 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2375 assert user.info.hide_favorites
2376 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2380 test "get instance information", %{conn: conn} do
2381 conn = get(conn, "/api/v1/instance")
2382 assert result = json_response(conn, 200)
2384 email = Pleroma.Config.get([:instance, :email])
2385 # Note: not checking for "max_toot_chars" since it's optional
2391 "email" => from_config_email,
2393 "streaming_api" => _
2398 "registrations" => _,
2402 assert email == from_config_email
2405 test "get instance stats", %{conn: conn} do
2406 user = insert(:user, %{local: true})
2408 user2 = insert(:user, %{local: true})
2409 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2411 insert(:user, %{local: false, nickname: "u@peer1.com"})
2412 insert(:user, %{local: false, nickname: "u@peer2.com"})
2414 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2416 # Stats should count users with missing or nil `info.deactivated` value
2417 user = User.get_cached_by_id(user.id)
2418 info_change = Changeset.change(user.info, %{deactivated: nil})
2422 |> Changeset.change()
2423 |> Changeset.put_embed(:info, info_change)
2424 |> User.update_and_set_cache()
2426 Pleroma.Stats.update_stats()
2428 conn = get(conn, "/api/v1/instance")
2430 assert result = json_response(conn, 200)
2432 stats = result["stats"]
2435 assert stats["user_count"] == 1
2436 assert stats["status_count"] == 1
2437 assert stats["domain_count"] == 2
2440 test "get peers", %{conn: conn} do
2441 insert(:user, %{local: false, nickname: "u@peer1.com"})
2442 insert(:user, %{local: false, nickname: "u@peer2.com"})
2444 Pleroma.Stats.update_stats()
2446 conn = get(conn, "/api/v1/instance/peers")
2448 assert result = json_response(conn, 200)
2450 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2453 test "put settings", %{conn: conn} do
2454 user = insert(:user)
2458 |> assign(:user, user)
2459 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2461 assert _result = json_response(conn, 200)
2463 user = User.get_cached_by_ap_id(user.ap_id)
2464 assert user.info.settings == %{"programming" => "socks"}
2467 describe "pinned statuses" do
2469 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2471 user = insert(:user)
2472 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2474 [user: user, activity: activity]
2477 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2478 {:ok, _} = CommonAPI.pin(activity.id, user)
2482 |> assign(:user, user)
2483 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2484 |> json_response(200)
2486 id_str = to_string(activity.id)
2488 assert [%{"id" => ^id_str, "pinned" => true}] = result
2491 test "pin status", %{conn: conn, user: user, activity: activity} do
2492 id_str = to_string(activity.id)
2494 assert %{"id" => ^id_str, "pinned" => true} =
2496 |> assign(:user, user)
2497 |> post("/api/v1/statuses/#{activity.id}/pin")
2498 |> json_response(200)
2500 assert [%{"id" => ^id_str, "pinned" => true}] =
2502 |> assign(:user, user)
2503 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2504 |> json_response(200)
2507 test "unpin status", %{conn: conn, user: user, activity: activity} do
2508 {:ok, _} = CommonAPI.pin(activity.id, user)
2510 id_str = to_string(activity.id)
2511 user = refresh_record(user)
2513 assert %{"id" => ^id_str, "pinned" => false} =
2515 |> assign(:user, user)
2516 |> post("/api/v1/statuses/#{activity.id}/unpin")
2517 |> json_response(200)
2521 |> assign(:user, user)
2522 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2523 |> json_response(200)
2526 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2527 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2529 id_str_one = to_string(activity_one.id)
2531 assert %{"id" => ^id_str_one, "pinned" => true} =
2533 |> assign(:user, user)
2534 |> post("/api/v1/statuses/#{id_str_one}/pin")
2535 |> json_response(200)
2537 user = refresh_record(user)
2539 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2541 |> assign(:user, user)
2542 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2543 |> json_response(400)
2549 Pleroma.Config.put([:rich_media, :enabled], true)
2552 Pleroma.Config.put([:rich_media, :enabled], false)
2555 user = insert(:user)
2559 test "returns rich-media card", %{conn: conn, user: user} do
2560 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2563 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2564 "provider_name" => "www.imdb.com",
2565 "provider_url" => "http://www.imdb.com",
2566 "title" => "The Rock",
2568 "url" => "http://www.imdb.com/title/tt0117500/",
2570 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2573 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2574 "title" => "The Rock",
2575 "type" => "video.movie",
2576 "url" => "http://www.imdb.com/title/tt0117500/",
2578 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2585 |> get("/api/v1/statuses/#{activity.id}/card")
2586 |> json_response(200)
2588 assert response == card_data
2590 # works with private posts
2592 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2596 |> assign(:user, user)
2597 |> get("/api/v1/statuses/#{activity.id}/card")
2598 |> json_response(200)
2600 assert response_two == card_data
2603 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2605 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2609 |> get("/api/v1/statuses/#{activity.id}/card")
2610 |> json_response(:ok)
2612 assert response == %{
2614 "title" => "Pleroma",
2615 "description" => "",
2617 "provider_name" => "pleroma.social",
2618 "provider_url" => "https://pleroma.social",
2619 "url" => "https://pleroma.social/",
2622 "title" => "Pleroma",
2623 "type" => "website",
2624 "url" => "https://pleroma.social/"
2632 user = insert(:user)
2633 for_user = insert(:user)
2636 CommonAPI.post(user, %{
2637 "status" => "heweoo?"
2641 CommonAPI.post(user, %{
2642 "status" => "heweoo!"
2647 |> assign(:user, for_user)
2648 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2650 assert json_response(response1, 200)["bookmarked"] == true
2654 |> assign(:user, for_user)
2655 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2657 assert json_response(response2, 200)["bookmarked"] == true
2661 |> assign(:user, for_user)
2662 |> get("/api/v1/bookmarks")
2664 assert [json_response(response2, 200), json_response(response1, 200)] ==
2665 json_response(bookmarks, 200)
2669 |> assign(:user, for_user)
2670 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2672 assert json_response(response1, 200)["bookmarked"] == false
2676 |> assign(:user, for_user)
2677 |> get("/api/v1/bookmarks")
2679 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2682 describe "conversation muting" do
2684 user = insert(:user)
2685 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2687 [user: user, activity: activity]
2690 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2691 id_str = to_string(activity.id)
2693 assert %{"id" => ^id_str, "muted" => true} =
2695 |> assign(:user, user)
2696 |> post("/api/v1/statuses/#{activity.id}/mute")
2697 |> json_response(200)
2700 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2701 {:ok, _} = CommonAPI.add_mute(user, activity)
2703 id_str = to_string(activity.id)
2704 user = refresh_record(user)
2706 assert %{"id" => ^id_str, "muted" => false} =
2708 |> assign(:user, user)
2709 |> post("/api/v1/statuses/#{activity.id}/unmute")
2710 |> json_response(200)
2714 describe "reports" do
2716 reporter = insert(:user)
2717 target_user = insert(:user)
2719 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2721 [reporter: reporter, target_user: target_user, activity: activity]
2724 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2725 assert %{"action_taken" => false, "id" => _} =
2727 |> assign(:user, reporter)
2728 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2729 |> json_response(200)
2732 test "submit a report with statuses and comment", %{
2735 target_user: target_user,
2738 assert %{"action_taken" => false, "id" => _} =
2740 |> assign(:user, reporter)
2741 |> post("/api/v1/reports", %{
2742 "account_id" => target_user.id,
2743 "status_ids" => [activity.id],
2744 "comment" => "bad status!"
2746 |> json_response(200)
2749 test "account_id is required", %{
2754 assert %{"error" => "Valid `account_id` required"} =
2756 |> assign(:user, reporter)
2757 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2758 |> json_response(400)
2761 test "comment must be up to the size specified in the config", %{
2764 target_user: target_user
2766 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2767 comment = String.pad_trailing("a", max_size + 1, "a")
2769 error = %{"error" => "Comment must be up to #{max_size} characters"}
2773 |> assign(:user, reporter)
2774 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2775 |> json_response(400)
2779 describe "link headers" do
2780 test "preserves parameters in link headers", %{conn: conn} do
2781 user = insert(:user)
2782 other_user = insert(:user)
2785 CommonAPI.post(other_user, %{
2786 "status" => "hi @#{user.nickname}",
2787 "visibility" => "public"
2791 CommonAPI.post(other_user, %{
2792 "status" => "hi @#{user.nickname}",
2793 "visibility" => "public"
2796 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2797 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2801 |> assign(:user, user)
2802 |> get("/api/v1/notifications", %{media_only: true})
2804 assert [link_header] = get_resp_header(conn, "link")
2805 assert link_header =~ ~r/media_only=true/
2806 assert link_header =~ ~r/min_id=#{notification2.id}/
2807 assert link_header =~ ~r/max_id=#{notification1.id}/
2811 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2812 # Need to set an old-style integer ID to reproduce the problem
2813 # (these are no longer assigned to new accounts but were preserved
2814 # for existing accounts during the migration to flakeIDs)
2815 user_one = insert(:user, %{id: 1212})
2816 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2820 |> get("/api/v1/accounts/#{user_one.id}")
2824 |> get("/api/v1/accounts/#{user_two.nickname}")
2828 |> get("/api/v1/accounts/#{user_two.id}")
2830 acc_one = json_response(resp_one, 200)
2831 acc_two = json_response(resp_two, 200)
2832 acc_three = json_response(resp_three, 200)
2833 refute acc_one == acc_two
2834 assert acc_two == acc_three
2837 describe "custom emoji" do
2838 test "with tags", %{conn: conn} do
2841 |> get("/api/v1/custom_emojis")
2842 |> json_response(200)
2844 assert Map.has_key?(emoji, "shortcode")
2845 assert Map.has_key?(emoji, "static_url")
2846 assert Map.has_key?(emoji, "tags")
2847 assert is_list(emoji["tags"])
2848 assert Map.has_key?(emoji, "url")
2849 assert Map.has_key?(emoji, "visible_in_picker")
2853 describe "index/2 redirections" do
2854 setup %{conn: conn} do
2858 signing_salt: "cooldude"
2863 |> Plug.Session.call(Plug.Session.init(session_opts))
2866 test_path = "/web/statuses/test"
2867 %{conn: conn, path: test_path}
2870 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
2871 conn = get(conn, path)
2873 assert conn.status == 302
2874 assert redirected_to(conn) == "/web/login"
2877 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
2878 token = insert(:oauth_token)
2882 |> assign(:user, token.user)
2883 |> put_session(:oauth_token, token.token)
2886 assert conn.status == 200
2889 test "saves referer path to session", %{conn: conn, path: path} do
2890 conn = get(conn, path)
2891 return_to = Plug.Conn.get_session(conn, :return_to)
2893 assert return_to == path
2896 test "redirects to the saved path after log in", %{conn: conn, path: path} do
2897 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2898 auth = insert(:oauth_authorization, app: app)
2902 |> put_session(:return_to, path)
2903 |> get("/web/login", %{code: auth.token})
2905 assert conn.status == 302
2906 assert redirected_to(conn) == path
2909 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
2910 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2911 auth = insert(:oauth_authorization, app: app)
2913 conn = get(conn, "/web/login", %{code: auth.token})
2915 assert conn.status == 302
2916 assert redirected_to(conn) == "/web/getting-started"
2920 describe "scheduled activities" do
2921 test "creates a scheduled activity", %{conn: conn} do
2922 user = insert(:user)
2923 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2927 |> assign(:user, user)
2928 |> post("/api/v1/statuses", %{
2929 "status" => "scheduled",
2930 "scheduled_at" => scheduled_at
2933 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
2934 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
2935 assert [] == Repo.all(Activity)
2938 test "creates a scheduled activity with a media attachment", %{conn: conn} do
2939 user = insert(:user)
2940 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2942 file = %Plug.Upload{
2943 content_type: "image/jpg",
2944 path: Path.absname("test/fixtures/image.jpg"),
2945 filename: "an_image.jpg"
2948 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
2952 |> assign(:user, user)
2953 |> post("/api/v1/statuses", %{
2954 "media_ids" => [to_string(upload.id)],
2955 "status" => "scheduled",
2956 "scheduled_at" => scheduled_at
2959 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
2960 assert %{"type" => "image"} = media_attachment
2963 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
2965 user = insert(:user)
2968 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
2972 |> assign(:user, user)
2973 |> post("/api/v1/statuses", %{
2974 "status" => "not scheduled",
2975 "scheduled_at" => scheduled_at
2978 assert %{"content" => "not scheduled"} = json_response(conn, 200)
2979 assert [] == Repo.all(ScheduledActivity)
2982 test "returns error when daily user limit is exceeded", %{conn: conn} do
2983 user = insert(:user)
2986 NaiveDateTime.utc_now()
2987 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
2988 |> NaiveDateTime.to_iso8601()
2990 attrs = %{params: %{}, scheduled_at: today}
2991 {:ok, _} = ScheduledActivity.create(user, attrs)
2992 {:ok, _} = ScheduledActivity.create(user, attrs)
2996 |> assign(:user, user)
2997 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
2999 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3002 test "returns error when total user limit is exceeded", %{conn: conn} do
3003 user = insert(:user)
3006 NaiveDateTime.utc_now()
3007 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3008 |> NaiveDateTime.to_iso8601()
3011 NaiveDateTime.utc_now()
3012 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3013 |> NaiveDateTime.to_iso8601()
3015 attrs = %{params: %{}, scheduled_at: today}
3016 {:ok, _} = ScheduledActivity.create(user, attrs)
3017 {:ok, _} = ScheduledActivity.create(user, attrs)
3018 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3022 |> assign(:user, user)
3023 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3025 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3028 test "shows scheduled activities", %{conn: conn} do
3029 user = insert(:user)
3030 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3031 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3032 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3033 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3037 |> assign(:user, user)
3042 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3044 result = json_response(conn_res, 200)
3045 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3050 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3052 result = json_response(conn_res, 200)
3053 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3058 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3060 result = json_response(conn_res, 200)
3061 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3064 test "shows a scheduled activity", %{conn: conn} do
3065 user = insert(:user)
3066 scheduled_activity = insert(:scheduled_activity, user: user)
3070 |> assign(:user, user)
3071 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3073 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3074 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3078 |> assign(:user, user)
3079 |> get("/api/v1/scheduled_statuses/404")
3081 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3084 test "updates a scheduled activity", %{conn: conn} do
3085 user = insert(:user)
3086 scheduled_activity = insert(:scheduled_activity, user: user)
3089 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3093 |> assign(:user, user)
3094 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3095 scheduled_at: new_scheduled_at
3098 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3099 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3103 |> assign(:user, user)
3104 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3106 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3109 test "deletes a scheduled activity", %{conn: conn} do
3110 user = insert(:user)
3111 scheduled_activity = insert(:scheduled_activity, user: user)
3115 |> assign(:user, user)
3116 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3118 assert %{} = json_response(res_conn, 200)
3119 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3123 |> assign(:user, user)
3124 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3126 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3130 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3131 user1 = insert(:user)
3132 user2 = insert(:user)
3133 user3 = insert(:user)
3135 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3137 # Reply to status from another user
3140 |> assign(:user, user2)
3141 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3143 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3145 activity = Activity.get_by_id_with_object(id)
3147 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3148 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3150 # Reblog from the third user
3153 |> assign(:user, user3)
3154 |> post("/api/v1/statuses/#{activity.id}/reblog")
3156 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3157 json_response(conn2, 200)
3159 assert to_string(activity.id) == id
3161 # Getting third user status
3164 |> assign(:user, user3)
3165 |> get("api/v1/timelines/home")
3167 [reblogged_activity] = json_response(conn3, 200)
3169 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3171 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3172 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3175 describe "create account by app" do
3176 test "Account registration via Application", %{conn: conn} do
3179 |> post("/api/v1/apps", %{
3180 client_name: "client_name",
3181 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3182 scopes: "read, write, follow"
3186 "client_id" => client_id,
3187 "client_secret" => client_secret,
3189 "name" => "client_name",
3190 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3193 } = json_response(conn, 200)
3197 |> post("/oauth/token", %{
3198 grant_type: "client_credentials",
3199 client_id: client_id,
3200 client_secret: client_secret
3203 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3204 json_response(conn, 200)
3207 token_from_db = Repo.get_by(Token, token: token)
3208 assert token_from_db
3210 assert scope == "read write follow"
3214 |> put_req_header("authorization", "Bearer " <> token)
3215 |> post("/api/v1/accounts", %{
3217 email: "lain@example.org",
3218 password: "PlzDontHackLain",
3223 "access_token" => token,
3224 "created_at" => _created_at,
3226 "token_type" => "Bearer"
3227 } = json_response(conn, 200)
3229 token_from_db = Repo.get_by(Token, token: token)
3230 assert token_from_db
3231 token_from_db = Repo.preload(token_from_db, :user)
3232 assert token_from_db.user
3234 assert token_from_db.user.info.confirmation_pending
3237 test "rate limit", %{conn: conn} do
3238 app_token = insert(:oauth_token, user: nil)
3241 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3242 |> Map.put(:remote_ip, {15, 15, 15, 15})
3247 |> post("/api/v1/accounts", %{
3248 username: "#{i}lain",
3249 email: "#{i}lain@example.org",
3250 password: "PlzDontHackLain",
3255 "access_token" => token,
3256 "created_at" => _created_at,
3258 "token_type" => "Bearer"
3259 } = json_response(conn, 200)
3261 token_from_db = Repo.get_by(Token, token: token)
3262 assert token_from_db
3263 token_from_db = Repo.preload(token_from_db, :user)
3264 assert token_from_db.user
3266 assert token_from_db.user.info.confirmation_pending
3271 |> post("/api/v1/accounts", %{
3273 email: "6lain@example.org",
3274 password: "PlzDontHackLain",
3278 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3282 describe "GET /api/v1/polls/:id" do
3283 test "returns poll entity for object id", %{conn: conn} do
3284 user = insert(:user)
3287 CommonAPI.post(user, %{
3288 "status" => "Pleroma does",
3289 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3292 object = Object.normalize(activity)
3296 |> assign(:user, user)
3297 |> get("/api/v1/polls/#{object.id}")
3299 response = json_response(conn, 200)
3301 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3304 test "does not expose polls for private statuses", %{conn: conn} do
3305 user = insert(:user)
3306 other_user = insert(:user)
3309 CommonAPI.post(user, %{
3310 "status" => "Pleroma does",
3311 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3312 "visibility" => "private"
3315 object = Object.normalize(activity)
3319 |> assign(:user, other_user)
3320 |> get("/api/v1/polls/#{object.id}")
3322 assert json_response(conn, 404)
3326 describe "POST /api/v1/polls/:id/votes" do
3327 test "votes are added to the poll", %{conn: conn} do
3328 user = insert(:user)
3329 other_user = insert(:user)
3332 CommonAPI.post(user, %{
3333 "status" => "A very delicious sandwich",
3335 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3341 object = Object.normalize(activity)
3345 |> assign(:user, other_user)
3346 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3348 assert json_response(conn, 200)
3349 object = Object.get_by_id(object.id)
3351 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3356 test "author can't vote", %{conn: conn} do
3357 user = insert(:user)
3360 CommonAPI.post(user, %{
3361 "status" => "Am I cute?",
3362 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3365 object = Object.normalize(activity)
3368 |> assign(:user, user)
3369 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3370 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3372 object = Object.get_by_id(object.id)
3374 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3377 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3378 user = insert(:user)
3379 other_user = insert(:user)
3382 CommonAPI.post(user, %{
3383 "status" => "The glass is",
3384 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3387 object = Object.normalize(activity)
3390 |> assign(:user, other_user)
3391 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3392 |> json_response(422) == %{"error" => "Too many choices"}
3394 object = Object.get_by_id(object.id)
3396 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->