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)
1412 test "filters user's statuses by a hashtag", %{conn: conn} do
1413 user = insert(:user)
1414 {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
1415 {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
1419 |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
1421 assert [%{"id" => id}] = json_response(conn, 200)
1422 assert id == to_string(post.id)
1426 describe "user relationships" do
1427 test "returns the relationships for the current user", %{conn: conn} do
1428 user = insert(:user)
1429 other_user = insert(:user)
1430 {:ok, user} = User.follow(user, other_user)
1434 |> assign(:user, user)
1435 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1437 assert [relationship] = json_response(conn, 200)
1439 assert to_string(other_user.id) == relationship["id"]
1443 describe "media upload" do
1445 upload_config = Pleroma.Config.get([Pleroma.Upload])
1446 proxy_config = Pleroma.Config.get([:media_proxy])
1449 Pleroma.Config.put([Pleroma.Upload], upload_config)
1450 Pleroma.Config.put([:media_proxy], proxy_config)
1453 user = insert(:user)
1457 |> assign(:user, user)
1459 image = %Plug.Upload{
1460 content_type: "image/jpg",
1461 path: Path.absname("test/fixtures/image.jpg"),
1462 filename: "an_image.jpg"
1465 [conn: conn, image: image]
1468 test "returns uploaded image", %{conn: conn, image: image} do
1469 desc = "Description of the image"
1473 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1474 |> json_response(:ok)
1476 assert media["type"] == "image"
1477 assert media["description"] == desc
1480 object = Repo.get(Object, media["id"])
1481 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1484 test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
1485 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
1487 proxy_url = "https://cache.pleroma.social"
1488 Pleroma.Config.put([:media_proxy, :enabled], true)
1489 Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
1493 |> post("/api/v1/media", %{"file" => image})
1494 |> json_response(:ok)
1496 assert String.starts_with?(media["url"], proxy_url)
1499 test "returns media url when proxy is enabled but media url is whitelisted", %{
1503 media_url = "https://media.pleroma.social"
1504 Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
1506 Pleroma.Config.put([:media_proxy, :enabled], true)
1507 Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
1508 Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
1512 |> post("/api/v1/media", %{"file" => image})
1513 |> json_response(:ok)
1515 assert String.starts_with?(media["url"], media_url)
1519 describe "locked accounts" do
1520 test "/api/v1/follow_requests works" do
1521 user = insert(:user, %{info: %User.Info{locked: true}})
1522 other_user = insert(:user)
1524 {:ok, _activity} = ActivityPub.follow(other_user, user)
1526 user = User.get_cached_by_id(user.id)
1527 other_user = User.get_cached_by_id(other_user.id)
1529 assert User.following?(other_user, user) == false
1533 |> assign(:user, user)
1534 |> get("/api/v1/follow_requests")
1536 assert [relationship] = json_response(conn, 200)
1537 assert to_string(other_user.id) == relationship["id"]
1540 test "/api/v1/follow_requests/:id/authorize works" do
1541 user = insert(:user, %{info: %User.Info{locked: true}})
1542 other_user = insert(:user)
1544 {:ok, _activity} = ActivityPub.follow(other_user, user)
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) == false
1553 |> assign(:user, user)
1554 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1556 assert relationship = json_response(conn, 200)
1557 assert to_string(other_user.id) == relationship["id"]
1559 user = User.get_cached_by_id(user.id)
1560 other_user = User.get_cached_by_id(other_user.id)
1562 assert User.following?(other_user, user) == true
1565 test "verify_credentials", %{conn: conn} do
1566 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1570 |> assign(:user, user)
1571 |> get("/api/v1/accounts/verify_credentials")
1573 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1574 assert id == to_string(user.id)
1577 test "/api/v1/follow_requests/:id/reject works" do
1578 user = insert(:user, %{info: %User.Info{locked: true}})
1579 other_user = insert(:user)
1581 {:ok, _activity} = ActivityPub.follow(other_user, user)
1583 user = User.get_cached_by_id(user.id)
1587 |> assign(:user, user)
1588 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1590 assert relationship = json_response(conn, 200)
1591 assert to_string(other_user.id) == relationship["id"]
1593 user = User.get_cached_by_id(user.id)
1594 other_user = User.get_cached_by_id(other_user.id)
1596 assert User.following?(other_user, user) == false
1600 test "account fetching", %{conn: conn} do
1601 user = insert(:user)
1605 |> get("/api/v1/accounts/#{user.id}")
1607 assert %{"id" => id} = json_response(conn, 200)
1608 assert id == to_string(user.id)
1612 |> get("/api/v1/accounts/-1")
1614 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1617 test "account fetching also works nickname", %{conn: conn} do
1618 user = insert(:user)
1622 |> get("/api/v1/accounts/#{user.nickname}")
1624 assert %{"id" => id} = json_response(conn, 200)
1625 assert id == user.id
1628 test "mascot upload", %{conn: conn} do
1629 user = insert(:user)
1631 non_image_file = %Plug.Upload{
1632 content_type: "audio/mpeg",
1633 path: Path.absname("test/fixtures/sound.mp3"),
1634 filename: "sound.mp3"
1639 |> assign(:user, user)
1640 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1642 assert json_response(conn, 415)
1644 file = %Plug.Upload{
1645 content_type: "image/jpg",
1646 path: Path.absname("test/fixtures/image.jpg"),
1647 filename: "an_image.jpg"
1652 |> assign(:user, user)
1653 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1655 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1658 test "mascot retrieving", %{conn: conn} do
1659 user = insert(:user)
1660 # When user hasn't set a mascot, we should just get pleroma tan back
1663 |> assign(:user, user)
1664 |> get("/api/v1/pleroma/mascot")
1666 assert %{"url" => url} = json_response(conn, 200)
1667 assert url =~ "pleroma-fox-tan-smol"
1669 # When a user sets their mascot, we should get that back
1670 file = %Plug.Upload{
1671 content_type: "image/jpg",
1672 path: Path.absname("test/fixtures/image.jpg"),
1673 filename: "an_image.jpg"
1678 |> assign(:user, user)
1679 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1681 assert json_response(conn, 200)
1683 user = User.get_cached_by_id(user.id)
1687 |> assign(:user, user)
1688 |> get("/api/v1/pleroma/mascot")
1690 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1691 assert url =~ "an_image"
1694 test "hashtag timeline", %{conn: conn} do
1695 following = insert(:user)
1698 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1700 {:ok, [_activity]} =
1701 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1705 |> get("/api/v1/timelines/tag/2hu")
1707 assert [%{"id" => id}] = json_response(nconn, 200)
1709 assert id == to_string(activity.id)
1711 # works for different capitalization too
1714 |> get("/api/v1/timelines/tag/2HU")
1716 assert [%{"id" => id}] = json_response(nconn, 200)
1718 assert id == to_string(activity.id)
1722 test "multi-hashtag timeline", %{conn: conn} do
1723 user = insert(:user)
1725 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1726 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1727 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1731 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1733 [status_none, status_test1, status_test] = json_response(any_test, 200)
1735 assert to_string(activity_test.id) == status_test["id"]
1736 assert to_string(activity_test1.id) == status_test1["id"]
1737 assert to_string(activity_none.id) == status_none["id"]
1741 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1743 assert [status_test1] == json_response(restricted_test, 200)
1745 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1747 assert [status_none] == json_response(all_test, 200)
1750 test "getting followers", %{conn: conn} do
1751 user = insert(:user)
1752 other_user = insert(:user)
1753 {:ok, user} = User.follow(user, other_user)
1757 |> get("/api/v1/accounts/#{other_user.id}/followers")
1759 assert [%{"id" => id}] = json_response(conn, 200)
1760 assert id == to_string(user.id)
1763 test "getting followers, hide_followers", %{conn: conn} do
1764 user = insert(:user)
1765 other_user = insert(:user, %{info: %{hide_followers: true}})
1766 {:ok, _user} = User.follow(user, other_user)
1770 |> get("/api/v1/accounts/#{other_user.id}/followers")
1772 assert [] == json_response(conn, 200)
1775 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1776 user = insert(:user)
1777 other_user = insert(:user, %{info: %{hide_followers: true}})
1778 {:ok, _user} = User.follow(user, other_user)
1782 |> assign(:user, other_user)
1783 |> get("/api/v1/accounts/#{other_user.id}/followers")
1785 refute [] == json_response(conn, 200)
1788 test "getting followers, pagination", %{conn: conn} do
1789 user = insert(:user)
1790 follower1 = insert(:user)
1791 follower2 = insert(:user)
1792 follower3 = insert(:user)
1793 {:ok, _} = User.follow(follower1, user)
1794 {:ok, _} = User.follow(follower2, user)
1795 {:ok, _} = User.follow(follower3, user)
1799 |> assign(:user, user)
1803 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1805 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1806 assert id3 == follower3.id
1807 assert id2 == follower2.id
1811 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1813 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1814 assert id2 == follower2.id
1815 assert id1 == follower1.id
1819 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1821 assert [%{"id" => id2}] = json_response(res_conn, 200)
1822 assert id2 == follower2.id
1824 assert [link_header] = get_resp_header(res_conn, "link")
1825 assert link_header =~ ~r/min_id=#{follower2.id}/
1826 assert link_header =~ ~r/max_id=#{follower2.id}/
1829 test "getting following", %{conn: conn} do
1830 user = insert(:user)
1831 other_user = insert(:user)
1832 {:ok, user} = User.follow(user, other_user)
1836 |> get("/api/v1/accounts/#{user.id}/following")
1838 assert [%{"id" => id}] = json_response(conn, 200)
1839 assert id == to_string(other_user.id)
1842 test "getting following, hide_follows", %{conn: conn} do
1843 user = insert(:user, %{info: %{hide_follows: true}})
1844 other_user = insert(:user)
1845 {:ok, user} = User.follow(user, other_user)
1849 |> get("/api/v1/accounts/#{user.id}/following")
1851 assert [] == json_response(conn, 200)
1854 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1855 user = insert(:user, %{info: %{hide_follows: true}})
1856 other_user = insert(:user)
1857 {:ok, user} = User.follow(user, other_user)
1861 |> assign(:user, user)
1862 |> get("/api/v1/accounts/#{user.id}/following")
1864 refute [] == json_response(conn, 200)
1867 test "getting following, pagination", %{conn: conn} do
1868 user = insert(:user)
1869 following1 = insert(:user)
1870 following2 = insert(:user)
1871 following3 = insert(:user)
1872 {:ok, _} = User.follow(user, following1)
1873 {:ok, _} = User.follow(user, following2)
1874 {:ok, _} = User.follow(user, following3)
1878 |> assign(:user, user)
1882 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1884 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1885 assert id3 == following3.id
1886 assert id2 == following2.id
1890 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1892 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1893 assert id2 == following2.id
1894 assert id1 == following1.id
1898 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1900 assert [%{"id" => id2}] = json_response(res_conn, 200)
1901 assert id2 == following2.id
1903 assert [link_header] = get_resp_header(res_conn, "link")
1904 assert link_header =~ ~r/min_id=#{following2.id}/
1905 assert link_header =~ ~r/max_id=#{following2.id}/
1908 test "following / unfollowing a user", %{conn: conn} do
1909 user = insert(:user)
1910 other_user = insert(:user)
1914 |> assign(:user, user)
1915 |> post("/api/v1/accounts/#{other_user.id}/follow")
1917 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
1919 user = User.get_cached_by_id(user.id)
1923 |> assign(:user, user)
1924 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
1926 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
1928 user = User.get_cached_by_id(user.id)
1932 |> assign(:user, user)
1933 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
1935 assert %{"id" => id} = json_response(conn, 200)
1936 assert id == to_string(other_user.id)
1939 test "following without reblogs" do
1940 follower = insert(:user)
1941 followed = insert(:user)
1942 other_user = insert(:user)
1946 |> assign(:user, follower)
1947 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
1949 assert %{"showing_reblogs" => false} = json_response(conn, 200)
1951 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
1952 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
1956 |> assign(:user, User.get_cached_by_id(follower.id))
1957 |> get("/api/v1/timelines/home")
1959 assert [] == json_response(conn, 200)
1963 |> assign(:user, follower)
1964 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
1966 assert %{"showing_reblogs" => true} = json_response(conn, 200)
1970 |> assign(:user, User.get_cached_by_id(follower.id))
1971 |> get("/api/v1/timelines/home")
1973 expected_activity_id = reblog.id
1974 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
1977 test "following / unfollowing errors" do
1978 user = insert(:user)
1982 |> assign(:user, user)
1985 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
1986 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1989 user = User.get_cached_by_id(user.id)
1990 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
1991 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1993 # self follow via uri
1994 user = User.get_cached_by_id(user.id)
1995 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
1996 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1998 # follow non existing user
1999 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
2000 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2002 # follow non existing user via uri
2003 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
2004 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2006 # unfollow non existing user
2007 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
2008 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2011 test "muting / unmuting a user", %{conn: conn} do
2012 user = insert(:user)
2013 other_user = insert(:user)
2017 |> assign(:user, user)
2018 |> post("/api/v1/accounts/#{other_user.id}/mute")
2020 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
2022 user = User.get_cached_by_id(user.id)
2026 |> assign(:user, user)
2027 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2029 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
2032 test "subscribing / unsubscribing to a user", %{conn: conn} do
2033 user = insert(:user)
2034 subscription_target = insert(:user)
2038 |> assign(:user, user)
2039 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2041 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2045 |> assign(:user, user)
2046 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2048 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2051 test "getting a list of mutes", %{conn: conn} do
2052 user = insert(:user)
2053 other_user = insert(:user)
2055 {:ok, user} = User.mute(user, other_user)
2059 |> assign(:user, user)
2060 |> get("/api/v1/mutes")
2062 other_user_id = to_string(other_user.id)
2063 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2066 test "blocking / unblocking a user", %{conn: conn} do
2067 user = insert(:user)
2068 other_user = insert(:user)
2072 |> assign(:user, user)
2073 |> post("/api/v1/accounts/#{other_user.id}/block")
2075 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2077 user = User.get_cached_by_id(user.id)
2081 |> assign(:user, user)
2082 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2084 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2087 test "getting a list of blocks", %{conn: conn} do
2088 user = insert(:user)
2089 other_user = insert(:user)
2091 {:ok, user} = User.block(user, other_user)
2095 |> assign(:user, user)
2096 |> get("/api/v1/blocks")
2098 other_user_id = to_string(other_user.id)
2099 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2102 test "blocking / unblocking a domain", %{conn: conn} do
2103 user = insert(:user)
2104 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2108 |> assign(:user, user)
2109 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2111 assert %{} = json_response(conn, 200)
2112 user = User.get_cached_by_ap_id(user.ap_id)
2113 assert User.blocks?(user, other_user)
2117 |> assign(:user, user)
2118 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2120 assert %{} = json_response(conn, 200)
2121 user = User.get_cached_by_ap_id(user.ap_id)
2122 refute User.blocks?(user, other_user)
2125 test "getting a list of domain blocks", %{conn: conn} do
2126 user = insert(:user)
2128 {:ok, user} = User.block_domain(user, "bad.site")
2129 {:ok, user} = User.block_domain(user, "even.worse.site")
2133 |> assign(:user, user)
2134 |> get("/api/v1/domain_blocks")
2136 domain_blocks = json_response(conn, 200)
2138 assert "bad.site" in domain_blocks
2139 assert "even.worse.site" in domain_blocks
2142 test "unimplemented follow_requests, blocks, domain blocks" do
2143 user = insert(:user)
2145 ["blocks", "domain_blocks", "follow_requests"]
2146 |> Enum.each(fn endpoint ->
2149 |> assign(:user, user)
2150 |> get("/api/v1/#{endpoint}")
2152 assert [] = json_response(conn, 200)
2156 test "returns the favorites of a user", %{conn: conn} do
2157 user = insert(:user)
2158 other_user = insert(:user)
2160 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2161 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2163 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2167 |> assign(:user, user)
2168 |> get("/api/v1/favourites")
2170 assert [status] = json_response(first_conn, 200)
2171 assert status["id"] == to_string(activity.id)
2173 assert [{"link", _link_header}] =
2174 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2176 # Honours query params
2177 {:ok, second_activity} =
2178 CommonAPI.post(other_user, %{
2180 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2183 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2185 last_like = status["id"]
2189 |> assign(:user, user)
2190 |> get("/api/v1/favourites?since_id=#{last_like}")
2192 assert [second_status] = json_response(second_conn, 200)
2193 assert second_status["id"] == to_string(second_activity.id)
2197 |> assign(:user, user)
2198 |> get("/api/v1/favourites?limit=0")
2200 assert [] = json_response(third_conn, 200)
2203 describe "getting favorites timeline of specified user" do
2205 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2206 [current_user: current_user, user: user]
2209 test "returns list of statuses favorited by specified user", %{
2211 current_user: current_user,
2214 [activity | _] = insert_pair(:note_activity)
2215 CommonAPI.favorite(activity.id, user)
2219 |> assign(:user, current_user)
2220 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2221 |> json_response(:ok)
2225 assert length(response) == 1
2226 assert like["id"] == activity.id
2229 test "returns favorites for specified user_id when user is not logged in", %{
2233 activity = insert(:note_activity)
2234 CommonAPI.favorite(activity.id, user)
2238 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2239 |> json_response(:ok)
2241 assert length(response) == 1
2244 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2246 current_user: current_user,
2250 CommonAPI.post(current_user, %{
2251 "status" => "Hi @#{user.nickname}!",
2252 "visibility" => "direct"
2255 CommonAPI.favorite(direct.id, user)
2259 |> assign(:user, current_user)
2260 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2261 |> json_response(:ok)
2263 assert length(response) == 1
2265 anonymous_response =
2267 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2268 |> json_response(:ok)
2270 assert Enum.empty?(anonymous_response)
2273 test "does not return others' favorited DM when user is not one of recipients", %{
2275 current_user: current_user,
2278 user_two = insert(:user)
2281 CommonAPI.post(user_two, %{
2282 "status" => "Hi @#{user.nickname}!",
2283 "visibility" => "direct"
2286 CommonAPI.favorite(direct.id, user)
2290 |> assign(:user, current_user)
2291 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2292 |> json_response(:ok)
2294 assert Enum.empty?(response)
2297 test "paginates favorites using since_id and max_id", %{
2299 current_user: current_user,
2302 activities = insert_list(10, :note_activity)
2304 Enum.each(activities, fn activity ->
2305 CommonAPI.favorite(activity.id, user)
2308 third_activity = Enum.at(activities, 2)
2309 seventh_activity = Enum.at(activities, 6)
2313 |> assign(:user, current_user)
2314 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2315 since_id: third_activity.id,
2316 max_id: seventh_activity.id
2318 |> json_response(:ok)
2320 assert length(response) == 3
2321 refute third_activity in response
2322 refute seventh_activity in response
2325 test "limits favorites using limit parameter", %{
2327 current_user: current_user,
2331 |> insert_list(:note_activity)
2332 |> Enum.each(fn activity ->
2333 CommonAPI.favorite(activity.id, user)
2338 |> assign(:user, current_user)
2339 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2340 |> json_response(:ok)
2342 assert length(response) == 3
2345 test "returns empty response when user does not have any favorited statuses", %{
2347 current_user: current_user,
2352 |> assign(:user, current_user)
2353 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2354 |> json_response(:ok)
2356 assert Enum.empty?(response)
2359 test "returns 404 error when specified user is not exist", %{conn: conn} do
2360 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2362 assert json_response(conn, 404) == %{"error" => "Record not found"}
2365 test "returns 403 error when user has hidden own favorites", %{
2367 current_user: current_user
2369 user = insert(:user, %{info: %{hide_favorites: true}})
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 json_response(conn, 403) == %{"error" => "Can't get favorites"}
2381 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2382 user = insert(:user)
2383 activity = insert(:note_activity)
2384 CommonAPI.favorite(activity.id, user)
2388 |> assign(:user, current_user)
2389 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2391 assert user.info.hide_favorites
2392 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2396 test "get instance information", %{conn: conn} do
2397 conn = get(conn, "/api/v1/instance")
2398 assert result = json_response(conn, 200)
2400 email = Pleroma.Config.get([:instance, :email])
2401 # Note: not checking for "max_toot_chars" since it's optional
2407 "email" => from_config_email,
2409 "streaming_api" => _
2414 "registrations" => _,
2418 assert email == from_config_email
2421 test "get instance stats", %{conn: conn} do
2422 user = insert(:user, %{local: true})
2424 user2 = insert(:user, %{local: true})
2425 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2427 insert(:user, %{local: false, nickname: "u@peer1.com"})
2428 insert(:user, %{local: false, nickname: "u@peer2.com"})
2430 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2432 # Stats should count users with missing or nil `info.deactivated` value
2433 user = User.get_cached_by_id(user.id)
2434 info_change = Changeset.change(user.info, %{deactivated: nil})
2438 |> Changeset.change()
2439 |> Changeset.put_embed(:info, info_change)
2440 |> User.update_and_set_cache()
2442 Pleroma.Stats.update_stats()
2444 conn = get(conn, "/api/v1/instance")
2446 assert result = json_response(conn, 200)
2448 stats = result["stats"]
2451 assert stats["user_count"] == 1
2452 assert stats["status_count"] == 1
2453 assert stats["domain_count"] == 2
2456 test "get peers", %{conn: conn} do
2457 insert(:user, %{local: false, nickname: "u@peer1.com"})
2458 insert(:user, %{local: false, nickname: "u@peer2.com"})
2460 Pleroma.Stats.update_stats()
2462 conn = get(conn, "/api/v1/instance/peers")
2464 assert result = json_response(conn, 200)
2466 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2469 test "put settings", %{conn: conn} do
2470 user = insert(:user)
2474 |> assign(:user, user)
2475 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2477 assert _result = json_response(conn, 200)
2479 user = User.get_cached_by_ap_id(user.ap_id)
2480 assert user.info.settings == %{"programming" => "socks"}
2483 describe "pinned statuses" do
2485 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2487 user = insert(:user)
2488 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2490 [user: user, activity: activity]
2493 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2494 {:ok, _} = CommonAPI.pin(activity.id, user)
2498 |> assign(:user, user)
2499 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2500 |> json_response(200)
2502 id_str = to_string(activity.id)
2504 assert [%{"id" => ^id_str, "pinned" => true}] = result
2507 test "pin status", %{conn: conn, user: user, activity: activity} do
2508 id_str = to_string(activity.id)
2510 assert %{"id" => ^id_str, "pinned" => true} =
2512 |> assign(:user, user)
2513 |> post("/api/v1/statuses/#{activity.id}/pin")
2514 |> json_response(200)
2516 assert [%{"id" => ^id_str, "pinned" => true}] =
2518 |> assign(:user, user)
2519 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2520 |> json_response(200)
2523 test "unpin status", %{conn: conn, user: user, activity: activity} do
2524 {:ok, _} = CommonAPI.pin(activity.id, user)
2526 id_str = to_string(activity.id)
2527 user = refresh_record(user)
2529 assert %{"id" => ^id_str, "pinned" => false} =
2531 |> assign(:user, user)
2532 |> post("/api/v1/statuses/#{activity.id}/unpin")
2533 |> json_response(200)
2537 |> assign(:user, user)
2538 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2539 |> json_response(200)
2542 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2543 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2545 id_str_one = to_string(activity_one.id)
2547 assert %{"id" => ^id_str_one, "pinned" => true} =
2549 |> assign(:user, user)
2550 |> post("/api/v1/statuses/#{id_str_one}/pin")
2551 |> json_response(200)
2553 user = refresh_record(user)
2555 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2557 |> assign(:user, user)
2558 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2559 |> json_response(400)
2565 Pleroma.Config.put([:rich_media, :enabled], true)
2568 Pleroma.Config.put([:rich_media, :enabled], false)
2571 user = insert(:user)
2575 test "returns rich-media card", %{conn: conn, user: user} do
2576 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2579 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2580 "provider_name" => "www.imdb.com",
2581 "provider_url" => "http://www.imdb.com",
2582 "title" => "The Rock",
2584 "url" => "http://www.imdb.com/title/tt0117500/",
2586 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2589 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2590 "title" => "The Rock",
2591 "type" => "video.movie",
2592 "url" => "http://www.imdb.com/title/tt0117500/",
2594 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2601 |> get("/api/v1/statuses/#{activity.id}/card")
2602 |> json_response(200)
2604 assert response == card_data
2606 # works with private posts
2608 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2612 |> assign(:user, user)
2613 |> get("/api/v1/statuses/#{activity.id}/card")
2614 |> json_response(200)
2616 assert response_two == card_data
2619 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2621 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2625 |> get("/api/v1/statuses/#{activity.id}/card")
2626 |> json_response(:ok)
2628 assert response == %{
2630 "title" => "Pleroma",
2631 "description" => "",
2633 "provider_name" => "pleroma.social",
2634 "provider_url" => "https://pleroma.social",
2635 "url" => "https://pleroma.social/",
2638 "title" => "Pleroma",
2639 "type" => "website",
2640 "url" => "https://pleroma.social/"
2648 user = insert(:user)
2649 for_user = insert(:user)
2652 CommonAPI.post(user, %{
2653 "status" => "heweoo?"
2657 CommonAPI.post(user, %{
2658 "status" => "heweoo!"
2663 |> assign(:user, for_user)
2664 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2666 assert json_response(response1, 200)["bookmarked"] == true
2670 |> assign(:user, for_user)
2671 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2673 assert json_response(response2, 200)["bookmarked"] == true
2677 |> assign(:user, for_user)
2678 |> get("/api/v1/bookmarks")
2680 assert [json_response(response2, 200), json_response(response1, 200)] ==
2681 json_response(bookmarks, 200)
2685 |> assign(:user, for_user)
2686 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2688 assert json_response(response1, 200)["bookmarked"] == false
2692 |> assign(:user, for_user)
2693 |> get("/api/v1/bookmarks")
2695 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2698 describe "conversation muting" do
2700 user = insert(:user)
2701 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2703 [user: user, activity: activity]
2706 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2707 id_str = to_string(activity.id)
2709 assert %{"id" => ^id_str, "muted" => true} =
2711 |> assign(:user, user)
2712 |> post("/api/v1/statuses/#{activity.id}/mute")
2713 |> json_response(200)
2716 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2717 {:ok, _} = CommonAPI.add_mute(user, activity)
2719 id_str = to_string(activity.id)
2720 user = refresh_record(user)
2722 assert %{"id" => ^id_str, "muted" => false} =
2724 |> assign(:user, user)
2725 |> post("/api/v1/statuses/#{activity.id}/unmute")
2726 |> json_response(200)
2730 describe "reports" do
2732 reporter = insert(:user)
2733 target_user = insert(:user)
2735 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2737 [reporter: reporter, target_user: target_user, activity: activity]
2740 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2741 assert %{"action_taken" => false, "id" => _} =
2743 |> assign(:user, reporter)
2744 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2745 |> json_response(200)
2748 test "submit a report with statuses and comment", %{
2751 target_user: target_user,
2754 assert %{"action_taken" => false, "id" => _} =
2756 |> assign(:user, reporter)
2757 |> post("/api/v1/reports", %{
2758 "account_id" => target_user.id,
2759 "status_ids" => [activity.id],
2760 "comment" => "bad status!"
2762 |> json_response(200)
2765 test "account_id is required", %{
2770 assert %{"error" => "Valid `account_id` required"} =
2772 |> assign(:user, reporter)
2773 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2774 |> json_response(400)
2777 test "comment must be up to the size specified in the config", %{
2780 target_user: target_user
2782 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2783 comment = String.pad_trailing("a", max_size + 1, "a")
2785 error = %{"error" => "Comment must be up to #{max_size} characters"}
2789 |> assign(:user, reporter)
2790 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2791 |> json_response(400)
2795 describe "link headers" do
2796 test "preserves parameters in link headers", %{conn: conn} do
2797 user = insert(:user)
2798 other_user = insert(:user)
2801 CommonAPI.post(other_user, %{
2802 "status" => "hi @#{user.nickname}",
2803 "visibility" => "public"
2807 CommonAPI.post(other_user, %{
2808 "status" => "hi @#{user.nickname}",
2809 "visibility" => "public"
2812 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2813 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2817 |> assign(:user, user)
2818 |> get("/api/v1/notifications", %{media_only: true})
2820 assert [link_header] = get_resp_header(conn, "link")
2821 assert link_header =~ ~r/media_only=true/
2822 assert link_header =~ ~r/min_id=#{notification2.id}/
2823 assert link_header =~ ~r/max_id=#{notification1.id}/
2827 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2828 # Need to set an old-style integer ID to reproduce the problem
2829 # (these are no longer assigned to new accounts but were preserved
2830 # for existing accounts during the migration to flakeIDs)
2831 user_one = insert(:user, %{id: 1212})
2832 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2836 |> get("/api/v1/accounts/#{user_one.id}")
2840 |> get("/api/v1/accounts/#{user_two.nickname}")
2844 |> get("/api/v1/accounts/#{user_two.id}")
2846 acc_one = json_response(resp_one, 200)
2847 acc_two = json_response(resp_two, 200)
2848 acc_three = json_response(resp_three, 200)
2849 refute acc_one == acc_two
2850 assert acc_two == acc_three
2853 describe "custom emoji" do
2854 test "with tags", %{conn: conn} do
2857 |> get("/api/v1/custom_emojis")
2858 |> json_response(200)
2860 assert Map.has_key?(emoji, "shortcode")
2861 assert Map.has_key?(emoji, "static_url")
2862 assert Map.has_key?(emoji, "tags")
2863 assert is_list(emoji["tags"])
2864 assert Map.has_key?(emoji, "url")
2865 assert Map.has_key?(emoji, "visible_in_picker")
2869 describe "index/2 redirections" do
2870 setup %{conn: conn} do
2874 signing_salt: "cooldude"
2879 |> Plug.Session.call(Plug.Session.init(session_opts))
2882 test_path = "/web/statuses/test"
2883 %{conn: conn, path: test_path}
2886 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
2887 conn = get(conn, path)
2889 assert conn.status == 302
2890 assert redirected_to(conn) == "/web/login"
2893 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
2894 token = insert(:oauth_token)
2898 |> assign(:user, token.user)
2899 |> put_session(:oauth_token, token.token)
2902 assert conn.status == 200
2905 test "saves referer path to session", %{conn: conn, path: path} do
2906 conn = get(conn, path)
2907 return_to = Plug.Conn.get_session(conn, :return_to)
2909 assert return_to == path
2912 test "redirects to the saved path after log in", %{conn: conn, path: path} do
2913 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2914 auth = insert(:oauth_authorization, app: app)
2918 |> put_session(:return_to, path)
2919 |> get("/web/login", %{code: auth.token})
2921 assert conn.status == 302
2922 assert redirected_to(conn) == path
2925 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
2926 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
2927 auth = insert(:oauth_authorization, app: app)
2929 conn = get(conn, "/web/login", %{code: auth.token})
2931 assert conn.status == 302
2932 assert redirected_to(conn) == "/web/getting-started"
2936 describe "scheduled activities" do
2937 test "creates a scheduled activity", %{conn: conn} do
2938 user = insert(:user)
2939 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2943 |> assign(:user, user)
2944 |> post("/api/v1/statuses", %{
2945 "status" => "scheduled",
2946 "scheduled_at" => scheduled_at
2949 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
2950 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
2951 assert [] == Repo.all(Activity)
2954 test "creates a scheduled activity with a media attachment", %{conn: conn} do
2955 user = insert(:user)
2956 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
2958 file = %Plug.Upload{
2959 content_type: "image/jpg",
2960 path: Path.absname("test/fixtures/image.jpg"),
2961 filename: "an_image.jpg"
2964 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
2968 |> assign(:user, user)
2969 |> post("/api/v1/statuses", %{
2970 "media_ids" => [to_string(upload.id)],
2971 "status" => "scheduled",
2972 "scheduled_at" => scheduled_at
2975 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
2976 assert %{"type" => "image"} = media_attachment
2979 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
2981 user = insert(:user)
2984 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
2988 |> assign(:user, user)
2989 |> post("/api/v1/statuses", %{
2990 "status" => "not scheduled",
2991 "scheduled_at" => scheduled_at
2994 assert %{"content" => "not scheduled"} = json_response(conn, 200)
2995 assert [] == Repo.all(ScheduledActivity)
2998 test "returns error when daily user limit is exceeded", %{conn: conn} do
2999 user = insert(:user)
3002 NaiveDateTime.utc_now()
3003 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3004 |> NaiveDateTime.to_iso8601()
3006 attrs = %{params: %{}, scheduled_at: today}
3007 {:ok, _} = ScheduledActivity.create(user, attrs)
3008 {:ok, _} = ScheduledActivity.create(user, attrs)
3012 |> assign(:user, user)
3013 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3015 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3018 test "returns error when total user limit is exceeded", %{conn: conn} do
3019 user = insert(:user)
3022 NaiveDateTime.utc_now()
3023 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3024 |> NaiveDateTime.to_iso8601()
3027 NaiveDateTime.utc_now()
3028 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3029 |> NaiveDateTime.to_iso8601()
3031 attrs = %{params: %{}, scheduled_at: today}
3032 {:ok, _} = ScheduledActivity.create(user, attrs)
3033 {:ok, _} = ScheduledActivity.create(user, attrs)
3034 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3038 |> assign(:user, user)
3039 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3041 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3044 test "shows scheduled activities", %{conn: conn} do
3045 user = insert(:user)
3046 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3047 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3048 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3049 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3053 |> assign(:user, user)
3058 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3060 result = json_response(conn_res, 200)
3061 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3066 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3068 result = json_response(conn_res, 200)
3069 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3074 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3076 result = json_response(conn_res, 200)
3077 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3080 test "shows a scheduled activity", %{conn: conn} do
3081 user = insert(:user)
3082 scheduled_activity = insert(:scheduled_activity, user: user)
3086 |> assign(:user, user)
3087 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3089 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3090 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3094 |> assign(:user, user)
3095 |> get("/api/v1/scheduled_statuses/404")
3097 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3100 test "updates a scheduled activity", %{conn: conn} do
3101 user = insert(:user)
3102 scheduled_activity = insert(:scheduled_activity, user: user)
3105 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3109 |> assign(:user, user)
3110 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3111 scheduled_at: new_scheduled_at
3114 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3115 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3119 |> assign(:user, user)
3120 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3122 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3125 test "deletes a scheduled activity", %{conn: conn} do
3126 user = insert(:user)
3127 scheduled_activity = insert(:scheduled_activity, user: user)
3131 |> assign(:user, user)
3132 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3134 assert %{} = json_response(res_conn, 200)
3135 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3139 |> assign(:user, user)
3140 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3142 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3146 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3147 user1 = insert(:user)
3148 user2 = insert(:user)
3149 user3 = insert(:user)
3151 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3153 # Reply to status from another user
3156 |> assign(:user, user2)
3157 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3159 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3161 activity = Activity.get_by_id_with_object(id)
3163 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3164 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3166 # Reblog from the third user
3169 |> assign(:user, user3)
3170 |> post("/api/v1/statuses/#{activity.id}/reblog")
3172 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3173 json_response(conn2, 200)
3175 assert to_string(activity.id) == id
3177 # Getting third user status
3180 |> assign(:user, user3)
3181 |> get("api/v1/timelines/home")
3183 [reblogged_activity] = json_response(conn3, 200)
3185 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3187 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3188 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3191 describe "create account by app" do
3192 test "Account registration via Application", %{conn: conn} do
3195 |> post("/api/v1/apps", %{
3196 client_name: "client_name",
3197 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3198 scopes: "read, write, follow"
3202 "client_id" => client_id,
3203 "client_secret" => client_secret,
3205 "name" => "client_name",
3206 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3209 } = json_response(conn, 200)
3213 |> post("/oauth/token", %{
3214 grant_type: "client_credentials",
3215 client_id: client_id,
3216 client_secret: client_secret
3219 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3220 json_response(conn, 200)
3223 token_from_db = Repo.get_by(Token, token: token)
3224 assert token_from_db
3226 assert scope == "read write follow"
3230 |> put_req_header("authorization", "Bearer " <> token)
3231 |> post("/api/v1/accounts", %{
3233 email: "lain@example.org",
3234 password: "PlzDontHackLain",
3239 "access_token" => token,
3240 "created_at" => _created_at,
3242 "token_type" => "Bearer"
3243 } = json_response(conn, 200)
3245 token_from_db = Repo.get_by(Token, token: token)
3246 assert token_from_db
3247 token_from_db = Repo.preload(token_from_db, :user)
3248 assert token_from_db.user
3250 assert token_from_db.user.info.confirmation_pending
3253 test "rate limit", %{conn: conn} do
3254 app_token = insert(:oauth_token, user: nil)
3257 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3258 |> Map.put(:remote_ip, {15, 15, 15, 15})
3263 |> post("/api/v1/accounts", %{
3264 username: "#{i}lain",
3265 email: "#{i}lain@example.org",
3266 password: "PlzDontHackLain",
3271 "access_token" => token,
3272 "created_at" => _created_at,
3274 "token_type" => "Bearer"
3275 } = json_response(conn, 200)
3277 token_from_db = Repo.get_by(Token, token: token)
3278 assert token_from_db
3279 token_from_db = Repo.preload(token_from_db, :user)
3280 assert token_from_db.user
3282 assert token_from_db.user.info.confirmation_pending
3287 |> post("/api/v1/accounts", %{
3289 email: "6lain@example.org",
3290 password: "PlzDontHackLain",
3294 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3298 describe "GET /api/v1/polls/:id" do
3299 test "returns poll entity for object id", %{conn: conn} do
3300 user = insert(:user)
3303 CommonAPI.post(user, %{
3304 "status" => "Pleroma does",
3305 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3308 object = Object.normalize(activity)
3312 |> assign(:user, user)
3313 |> get("/api/v1/polls/#{object.id}")
3315 response = json_response(conn, 200)
3317 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3320 test "does not expose polls for private statuses", %{conn: conn} do
3321 user = insert(:user)
3322 other_user = insert(:user)
3325 CommonAPI.post(user, %{
3326 "status" => "Pleroma does",
3327 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3328 "visibility" => "private"
3331 object = Object.normalize(activity)
3335 |> assign(:user, other_user)
3336 |> get("/api/v1/polls/#{object.id}")
3338 assert json_response(conn, 404)
3342 describe "POST /api/v1/polls/:id/votes" do
3343 test "votes are added to the poll", %{conn: conn} do
3344 user = insert(:user)
3345 other_user = insert(:user)
3348 CommonAPI.post(user, %{
3349 "status" => "A very delicious sandwich",
3351 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3357 object = Object.normalize(activity)
3361 |> assign(:user, other_user)
3362 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3364 assert json_response(conn, 200)
3365 object = Object.get_by_id(object.id)
3367 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3372 test "author can't vote", %{conn: conn} do
3373 user = insert(:user)
3376 CommonAPI.post(user, %{
3377 "status" => "Am I cute?",
3378 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3381 object = Object.normalize(activity)
3384 |> assign(:user, user)
3385 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3386 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3388 object = Object.get_by_id(object.id)
3390 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3393 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3394 user = insert(:user)
3395 other_user = insert(:user)
3398 CommonAPI.post(user, %{
3399 "status" => "The glass is",
3400 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3403 object = Object.normalize(activity)
3406 |> assign(:user, other_user)
3407 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3408 |> json_response(422) == %{"error" => "Too many choices"}
3410 object = Object.get_by_id(object.id)
3412 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->