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 describe "posting statuses" do
103 |> assign(:user, user)
108 test "posting a status", %{conn: conn} do
109 idempotency_key = "Pikachu rocks!"
113 |> put_req_header("idempotency-key", idempotency_key)
114 |> post("/api/v1/statuses", %{
116 "spoiler_text" => "2hu",
117 "sensitive" => "false"
120 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
122 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
124 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
125 json_response(conn_one, 200)
127 assert Activity.get_by_id(id)
131 |> put_req_header("idempotency-key", idempotency_key)
132 |> post("/api/v1/statuses", %{
134 "spoiler_text" => "2hu",
135 "sensitive" => "false"
138 assert %{"id" => second_id} = json_response(conn_two, 200)
139 assert id == second_id
143 |> post("/api/v1/statuses", %{
145 "spoiler_text" => "2hu",
146 "sensitive" => "false"
149 assert %{"id" => third_id} = json_response(conn_three, 200)
150 refute id == third_id
153 test "replying to a status", %{conn: conn} do
155 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
159 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
161 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
163 activity = Activity.get_by_id(id)
165 assert activity.data["context"] == replied_to.data["context"]
166 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
169 test "replying to a direct message with visibility other than direct", %{conn: conn} do
171 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
173 Enum.each(["public", "private", "unlisted"], fn visibility ->
176 |> post("/api/v1/statuses", %{
177 "status" => "@#{user.nickname} hey",
178 "in_reply_to_id" => replied_to.id,
179 "visibility" => visibility
182 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
186 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
189 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
191 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
192 assert Activity.get_by_id(id)
195 test "posting a sensitive status", %{conn: conn} do
198 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
200 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
201 assert Activity.get_by_id(id)
204 test "posting a fake status", %{conn: conn} do
207 |> post("/api/v1/statuses", %{
209 "\"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"
212 real_status = json_response(real_conn, 200)
215 assert Object.get_by_ap_id(real_status["uri"])
219 |> Map.put("id", nil)
220 |> Map.put("url", nil)
221 |> Map.put("uri", nil)
222 |> Map.put("created_at", nil)
223 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
227 |> post("/api/v1/statuses", %{
229 "\"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",
233 fake_status = json_response(fake_conn, 200)
236 refute Object.get_by_ap_id(fake_status["uri"])
240 |> Map.put("id", nil)
241 |> Map.put("url", nil)
242 |> Map.put("uri", nil)
243 |> Map.put("created_at", nil)
244 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
246 assert real_status == fake_status
249 test "posting a status with OGP link preview", %{conn: conn} do
250 Pleroma.Config.put([:rich_media, :enabled], true)
254 |> post("/api/v1/statuses", %{
255 "status" => "https://example.com/ogp"
258 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
259 assert Activity.get_by_id(id)
260 Pleroma.Config.put([:rich_media, :enabled], false)
263 test "posting a direct status", %{conn: conn} do
264 user2 = insert(:user)
265 content = "direct cofe @#{user2.nickname}"
269 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
271 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
272 assert activity = Activity.get_by_id(id)
273 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
274 assert activity.data["to"] == [user2.ap_id]
275 assert activity.data["cc"] == []
279 describe "posting polls" do
280 test "posting a poll", %{conn: conn} do
282 time = NaiveDateTime.utc_now()
286 |> assign(:user, user)
287 |> post("/api/v1/statuses", %{
288 "status" => "Who is the #bestgrill?",
289 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
292 response = json_response(conn, 200)
294 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
295 title in ["Rei", "Asuka", "Misato"]
298 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
299 refute response["poll"]["expred"]
302 test "option limit is enforced", %{conn: conn} do
304 limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
308 |> assign(:user, user)
309 |> post("/api/v1/statuses", %{
311 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
314 %{"error" => error} = json_response(conn, 422)
315 assert error == "Poll can't contain more than #{limit} options"
318 test "option character limit is enforced", %{conn: conn} do
320 limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
324 |> assign(:user, user)
325 |> post("/api/v1/statuses", %{
328 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
333 %{"error" => error} = json_response(conn, 422)
334 assert error == "Poll options cannot be longer than #{limit} characters each"
337 test "minimal date limit is enforced", %{conn: conn} do
339 limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
343 |> assign(:user, user)
344 |> post("/api/v1/statuses", %{
345 "status" => "imagine arbitrary limits",
347 "options" => ["this post was made by pleroma gang"],
348 "expires_in" => limit - 1
352 %{"error" => error} = json_response(conn, 422)
353 assert error == "Expiration date is too soon"
356 test "maximum date limit is enforced", %{conn: conn} do
358 limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
362 |> assign(:user, user)
363 |> post("/api/v1/statuses", %{
364 "status" => "imagine arbitrary limits",
366 "options" => ["this post was made by pleroma gang"],
367 "expires_in" => limit + 1
371 %{"error" => error} = json_response(conn, 422)
372 assert error == "Expiration date is too far in the future"
376 test "direct timeline", %{conn: conn} do
377 user_one = insert(:user)
378 user_two = insert(:user)
380 {:ok, user_two} = User.follow(user_two, user_one)
383 CommonAPI.post(user_one, %{
384 "status" => "Hi @#{user_two.nickname}!",
385 "visibility" => "direct"
388 {:ok, _follower_only} =
389 CommonAPI.post(user_one, %{
390 "status" => "Hi @#{user_two.nickname}!",
391 "visibility" => "private"
394 # Only direct should be visible here
397 |> assign(:user, user_two)
398 |> get("api/v1/timelines/direct")
400 [status] = json_response(res_conn, 200)
402 assert %{"visibility" => "direct"} = status
403 assert status["url"] != direct.data["id"]
405 # User should be able to see his own direct message
408 |> assign(:user, user_one)
409 |> get("api/v1/timelines/direct")
411 [status] = json_response(res_conn, 200)
413 assert %{"visibility" => "direct"} = status
415 # Both should be visible here
418 |> assign(:user, user_two)
419 |> get("api/v1/timelines/home")
421 [_s1, _s2] = json_response(res_conn, 200)
424 Enum.each(1..20, fn _ ->
426 CommonAPI.post(user_one, %{
427 "status" => "Hi @#{user_two.nickname}!",
428 "visibility" => "direct"
434 |> assign(:user, user_two)
435 |> get("api/v1/timelines/direct")
437 statuses = json_response(res_conn, 200)
438 assert length(statuses) == 20
442 |> assign(:user, user_two)
443 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
445 [status] = json_response(res_conn, 200)
447 assert status["url"] != direct.data["id"]
450 test "Conversations", %{conn: conn} do
451 user_one = insert(:user)
452 user_two = insert(:user)
453 user_three = insert(:user)
455 {:ok, user_two} = User.follow(user_two, user_one)
458 CommonAPI.post(user_one, %{
459 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
460 "visibility" => "direct"
463 {:ok, _follower_only} =
464 CommonAPI.post(user_one, %{
465 "status" => "Hi @#{user_two.nickname}!",
466 "visibility" => "private"
471 |> assign(:user, user_one)
472 |> get("/api/v1/conversations")
474 assert response = json_response(res_conn, 200)
479 "accounts" => res_accounts,
480 "last_status" => res_last_status,
485 account_ids = Enum.map(res_accounts, & &1["id"])
486 assert length(res_accounts) == 2
487 assert user_two.id in account_ids
488 assert user_three.id in account_ids
489 assert is_binary(res_id)
490 assert unread == true
491 assert res_last_status["id"] == direct.id
493 # Apparently undocumented API endpoint
496 |> assign(:user, user_one)
497 |> post("/api/v1/conversations/#{res_id}/read")
499 assert response = json_response(res_conn, 200)
500 assert length(response["accounts"]) == 2
501 assert response["last_status"]["id"] == direct.id
502 assert response["unread"] == false
504 # (vanilla) Mastodon frontend behaviour
507 |> assign(:user, user_one)
508 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
510 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
513 test "doesn't include DMs from blocked users", %{conn: conn} do
514 blocker = insert(:user)
515 blocked = insert(:user)
517 {:ok, blocker} = User.block(blocker, blocked)
519 {:ok, _blocked_direct} =
520 CommonAPI.post(blocked, %{
521 "status" => "Hi @#{blocker.nickname}!",
522 "visibility" => "direct"
526 CommonAPI.post(user, %{
527 "status" => "Hi @#{blocker.nickname}!",
528 "visibility" => "direct"
533 |> assign(:user, user)
534 |> get("api/v1/timelines/direct")
536 [status] = json_response(res_conn, 200)
537 assert status["id"] == direct.id
540 test "verify_credentials", %{conn: conn} do
545 |> assign(:user, user)
546 |> get("/api/v1/accounts/verify_credentials")
548 response = json_response(conn, 200)
550 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
551 assert response["pleroma"]["chat_token"]
552 assert id == to_string(user.id)
555 test "verify_credentials default scope unlisted", %{conn: conn} do
556 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
560 |> assign(:user, user)
561 |> get("/api/v1/accounts/verify_credentials")
563 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
564 assert id == to_string(user.id)
567 test "apps/verify_credentials", %{conn: conn} do
568 token = insert(:oauth_token)
572 |> assign(:user, token.user)
573 |> assign(:token, token)
574 |> get("/api/v1/apps/verify_credentials")
576 app = Repo.preload(token, :app).app
579 "name" => app.client_name,
580 "website" => app.website,
581 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
584 assert expected == json_response(conn, 200)
587 test "creates an oauth app", %{conn: conn} do
589 app_attrs = build(:oauth_app)
593 |> assign(:user, user)
594 |> post("/api/v1/apps", %{
595 client_name: app_attrs.client_name,
596 redirect_uris: app_attrs.redirect_uris
599 [app] = Repo.all(App)
602 "name" => app.client_name,
603 "website" => app.website,
604 "client_id" => app.client_id,
605 "client_secret" => app.client_secret,
606 "id" => app.id |> to_string(),
607 "redirect_uri" => app.redirect_uris,
608 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
611 assert expected == json_response(conn, 200)
614 test "get a status", %{conn: conn} do
615 activity = insert(:note_activity)
619 |> get("/api/v1/statuses/#{activity.id}")
621 assert %{"id" => id} = json_response(conn, 200)
622 assert id == to_string(activity.id)
625 describe "deleting a status" do
626 test "when you created it", %{conn: conn} do
627 activity = insert(:note_activity)
628 author = User.get_cached_by_ap_id(activity.data["actor"])
632 |> assign(:user, author)
633 |> delete("/api/v1/statuses/#{activity.id}")
635 assert %{} = json_response(conn, 200)
637 refute Activity.get_by_id(activity.id)
640 test "when you didn't create it", %{conn: conn} do
641 activity = insert(:note_activity)
646 |> assign(:user, user)
647 |> delete("/api/v1/statuses/#{activity.id}")
649 assert %{"error" => _} = json_response(conn, 403)
651 assert Activity.get_by_id(activity.id) == activity
654 test "when you're an admin or moderator", %{conn: conn} do
655 activity1 = insert(:note_activity)
656 activity2 = insert(:note_activity)
657 admin = insert(:user, info: %{is_admin: true})
658 moderator = insert(:user, info: %{is_moderator: true})
662 |> assign(:user, admin)
663 |> delete("/api/v1/statuses/#{activity1.id}")
665 assert %{} = json_response(res_conn, 200)
669 |> assign(:user, moderator)
670 |> delete("/api/v1/statuses/#{activity2.id}")
672 assert %{} = json_response(res_conn, 200)
674 refute Activity.get_by_id(activity1.id)
675 refute Activity.get_by_id(activity2.id)
679 describe "filters" do
680 test "creating a filter", %{conn: conn} do
683 filter = %Pleroma.Filter{
690 |> assign(:user, user)
691 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
693 assert response = json_response(conn, 200)
694 assert response["phrase"] == filter.phrase
695 assert response["context"] == filter.context
696 assert response["irreversible"] == false
697 assert response["id"] != nil
698 assert response["id"] != ""
701 test "fetching a list of filters", %{conn: conn} do
704 query_one = %Pleroma.Filter{
711 query_two = %Pleroma.Filter{
718 {:ok, filter_one} = Pleroma.Filter.create(query_one)
719 {:ok, filter_two} = Pleroma.Filter.create(query_two)
723 |> assign(:user, user)
724 |> get("/api/v1/filters")
725 |> json_response(200)
731 filters: [filter_two, filter_one]
735 test "get a filter", %{conn: conn} do
738 query = %Pleroma.Filter{
745 {:ok, filter} = Pleroma.Filter.create(query)
749 |> assign(:user, user)
750 |> get("/api/v1/filters/#{filter.filter_id}")
752 assert _response = json_response(conn, 200)
755 test "update a filter", %{conn: conn} do
758 query = %Pleroma.Filter{
765 {:ok, _filter} = Pleroma.Filter.create(query)
767 new = %Pleroma.Filter{
774 |> assign(:user, user)
775 |> put("/api/v1/filters/#{query.filter_id}", %{
780 assert response = json_response(conn, 200)
781 assert response["phrase"] == new.phrase
782 assert response["context"] == new.context
785 test "delete a filter", %{conn: conn} do
788 query = %Pleroma.Filter{
795 {:ok, filter} = Pleroma.Filter.create(query)
799 |> assign(:user, user)
800 |> delete("/api/v1/filters/#{filter.filter_id}")
802 assert response = json_response(conn, 200)
803 assert response == %{}
808 test "creating a list", %{conn: conn} do
813 |> assign(:user, user)
814 |> post("/api/v1/lists", %{"title" => "cuties"})
816 assert %{"title" => title} = json_response(conn, 200)
817 assert title == "cuties"
820 test "adding users to a list", %{conn: conn} do
822 other_user = insert(:user)
823 {:ok, list} = Pleroma.List.create("name", user)
827 |> assign(:user, user)
828 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
830 assert %{} == json_response(conn, 200)
831 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
832 assert following == [other_user.follower_address]
835 test "removing users from a list", %{conn: conn} do
837 other_user = insert(:user)
838 third_user = insert(:user)
839 {:ok, list} = Pleroma.List.create("name", user)
840 {:ok, list} = Pleroma.List.follow(list, other_user)
841 {:ok, list} = Pleroma.List.follow(list, third_user)
845 |> assign(:user, user)
846 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
848 assert %{} == json_response(conn, 200)
849 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
850 assert following == [third_user.follower_address]
853 test "listing users in a list", %{conn: conn} do
855 other_user = insert(:user)
856 {:ok, list} = Pleroma.List.create("name", user)
857 {:ok, list} = Pleroma.List.follow(list, other_user)
861 |> assign(:user, user)
862 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
864 assert [%{"id" => id}] = json_response(conn, 200)
865 assert id == to_string(other_user.id)
868 test "retrieving a list", %{conn: conn} do
870 {:ok, list} = Pleroma.List.create("name", user)
874 |> assign(:user, user)
875 |> get("/api/v1/lists/#{list.id}")
877 assert %{"id" => id} = json_response(conn, 200)
878 assert id == to_string(list.id)
881 test "renaming a list", %{conn: conn} do
883 {:ok, list} = Pleroma.List.create("name", user)
887 |> assign(:user, user)
888 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
890 assert %{"title" => name} = json_response(conn, 200)
891 assert name == "newname"
894 test "deleting a list", %{conn: conn} do
896 {:ok, list} = Pleroma.List.create("name", user)
900 |> assign(:user, user)
901 |> delete("/api/v1/lists/#{list.id}")
903 assert %{} = json_response(conn, 200)
904 assert is_nil(Repo.get(Pleroma.List, list.id))
907 test "list timeline", %{conn: conn} do
909 other_user = insert(:user)
910 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
911 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
912 {:ok, list} = Pleroma.List.create("name", user)
913 {:ok, list} = Pleroma.List.follow(list, other_user)
917 |> assign(:user, user)
918 |> get("/api/v1/timelines/list/#{list.id}")
920 assert [%{"id" => id}] = json_response(conn, 200)
922 assert id == to_string(activity_two.id)
925 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
927 other_user = insert(:user)
928 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
930 {:ok, _activity_two} =
931 TwitterAPI.create_status(other_user, %{
932 "status" => "Marisa is cute.",
933 "visibility" => "private"
936 {:ok, list} = Pleroma.List.create("name", user)
937 {:ok, list} = Pleroma.List.follow(list, other_user)
941 |> assign(:user, user)
942 |> get("/api/v1/timelines/list/#{list.id}")
944 assert [%{"id" => id}] = json_response(conn, 200)
946 assert id == to_string(activity_one.id)
950 describe "notifications" do
951 test "list of notifications", %{conn: conn} do
953 other_user = insert(:user)
956 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
958 {:ok, [_notification]} = Notification.create_notifications(activity)
962 |> assign(:user, user)
963 |> get("/api/v1/notifications")
966 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
968 }\">@<span>#{user.nickname}</span></a></span>"
970 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
971 assert response == expected_response
974 test "getting a single notification", %{conn: conn} do
976 other_user = insert(:user)
979 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
981 {:ok, [notification]} = Notification.create_notifications(activity)
985 |> assign(:user, user)
986 |> get("/api/v1/notifications/#{notification.id}")
989 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
991 }\">@<span>#{user.nickname}</span></a></span>"
993 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
994 assert response == expected_response
997 test "dismissing a single notification", %{conn: conn} do
999 other_user = insert(:user)
1002 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1004 {:ok, [notification]} = Notification.create_notifications(activity)
1008 |> assign(:user, user)
1009 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
1011 assert %{} = json_response(conn, 200)
1014 test "clearing all notifications", %{conn: conn} do
1015 user = insert(:user)
1016 other_user = insert(:user)
1019 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1021 {:ok, [_notification]} = Notification.create_notifications(activity)
1025 |> assign(:user, user)
1026 |> post("/api/v1/notifications/clear")
1028 assert %{} = json_response(conn, 200)
1032 |> assign(:user, user)
1033 |> get("/api/v1/notifications")
1035 assert all = json_response(conn, 200)
1039 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
1040 user = insert(:user)
1041 other_user = insert(:user)
1043 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1044 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1045 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1046 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1048 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1049 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1050 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1051 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1055 |> assign(:user, user)
1060 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
1062 result = json_response(conn_res, 200)
1063 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1068 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
1070 result = json_response(conn_res, 200)
1071 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1076 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
1078 result = json_response(conn_res, 200)
1079 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1082 test "filters notifications using exclude_types", %{conn: conn} do
1083 user = insert(:user)
1084 other_user = insert(:user)
1086 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
1087 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
1088 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
1089 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
1090 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
1092 mention_notification_id =
1093 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
1095 favorite_notification_id =
1096 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
1098 reblog_notification_id =
1099 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
1101 follow_notification_id =
1102 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1106 |> assign(:user, user)
1109 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1111 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1114 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1116 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1119 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1121 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1124 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1126 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1129 test "destroy multiple", %{conn: conn} do
1130 user = insert(:user)
1131 other_user = insert(:user)
1133 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1134 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1135 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1136 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1138 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1139 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1140 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1141 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1145 |> assign(:user, user)
1149 |> get("/api/v1/notifications")
1151 result = json_response(conn_res, 200)
1152 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1156 |> assign(:user, other_user)
1160 |> get("/api/v1/notifications")
1162 result = json_response(conn_res, 200)
1163 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1167 |> delete("/api/v1/notifications/destroy_multiple", %{
1168 "ids" => [notification1_id, notification2_id]
1171 assert json_response(conn_destroy, 200) == %{}
1175 |> get("/api/v1/notifications")
1177 result = json_response(conn_res, 200)
1178 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1182 describe "reblogging" do
1183 test "reblogs and returns the reblogged status", %{conn: conn} do
1184 activity = insert(:note_activity)
1185 user = insert(:user)
1189 |> assign(:user, user)
1190 |> post("/api/v1/statuses/#{activity.id}/reblog")
1193 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1195 } = json_response(conn, 200)
1197 assert to_string(activity.id) == id
1200 test "reblogged status for another user", %{conn: conn} do
1201 activity = insert(:note_activity)
1202 user1 = insert(:user)
1203 user2 = insert(:user)
1204 user3 = insert(:user)
1205 CommonAPI.favorite(activity.id, user2)
1206 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1207 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1208 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1212 |> assign(:user, user3)
1213 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1216 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1217 "reblogged" => false,
1218 "favourited" => false,
1219 "bookmarked" => false
1220 } = json_response(conn_res, 200)
1224 |> assign(:user, user2)
1225 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1228 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1229 "reblogged" => true,
1230 "favourited" => true,
1231 "bookmarked" => true
1232 } = json_response(conn_res, 200)
1234 assert to_string(activity.id) == id
1238 describe "unreblogging" do
1239 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1240 activity = insert(:note_activity)
1241 user = insert(:user)
1243 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1247 |> assign(:user, user)
1248 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1250 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1252 assert to_string(activity.id) == id
1256 describe "favoriting" do
1257 test "favs a status and returns it", %{conn: conn} do
1258 activity = insert(:note_activity)
1259 user = insert(:user)
1263 |> assign(:user, user)
1264 |> post("/api/v1/statuses/#{activity.id}/favourite")
1266 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1267 json_response(conn, 200)
1269 assert to_string(activity.id) == id
1272 test "returns 500 for a wrong id", %{conn: conn} do
1273 user = insert(:user)
1277 |> assign(:user, user)
1278 |> post("/api/v1/statuses/1/favourite")
1279 |> json_response(500)
1281 assert resp == "Something went wrong"
1285 describe "unfavoriting" do
1286 test "unfavorites a status and returns it", %{conn: conn} do
1287 activity = insert(:note_activity)
1288 user = insert(:user)
1290 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1294 |> assign(:user, user)
1295 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1297 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1298 json_response(conn, 200)
1300 assert to_string(activity.id) == id
1304 describe "user timelines" do
1305 test "gets a users statuses", %{conn: conn} do
1306 user_one = insert(:user)
1307 user_two = insert(:user)
1308 user_three = insert(:user)
1310 {:ok, user_three} = User.follow(user_three, user_one)
1312 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1314 {:ok, direct_activity} =
1315 CommonAPI.post(user_one, %{
1316 "status" => "Hi, @#{user_two.nickname}.",
1317 "visibility" => "direct"
1320 {:ok, private_activity} =
1321 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1325 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1327 assert [%{"id" => id}] = json_response(resp, 200)
1328 assert id == to_string(activity.id)
1332 |> assign(:user, user_two)
1333 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1335 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1336 assert id_one == to_string(direct_activity.id)
1337 assert id_two == to_string(activity.id)
1341 |> assign(:user, user_three)
1342 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1344 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1345 assert id_one == to_string(private_activity.id)
1346 assert id_two == to_string(activity.id)
1349 test "unimplemented pinned statuses feature", %{conn: conn} do
1350 note = insert(:note_activity)
1351 user = User.get_cached_by_ap_id(note.data["actor"])
1355 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1357 assert json_response(conn, 200) == []
1360 test "gets an users media", %{conn: conn} do
1361 note = insert(:note_activity)
1362 user = User.get_cached_by_ap_id(note.data["actor"])
1364 file = %Plug.Upload{
1365 content_type: "image/jpg",
1366 path: Path.absname("test/fixtures/image.jpg"),
1367 filename: "an_image.jpg"
1371 TwitterAPI.upload(file, user, "json")
1375 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1379 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1381 assert [%{"id" => id}] = json_response(conn, 200)
1382 assert id == to_string(image_post.id)
1386 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1388 assert [%{"id" => id}] = json_response(conn, 200)
1389 assert id == to_string(image_post.id)
1392 test "gets a user's statuses without reblogs", %{conn: conn} do
1393 user = insert(:user)
1394 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1395 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1399 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1401 assert [%{"id" => id}] = json_response(conn, 200)
1402 assert id == to_string(post.id)
1406 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1408 assert [%{"id" => id}] = json_response(conn, 200)
1409 assert id == to_string(post.id)
1413 describe "user relationships" do
1414 test "returns the relationships for the current user", %{conn: conn} do
1415 user = insert(:user)
1416 other_user = insert(:user)
1417 {:ok, user} = User.follow(user, other_user)
1421 |> assign(:user, user)
1422 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1424 assert [relationship] = json_response(conn, 200)
1426 assert to_string(other_user.id) == relationship["id"]
1430 describe "media upload" do
1432 upload_config = Pleroma.Config.get([Pleroma.Upload])
1433 proxy_config = Pleroma.Config.get([:media_proxy])
1436 Pleroma.Config.put([Pleroma.Upload], upload_config)
1437 Pleroma.Config.put([:media_proxy], proxy_config)
1440 user = insert(:user)
1444 |> assign(:user, user)
1446 image = %Plug.Upload{
1447 content_type: "image/jpg",
1448 path: Path.absname("test/fixtures/image.jpg"),
1449 filename: "an_image.jpg"
1452 [conn: conn, image: image]
1455 test "returns uploaded image", %{conn: conn, image: image} do
1456 desc = "Description of the image"
1460 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1461 |> json_response(:ok)
1463 assert media["type"] == "image"
1464 assert media["description"] == desc
1467 object = Repo.get(Object, media["id"])
1468 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1471 test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
1472 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
1474 proxy_url = "https://cache.pleroma.social"
1475 Pleroma.Config.put([:media_proxy, :enabled], true)
1476 Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
1480 |> post("/api/v1/media", %{"file" => image})
1481 |> json_response(:ok)
1483 assert String.starts_with?(media["url"], proxy_url)
1486 test "returns media url when proxy is enabled but media url is whitelisted", %{
1490 media_url = "https://media.pleroma.social"
1491 Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
1493 Pleroma.Config.put([:media_proxy, :enabled], true)
1494 Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
1495 Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
1499 |> post("/api/v1/media", %{"file" => image})
1500 |> json_response(:ok)
1502 assert String.starts_with?(media["url"], media_url)
1506 describe "locked accounts" do
1507 test "/api/v1/follow_requests works" do
1508 user = insert(:user, %{info: %User.Info{locked: true}})
1509 other_user = insert(:user)
1511 {:ok, _activity} = ActivityPub.follow(other_user, user)
1513 user = User.get_cached_by_id(user.id)
1514 other_user = User.get_cached_by_id(other_user.id)
1516 assert User.following?(other_user, user) == false
1520 |> assign(:user, user)
1521 |> get("/api/v1/follow_requests")
1523 assert [relationship] = json_response(conn, 200)
1524 assert to_string(other_user.id) == relationship["id"]
1527 test "/api/v1/follow_requests/:id/authorize works" do
1528 user = insert(:user, %{info: %User.Info{locked: true}})
1529 other_user = insert(:user)
1531 {:ok, _activity} = ActivityPub.follow(other_user, user)
1533 user = User.get_cached_by_id(user.id)
1534 other_user = User.get_cached_by_id(other_user.id)
1536 assert User.following?(other_user, user) == false
1540 |> assign(:user, user)
1541 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1543 assert relationship = json_response(conn, 200)
1544 assert to_string(other_user.id) == relationship["id"]
1546 user = User.get_cached_by_id(user.id)
1547 other_user = User.get_cached_by_id(other_user.id)
1549 assert User.following?(other_user, user) == true
1552 test "verify_credentials", %{conn: conn} do
1553 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1557 |> assign(:user, user)
1558 |> get("/api/v1/accounts/verify_credentials")
1560 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1561 assert id == to_string(user.id)
1564 test "/api/v1/follow_requests/:id/reject works" do
1565 user = insert(:user, %{info: %User.Info{locked: true}})
1566 other_user = insert(:user)
1568 {:ok, _activity} = ActivityPub.follow(other_user, user)
1570 user = User.get_cached_by_id(user.id)
1574 |> assign(:user, user)
1575 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1577 assert relationship = json_response(conn, 200)
1578 assert to_string(other_user.id) == relationship["id"]
1580 user = User.get_cached_by_id(user.id)
1581 other_user = User.get_cached_by_id(other_user.id)
1583 assert User.following?(other_user, user) == false
1587 test "account fetching", %{conn: conn} do
1588 user = insert(:user)
1592 |> get("/api/v1/accounts/#{user.id}")
1594 assert %{"id" => id} = json_response(conn, 200)
1595 assert id == to_string(user.id)
1599 |> get("/api/v1/accounts/-1")
1601 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1604 test "account fetching also works nickname", %{conn: conn} do
1605 user = insert(:user)
1609 |> get("/api/v1/accounts/#{user.nickname}")
1611 assert %{"id" => id} = json_response(conn, 200)
1612 assert id == user.id
1615 test "mascot upload", %{conn: conn} do
1616 user = insert(:user)
1618 non_image_file = %Plug.Upload{
1619 content_type: "audio/mpeg",
1620 path: Path.absname("test/fixtures/sound.mp3"),
1621 filename: "sound.mp3"
1626 |> assign(:user, user)
1627 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1629 assert json_response(conn, 415)
1631 file = %Plug.Upload{
1632 content_type: "image/jpg",
1633 path: Path.absname("test/fixtures/image.jpg"),
1634 filename: "an_image.jpg"
1639 |> assign(:user, user)
1640 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1642 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1645 test "mascot retrieving", %{conn: conn} do
1646 user = insert(:user)
1647 # When user hasn't set a mascot, we should just get pleroma tan back
1650 |> assign(:user, user)
1651 |> get("/api/v1/pleroma/mascot")
1653 assert %{"url" => url} = json_response(conn, 200)
1654 assert url =~ "pleroma-fox-tan-smol"
1656 # When a user sets their mascot, we should get that back
1657 file = %Plug.Upload{
1658 content_type: "image/jpg",
1659 path: Path.absname("test/fixtures/image.jpg"),
1660 filename: "an_image.jpg"
1665 |> assign(:user, user)
1666 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1668 assert json_response(conn, 200)
1670 user = User.get_cached_by_id(user.id)
1674 |> assign(:user, user)
1675 |> get("/api/v1/pleroma/mascot")
1677 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1678 assert url =~ "an_image"
1681 test "hashtag timeline", %{conn: conn} do
1682 following = insert(:user)
1685 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1687 {:ok, [_activity]} =
1688 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1692 |> get("/api/v1/timelines/tag/2hu")
1694 assert [%{"id" => id}] = json_response(nconn, 200)
1696 assert id == to_string(activity.id)
1698 # works for different capitalization too
1701 |> get("/api/v1/timelines/tag/2HU")
1703 assert [%{"id" => id}] = json_response(nconn, 200)
1705 assert id == to_string(activity.id)
1709 test "multi-hashtag timeline", %{conn: conn} do
1710 user = insert(:user)
1712 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1713 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1714 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1718 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1720 [status_none, status_test1, status_test] = json_response(any_test, 200)
1722 assert to_string(activity_test.id) == status_test["id"]
1723 assert to_string(activity_test1.id) == status_test1["id"]
1724 assert to_string(activity_none.id) == status_none["id"]
1728 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1730 assert [status_test1] == json_response(restricted_test, 200)
1732 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1734 assert [status_none] == json_response(all_test, 200)
1737 test "getting followers", %{conn: conn} do
1738 user = insert(:user)
1739 other_user = insert(:user)
1740 {:ok, user} = User.follow(user, other_user)
1744 |> get("/api/v1/accounts/#{other_user.id}/followers")
1746 assert [%{"id" => id}] = json_response(conn, 200)
1747 assert id == to_string(user.id)
1750 test "getting followers, hide_followers", %{conn: conn} do
1751 user = insert(:user)
1752 other_user = insert(:user, %{info: %{hide_followers: true}})
1753 {:ok, _user} = User.follow(user, other_user)
1757 |> get("/api/v1/accounts/#{other_user.id}/followers")
1759 assert [] == json_response(conn, 200)
1762 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1763 user = insert(:user)
1764 other_user = insert(:user, %{info: %{hide_followers: true}})
1765 {:ok, _user} = User.follow(user, other_user)
1769 |> assign(:user, other_user)
1770 |> get("/api/v1/accounts/#{other_user.id}/followers")
1772 refute [] == json_response(conn, 200)
1775 test "getting followers, pagination", %{conn: conn} do
1776 user = insert(:user)
1777 follower1 = insert(:user)
1778 follower2 = insert(:user)
1779 follower3 = insert(:user)
1780 {:ok, _} = User.follow(follower1, user)
1781 {:ok, _} = User.follow(follower2, user)
1782 {:ok, _} = User.follow(follower3, user)
1786 |> assign(:user, user)
1790 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1792 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1793 assert id3 == follower3.id
1794 assert id2 == follower2.id
1798 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1800 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1801 assert id2 == follower2.id
1802 assert id1 == follower1.id
1806 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1808 assert [%{"id" => id2}] = json_response(res_conn, 200)
1809 assert id2 == follower2.id
1811 assert [link_header] = get_resp_header(res_conn, "link")
1812 assert link_header =~ ~r/min_id=#{follower2.id}/
1813 assert link_header =~ ~r/max_id=#{follower2.id}/
1816 test "getting following", %{conn: conn} do
1817 user = insert(:user)
1818 other_user = insert(:user)
1819 {:ok, user} = User.follow(user, other_user)
1823 |> get("/api/v1/accounts/#{user.id}/following")
1825 assert [%{"id" => id}] = json_response(conn, 200)
1826 assert id == to_string(other_user.id)
1829 test "getting following, hide_follows", %{conn: conn} do
1830 user = insert(:user, %{info: %{hide_follows: true}})
1831 other_user = insert(:user)
1832 {:ok, user} = User.follow(user, other_user)
1836 |> get("/api/v1/accounts/#{user.id}/following")
1838 assert [] == json_response(conn, 200)
1841 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1842 user = insert(:user, %{info: %{hide_follows: true}})
1843 other_user = insert(:user)
1844 {:ok, user} = User.follow(user, other_user)
1848 |> assign(:user, user)
1849 |> get("/api/v1/accounts/#{user.id}/following")
1851 refute [] == json_response(conn, 200)
1854 test "getting following, pagination", %{conn: conn} do
1855 user = insert(:user)
1856 following1 = insert(:user)
1857 following2 = insert(:user)
1858 following3 = insert(:user)
1859 {:ok, _} = User.follow(user, following1)
1860 {:ok, _} = User.follow(user, following2)
1861 {:ok, _} = User.follow(user, following3)
1865 |> assign(:user, user)
1869 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1871 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1872 assert id3 == following3.id
1873 assert id2 == following2.id
1877 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1879 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1880 assert id2 == following2.id
1881 assert id1 == following1.id
1885 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1887 assert [%{"id" => id2}] = json_response(res_conn, 200)
1888 assert id2 == following2.id
1890 assert [link_header] = get_resp_header(res_conn, "link")
1891 assert link_header =~ ~r/min_id=#{following2.id}/
1892 assert link_header =~ ~r/max_id=#{following2.id}/
1895 test "following / unfollowing a user", %{conn: conn} do
1896 user = insert(:user)
1897 other_user = insert(:user)
1901 |> assign(:user, user)
1902 |> post("/api/v1/accounts/#{other_user.id}/follow")
1904 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
1906 user = User.get_cached_by_id(user.id)
1910 |> assign(:user, user)
1911 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
1913 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
1915 user = User.get_cached_by_id(user.id)
1919 |> assign(:user, user)
1920 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
1922 assert %{"id" => id} = json_response(conn, 200)
1923 assert id == to_string(other_user.id)
1926 test "following without reblogs" do
1927 follower = insert(:user)
1928 followed = insert(:user)
1929 other_user = insert(:user)
1933 |> assign(:user, follower)
1934 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
1936 assert %{"showing_reblogs" => false} = json_response(conn, 200)
1938 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
1939 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
1943 |> assign(:user, User.get_cached_by_id(follower.id))
1944 |> get("/api/v1/timelines/home")
1946 assert [] == json_response(conn, 200)
1950 |> assign(:user, follower)
1951 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
1953 assert %{"showing_reblogs" => true} = json_response(conn, 200)
1957 |> assign(:user, User.get_cached_by_id(follower.id))
1958 |> get("/api/v1/timelines/home")
1960 expected_activity_id = reblog.id
1961 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
1964 test "following / unfollowing errors" do
1965 user = insert(:user)
1969 |> assign(:user, user)
1972 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
1973 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1976 user = User.get_cached_by_id(user.id)
1977 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
1978 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1980 # self follow via uri
1981 user = User.get_cached_by_id(user.id)
1982 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
1983 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1985 # follow non existing user
1986 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
1987 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1989 # follow non existing user via uri
1990 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
1991 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1993 # unfollow non existing user
1994 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
1995 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1998 test "muting / unmuting a user", %{conn: conn} do
1999 user = insert(:user)
2000 other_user = insert(:user)
2004 |> assign(:user, user)
2005 |> post("/api/v1/accounts/#{other_user.id}/mute")
2007 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
2009 user = User.get_cached_by_id(user.id)
2013 |> assign(:user, user)
2014 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2016 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
2019 test "subscribing / unsubscribing to a user", %{conn: conn} do
2020 user = insert(:user)
2021 subscription_target = insert(:user)
2025 |> assign(:user, user)
2026 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2028 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2032 |> assign(:user, user)
2033 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2035 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2038 test "getting a list of mutes", %{conn: conn} do
2039 user = insert(:user)
2040 other_user = insert(:user)
2042 {:ok, user} = User.mute(user, other_user)
2046 |> assign(:user, user)
2047 |> get("/api/v1/mutes")
2049 other_user_id = to_string(other_user.id)
2050 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2053 test "blocking / unblocking a user", %{conn: conn} do
2054 user = insert(:user)
2055 other_user = insert(:user)
2059 |> assign(:user, user)
2060 |> post("/api/v1/accounts/#{other_user.id}/block")
2062 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2064 user = User.get_cached_by_id(user.id)
2068 |> assign(:user, user)
2069 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2071 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2074 test "getting a list of blocks", %{conn: conn} do
2075 user = insert(:user)
2076 other_user = insert(:user)
2078 {:ok, user} = User.block(user, other_user)
2082 |> assign(:user, user)
2083 |> get("/api/v1/blocks")
2085 other_user_id = to_string(other_user.id)
2086 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2089 test "blocking / unblocking a domain", %{conn: conn} do
2090 user = insert(:user)
2091 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2095 |> assign(:user, user)
2096 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2098 assert %{} = json_response(conn, 200)
2099 user = User.get_cached_by_ap_id(user.ap_id)
2100 assert User.blocks?(user, other_user)
2104 |> assign(:user, user)
2105 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2107 assert %{} = json_response(conn, 200)
2108 user = User.get_cached_by_ap_id(user.ap_id)
2109 refute User.blocks?(user, other_user)
2112 test "getting a list of domain blocks", %{conn: conn} do
2113 user = insert(:user)
2115 {:ok, user} = User.block_domain(user, "bad.site")
2116 {:ok, user} = User.block_domain(user, "even.worse.site")
2120 |> assign(:user, user)
2121 |> get("/api/v1/domain_blocks")
2123 domain_blocks = json_response(conn, 200)
2125 assert "bad.site" in domain_blocks
2126 assert "even.worse.site" in domain_blocks
2129 test "unimplemented follow_requests, blocks, domain blocks" do
2130 user = insert(:user)
2132 ["blocks", "domain_blocks", "follow_requests"]
2133 |> Enum.each(fn endpoint ->
2136 |> assign(:user, user)
2137 |> get("/api/v1/#{endpoint}")
2139 assert [] = json_response(conn, 200)
2143 test "returns the favorites of a user", %{conn: conn} do
2144 user = insert(:user)
2145 other_user = insert(:user)
2147 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2148 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2150 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2154 |> assign(:user, user)
2155 |> get("/api/v1/favourites")
2157 assert [status] = json_response(first_conn, 200)
2158 assert status["id"] == to_string(activity.id)
2160 assert [{"link", _link_header}] =
2161 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2163 # Honours query params
2164 {:ok, second_activity} =
2165 CommonAPI.post(other_user, %{
2167 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2170 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2172 last_like = status["id"]
2176 |> assign(:user, user)
2177 |> get("/api/v1/favourites?since_id=#{last_like}")
2179 assert [second_status] = json_response(second_conn, 200)
2180 assert second_status["id"] == to_string(second_activity.id)
2184 |> assign(:user, user)
2185 |> get("/api/v1/favourites?limit=0")
2187 assert [] = json_response(third_conn, 200)
2190 describe "getting favorites timeline of specified user" do
2192 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2193 [current_user: current_user, user: user]
2196 test "returns list of statuses favorited by specified user", %{
2198 current_user: current_user,
2201 [activity | _] = insert_pair(:note_activity)
2202 CommonAPI.favorite(activity.id, user)
2206 |> assign(:user, current_user)
2207 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2208 |> json_response(:ok)
2212 assert length(response) == 1
2213 assert like["id"] == activity.id
2216 test "returns favorites for specified user_id when user is not logged in", %{
2220 activity = insert(:note_activity)
2221 CommonAPI.favorite(activity.id, user)
2225 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2226 |> json_response(:ok)
2228 assert length(response) == 1
2231 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2233 current_user: current_user,
2237 CommonAPI.post(current_user, %{
2238 "status" => "Hi @#{user.nickname}!",
2239 "visibility" => "direct"
2242 CommonAPI.favorite(direct.id, user)
2246 |> assign(:user, current_user)
2247 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2248 |> json_response(:ok)
2250 assert length(response) == 1
2252 anonymous_response =
2254 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2255 |> json_response(:ok)
2257 assert Enum.empty?(anonymous_response)
2260 test "does not return others' favorited DM when user is not one of recipients", %{
2262 current_user: current_user,
2265 user_two = insert(:user)
2268 CommonAPI.post(user_two, %{
2269 "status" => "Hi @#{user.nickname}!",
2270 "visibility" => "direct"
2273 CommonAPI.favorite(direct.id, user)
2277 |> assign(:user, current_user)
2278 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2279 |> json_response(:ok)
2281 assert Enum.empty?(response)
2284 test "paginates favorites using since_id and max_id", %{
2286 current_user: current_user,
2289 activities = insert_list(10, :note_activity)
2291 Enum.each(activities, fn activity ->
2292 CommonAPI.favorite(activity.id, user)
2295 third_activity = Enum.at(activities, 2)
2296 seventh_activity = Enum.at(activities, 6)
2300 |> assign(:user, current_user)
2301 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2302 since_id: third_activity.id,
2303 max_id: seventh_activity.id
2305 |> json_response(:ok)
2307 assert length(response) == 3
2308 refute third_activity in response
2309 refute seventh_activity in response
2312 test "limits favorites using limit parameter", %{
2314 current_user: current_user,
2318 |> insert_list(:note_activity)
2319 |> Enum.each(fn activity ->
2320 CommonAPI.favorite(activity.id, user)
2325 |> assign(:user, current_user)
2326 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2327 |> json_response(:ok)
2329 assert length(response) == 3
2332 test "returns empty response when user does not have any favorited statuses", %{
2334 current_user: current_user,
2339 |> assign(:user, current_user)
2340 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2341 |> json_response(:ok)
2343 assert Enum.empty?(response)
2346 test "returns 404 error when specified user is not exist", %{conn: conn} do
2347 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2349 assert json_response(conn, 404) == %{"error" => "Record not found"}
2352 test "returns 403 error when user has hidden own favorites", %{
2354 current_user: current_user
2356 user = insert(:user, %{info: %{hide_favorites: true}})
2357 activity = insert(:note_activity)
2358 CommonAPI.favorite(activity.id, user)
2362 |> assign(:user, current_user)
2363 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2365 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2368 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2369 user = insert(:user)
2370 activity = insert(:note_activity)
2371 CommonAPI.favorite(activity.id, user)
2375 |> assign(:user, current_user)
2376 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2378 assert user.info.hide_favorites
2379 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2383 test "get instance information", %{conn: conn} do
2384 conn = get(conn, "/api/v1/instance")
2385 assert result = json_response(conn, 200)
2387 email = Pleroma.Config.get([:instance, :email])
2388 # Note: not checking for "max_toot_chars" since it's optional
2394 "email" => from_config_email,
2396 "streaming_api" => _
2401 "registrations" => _,
2405 assert email == from_config_email
2408 test "get instance stats", %{conn: conn} do
2409 user = insert(:user, %{local: true})
2411 user2 = insert(:user, %{local: true})
2412 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2414 insert(:user, %{local: false, nickname: "u@peer1.com"})
2415 insert(:user, %{local: false, nickname: "u@peer2.com"})
2417 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2419 # Stats should count users with missing or nil `info.deactivated` value
2420 user = User.get_cached_by_id(user.id)
2421 info_change = Changeset.change(user.info, %{deactivated: nil})
2425 |> Changeset.change()
2426 |> Changeset.put_embed(:info, info_change)
2427 |> User.update_and_set_cache()
2429 Pleroma.Stats.update_stats()
2431 conn = get(conn, "/api/v1/instance")
2433 assert result = json_response(conn, 200)
2435 stats = result["stats"]
2438 assert stats["user_count"] == 1
2439 assert stats["status_count"] == 1
2440 assert stats["domain_count"] == 2
2443 test "get peers", %{conn: conn} do
2444 insert(:user, %{local: false, nickname: "u@peer1.com"})
2445 insert(:user, %{local: false, nickname: "u@peer2.com"})
2447 Pleroma.Stats.update_stats()
2449 conn = get(conn, "/api/v1/instance/peers")
2451 assert result = json_response(conn, 200)
2453 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2456 test "put settings", %{conn: conn} do
2457 user = insert(:user)
2461 |> assign(:user, user)
2462 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2464 assert _result = json_response(conn, 200)
2466 user = User.get_cached_by_ap_id(user.ap_id)
2467 assert user.info.settings == %{"programming" => "socks"}
2470 describe "pinned statuses" do
2472 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2474 user = insert(:user)
2475 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2477 [user: user, activity: activity]
2480 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2481 {:ok, _} = CommonAPI.pin(activity.id, user)
2485 |> assign(:user, user)
2486 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2487 |> json_response(200)
2489 id_str = to_string(activity.id)
2491 assert [%{"id" => ^id_str, "pinned" => true}] = result
2494 test "pin status", %{conn: conn, user: user, activity: activity} do
2495 id_str = to_string(activity.id)
2497 assert %{"id" => ^id_str, "pinned" => true} =
2499 |> assign(:user, user)
2500 |> post("/api/v1/statuses/#{activity.id}/pin")
2501 |> json_response(200)
2503 assert [%{"id" => ^id_str, "pinned" => true}] =
2505 |> assign(:user, user)
2506 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2507 |> json_response(200)
2510 test "unpin status", %{conn: conn, user: user, activity: activity} do
2511 {:ok, _} = CommonAPI.pin(activity.id, user)
2513 id_str = to_string(activity.id)
2514 user = refresh_record(user)
2516 assert %{"id" => ^id_str, "pinned" => false} =
2518 |> assign(:user, user)
2519 |> post("/api/v1/statuses/#{activity.id}/unpin")
2520 |> json_response(200)
2524 |> assign(:user, user)
2525 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2526 |> json_response(200)
2529 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2530 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2532 id_str_one = to_string(activity_one.id)
2534 assert %{"id" => ^id_str_one, "pinned" => true} =
2536 |> assign(:user, user)
2537 |> post("/api/v1/statuses/#{id_str_one}/pin")
2538 |> json_response(200)
2540 user = refresh_record(user)
2542 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2544 |> assign(:user, user)
2545 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2546 |> json_response(400)
2552 Pleroma.Config.put([:rich_media, :enabled], true)
2555 Pleroma.Config.put([:rich_media, :enabled], false)
2558 user = insert(:user)
2562 test "returns rich-media card", %{conn: conn, user: user} do
2563 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2566 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2567 "provider_name" => "www.imdb.com",
2568 "provider_url" => "http://www.imdb.com",
2569 "title" => "The Rock",
2571 "url" => "http://www.imdb.com/title/tt0117500/",
2573 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2576 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2577 "title" => "The Rock",
2578 "type" => "video.movie",
2579 "url" => "http://www.imdb.com/title/tt0117500/",
2581 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2588 |> get("/api/v1/statuses/#{activity.id}/card")
2589 |> json_response(200)
2591 assert response == card_data
2593 # works with private posts
2595 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2599 |> assign(:user, user)
2600 |> get("/api/v1/statuses/#{activity.id}/card")
2601 |> json_response(200)
2603 assert response_two == card_data
2606 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2608 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2612 |> get("/api/v1/statuses/#{activity.id}/card")
2613 |> json_response(:ok)
2615 assert response == %{
2617 "title" => "Pleroma",
2618 "description" => "",
2620 "provider_name" => "pleroma.social",
2621 "provider_url" => "https://pleroma.social",
2622 "url" => "https://pleroma.social/",
2625 "title" => "Pleroma",
2626 "type" => "website",
2627 "url" => "https://pleroma.social/"
2635 user = insert(:user)
2636 for_user = insert(:user)
2639 CommonAPI.post(user, %{
2640 "status" => "heweoo?"
2644 CommonAPI.post(user, %{
2645 "status" => "heweoo!"
2650 |> assign(:user, for_user)
2651 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2653 assert json_response(response1, 200)["bookmarked"] == true
2657 |> assign(:user, for_user)
2658 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2660 assert json_response(response2, 200)["bookmarked"] == true
2664 |> assign(:user, for_user)
2665 |> get("/api/v1/bookmarks")
2667 assert [json_response(response2, 200), json_response(response1, 200)] ==
2668 json_response(bookmarks, 200)
2672 |> assign(:user, for_user)
2673 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2675 assert json_response(response1, 200)["bookmarked"] == false
2679 |> assign(:user, for_user)
2680 |> get("/api/v1/bookmarks")
2682 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2685 describe "conversation muting" do
2687 user = insert(:user)
2688 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2690 [user: user, activity: activity]
2693 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2694 id_str = to_string(activity.id)
2696 assert %{"id" => ^id_str, "muted" => true} =
2698 |> assign(:user, user)
2699 |> post("/api/v1/statuses/#{activity.id}/mute")
2700 |> json_response(200)
2703 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2704 {:ok, _} = CommonAPI.add_mute(user, activity)
2706 id_str = to_string(activity.id)
2707 user = refresh_record(user)
2709 assert %{"id" => ^id_str, "muted" => false} =
2711 |> assign(:user, user)
2712 |> post("/api/v1/statuses/#{activity.id}/unmute")
2713 |> json_response(200)
2717 describe "reports" do
2719 reporter = insert(:user)
2720 target_user = insert(:user)
2722 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2724 [reporter: reporter, target_user: target_user, activity: activity]
2727 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2728 assert %{"action_taken" => false, "id" => _} =
2730 |> assign(:user, reporter)
2731 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2732 |> json_response(200)
2735 test "submit a report with statuses and comment", %{
2738 target_user: target_user,
2741 assert %{"action_taken" => false, "id" => _} =
2743 |> assign(:user, reporter)
2744 |> post("/api/v1/reports", %{
2745 "account_id" => target_user.id,
2746 "status_ids" => [activity.id],
2747 "comment" => "bad status!"
2749 |> json_response(200)
2752 test "account_id is required", %{
2757 assert %{"error" => "Valid `account_id` required"} =
2759 |> assign(:user, reporter)
2760 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2761 |> json_response(400)
2764 test "comment must be up to the size specified in the config", %{
2767 target_user: target_user
2769 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2770 comment = String.pad_trailing("a", max_size + 1, "a")
2772 error = %{"error" => "Comment must be up to #{max_size} characters"}
2776 |> assign(:user, reporter)
2777 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2778 |> json_response(400)
2782 describe "link headers" do
2783 test "preserves parameters in link headers", %{conn: conn} do
2784 user = insert(:user)
2785 other_user = insert(:user)
2788 CommonAPI.post(other_user, %{
2789 "status" => "hi @#{user.nickname}",
2790 "visibility" => "public"
2794 CommonAPI.post(other_user, %{
2795 "status" => "hi @#{user.nickname}",
2796 "visibility" => "public"
2799 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2800 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2804 |> assign(:user, user)
2805 |> get("/api/v1/notifications", %{media_only: true})
2807 assert [link_header] = get_resp_header(conn, "link")
2808 assert link_header =~ ~r/media_only=true/
2809 assert link_header =~ ~r/min_id=#{notification2.id}/
2810 assert link_header =~ ~r/max_id=#{notification1.id}/
2814 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2815 # Need to set an old-style integer ID to reproduce the problem
2816 # (these are no longer assigned to new accounts but were preserved
2817 # for existing accounts during the migration to flakeIDs)
2818 user_one = insert(:user, %{id: 1212})
2819 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2823 |> get("/api/v1/accounts/#{user_one.id}")
2827 |> get("/api/v1/accounts/#{user_two.nickname}")
2831 |> get("/api/v1/accounts/#{user_two.id}")
2833 acc_one = json_response(resp_one, 200)
2834 acc_two = json_response(resp_two, 200)
2835 acc_three = json_response(resp_three, 200)
2836 refute acc_one == acc_two
2837 assert acc_two == acc_three
2840 describe "custom emoji" do
2841 test "with tags", %{conn: conn} do
2844 |> get("/api/v1/custom_emojis")
2845 |> json_response(200)
2847 assert Map.has_key?(emoji, "shortcode")
2848 assert Map.has_key?(emoji, "static_url")
2849 assert Map.has_key?(emoji, "tags")
2850 assert is_list(emoji["tags"])
2851 assert Map.has_key?(emoji, "url")
2852 assert Map.has_key?(emoji, "visible_in_picker")
2856 describe "index/2 redirections" do
2857 setup %{conn: conn} do
2861 signing_salt: "cooldude"
2866 |> Plug.Session.call(Plug.Session.init(session_opts))
2869 test_path = "/web/statuses/test"
2870 %{conn: conn, path: test_path}
2873 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
2874 conn = get(conn, path)
2876 assert conn.status == 302
2877 assert redirected_to(conn) == "/web/login"
2880 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
2881 token = insert(:oauth_token)
2885 |> assign(:user, token.user)
2886 |> put_session(:oauth_token, token.token)
2889 assert conn.status == 200
2892 test "saves referer path to session", %{conn: conn, path: path} do
2893 conn = get(conn, path)
2894 return_to = Plug.Conn.get_session(conn, :return_to)
2896 assert return_to == path
2899 test "redirects to the saved path after log in", %{conn: conn, path: path} do
2900 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2901 auth = insert(:oauth_authorization, app: app)
2905 |> put_session(:return_to, path)
2906 |> get("/web/login", %{code: auth.token})
2908 assert conn.status == 302
2909 assert redirected_to(conn) == path
2912 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
2913 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2914 auth = insert(:oauth_authorization, app: app)
2916 conn = get(conn, "/web/login", %{code: auth.token})
2918 assert conn.status == 302
2919 assert redirected_to(conn) == "/web/getting-started"
2923 describe "scheduled activities" do
2924 test "creates a scheduled activity", %{conn: conn} do
2925 user = insert(:user)
2926 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2930 |> assign(:user, user)
2931 |> post("/api/v1/statuses", %{
2932 "status" => "scheduled",
2933 "scheduled_at" => scheduled_at
2936 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
2937 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
2938 assert [] == Repo.all(Activity)
2941 test "creates a scheduled activity with a media attachment", %{conn: conn} do
2942 user = insert(:user)
2943 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2945 file = %Plug.Upload{
2946 content_type: "image/jpg",
2947 path: Path.absname("test/fixtures/image.jpg"),
2948 filename: "an_image.jpg"
2951 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
2955 |> assign(:user, user)
2956 |> post("/api/v1/statuses", %{
2957 "media_ids" => [to_string(upload.id)],
2958 "status" => "scheduled",
2959 "scheduled_at" => scheduled_at
2962 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
2963 assert %{"type" => "image"} = media_attachment
2966 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
2968 user = insert(:user)
2971 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
2975 |> assign(:user, user)
2976 |> post("/api/v1/statuses", %{
2977 "status" => "not scheduled",
2978 "scheduled_at" => scheduled_at
2981 assert %{"content" => "not scheduled"} = json_response(conn, 200)
2982 assert [] == Repo.all(ScheduledActivity)
2985 test "returns error when daily user limit is exceeded", %{conn: conn} do
2986 user = insert(:user)
2989 NaiveDateTime.utc_now()
2990 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
2991 |> NaiveDateTime.to_iso8601()
2993 attrs = %{params: %{}, scheduled_at: today}
2994 {:ok, _} = ScheduledActivity.create(user, attrs)
2995 {:ok, _} = ScheduledActivity.create(user, attrs)
2999 |> assign(:user, user)
3000 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3002 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3005 test "returns error when total user limit is exceeded", %{conn: conn} do
3006 user = insert(:user)
3009 NaiveDateTime.utc_now()
3010 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3011 |> NaiveDateTime.to_iso8601()
3014 NaiveDateTime.utc_now()
3015 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3016 |> NaiveDateTime.to_iso8601()
3018 attrs = %{params: %{}, scheduled_at: today}
3019 {:ok, _} = ScheduledActivity.create(user, attrs)
3020 {:ok, _} = ScheduledActivity.create(user, attrs)
3021 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3025 |> assign(:user, user)
3026 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3028 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3031 test "shows scheduled activities", %{conn: conn} do
3032 user = insert(:user)
3033 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3034 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3035 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3036 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3040 |> assign(:user, user)
3045 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3047 result = json_response(conn_res, 200)
3048 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3053 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3055 result = json_response(conn_res, 200)
3056 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3061 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3063 result = json_response(conn_res, 200)
3064 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3067 test "shows a scheduled activity", %{conn: conn} do
3068 user = insert(:user)
3069 scheduled_activity = insert(:scheduled_activity, user: user)
3073 |> assign(:user, user)
3074 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3076 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3077 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3081 |> assign(:user, user)
3082 |> get("/api/v1/scheduled_statuses/404")
3084 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3087 test "updates a scheduled activity", %{conn: conn} do
3088 user = insert(:user)
3089 scheduled_activity = insert(:scheduled_activity, user: user)
3092 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3096 |> assign(:user, user)
3097 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3098 scheduled_at: new_scheduled_at
3101 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3102 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3106 |> assign(:user, user)
3107 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3109 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3112 test "deletes a scheduled activity", %{conn: conn} do
3113 user = insert(:user)
3114 scheduled_activity = insert(:scheduled_activity, user: user)
3118 |> assign(:user, user)
3119 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3121 assert %{} = json_response(res_conn, 200)
3122 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3126 |> assign(:user, user)
3127 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3129 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3133 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3134 user1 = insert(:user)
3135 user2 = insert(:user)
3136 user3 = insert(:user)
3138 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3140 # Reply to status from another user
3143 |> assign(:user, user2)
3144 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3146 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3148 activity = Activity.get_by_id_with_object(id)
3150 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3151 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3153 # Reblog from the third user
3156 |> assign(:user, user3)
3157 |> post("/api/v1/statuses/#{activity.id}/reblog")
3159 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3160 json_response(conn2, 200)
3162 assert to_string(activity.id) == id
3164 # Getting third user status
3167 |> assign(:user, user3)
3168 |> get("api/v1/timelines/home")
3170 [reblogged_activity] = json_response(conn3, 200)
3172 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3174 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3175 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3178 describe "create account by app" do
3179 test "Account registration via Application", %{conn: conn} do
3182 |> post("/api/v1/apps", %{
3183 client_name: "client_name",
3184 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3185 scopes: "read, write, follow"
3189 "client_id" => client_id,
3190 "client_secret" => client_secret,
3192 "name" => "client_name",
3193 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3196 } = json_response(conn, 200)
3200 |> post("/oauth/token", %{
3201 grant_type: "client_credentials",
3202 client_id: client_id,
3203 client_secret: client_secret
3206 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3207 json_response(conn, 200)
3210 token_from_db = Repo.get_by(Token, token: token)
3211 assert token_from_db
3213 assert scope == "read write follow"
3217 |> put_req_header("authorization", "Bearer " <> token)
3218 |> post("/api/v1/accounts", %{
3220 email: "lain@example.org",
3221 password: "PlzDontHackLain",
3226 "access_token" => token,
3227 "created_at" => _created_at,
3229 "token_type" => "Bearer"
3230 } = json_response(conn, 200)
3232 token_from_db = Repo.get_by(Token, token: token)
3233 assert token_from_db
3234 token_from_db = Repo.preload(token_from_db, :user)
3235 assert token_from_db.user
3237 assert token_from_db.user.info.confirmation_pending
3240 test "rate limit", %{conn: conn} do
3241 app_token = insert(:oauth_token, user: nil)
3244 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3245 |> Map.put(:remote_ip, {15, 15, 15, 15})
3250 |> post("/api/v1/accounts", %{
3251 username: "#{i}lain",
3252 email: "#{i}lain@example.org",
3253 password: "PlzDontHackLain",
3258 "access_token" => token,
3259 "created_at" => _created_at,
3261 "token_type" => "Bearer"
3262 } = json_response(conn, 200)
3264 token_from_db = Repo.get_by(Token, token: token)
3265 assert token_from_db
3266 token_from_db = Repo.preload(token_from_db, :user)
3267 assert token_from_db.user
3269 assert token_from_db.user.info.confirmation_pending
3274 |> post("/api/v1/accounts", %{
3276 email: "6lain@example.org",
3277 password: "PlzDontHackLain",
3281 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3285 describe "GET /api/v1/polls/:id" do
3286 test "returns poll entity for object id", %{conn: conn} do
3287 user = insert(:user)
3290 CommonAPI.post(user, %{
3291 "status" => "Pleroma does",
3292 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3295 object = Object.normalize(activity)
3299 |> assign(:user, user)
3300 |> get("/api/v1/polls/#{object.id}")
3302 response = json_response(conn, 200)
3304 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3307 test "does not expose polls for private statuses", %{conn: conn} do
3308 user = insert(:user)
3309 other_user = insert(:user)
3312 CommonAPI.post(user, %{
3313 "status" => "Pleroma does",
3314 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3315 "visibility" => "private"
3318 object = Object.normalize(activity)
3322 |> assign(:user, other_user)
3323 |> get("/api/v1/polls/#{object.id}")
3325 assert json_response(conn, 404)
3329 describe "POST /api/v1/polls/:id/votes" do
3330 test "votes are added to the poll", %{conn: conn} do
3331 user = insert(:user)
3332 other_user = insert(:user)
3335 CommonAPI.post(user, %{
3336 "status" => "A very delicious sandwich",
3338 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3344 object = Object.normalize(activity)
3348 |> assign(:user, other_user)
3349 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3351 assert json_response(conn, 200)
3352 object = Object.get_by_id(object.id)
3354 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3359 test "author can't vote", %{conn: conn} do
3360 user = insert(:user)
3363 CommonAPI.post(user, %{
3364 "status" => "Am I cute?",
3365 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3368 object = Object.normalize(activity)
3371 |> assign(:user, user)
3372 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3373 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3375 object = Object.get_by_id(object.id)
3377 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3380 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3381 user = insert(:user)
3382 other_user = insert(:user)
3385 CommonAPI.post(user, %{
3386 "status" => "The glass is",
3387 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3390 object = Object.normalize(activity)
3393 |> assign(:user, other_user)
3394 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3395 |> json_response(422) == %{"error" => "Too many choices"}
3397 object = Object.get_by_id(object.id)
3399 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->