1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.Notification
13 alias Pleroma.ScheduledActivity
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.MastodonAPI.FilterView
18 alias Pleroma.Web.OAuth.App
19 alias Pleroma.Web.OAuth.Token
20 alias Pleroma.Web.OStatus
21 alias Pleroma.Web.Push
22 alias Pleroma.Web.TwitterAPI.TwitterAPI
23 import Pleroma.Factory
24 import ExUnit.CaptureLog
28 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
32 test "the home timeline", %{conn: conn} do
34 following = insert(:user)
36 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
40 |> assign(:user, user)
41 |> get("/api/v1/timelines/home")
43 assert Enum.empty?(json_response(conn, 200))
45 {:ok, user} = User.follow(user, following)
49 |> assign(:user, user)
50 |> get("/api/v1/timelines/home")
52 assert [%{"content" => "test"}] = json_response(conn, 200)
55 test "the public timeline", %{conn: conn} do
56 following = insert(:user)
59 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
62 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
66 |> get("/api/v1/timelines/public", %{"local" => "False"})
68 assert length(json_response(conn, 200)) == 2
72 |> get("/api/v1/timelines/public", %{"local" => "True"})
74 assert [%{"content" => "test"}] = json_response(conn, 200)
78 |> get("/api/v1/timelines/public", %{"local" => "1"})
80 assert [%{"content" => "test"}] = json_response(conn, 200)
84 test "the public timeline when public is set to false", %{conn: conn} do
85 public = Pleroma.Config.get([:instance, :public])
86 Pleroma.Config.put([:instance, :public], false)
89 Pleroma.Config.put([:instance, :public], public)
93 |> get("/api/v1/timelines/public", %{"local" => "False"})
94 |> json_response(403) == %{"error" => "This resource requires authentication."}
97 test "posting a status", %{conn: conn} do
100 idempotency_key = "Pikachu rocks!"
104 |> assign(:user, user)
105 |> put_req_header("idempotency-key", idempotency_key)
106 |> post("/api/v1/statuses", %{
108 "spoiler_text" => "2hu",
109 "sensitive" => "false"
112 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
114 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
116 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
117 json_response(conn_one, 200)
119 assert Activity.get_by_id(id)
123 |> assign(:user, user)
124 |> put_req_header("idempotency-key", idempotency_key)
125 |> post("/api/v1/statuses", %{
127 "spoiler_text" => "2hu",
128 "sensitive" => "false"
131 assert %{"id" => second_id} = json_response(conn_two, 200)
133 assert id == second_id
137 |> assign(:user, user)
138 |> post("/api/v1/statuses", %{
140 "spoiler_text" => "2hu",
141 "sensitive" => "false"
144 assert %{"id" => third_id} = json_response(conn_three, 200)
146 refute id == third_id
149 test "posting a sensitive status", %{conn: conn} do
154 |> assign(:user, user)
155 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
157 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
158 assert Activity.get_by_id(id)
161 test "posting a fake status", %{conn: conn} do
166 |> assign(:user, user)
167 |> post("/api/v1/statuses", %{
169 "\"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"
172 real_status = json_response(real_conn, 200)
175 assert Object.get_by_ap_id(real_status["uri"])
179 |> Map.put("id", nil)
180 |> Map.put("url", nil)
181 |> Map.put("uri", nil)
182 |> Map.put("created_at", nil)
183 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
187 |> assign(:user, user)
188 |> post("/api/v1/statuses", %{
190 "\"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",
194 fake_status = json_response(fake_conn, 200)
197 refute Object.get_by_ap_id(fake_status["uri"])
201 |> Map.put("id", nil)
202 |> Map.put("url", nil)
203 |> Map.put("uri", nil)
204 |> Map.put("created_at", nil)
205 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
207 assert real_status == fake_status
210 test "posting a status with OGP link preview", %{conn: conn} do
211 Pleroma.Config.put([:rich_media, :enabled], true)
216 |> assign(:user, user)
217 |> post("/api/v1/statuses", %{
218 "status" => "http://example.com/ogp"
221 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
222 assert Activity.get_by_id(id)
223 Pleroma.Config.put([:rich_media, :enabled], false)
226 test "posting a direct status", %{conn: conn} do
227 user1 = insert(:user)
228 user2 = insert(:user)
229 content = "direct cofe @#{user2.nickname}"
233 |> assign(:user, user1)
234 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
236 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
237 assert activity = Activity.get_by_id(id)
238 assert activity.recipients == [user2.ap_id, user1.ap_id]
239 assert activity.data["to"] == [user2.ap_id]
240 assert activity.data["cc"] == []
243 test "direct timeline", %{conn: conn} do
244 user_one = insert(:user)
245 user_two = insert(:user)
247 {:ok, user_two} = User.follow(user_two, user_one)
250 CommonAPI.post(user_one, %{
251 "status" => "Hi @#{user_two.nickname}!",
252 "visibility" => "direct"
255 {:ok, _follower_only} =
256 CommonAPI.post(user_one, %{
257 "status" => "Hi @#{user_two.nickname}!",
258 "visibility" => "private"
261 # Only direct should be visible here
264 |> assign(:user, user_two)
265 |> get("api/v1/timelines/direct")
267 [status] = json_response(res_conn, 200)
269 assert %{"visibility" => "direct"} = status
270 assert status["url"] != direct.data["id"]
272 # User should be able to see his own direct message
275 |> assign(:user, user_one)
276 |> get("api/v1/timelines/direct")
278 [status] = json_response(res_conn, 200)
280 assert %{"visibility" => "direct"} = status
282 # Both should be visible here
285 |> assign(:user, user_two)
286 |> get("api/v1/timelines/home")
288 [_s1, _s2] = json_response(res_conn, 200)
291 Enum.each(1..20, fn _ ->
293 CommonAPI.post(user_one, %{
294 "status" => "Hi @#{user_two.nickname}!",
295 "visibility" => "direct"
301 |> assign(:user, user_two)
302 |> get("api/v1/timelines/direct")
304 statuses = json_response(res_conn, 200)
305 assert length(statuses) == 20
309 |> assign(:user, user_two)
310 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
312 [status] = json_response(res_conn, 200)
314 assert status["url"] != direct.data["id"]
317 test "Conversations", %{conn: conn} do
318 user_one = insert(:user)
319 user_two = insert(:user)
320 user_three = insert(:user)
322 {:ok, user_two} = User.follow(user_two, user_one)
325 CommonAPI.post(user_one, %{
326 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
327 "visibility" => "direct"
330 {:ok, _follower_only} =
331 CommonAPI.post(user_one, %{
332 "status" => "Hi @#{user_two.nickname}!",
333 "visibility" => "private"
338 |> assign(:user, user_one)
339 |> get("/api/v1/conversations")
341 assert response = json_response(res_conn, 200)
346 "accounts" => res_accounts,
347 "last_status" => res_last_status,
352 account_ids = Enum.map(res_accounts, & &1["id"])
353 assert length(res_accounts) == 2
354 assert user_two.id in account_ids
355 assert user_three.id in account_ids
356 assert is_binary(res_id)
357 assert unread == true
358 assert res_last_status["id"] == direct.id
360 # Apparently undocumented API endpoint
363 |> assign(:user, user_one)
364 |> post("/api/v1/conversations/#{res_id}/read")
366 assert response = json_response(res_conn, 200)
367 assert length(response["accounts"]) == 2
368 assert response["last_status"]["id"] == direct.id
369 assert response["unread"] == false
371 # (vanilla) Mastodon frontend behaviour
374 |> assign(:user, user_one)
375 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
377 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
380 test "doesn't include DMs from blocked users", %{conn: conn} do
381 blocker = insert(:user)
382 blocked = insert(:user)
384 {:ok, blocker} = User.block(blocker, blocked)
386 {:ok, _blocked_direct} =
387 CommonAPI.post(blocked, %{
388 "status" => "Hi @#{blocker.nickname}!",
389 "visibility" => "direct"
393 CommonAPI.post(user, %{
394 "status" => "Hi @#{blocker.nickname}!",
395 "visibility" => "direct"
400 |> assign(:user, user)
401 |> get("api/v1/timelines/direct")
403 [status] = json_response(res_conn, 200)
404 assert status["id"] == direct.id
407 test "replying to a status", %{conn: conn} do
410 {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
414 |> assign(:user, user)
415 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
417 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
419 activity = Activity.get_by_id(id)
421 assert activity.data["context"] == replied_to.data["context"]
422 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
425 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
430 |> assign(:user, user)
431 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
433 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
435 activity = Activity.get_by_id(id)
440 test "verify_credentials", %{conn: conn} do
445 |> assign(:user, user)
446 |> get("/api/v1/accounts/verify_credentials")
448 assert %{"id" => id, "source" => %{"privacy" => "public"}} = json_response(conn, 200)
449 assert id == to_string(user.id)
452 test "verify_credentials default scope unlisted", %{conn: conn} do
453 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
457 |> assign(:user, user)
458 |> get("/api/v1/accounts/verify_credentials")
460 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
461 assert id == to_string(user.id)
464 test "apps/verify_credentials", %{conn: conn} do
465 token = insert(:oauth_token)
469 |> assign(:user, token.user)
470 |> assign(:token, token)
471 |> get("/api/v1/apps/verify_credentials")
473 app = Repo.preload(token, :app).app
476 "name" => app.client_name,
477 "website" => app.website,
478 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
481 assert expected == json_response(conn, 200)
484 test "creates an oauth app", %{conn: conn} do
486 app_attrs = build(:oauth_app)
490 |> assign(:user, user)
491 |> post("/api/v1/apps", %{
492 client_name: app_attrs.client_name,
493 redirect_uris: app_attrs.redirect_uris
496 [app] = Repo.all(App)
499 "name" => app.client_name,
500 "website" => app.website,
501 "client_id" => app.client_id,
502 "client_secret" => app.client_secret,
503 "id" => app.id |> to_string(),
504 "redirect_uri" => app.redirect_uris,
505 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
508 assert expected == json_response(conn, 200)
511 test "get a status", %{conn: conn} do
512 activity = insert(:note_activity)
516 |> get("/api/v1/statuses/#{activity.id}")
518 assert %{"id" => id} = json_response(conn, 200)
519 assert id == to_string(activity.id)
522 describe "deleting a status" do
523 test "when you created it", %{conn: conn} do
524 activity = insert(:note_activity)
525 author = User.get_cached_by_ap_id(activity.data["actor"])
529 |> assign(:user, author)
530 |> delete("/api/v1/statuses/#{activity.id}")
532 assert %{} = json_response(conn, 200)
534 refute Activity.get_by_id(activity.id)
537 test "when you didn't create it", %{conn: conn} do
538 activity = insert(:note_activity)
543 |> assign(:user, user)
544 |> delete("/api/v1/statuses/#{activity.id}")
546 assert %{"error" => _} = json_response(conn, 403)
548 assert Activity.get_by_id(activity.id) == activity
551 test "when you're an admin or moderator", %{conn: conn} do
552 activity1 = insert(:note_activity)
553 activity2 = insert(:note_activity)
554 admin = insert(:user, info: %{is_admin: true})
555 moderator = insert(:user, info: %{is_moderator: true})
559 |> assign(:user, admin)
560 |> delete("/api/v1/statuses/#{activity1.id}")
562 assert %{} = json_response(res_conn, 200)
566 |> assign(:user, moderator)
567 |> delete("/api/v1/statuses/#{activity2.id}")
569 assert %{} = json_response(res_conn, 200)
571 refute Activity.get_by_id(activity1.id)
572 refute Activity.get_by_id(activity2.id)
576 describe "filters" do
577 test "creating a filter", %{conn: conn} do
580 filter = %Pleroma.Filter{
587 |> assign(:user, user)
588 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
590 assert response = json_response(conn, 200)
591 assert response["phrase"] == filter.phrase
592 assert response["context"] == filter.context
593 assert response["irreversible"] == false
594 assert response["id"] != nil
595 assert response["id"] != ""
598 test "fetching a list of filters", %{conn: conn} do
601 query_one = %Pleroma.Filter{
608 query_two = %Pleroma.Filter{
615 {:ok, filter_one} = Pleroma.Filter.create(query_one)
616 {:ok, filter_two} = Pleroma.Filter.create(query_two)
620 |> assign(:user, user)
621 |> get("/api/v1/filters")
622 |> json_response(200)
628 filters: [filter_two, filter_one]
632 test "get a filter", %{conn: conn} do
635 query = %Pleroma.Filter{
642 {:ok, filter} = Pleroma.Filter.create(query)
646 |> assign(:user, user)
647 |> get("/api/v1/filters/#{filter.filter_id}")
649 assert _response = json_response(conn, 200)
652 test "update a filter", %{conn: conn} do
655 query = %Pleroma.Filter{
662 {:ok, _filter} = Pleroma.Filter.create(query)
664 new = %Pleroma.Filter{
671 |> assign(:user, user)
672 |> put("/api/v1/filters/#{query.filter_id}", %{
677 assert response = json_response(conn, 200)
678 assert response["phrase"] == new.phrase
679 assert response["context"] == new.context
682 test "delete a filter", %{conn: conn} do
685 query = %Pleroma.Filter{
692 {:ok, filter} = Pleroma.Filter.create(query)
696 |> assign(:user, user)
697 |> delete("/api/v1/filters/#{filter.filter_id}")
699 assert response = json_response(conn, 200)
700 assert response == %{}
705 test "creating a list", %{conn: conn} do
710 |> assign(:user, user)
711 |> post("/api/v1/lists", %{"title" => "cuties"})
713 assert %{"title" => title} = json_response(conn, 200)
714 assert title == "cuties"
717 test "adding users to a list", %{conn: conn} do
719 other_user = insert(:user)
720 {:ok, list} = Pleroma.List.create("name", user)
724 |> assign(:user, user)
725 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
727 assert %{} == json_response(conn, 200)
728 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
729 assert following == [other_user.follower_address]
732 test "removing users from a list", %{conn: conn} do
734 other_user = insert(:user)
735 third_user = insert(:user)
736 {:ok, list} = Pleroma.List.create("name", user)
737 {:ok, list} = Pleroma.List.follow(list, other_user)
738 {:ok, list} = Pleroma.List.follow(list, third_user)
742 |> assign(:user, user)
743 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
745 assert %{} == json_response(conn, 200)
746 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
747 assert following == [third_user.follower_address]
750 test "listing users in a list", %{conn: conn} do
752 other_user = insert(:user)
753 {:ok, list} = Pleroma.List.create("name", user)
754 {:ok, list} = Pleroma.List.follow(list, other_user)
758 |> assign(:user, user)
759 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
761 assert [%{"id" => id}] = json_response(conn, 200)
762 assert id == to_string(other_user.id)
765 test "retrieving a list", %{conn: conn} do
767 {:ok, list} = Pleroma.List.create("name", user)
771 |> assign(:user, user)
772 |> get("/api/v1/lists/#{list.id}")
774 assert %{"id" => id} = json_response(conn, 200)
775 assert id == to_string(list.id)
778 test "renaming a list", %{conn: conn} do
780 {:ok, list} = Pleroma.List.create("name", user)
784 |> assign(:user, user)
785 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
787 assert %{"title" => name} = json_response(conn, 200)
788 assert name == "newname"
791 test "deleting a list", %{conn: conn} do
793 {:ok, list} = Pleroma.List.create("name", user)
797 |> assign(:user, user)
798 |> delete("/api/v1/lists/#{list.id}")
800 assert %{} = json_response(conn, 200)
801 assert is_nil(Repo.get(Pleroma.List, list.id))
804 test "list timeline", %{conn: conn} do
806 other_user = insert(:user)
807 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
808 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
809 {:ok, list} = Pleroma.List.create("name", user)
810 {:ok, list} = Pleroma.List.follow(list, other_user)
814 |> assign(:user, user)
815 |> get("/api/v1/timelines/list/#{list.id}")
817 assert [%{"id" => id}] = json_response(conn, 200)
819 assert id == to_string(activity_two.id)
822 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
824 other_user = insert(:user)
825 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
827 {:ok, _activity_two} =
828 TwitterAPI.create_status(other_user, %{
829 "status" => "Marisa is cute.",
830 "visibility" => "private"
833 {:ok, list} = Pleroma.List.create("name", user)
834 {:ok, list} = Pleroma.List.follow(list, other_user)
838 |> assign(:user, user)
839 |> get("/api/v1/timelines/list/#{list.id}")
841 assert [%{"id" => id}] = json_response(conn, 200)
843 assert id == to_string(activity_one.id)
847 describe "notifications" do
848 test "list of notifications", %{conn: conn} do
850 other_user = insert(:user)
853 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
855 {:ok, [_notification]} = Notification.create_notifications(activity)
859 |> assign(:user, user)
860 |> get("/api/v1/notifications")
863 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
865 }\">@<span>#{user.nickname}</span></a></span>"
867 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
868 assert response == expected_response
871 test "getting a single notification", %{conn: conn} do
873 other_user = insert(:user)
876 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
878 {:ok, [notification]} = Notification.create_notifications(activity)
882 |> assign(:user, user)
883 |> get("/api/v1/notifications/#{notification.id}")
886 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
888 }\">@<span>#{user.nickname}</span></a></span>"
890 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
891 assert response == expected_response
894 test "dismissing a single notification", %{conn: conn} do
896 other_user = insert(:user)
899 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
901 {:ok, [notification]} = Notification.create_notifications(activity)
905 |> assign(:user, user)
906 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
908 assert %{} = json_response(conn, 200)
911 test "clearing all notifications", %{conn: conn} do
913 other_user = insert(:user)
916 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
918 {:ok, [_notification]} = Notification.create_notifications(activity)
922 |> assign(:user, user)
923 |> post("/api/v1/notifications/clear")
925 assert %{} = json_response(conn, 200)
929 |> assign(:user, user)
930 |> get("/api/v1/notifications")
932 assert all = json_response(conn, 200)
936 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
938 other_user = insert(:user)
940 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
941 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
942 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
943 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
945 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
946 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
947 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
948 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
952 |> assign(:user, user)
957 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
959 result = json_response(conn_res, 200)
960 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
965 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
967 result = json_response(conn_res, 200)
968 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
973 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
975 result = json_response(conn_res, 200)
976 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
979 test "filters notifications using exclude_types", %{conn: conn} do
981 other_user = insert(:user)
983 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
984 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
985 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
986 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
987 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
989 mention_notification_id =
990 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
992 favorite_notification_id =
993 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
995 reblog_notification_id =
996 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
998 follow_notification_id =
999 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1003 |> assign(:user, user)
1006 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1008 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1011 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1013 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1016 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1018 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1021 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1023 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1026 test "destroy multiple", %{conn: conn} do
1027 user = insert(:user)
1028 other_user = insert(:user)
1030 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1031 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1032 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1033 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1035 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1036 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1037 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1038 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1042 |> assign(:user, user)
1046 |> get("/api/v1/notifications")
1048 result = json_response(conn_res, 200)
1049 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1053 |> assign(:user, other_user)
1057 |> get("/api/v1/notifications")
1059 result = json_response(conn_res, 200)
1060 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1064 |> delete("/api/v1/notifications/destroy_multiple", %{
1065 "ids" => [notification1_id, notification2_id]
1068 assert json_response(conn_destroy, 200) == %{}
1072 |> get("/api/v1/notifications")
1074 result = json_response(conn_res, 200)
1075 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1079 describe "reblogging" do
1080 test "reblogs and returns the reblogged status", %{conn: conn} do
1081 activity = insert(:note_activity)
1082 user = insert(:user)
1086 |> assign(:user, user)
1087 |> post("/api/v1/statuses/#{activity.id}/reblog")
1090 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1092 } = json_response(conn, 200)
1094 assert to_string(activity.id) == id
1097 test "reblogged status for another user", %{conn: conn} do
1098 activity = insert(:note_activity)
1099 user1 = insert(:user)
1100 user2 = insert(:user)
1101 user3 = insert(:user)
1102 CommonAPI.favorite(activity.id, user2)
1103 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1104 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1105 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1109 |> assign(:user, user3)
1110 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1113 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1114 "reblogged" => false,
1115 "favourited" => false,
1116 "bookmarked" => false
1117 } = json_response(conn_res, 200)
1121 |> assign(:user, user2)
1122 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1125 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1126 "reblogged" => true,
1127 "favourited" => true,
1128 "bookmarked" => true
1129 } = json_response(conn_res, 200)
1131 assert to_string(activity.id) == id
1135 describe "unreblogging" do
1136 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1137 activity = insert(:note_activity)
1138 user = insert(:user)
1140 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1144 |> assign(:user, user)
1145 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1147 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1149 assert to_string(activity.id) == id
1153 describe "favoriting" do
1154 test "favs a status and returns it", %{conn: conn} do
1155 activity = insert(:note_activity)
1156 user = insert(:user)
1160 |> assign(:user, user)
1161 |> post("/api/v1/statuses/#{activity.id}/favourite")
1163 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1164 json_response(conn, 200)
1166 assert to_string(activity.id) == id
1169 test "returns 500 for a wrong id", %{conn: conn} do
1170 user = insert(:user)
1174 |> assign(:user, user)
1175 |> post("/api/v1/statuses/1/favourite")
1176 |> json_response(500)
1178 assert resp == "Something went wrong"
1182 describe "unfavoriting" do
1183 test "unfavorites a status and returns it", %{conn: conn} do
1184 activity = insert(:note_activity)
1185 user = insert(:user)
1187 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1191 |> assign(:user, user)
1192 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1194 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1195 json_response(conn, 200)
1197 assert to_string(activity.id) == id
1201 describe "user timelines" do
1202 test "gets a users statuses", %{conn: conn} do
1203 user_one = insert(:user)
1204 user_two = insert(:user)
1205 user_three = insert(:user)
1207 {:ok, user_three} = User.follow(user_three, user_one)
1209 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1211 {:ok, direct_activity} =
1212 CommonAPI.post(user_one, %{
1213 "status" => "Hi, @#{user_two.nickname}.",
1214 "visibility" => "direct"
1217 {:ok, private_activity} =
1218 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1222 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1224 assert [%{"id" => id}] = json_response(resp, 200)
1225 assert id == to_string(activity.id)
1229 |> assign(:user, user_two)
1230 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1232 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1233 assert id_one == to_string(direct_activity.id)
1234 assert id_two == to_string(activity.id)
1238 |> assign(:user, user_three)
1239 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1241 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1242 assert id_one == to_string(private_activity.id)
1243 assert id_two == to_string(activity.id)
1246 test "unimplemented pinned statuses feature", %{conn: conn} do
1247 note = insert(:note_activity)
1248 user = User.get_cached_by_ap_id(note.data["actor"])
1252 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1254 assert json_response(conn, 200) == []
1257 test "gets an users media", %{conn: conn} do
1258 note = insert(:note_activity)
1259 user = User.get_cached_by_ap_id(note.data["actor"])
1261 file = %Plug.Upload{
1262 content_type: "image/jpg",
1263 path: Path.absname("test/fixtures/image.jpg"),
1264 filename: "an_image.jpg"
1268 TwitterAPI.upload(file, user, "json")
1272 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1276 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1278 assert [%{"id" => id}] = json_response(conn, 200)
1279 assert id == to_string(image_post.id)
1283 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1285 assert [%{"id" => id}] = json_response(conn, 200)
1286 assert id == to_string(image_post.id)
1289 test "gets a user's statuses without reblogs", %{conn: conn} do
1290 user = insert(:user)
1291 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1292 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1296 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1298 assert [%{"id" => id}] = json_response(conn, 200)
1299 assert id == to_string(post.id)
1303 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1305 assert [%{"id" => id}] = json_response(conn, 200)
1306 assert id == to_string(post.id)
1310 describe "user relationships" do
1311 test "returns the relationships for the current user", %{conn: conn} do
1312 user = insert(:user)
1313 other_user = insert(:user)
1314 {:ok, user} = User.follow(user, other_user)
1318 |> assign(:user, user)
1319 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1321 assert [relationship] = json_response(conn, 200)
1323 assert to_string(other_user.id) == relationship["id"]
1327 describe "locked accounts" do
1328 test "/api/v1/follow_requests works" do
1329 user = insert(:user, %{info: %User.Info{locked: true}})
1330 other_user = insert(:user)
1332 {:ok, _activity} = ActivityPub.follow(other_user, user)
1334 user = User.get_cached_by_id(user.id)
1335 other_user = User.get_cached_by_id(other_user.id)
1337 assert User.following?(other_user, user) == false
1341 |> assign(:user, user)
1342 |> get("/api/v1/follow_requests")
1344 assert [relationship] = json_response(conn, 200)
1345 assert to_string(other_user.id) == relationship["id"]
1348 test "/api/v1/follow_requests/:id/authorize works" do
1349 user = insert(:user, %{info: %User.Info{locked: true}})
1350 other_user = insert(:user)
1352 {:ok, _activity} = ActivityPub.follow(other_user, user)
1354 user = User.get_cached_by_id(user.id)
1355 other_user = User.get_cached_by_id(other_user.id)
1357 assert User.following?(other_user, user) == false
1361 |> assign(:user, user)
1362 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1364 assert relationship = json_response(conn, 200)
1365 assert to_string(other_user.id) == relationship["id"]
1367 user = User.get_cached_by_id(user.id)
1368 other_user = User.get_cached_by_id(other_user.id)
1370 assert User.following?(other_user, user) == true
1373 test "verify_credentials", %{conn: conn} do
1374 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1378 |> assign(:user, user)
1379 |> get("/api/v1/accounts/verify_credentials")
1381 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1382 assert id == to_string(user.id)
1385 test "/api/v1/follow_requests/:id/reject works" do
1386 user = insert(:user, %{info: %User.Info{locked: true}})
1387 other_user = insert(:user)
1389 {:ok, _activity} = ActivityPub.follow(other_user, user)
1391 user = User.get_cached_by_id(user.id)
1395 |> assign(:user, user)
1396 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1398 assert relationship = json_response(conn, 200)
1399 assert to_string(other_user.id) == relationship["id"]
1401 user = User.get_cached_by_id(user.id)
1402 other_user = User.get_cached_by_id(other_user.id)
1404 assert User.following?(other_user, user) == false
1408 test "account fetching", %{conn: conn} do
1409 user = insert(:user)
1413 |> get("/api/v1/accounts/#{user.id}")
1415 assert %{"id" => id} = json_response(conn, 200)
1416 assert id == to_string(user.id)
1420 |> get("/api/v1/accounts/-1")
1422 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1425 test "account fetching also works nickname", %{conn: conn} do
1426 user = insert(:user)
1430 |> get("/api/v1/accounts/#{user.nickname}")
1432 assert %{"id" => id} = json_response(conn, 200)
1433 assert id == user.id
1436 test "media upload", %{conn: conn} do
1437 file = %Plug.Upload{
1438 content_type: "image/jpg",
1439 path: Path.absname("test/fixtures/image.jpg"),
1440 filename: "an_image.jpg"
1443 desc = "Description of the image"
1445 user = insert(:user)
1449 |> assign(:user, user)
1450 |> post("/api/v1/media", %{"file" => file, "description" => desc})
1452 assert media = json_response(conn, 200)
1454 assert media["type"] == "image"
1455 assert media["description"] == desc
1458 object = Repo.get(Object, media["id"])
1459 assert object.data["actor"] == User.ap_id(user)
1462 test "mascot upload", %{conn: conn} do
1463 user = insert(:user)
1465 non_image_file = %Plug.Upload{
1466 content_type: "audio/mpeg",
1467 path: Path.absname("test/fixtures/sound.mp3"),
1468 filename: "sound.mp3"
1473 |> assign(:user, user)
1474 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1476 assert json_response(conn, 415)
1478 file = %Plug.Upload{
1479 content_type: "image/jpg",
1480 path: Path.absname("test/fixtures/image.jpg"),
1481 filename: "an_image.jpg"
1486 |> assign(:user, user)
1487 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1489 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1492 test "mascot retrieving", %{conn: conn} do
1493 user = insert(:user)
1494 # When user hasn't set a mascot, we should just get pleroma tan back
1497 |> assign(:user, user)
1498 |> get("/api/v1/pleroma/mascot")
1500 assert %{"url" => url} = json_response(conn, 200)
1501 assert url =~ "pleroma-fox-tan-smol"
1503 # When a user sets their mascot, we should get that back
1504 file = %Plug.Upload{
1505 content_type: "image/jpg",
1506 path: Path.absname("test/fixtures/image.jpg"),
1507 filename: "an_image.jpg"
1512 |> assign(:user, user)
1513 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1515 assert json_response(conn, 200)
1517 user = User.get_cached_by_id(user.id)
1521 |> assign(:user, user)
1522 |> get("/api/v1/pleroma/mascot")
1524 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1525 assert url =~ "an_image"
1528 test "hashtag timeline", %{conn: conn} do
1529 following = insert(:user)
1532 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1534 {:ok, [_activity]} =
1535 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1539 |> get("/api/v1/timelines/tag/2hu")
1541 assert [%{"id" => id}] = json_response(nconn, 200)
1543 assert id == to_string(activity.id)
1545 # works for different capitalization too
1548 |> get("/api/v1/timelines/tag/2HU")
1550 assert [%{"id" => id}] = json_response(nconn, 200)
1552 assert id == to_string(activity.id)
1556 test "multi-hashtag timeline", %{conn: conn} do
1557 user = insert(:user)
1559 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1560 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1561 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1565 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1567 [status_none, status_test1, status_test] = json_response(any_test, 200)
1569 assert to_string(activity_test.id) == status_test["id"]
1570 assert to_string(activity_test1.id) == status_test1["id"]
1571 assert to_string(activity_none.id) == status_none["id"]
1575 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1577 assert [status_test1] == json_response(restricted_test, 200)
1579 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1581 assert [status_none] == json_response(all_test, 200)
1584 test "getting followers", %{conn: conn} do
1585 user = insert(:user)
1586 other_user = insert(:user)
1587 {:ok, user} = User.follow(user, other_user)
1591 |> get("/api/v1/accounts/#{other_user.id}/followers")
1593 assert [%{"id" => id}] = json_response(conn, 200)
1594 assert id == to_string(user.id)
1597 test "getting followers, hide_followers", %{conn: conn} do
1598 user = insert(:user)
1599 other_user = insert(:user, %{info: %{hide_followers: true}})
1600 {:ok, _user} = User.follow(user, other_user)
1604 |> get("/api/v1/accounts/#{other_user.id}/followers")
1606 assert [] == json_response(conn, 200)
1609 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1610 user = insert(:user)
1611 other_user = insert(:user, %{info: %{hide_followers: true}})
1612 {:ok, _user} = User.follow(user, other_user)
1616 |> assign(:user, other_user)
1617 |> get("/api/v1/accounts/#{other_user.id}/followers")
1619 refute [] == json_response(conn, 200)
1622 test "getting followers, pagination", %{conn: conn} do
1623 user = insert(:user)
1624 follower1 = insert(:user)
1625 follower2 = insert(:user)
1626 follower3 = insert(:user)
1627 {:ok, _} = User.follow(follower1, user)
1628 {:ok, _} = User.follow(follower2, user)
1629 {:ok, _} = User.follow(follower3, user)
1633 |> assign(:user, user)
1637 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1639 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1640 assert id3 == follower3.id
1641 assert id2 == follower2.id
1645 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1647 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1648 assert id2 == follower2.id
1649 assert id1 == follower1.id
1653 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1655 assert [%{"id" => id2}] = json_response(res_conn, 200)
1656 assert id2 == follower2.id
1658 assert [link_header] = get_resp_header(res_conn, "link")
1659 assert link_header =~ ~r/min_id=#{follower2.id}/
1660 assert link_header =~ ~r/max_id=#{follower2.id}/
1663 test "getting following", %{conn: conn} do
1664 user = insert(:user)
1665 other_user = insert(:user)
1666 {:ok, user} = User.follow(user, other_user)
1670 |> get("/api/v1/accounts/#{user.id}/following")
1672 assert [%{"id" => id}] = json_response(conn, 200)
1673 assert id == to_string(other_user.id)
1676 test "getting following, hide_follows", %{conn: conn} do
1677 user = insert(:user, %{info: %{hide_follows: true}})
1678 other_user = insert(:user)
1679 {:ok, user} = User.follow(user, other_user)
1683 |> get("/api/v1/accounts/#{user.id}/following")
1685 assert [] == json_response(conn, 200)
1688 test "getting following, hide_follows, same user requesting", %{conn: conn} do
1689 user = insert(:user, %{info: %{hide_follows: true}})
1690 other_user = insert(:user)
1691 {:ok, user} = User.follow(user, other_user)
1695 |> assign(:user, user)
1696 |> get("/api/v1/accounts/#{user.id}/following")
1698 refute [] == json_response(conn, 200)
1701 test "getting following, pagination", %{conn: conn} do
1702 user = insert(:user)
1703 following1 = insert(:user)
1704 following2 = insert(:user)
1705 following3 = insert(:user)
1706 {:ok, _} = User.follow(user, following1)
1707 {:ok, _} = User.follow(user, following2)
1708 {:ok, _} = User.follow(user, following3)
1712 |> assign(:user, user)
1716 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
1718 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1719 assert id3 == following3.id
1720 assert id2 == following2.id
1724 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
1726 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1727 assert id2 == following2.id
1728 assert id1 == following1.id
1732 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
1734 assert [%{"id" => id2}] = json_response(res_conn, 200)
1735 assert id2 == following2.id
1737 assert [link_header] = get_resp_header(res_conn, "link")
1738 assert link_header =~ ~r/min_id=#{following2.id}/
1739 assert link_header =~ ~r/max_id=#{following2.id}/
1742 test "following / unfollowing a user", %{conn: conn} do
1743 user = insert(:user)
1744 other_user = insert(:user)
1748 |> assign(:user, user)
1749 |> post("/api/v1/accounts/#{other_user.id}/follow")
1751 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
1753 user = User.get_cached_by_id(user.id)
1757 |> assign(:user, user)
1758 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
1760 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
1762 user = User.get_cached_by_id(user.id)
1766 |> assign(:user, user)
1767 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
1769 assert %{"id" => id} = json_response(conn, 200)
1770 assert id == to_string(other_user.id)
1773 test "following without reblogs" do
1774 follower = insert(:user)
1775 followed = insert(:user)
1776 other_user = insert(:user)
1780 |> assign(:user, follower)
1781 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
1783 assert %{"showing_reblogs" => false} = json_response(conn, 200)
1785 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
1786 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
1790 |> assign(:user, User.get_cached_by_id(follower.id))
1791 |> get("/api/v1/timelines/home")
1793 assert [] == json_response(conn, 200)
1797 |> assign(:user, follower)
1798 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
1800 assert %{"showing_reblogs" => true} = json_response(conn, 200)
1804 |> assign(:user, User.get_cached_by_id(follower.id))
1805 |> get("/api/v1/timelines/home")
1807 expected_activity_id = reblog.id
1808 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
1811 test "following / unfollowing errors" do
1812 user = insert(:user)
1816 |> assign(:user, user)
1819 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
1820 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1823 user = User.get_cached_by_id(user.id)
1824 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
1825 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1827 # self follow via uri
1828 user = User.get_cached_by_id(user.id)
1829 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
1830 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1832 # follow non existing user
1833 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
1834 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1836 # follow non existing user via uri
1837 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
1838 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1840 # unfollow non existing user
1841 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
1842 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
1845 test "muting / unmuting a user", %{conn: conn} do
1846 user = insert(:user)
1847 other_user = insert(:user)
1851 |> assign(:user, user)
1852 |> post("/api/v1/accounts/#{other_user.id}/mute")
1854 assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
1856 user = User.get_cached_by_id(user.id)
1860 |> assign(:user, user)
1861 |> post("/api/v1/accounts/#{other_user.id}/unmute")
1863 assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
1866 test "subscribing / unsubscribing to a user", %{conn: conn} do
1867 user = insert(:user)
1868 subscription_target = insert(:user)
1872 |> assign(:user, user)
1873 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
1875 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
1879 |> assign(:user, user)
1880 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
1882 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
1885 test "getting a list of mutes", %{conn: conn} do
1886 user = insert(:user)
1887 other_user = insert(:user)
1889 {:ok, user} = User.mute(user, other_user)
1893 |> assign(:user, user)
1894 |> get("/api/v1/mutes")
1896 other_user_id = to_string(other_user.id)
1897 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
1900 test "blocking / unblocking a user", %{conn: conn} do
1901 user = insert(:user)
1902 other_user = insert(:user)
1906 |> assign(:user, user)
1907 |> post("/api/v1/accounts/#{other_user.id}/block")
1909 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
1911 user = User.get_cached_by_id(user.id)
1915 |> assign(:user, user)
1916 |> post("/api/v1/accounts/#{other_user.id}/unblock")
1918 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
1921 test "getting a list of blocks", %{conn: conn} do
1922 user = insert(:user)
1923 other_user = insert(:user)
1925 {:ok, user} = User.block(user, other_user)
1929 |> assign(:user, user)
1930 |> get("/api/v1/blocks")
1932 other_user_id = to_string(other_user.id)
1933 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
1936 test "blocking / unblocking a domain", %{conn: conn} do
1937 user = insert(:user)
1938 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
1942 |> assign(:user, user)
1943 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
1945 assert %{} = json_response(conn, 200)
1946 user = User.get_cached_by_ap_id(user.ap_id)
1947 assert User.blocks?(user, other_user)
1951 |> assign(:user, user)
1952 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
1954 assert %{} = json_response(conn, 200)
1955 user = User.get_cached_by_ap_id(user.ap_id)
1956 refute User.blocks?(user, other_user)
1959 test "getting a list of domain blocks", %{conn: conn} do
1960 user = insert(:user)
1962 {:ok, user} = User.block_domain(user, "bad.site")
1963 {:ok, user} = User.block_domain(user, "even.worse.site")
1967 |> assign(:user, user)
1968 |> get("/api/v1/domain_blocks")
1970 domain_blocks = json_response(conn, 200)
1972 assert "bad.site" in domain_blocks
1973 assert "even.worse.site" in domain_blocks
1976 test "unimplemented follow_requests, blocks, domain blocks" do
1977 user = insert(:user)
1979 ["blocks", "domain_blocks", "follow_requests"]
1980 |> Enum.each(fn endpoint ->
1983 |> assign(:user, user)
1984 |> get("/api/v1/#{endpoint}")
1986 assert [] = json_response(conn, 200)
1990 test "account search", %{conn: conn} do
1991 user = insert(:user)
1992 user_two = insert(:user, %{nickname: "shp@shitposter.club"})
1993 user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
1997 |> assign(:user, user)
1998 |> get("/api/v1/accounts/search", %{"q" => "shp"})
1999 |> json_response(200)
2001 result_ids = for result <- results, do: result["acct"]
2003 assert user_two.nickname in result_ids
2004 assert user_three.nickname in result_ids
2008 |> assign(:user, user)
2009 |> get("/api/v1/accounts/search", %{"q" => "2hu"})
2010 |> json_response(200)
2012 result_ids = for result <- results, do: result["acct"]
2014 assert user_three.nickname in result_ids
2017 test "search", %{conn: conn} do
2018 user = insert(:user)
2019 user_two = insert(:user, %{nickname: "shp@shitposter.club"})
2020 user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
2022 {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
2025 CommonAPI.post(user, %{
2026 "status" => "This is about 2hu, but private",
2027 "visibility" => "private"
2030 {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
2034 |> get("/api/v1/search", %{"q" => "2hu"})
2036 assert results = json_response(conn, 200)
2038 [account | _] = results["accounts"]
2039 assert account["id"] == to_string(user_three.id)
2041 assert results["hashtags"] == []
2043 [status] = results["statuses"]
2044 assert status["id"] == to_string(activity.id)
2047 test "search fetches remote statuses", %{conn: conn} do
2051 |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
2053 assert results = json_response(conn, 200)
2055 [status] = results["statuses"]
2056 assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
2060 test "search doesn't show statuses that it shouldn't", %{conn: conn} do
2062 CommonAPI.post(insert(:user), %{
2063 "status" => "This is about 2hu, but private",
2064 "visibility" => "private"
2070 |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
2072 assert results = json_response(conn, 200)
2074 [] = results["statuses"]
2078 test "search fetches remote accounts", %{conn: conn} do
2081 |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
2083 assert results = json_response(conn, 200)
2084 [account] = results["accounts"]
2085 assert account["acct"] == "shp@social.heldscal.la"
2088 test "returns the favorites of a user", %{conn: conn} do
2089 user = insert(:user)
2090 other_user = insert(:user)
2092 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2093 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2095 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2099 |> assign(:user, user)
2100 |> get("/api/v1/favourites")
2102 assert [status] = json_response(first_conn, 200)
2103 assert status["id"] == to_string(activity.id)
2105 assert [{"link", _link_header}] =
2106 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2108 # Honours query params
2109 {:ok, second_activity} =
2110 CommonAPI.post(other_user, %{
2112 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2115 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2117 last_like = status["id"]
2121 |> assign(:user, user)
2122 |> get("/api/v1/favourites?since_id=#{last_like}")
2124 assert [second_status] = json_response(second_conn, 200)
2125 assert second_status["id"] == to_string(second_activity.id)
2129 |> assign(:user, user)
2130 |> get("/api/v1/favourites?limit=0")
2132 assert [] = json_response(third_conn, 200)
2135 describe "getting favorites timeline of specified user" do
2137 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2138 [current_user: current_user, user: user]
2141 test "returns list of statuses favorited by specified user", %{
2143 current_user: current_user,
2146 [activity | _] = insert_pair(:note_activity)
2147 CommonAPI.favorite(activity.id, user)
2151 |> assign(:user, current_user)
2152 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2153 |> json_response(:ok)
2157 assert length(response) == 1
2158 assert like["id"] == activity.id
2161 test "returns favorites for specified user_id when user is not logged in", %{
2165 activity = insert(:note_activity)
2166 CommonAPI.favorite(activity.id, user)
2170 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2171 |> json_response(:ok)
2173 assert length(response) == 1
2176 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2178 current_user: current_user,
2182 CommonAPI.post(current_user, %{
2183 "status" => "Hi @#{user.nickname}!",
2184 "visibility" => "direct"
2187 CommonAPI.favorite(direct.id, user)
2191 |> assign(:user, current_user)
2192 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2193 |> json_response(:ok)
2195 assert length(response) == 1
2197 anonymous_response =
2199 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2200 |> json_response(:ok)
2202 assert Enum.empty?(anonymous_response)
2205 test "does not return others' favorited DM when user is not one of recipients", %{
2207 current_user: current_user,
2210 user_two = insert(:user)
2213 CommonAPI.post(user_two, %{
2214 "status" => "Hi @#{user.nickname}!",
2215 "visibility" => "direct"
2218 CommonAPI.favorite(direct.id, user)
2222 |> assign(:user, current_user)
2223 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2224 |> json_response(:ok)
2226 assert Enum.empty?(response)
2229 test "paginates favorites using since_id and max_id", %{
2231 current_user: current_user,
2234 activities = insert_list(10, :note_activity)
2236 Enum.each(activities, fn activity ->
2237 CommonAPI.favorite(activity.id, user)
2240 third_activity = Enum.at(activities, 2)
2241 seventh_activity = Enum.at(activities, 6)
2245 |> assign(:user, current_user)
2246 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2247 since_id: third_activity.id,
2248 max_id: seventh_activity.id
2250 |> json_response(:ok)
2252 assert length(response) == 3
2253 refute third_activity in response
2254 refute seventh_activity in response
2257 test "limits favorites using limit parameter", %{
2259 current_user: current_user,
2263 |> insert_list(:note_activity)
2264 |> Enum.each(fn activity ->
2265 CommonAPI.favorite(activity.id, user)
2270 |> assign(:user, current_user)
2271 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2272 |> json_response(:ok)
2274 assert length(response) == 3
2277 test "returns empty response when user does not have any favorited statuses", %{
2279 current_user: current_user,
2284 |> assign(:user, current_user)
2285 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2286 |> json_response(:ok)
2288 assert Enum.empty?(response)
2291 test "returns 404 error when specified user is not exist", %{conn: conn} do
2292 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2294 assert json_response(conn, 404) == %{"error" => "Record not found"}
2297 test "returns 403 error when user has hidden own favorites", %{
2299 current_user: current_user
2301 user = insert(:user, %{info: %{hide_favorites: true}})
2302 activity = insert(:note_activity)
2303 CommonAPI.favorite(activity.id, user)
2307 |> assign(:user, current_user)
2308 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2310 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2313 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2314 user = insert(:user)
2315 activity = insert(:note_activity)
2316 CommonAPI.favorite(activity.id, user)
2320 |> assign(:user, current_user)
2321 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2323 assert user.info.hide_favorites
2324 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2328 describe "updating credentials" do
2329 test "updates the user's bio", %{conn: conn} do
2330 user = insert(:user)
2331 user2 = insert(:user)
2335 |> assign(:user, user)
2336 |> patch("/api/v1/accounts/update_credentials", %{
2337 "note" => "I drink #cofe with @#{user2.nickname}"
2340 assert user = json_response(conn, 200)
2342 assert user["note"] ==
2343 ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <>
2345 ~s(" class="u-url mention" href=") <>
2346 user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)
2349 test "updates the user's locking status", %{conn: conn} do
2350 user = insert(:user)
2354 |> assign(:user, user)
2355 |> patch("/api/v1/accounts/update_credentials", %{locked: "true"})
2357 assert user = json_response(conn, 200)
2358 assert user["locked"] == true
2361 test "updates the user's default scope", %{conn: conn} do
2362 user = insert(:user)
2366 |> assign(:user, user)
2367 |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
2369 assert user = json_response(conn, 200)
2370 assert user["source"]["privacy"] == "cofe"
2373 test "updates the user's hide_followers status", %{conn: conn} do
2374 user = insert(:user)
2378 |> assign(:user, user)
2379 |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
2381 assert user = json_response(conn, 200)
2382 assert user["pleroma"]["hide_followers"] == true
2385 test "updates the user's hide_follows status", %{conn: conn} do
2386 user = insert(:user)
2390 |> assign(:user, user)
2391 |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
2393 assert user = json_response(conn, 200)
2394 assert user["pleroma"]["hide_follows"] == true
2397 test "updates the user's hide_favorites status", %{conn: conn} do
2398 user = insert(:user)
2402 |> assign(:user, user)
2403 |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
2405 assert user = json_response(conn, 200)
2406 assert user["pleroma"]["hide_favorites"] == true
2409 test "updates the user's show_role status", %{conn: conn} do
2410 user = insert(:user)
2414 |> assign(:user, user)
2415 |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
2417 assert user = json_response(conn, 200)
2418 assert user["source"]["pleroma"]["show_role"] == false
2421 test "updates the user's no_rich_text status", %{conn: conn} do
2422 user = insert(:user)
2426 |> assign(:user, user)
2427 |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
2429 assert user = json_response(conn, 200)
2430 assert user["source"]["pleroma"]["no_rich_text"] == true
2433 test "updates the user's name", %{conn: conn} do
2434 user = insert(:user)
2438 |> assign(:user, user)
2439 |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
2441 assert user = json_response(conn, 200)
2442 assert user["display_name"] == "markorepairs"
2445 test "updates the user's avatar", %{conn: conn} do
2446 user = insert(:user)
2448 new_avatar = %Plug.Upload{
2449 content_type: "image/jpg",
2450 path: Path.absname("test/fixtures/image.jpg"),
2451 filename: "an_image.jpg"
2456 |> assign(:user, user)
2457 |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
2459 assert user_response = json_response(conn, 200)
2460 assert user_response["avatar"] != User.avatar_url(user)
2463 test "updates the user's banner", %{conn: conn} do
2464 user = insert(:user)
2466 new_header = %Plug.Upload{
2467 content_type: "image/jpg",
2468 path: Path.absname("test/fixtures/image.jpg"),
2469 filename: "an_image.jpg"
2474 |> assign(:user, user)
2475 |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
2477 assert user_response = json_response(conn, 200)
2478 assert user_response["header"] != User.banner_url(user)
2481 test "requires 'write' permission", %{conn: conn} do
2482 token1 = insert(:oauth_token, scopes: ["read"])
2483 token2 = insert(:oauth_token, scopes: ["write", "follow"])
2485 for token <- [token1, token2] do
2488 |> put_req_header("authorization", "Bearer #{token.token}")
2489 |> patch("/api/v1/accounts/update_credentials", %{})
2491 if token == token1 do
2492 assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403)
2494 assert json_response(conn, 200)
2499 test "updates profile emojos", %{conn: conn} do
2500 user = insert(:user)
2502 note = "*sips :blank:*"
2503 name = "I am :firefox:"
2507 |> assign(:user, user)
2508 |> patch("/api/v1/accounts/update_credentials", %{
2510 "display_name" => name
2513 assert json_response(conn, 200)
2517 |> get("/api/v1/accounts/#{user.id}")
2519 assert user = json_response(conn, 200)
2521 assert user["note"] == note
2522 assert user["display_name"] == name
2523 assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
2527 test "get instance information", %{conn: conn} do
2528 conn = get(conn, "/api/v1/instance")
2529 assert result = json_response(conn, 200)
2531 email = Pleroma.Config.get([:instance, :email])
2532 # Note: not checking for "max_toot_chars" since it's optional
2538 "email" => from_config_email,
2540 "streaming_api" => _
2545 "registrations" => _
2548 assert email == from_config_email
2551 test "get instance stats", %{conn: conn} do
2552 user = insert(:user, %{local: true})
2554 user2 = insert(:user, %{local: true})
2555 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2557 insert(:user, %{local: false, nickname: "u@peer1.com"})
2558 insert(:user, %{local: false, nickname: "u@peer2.com"})
2560 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2562 # Stats should count users with missing or nil `info.deactivated` value
2563 user = User.get_cached_by_id(user.id)
2564 info_change = Changeset.change(user.info, %{deactivated: nil})
2568 |> Changeset.change()
2569 |> Changeset.put_embed(:info, info_change)
2570 |> User.update_and_set_cache()
2572 Pleroma.Stats.update_stats()
2574 conn = get(conn, "/api/v1/instance")
2576 assert result = json_response(conn, 200)
2578 stats = result["stats"]
2581 assert stats["user_count"] == 1
2582 assert stats["status_count"] == 1
2583 assert stats["domain_count"] == 2
2586 test "get peers", %{conn: conn} do
2587 insert(:user, %{local: false, nickname: "u@peer1.com"})
2588 insert(:user, %{local: false, nickname: "u@peer2.com"})
2590 Pleroma.Stats.update_stats()
2592 conn = get(conn, "/api/v1/instance/peers")
2594 assert result = json_response(conn, 200)
2596 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2599 test "put settings", %{conn: conn} do
2600 user = insert(:user)
2604 |> assign(:user, user)
2605 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2607 assert _result = json_response(conn, 200)
2609 user = User.get_cached_by_ap_id(user.ap_id)
2610 assert user.info.settings == %{"programming" => "socks"}
2613 describe "pinned statuses" do
2615 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2617 user = insert(:user)
2618 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2620 [user: user, activity: activity]
2623 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2624 {:ok, _} = CommonAPI.pin(activity.id, user)
2628 |> assign(:user, user)
2629 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2630 |> json_response(200)
2632 id_str = to_string(activity.id)
2634 assert [%{"id" => ^id_str, "pinned" => true}] = result
2637 test "pin status", %{conn: conn, user: user, activity: activity} do
2638 id_str = to_string(activity.id)
2640 assert %{"id" => ^id_str, "pinned" => true} =
2642 |> assign(:user, user)
2643 |> post("/api/v1/statuses/#{activity.id}/pin")
2644 |> json_response(200)
2646 assert [%{"id" => ^id_str, "pinned" => true}] =
2648 |> assign(:user, user)
2649 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2650 |> json_response(200)
2653 test "unpin status", %{conn: conn, user: user, activity: activity} do
2654 {:ok, _} = CommonAPI.pin(activity.id, user)
2656 id_str = to_string(activity.id)
2657 user = refresh_record(user)
2659 assert %{"id" => ^id_str, "pinned" => false} =
2661 |> assign(:user, user)
2662 |> post("/api/v1/statuses/#{activity.id}/unpin")
2663 |> json_response(200)
2667 |> assign(:user, user)
2668 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2669 |> json_response(200)
2672 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2673 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2675 id_str_one = to_string(activity_one.id)
2677 assert %{"id" => ^id_str_one, "pinned" => true} =
2679 |> assign(:user, user)
2680 |> post("/api/v1/statuses/#{id_str_one}/pin")
2681 |> json_response(200)
2683 user = refresh_record(user)
2685 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2687 |> assign(:user, user)
2688 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2689 |> json_response(400)
2695 Pleroma.Config.put([:rich_media, :enabled], true)
2698 Pleroma.Config.put([:rich_media, :enabled], false)
2701 user = insert(:user)
2705 test "returns rich-media card", %{conn: conn, user: user} do
2706 {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
2709 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2710 "provider_name" => "www.imdb.com",
2711 "provider_url" => "http://www.imdb.com",
2712 "title" => "The Rock",
2714 "url" => "http://www.imdb.com/title/tt0117500/",
2716 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2719 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2720 "title" => "The Rock",
2721 "type" => "video.movie",
2722 "url" => "http://www.imdb.com/title/tt0117500/",
2724 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2731 |> get("/api/v1/statuses/#{activity.id}/card")
2732 |> json_response(200)
2734 assert response == card_data
2736 # works with private posts
2738 CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"})
2742 |> assign(:user, user)
2743 |> get("/api/v1/statuses/#{activity.id}/card")
2744 |> json_response(200)
2746 assert response_two == card_data
2749 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2750 {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"})
2754 |> get("/api/v1/statuses/#{activity.id}/card")
2755 |> json_response(:ok)
2757 assert response == %{
2759 "title" => "Pleroma",
2760 "description" => "",
2762 "provider_name" => "pleroma.social",
2763 "provider_url" => "https://pleroma.social",
2764 "url" => "https://pleroma.social/",
2767 "title" => "Pleroma",
2768 "type" => "website",
2769 "url" => "https://pleroma.social/"
2777 user = insert(:user)
2778 for_user = insert(:user)
2781 CommonAPI.post(user, %{
2782 "status" => "heweoo?"
2786 CommonAPI.post(user, %{
2787 "status" => "heweoo!"
2792 |> assign(:user, for_user)
2793 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2795 assert json_response(response1, 200)["bookmarked"] == true
2799 |> assign(:user, for_user)
2800 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2802 assert json_response(response2, 200)["bookmarked"] == true
2806 |> assign(:user, for_user)
2807 |> get("/api/v1/bookmarks")
2809 assert [json_response(response2, 200), json_response(response1, 200)] ==
2810 json_response(bookmarks, 200)
2814 |> assign(:user, for_user)
2815 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2817 assert json_response(response1, 200)["bookmarked"] == false
2821 |> assign(:user, for_user)
2822 |> get("/api/v1/bookmarks")
2824 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2827 describe "conversation muting" do
2829 user = insert(:user)
2830 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2832 [user: user, activity: activity]
2835 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2836 id_str = to_string(activity.id)
2838 assert %{"id" => ^id_str, "muted" => true} =
2840 |> assign(:user, user)
2841 |> post("/api/v1/statuses/#{activity.id}/mute")
2842 |> json_response(200)
2845 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2846 {:ok, _} = CommonAPI.add_mute(user, activity)
2848 id_str = to_string(activity.id)
2849 user = refresh_record(user)
2851 assert %{"id" => ^id_str, "muted" => false} =
2853 |> assign(:user, user)
2854 |> post("/api/v1/statuses/#{activity.id}/unmute")
2855 |> json_response(200)
2859 describe "reports" do
2861 reporter = insert(:user)
2862 target_user = insert(:user)
2864 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2866 [reporter: reporter, target_user: target_user, activity: activity]
2869 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2870 assert %{"action_taken" => false, "id" => _} =
2872 |> assign(:user, reporter)
2873 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2874 |> json_response(200)
2877 test "submit a report with statuses and comment", %{
2880 target_user: target_user,
2883 assert %{"action_taken" => false, "id" => _} =
2885 |> assign(:user, reporter)
2886 |> post("/api/v1/reports", %{
2887 "account_id" => target_user.id,
2888 "status_ids" => [activity.id],
2889 "comment" => "bad status!"
2891 |> json_response(200)
2894 test "account_id is required", %{
2899 assert %{"error" => "Valid `account_id` required"} =
2901 |> assign(:user, reporter)
2902 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2903 |> json_response(400)
2906 test "comment must be up to the size specified in the config", %{
2909 target_user: target_user
2911 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2912 comment = String.pad_trailing("a", max_size + 1, "a")
2914 error = %{"error" => "Comment must be up to #{max_size} characters"}
2918 |> assign(:user, reporter)
2919 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2920 |> json_response(400)
2924 describe "link headers" do
2925 test "preserves parameters in link headers", %{conn: conn} do
2926 user = insert(:user)
2927 other_user = insert(:user)
2930 CommonAPI.post(other_user, %{
2931 "status" => "hi @#{user.nickname}",
2932 "visibility" => "public"
2936 CommonAPI.post(other_user, %{
2937 "status" => "hi @#{user.nickname}",
2938 "visibility" => "public"
2941 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
2942 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
2946 |> assign(:user, user)
2947 |> get("/api/v1/notifications", %{media_only: true})
2949 assert [link_header] = get_resp_header(conn, "link")
2950 assert link_header =~ ~r/media_only=true/
2951 assert link_header =~ ~r/min_id=#{notification2.id}/
2952 assert link_header =~ ~r/max_id=#{notification1.id}/
2956 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
2957 # Need to set an old-style integer ID to reproduce the problem
2958 # (these are no longer assigned to new accounts but were preserved
2959 # for existing accounts during the migration to flakeIDs)
2960 user_one = insert(:user, %{id: 1212})
2961 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
2965 |> get("/api/v1/accounts/#{user_one.id}")
2969 |> get("/api/v1/accounts/#{user_two.nickname}")
2973 |> get("/api/v1/accounts/#{user_two.id}")
2975 acc_one = json_response(resp_one, 200)
2976 acc_two = json_response(resp_two, 200)
2977 acc_three = json_response(resp_three, 200)
2978 refute acc_one == acc_two
2979 assert acc_two == acc_three
2982 describe "custom emoji" do
2983 test "with tags", %{conn: conn} do
2986 |> get("/api/v1/custom_emojis")
2987 |> json_response(200)
2989 assert Map.has_key?(emoji, "shortcode")
2990 assert Map.has_key?(emoji, "static_url")
2991 assert Map.has_key?(emoji, "tags")
2992 assert is_list(emoji["tags"])
2993 assert Map.has_key?(emoji, "url")
2994 assert Map.has_key?(emoji, "visible_in_picker")
2998 describe "index/2 redirections" do
2999 setup %{conn: conn} do
3003 signing_salt: "cooldude"
3008 |> Plug.Session.call(Plug.Session.init(session_opts))
3011 test_path = "/web/statuses/test"
3012 %{conn: conn, path: test_path}
3015 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
3016 conn = get(conn, path)
3018 assert conn.status == 302
3019 assert redirected_to(conn) == "/web/login"
3022 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
3023 token = insert(:oauth_token)
3027 |> assign(:user, token.user)
3028 |> put_session(:oauth_token, token.token)
3031 assert conn.status == 200
3034 test "saves referer path to session", %{conn: conn, path: path} do
3035 conn = get(conn, path)
3036 return_to = Plug.Conn.get_session(conn, :return_to)
3038 assert return_to == path
3041 test "redirects to the saved path after log in", %{conn: conn, path: path} do
3042 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3043 auth = insert(:oauth_authorization, app: app)
3047 |> put_session(:return_to, path)
3048 |> get("/web/login", %{code: auth.token})
3050 assert conn.status == 302
3051 assert redirected_to(conn) == path
3054 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
3055 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3056 auth = insert(:oauth_authorization, app: app)
3058 conn = get(conn, "/web/login", %{code: auth.token})
3060 assert conn.status == 302
3061 assert redirected_to(conn) == "/web/getting-started"
3065 describe "scheduled activities" do
3066 test "creates a scheduled activity", %{conn: conn} do
3067 user = insert(:user)
3068 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3072 |> assign(:user, user)
3073 |> post("/api/v1/statuses", %{
3074 "status" => "scheduled",
3075 "scheduled_at" => scheduled_at
3078 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
3079 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
3080 assert [] == Repo.all(Activity)
3083 test "creates a scheduled activity with a media attachment", %{conn: conn} do
3084 user = insert(:user)
3085 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3087 file = %Plug.Upload{
3088 content_type: "image/jpg",
3089 path: Path.absname("test/fixtures/image.jpg"),
3090 filename: "an_image.jpg"
3093 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
3097 |> assign(:user, user)
3098 |> post("/api/v1/statuses", %{
3099 "media_ids" => [to_string(upload.id)],
3100 "status" => "scheduled",
3101 "scheduled_at" => scheduled_at
3104 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
3105 assert %{"type" => "image"} = media_attachment
3108 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
3110 user = insert(:user)
3113 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
3117 |> assign(:user, user)
3118 |> post("/api/v1/statuses", %{
3119 "status" => "not scheduled",
3120 "scheduled_at" => scheduled_at
3123 assert %{"content" => "not scheduled"} = json_response(conn, 200)
3124 assert [] == Repo.all(ScheduledActivity)
3127 test "returns error when daily user limit is exceeded", %{conn: conn} do
3128 user = insert(:user)
3131 NaiveDateTime.utc_now()
3132 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3133 |> NaiveDateTime.to_iso8601()
3135 attrs = %{params: %{}, scheduled_at: today}
3136 {:ok, _} = ScheduledActivity.create(user, attrs)
3137 {:ok, _} = ScheduledActivity.create(user, attrs)
3141 |> assign(:user, user)
3142 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3144 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3147 test "returns error when total user limit is exceeded", %{conn: conn} do
3148 user = insert(:user)
3151 NaiveDateTime.utc_now()
3152 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3153 |> NaiveDateTime.to_iso8601()
3156 NaiveDateTime.utc_now()
3157 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3158 |> NaiveDateTime.to_iso8601()
3160 attrs = %{params: %{}, scheduled_at: today}
3161 {:ok, _} = ScheduledActivity.create(user, attrs)
3162 {:ok, _} = ScheduledActivity.create(user, attrs)
3163 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3167 |> assign(:user, user)
3168 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3170 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3173 test "shows scheduled activities", %{conn: conn} do
3174 user = insert(:user)
3175 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3176 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3177 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3178 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3182 |> assign(:user, user)
3187 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3189 result = json_response(conn_res, 200)
3190 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3195 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3197 result = json_response(conn_res, 200)
3198 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3203 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3205 result = json_response(conn_res, 200)
3206 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3209 test "shows a scheduled activity", %{conn: conn} do
3210 user = insert(:user)
3211 scheduled_activity = insert(:scheduled_activity, user: user)
3215 |> assign(:user, user)
3216 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3218 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3219 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3223 |> assign(:user, user)
3224 |> get("/api/v1/scheduled_statuses/404")
3226 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3229 test "updates a scheduled activity", %{conn: conn} do
3230 user = insert(:user)
3231 scheduled_activity = insert(:scheduled_activity, user: user)
3234 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3238 |> assign(:user, user)
3239 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3240 scheduled_at: new_scheduled_at
3243 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3244 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3248 |> assign(:user, user)
3249 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3251 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3254 test "deletes a scheduled activity", %{conn: conn} do
3255 user = insert(:user)
3256 scheduled_activity = insert(:scheduled_activity, user: user)
3260 |> assign(:user, user)
3261 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3263 assert %{} = json_response(res_conn, 200)
3264 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3268 |> assign(:user, user)
3269 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3271 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3275 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3276 user1 = insert(:user)
3277 user2 = insert(:user)
3278 user3 = insert(:user)
3280 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3282 # Reply to status from another user
3285 |> assign(:user, user2)
3286 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3288 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3290 activity = Activity.get_by_id_with_object(id)
3292 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3293 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3295 # Reblog from the third user
3298 |> assign(:user, user3)
3299 |> post("/api/v1/statuses/#{activity.id}/reblog")
3301 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3302 json_response(conn2, 200)
3304 assert to_string(activity.id) == id
3306 # Getting third user status
3309 |> assign(:user, user3)
3310 |> get("api/v1/timelines/home")
3312 [reblogged_activity] = json_response(conn3, 200)
3314 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3316 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3317 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3320 describe "create account by app" do
3322 enabled = Pleroma.Config.get([:app_account_creation, :enabled])
3323 max_requests = Pleroma.Config.get([:app_account_creation, :max_requests])
3324 interval = Pleroma.Config.get([:app_account_creation, :interval])
3326 Pleroma.Config.put([:app_account_creation, :enabled], true)
3327 Pleroma.Config.put([:app_account_creation, :max_requests], 5)
3328 Pleroma.Config.put([:app_account_creation, :interval], 1)
3331 Pleroma.Config.put([:app_account_creation, :enabled], enabled)
3332 Pleroma.Config.put([:app_account_creation, :max_requests], max_requests)
3333 Pleroma.Config.put([:app_account_creation, :interval], interval)
3339 test "Account registration via Application", %{conn: conn} do
3342 |> post("/api/v1/apps", %{
3343 client_name: "client_name",
3344 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3345 scopes: "read, write, follow"
3349 "client_id" => client_id,
3350 "client_secret" => client_secret,
3352 "name" => "client_name",
3353 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3356 } = json_response(conn, 200)
3360 |> post("/oauth/token", %{
3361 grant_type: "client_credentials",
3362 client_id: client_id,
3363 client_secret: client_secret
3366 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3367 json_response(conn, 200)
3370 token_from_db = Repo.get_by(Token, token: token)
3371 assert token_from_db
3373 assert scope == "read write follow"
3377 |> put_req_header("authorization", "Bearer " <> token)
3378 |> post("/api/v1/accounts", %{
3380 email: "lain@example.org",
3381 password: "PlzDontHackLain",
3386 "access_token" => token,
3387 "created_at" => _created_at,
3389 "token_type" => "Bearer"
3390 } = json_response(conn, 200)
3392 token_from_db = Repo.get_by(Token, token: token)
3393 assert token_from_db
3394 token_from_db = Repo.preload(token_from_db, :user)
3395 assert token_from_db.user
3397 assert token_from_db.user.info.confirmation_pending
3400 test "rate limit", %{conn: conn} do
3401 app_token = insert(:oauth_token, user: nil)
3404 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3405 |> Map.put(:remote_ip, {15, 15, 15, 15})
3410 |> post("/api/v1/accounts", %{
3411 username: "#{i}lain",
3412 email: "#{i}lain@example.org",
3413 password: "PlzDontHackLain",
3418 "access_token" => token,
3419 "created_at" => _created_at,
3421 "token_type" => "Bearer"
3422 } = json_response(conn, 200)
3424 token_from_db = Repo.get_by(Token, token: token)
3425 assert token_from_db
3426 token_from_db = Repo.preload(token_from_db, :user)
3427 assert token_from_db.user
3429 assert token_from_db.user.info.confirmation_pending
3434 |> post("/api/v1/accounts", %{
3436 email: "6lain@example.org",
3437 password: "PlzDontHackLain",
3441 assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}