1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.Notification
13 alias Pleroma.ScheduledActivity
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.CommonAPI
17 alias Pleroma.Web.MastodonAPI.FilterView
18 alias Pleroma.Web.OAuth.App
19 alias Pleroma.Web.OAuth.Token
20 alias Pleroma.Web.OStatus
21 alias Pleroma.Web.Push
22 alias Pleroma.Web.TwitterAPI.TwitterAPI
23 import Pleroma.Factory
24 import ExUnit.CaptureLog
27 @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
30 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
34 test "the home timeline", %{conn: conn} do
36 following = insert(:user)
38 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
42 |> assign(:user, user)
43 |> get("/api/v1/timelines/home")
45 assert Enum.empty?(json_response(conn, 200))
47 {:ok, user} = User.follow(user, following)
51 |> assign(:user, user)
52 |> get("/api/v1/timelines/home")
54 assert [%{"content" => "test"}] = json_response(conn, 200)
57 test "the public timeline", %{conn: conn} do
58 following = insert(:user)
61 {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
64 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
68 |> get("/api/v1/timelines/public", %{"local" => "False"})
70 assert length(json_response(conn, 200)) == 2
74 |> get("/api/v1/timelines/public", %{"local" => "True"})
76 assert [%{"content" => "test"}] = json_response(conn, 200)
80 |> get("/api/v1/timelines/public", %{"local" => "1"})
82 assert [%{"content" => "test"}] = json_response(conn, 200)
86 test "the public timeline when public is set to false", %{conn: conn} do
87 public = Pleroma.Config.get([:instance, :public])
88 Pleroma.Config.put([:instance, :public], false)
91 Pleroma.Config.put([:instance, :public], public)
95 |> get("/api/v1/timelines/public", %{"local" => "False"})
96 |> json_response(403) == %{"error" => "This resource requires authentication."}
99 describe "posting statuses" do
105 |> assign(:user, user)
110 test "posting a status", %{conn: conn} do
111 idempotency_key = "Pikachu rocks!"
115 |> put_req_header("idempotency-key", idempotency_key)
116 |> post("/api/v1/statuses", %{
118 "spoiler_text" => "2hu",
119 "sensitive" => "false"
122 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
124 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
126 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
127 json_response(conn_one, 200)
129 assert Activity.get_by_id(id)
133 |> put_req_header("idempotency-key", idempotency_key)
134 |> post("/api/v1/statuses", %{
136 "spoiler_text" => "2hu",
137 "sensitive" => "false"
140 assert %{"id" => second_id} = json_response(conn_two, 200)
141 assert id == second_id
145 |> post("/api/v1/statuses", %{
147 "spoiler_text" => "2hu",
148 "sensitive" => "false"
151 assert %{"id" => third_id} = json_response(conn_three, 200)
152 refute id == third_id
155 test "replying to a status", %{conn: conn} do
157 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
161 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
163 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
165 activity = Activity.get_by_id(id)
167 assert activity.data["context"] == replied_to.data["context"]
168 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
171 test "replying to a direct message with visibility other than direct", %{conn: conn} do
173 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
175 Enum.each(["public", "private", "unlisted"], fn visibility ->
178 |> post("/api/v1/statuses", %{
179 "status" => "@#{user.nickname} hey",
180 "in_reply_to_id" => replied_to.id,
181 "visibility" => visibility
184 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
188 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
191 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
193 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
194 assert Activity.get_by_id(id)
197 test "posting a sensitive status", %{conn: conn} do
200 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
202 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
203 assert Activity.get_by_id(id)
206 test "posting a fake status", %{conn: conn} do
209 |> post("/api/v1/statuses", %{
211 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
214 real_status = json_response(real_conn, 200)
217 assert Object.get_by_ap_id(real_status["uri"])
221 |> Map.put("id", nil)
222 |> Map.put("url", nil)
223 |> Map.put("uri", nil)
224 |> Map.put("created_at", nil)
225 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
229 |> post("/api/v1/statuses", %{
231 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
235 fake_status = json_response(fake_conn, 200)
238 refute Object.get_by_ap_id(fake_status["uri"])
242 |> Map.put("id", nil)
243 |> Map.put("url", nil)
244 |> Map.put("uri", nil)
245 |> Map.put("created_at", nil)
246 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
248 assert real_status == fake_status
251 test "posting a status with OGP link preview", %{conn: conn} do
252 Pleroma.Config.put([:rich_media, :enabled], true)
256 |> post("/api/v1/statuses", %{
257 "status" => "https://example.com/ogp"
260 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
261 assert Activity.get_by_id(id)
262 Pleroma.Config.put([:rich_media, :enabled], false)
265 test "posting a direct status", %{conn: conn} do
266 user2 = insert(:user)
267 content = "direct cofe @#{user2.nickname}"
271 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
273 assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
274 assert activity = Activity.get_by_id(id)
275 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
276 assert activity.data["to"] == [user2.ap_id]
277 assert activity.data["cc"] == []
281 describe "posting polls" do
282 test "posting a poll", %{conn: conn} do
284 time = NaiveDateTime.utc_now()
288 |> assign(:user, user)
289 |> post("/api/v1/statuses", %{
290 "status" => "Who is the #bestgrill?",
291 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
294 response = json_response(conn, 200)
296 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
297 title in ["Rei", "Asuka", "Misato"]
300 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
301 refute response["poll"]["expred"]
304 test "option limit is enforced", %{conn: conn} do
306 limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
310 |> assign(:user, user)
311 |> post("/api/v1/statuses", %{
313 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
316 %{"error" => error} = json_response(conn, 422)
317 assert error == "Poll can't contain more than #{limit} options"
320 test "option character limit is enforced", %{conn: conn} do
322 limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
326 |> assign(:user, user)
327 |> post("/api/v1/statuses", %{
330 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
335 %{"error" => error} = json_response(conn, 422)
336 assert error == "Poll options cannot be longer than #{limit} characters each"
339 test "minimal date limit is enforced", %{conn: conn} do
341 limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
345 |> assign(:user, user)
346 |> post("/api/v1/statuses", %{
347 "status" => "imagine arbitrary limits",
349 "options" => ["this post was made by pleroma gang"],
350 "expires_in" => limit - 1
354 %{"error" => error} = json_response(conn, 422)
355 assert error == "Expiration date is too soon"
358 test "maximum date limit is enforced", %{conn: conn} do
360 limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
364 |> assign(:user, user)
365 |> post("/api/v1/statuses", %{
366 "status" => "imagine arbitrary limits",
368 "options" => ["this post was made by pleroma gang"],
369 "expires_in" => limit + 1
373 %{"error" => error} = json_response(conn, 422)
374 assert error == "Expiration date is too far in the future"
378 test "direct timeline", %{conn: conn} do
379 user_one = insert(:user)
380 user_two = insert(:user)
382 {:ok, user_two} = User.follow(user_two, user_one)
385 CommonAPI.post(user_one, %{
386 "status" => "Hi @#{user_two.nickname}!",
387 "visibility" => "direct"
390 {:ok, _follower_only} =
391 CommonAPI.post(user_one, %{
392 "status" => "Hi @#{user_two.nickname}!",
393 "visibility" => "private"
396 # Only direct should be visible here
399 |> assign(:user, user_two)
400 |> get("api/v1/timelines/direct")
402 [status] = json_response(res_conn, 200)
404 assert %{"visibility" => "direct"} = status
405 assert status["url"] != direct.data["id"]
407 # User should be able to see his own direct message
410 |> assign(:user, user_one)
411 |> get("api/v1/timelines/direct")
413 [status] = json_response(res_conn, 200)
415 assert %{"visibility" => "direct"} = status
417 # Both should be visible here
420 |> assign(:user, user_two)
421 |> get("api/v1/timelines/home")
423 [_s1, _s2] = json_response(res_conn, 200)
426 Enum.each(1..20, fn _ ->
428 CommonAPI.post(user_one, %{
429 "status" => "Hi @#{user_two.nickname}!",
430 "visibility" => "direct"
436 |> assign(:user, user_two)
437 |> get("api/v1/timelines/direct")
439 statuses = json_response(res_conn, 200)
440 assert length(statuses) == 20
444 |> assign(:user, user_two)
445 |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
447 [status] = json_response(res_conn, 200)
449 assert status["url"] != direct.data["id"]
452 test "Conversations", %{conn: conn} do
453 user_one = insert(:user)
454 user_two = insert(:user)
455 user_three = insert(:user)
457 {:ok, user_two} = User.follow(user_two, user_one)
460 CommonAPI.post(user_one, %{
461 "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
462 "visibility" => "direct"
465 {:ok, _follower_only} =
466 CommonAPI.post(user_one, %{
467 "status" => "Hi @#{user_two.nickname}!",
468 "visibility" => "private"
473 |> assign(:user, user_one)
474 |> get("/api/v1/conversations")
476 assert response = json_response(res_conn, 200)
481 "accounts" => res_accounts,
482 "last_status" => res_last_status,
487 account_ids = Enum.map(res_accounts, & &1["id"])
488 assert length(res_accounts) == 2
489 assert user_two.id in account_ids
490 assert user_three.id in account_ids
491 assert is_binary(res_id)
492 assert unread == true
493 assert res_last_status["id"] == direct.id
495 # Apparently undocumented API endpoint
498 |> assign(:user, user_one)
499 |> post("/api/v1/conversations/#{res_id}/read")
501 assert response = json_response(res_conn, 200)
502 assert length(response["accounts"]) == 2
503 assert response["last_status"]["id"] == direct.id
504 assert response["unread"] == false
506 # (vanilla) Mastodon frontend behaviour
509 |> assign(:user, user_one)
510 |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
512 assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
515 test "doesn't include DMs from blocked users", %{conn: conn} do
516 blocker = insert(:user)
517 blocked = insert(:user)
519 {:ok, blocker} = User.block(blocker, blocked)
521 {:ok, _blocked_direct} =
522 CommonAPI.post(blocked, %{
523 "status" => "Hi @#{blocker.nickname}!",
524 "visibility" => "direct"
528 CommonAPI.post(user, %{
529 "status" => "Hi @#{blocker.nickname}!",
530 "visibility" => "direct"
535 |> assign(:user, user)
536 |> get("api/v1/timelines/direct")
538 [status] = json_response(res_conn, 200)
539 assert status["id"] == direct.id
542 test "verify_credentials", %{conn: conn} do
547 |> assign(:user, user)
548 |> get("/api/v1/accounts/verify_credentials")
550 response = json_response(conn, 200)
552 assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
553 assert response["pleroma"]["chat_token"]
554 assert id == to_string(user.id)
557 test "verify_credentials default scope unlisted", %{conn: conn} do
558 user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
562 |> assign(:user, user)
563 |> get("/api/v1/accounts/verify_credentials")
565 assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
566 assert id == to_string(user.id)
569 test "apps/verify_credentials", %{conn: conn} do
570 token = insert(:oauth_token)
574 |> assign(:user, token.user)
575 |> assign(:token, token)
576 |> get("/api/v1/apps/verify_credentials")
578 app = Repo.preload(token, :app).app
581 "name" => app.client_name,
582 "website" => app.website,
583 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
586 assert expected == json_response(conn, 200)
589 test "user avatar can be set", %{conn: conn} do
591 avatar_image = File.read!("test/fixtures/avatar_data_uri")
595 |> assign(:user, user)
596 |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
598 user = refresh_record(user)
612 assert %{"url" => _} = json_response(conn, 200)
615 test "user avatar can be reset", %{conn: conn} do
620 |> assign(:user, user)
621 |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
623 user = User.get_cached_by_id(user.id)
625 assert user.avatar == nil
627 assert %{"url" => nil} = json_response(conn, 200)
630 test "can set profile banner", %{conn: conn} do
635 |> assign(:user, user)
636 |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
638 user = refresh_record(user)
639 assert user.info.banner["type"] == "Image"
641 assert %{"url" => _} = json_response(conn, 200)
644 test "can reset profile banner", %{conn: conn} do
649 |> assign(:user, user)
650 |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
652 user = refresh_record(user)
653 assert user.info.banner == %{}
655 assert %{"url" => nil} = json_response(conn, 200)
658 test "background image can be set", %{conn: conn} do
663 |> assign(:user, user)
664 |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
666 user = refresh_record(user)
667 assert user.info.background["type"] == "Image"
668 assert %{"url" => _} = json_response(conn, 200)
671 test "background image can be reset", %{conn: conn} do
676 |> assign(:user, user)
677 |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
679 user = refresh_record(user)
680 assert user.info.background == %{}
681 assert %{"url" => nil} = json_response(conn, 200)
684 test "creates an oauth app", %{conn: conn} do
686 app_attrs = build(:oauth_app)
690 |> assign(:user, user)
691 |> post("/api/v1/apps", %{
692 client_name: app_attrs.client_name,
693 redirect_uris: app_attrs.redirect_uris
696 [app] = Repo.all(App)
699 "name" => app.client_name,
700 "website" => app.website,
701 "client_id" => app.client_id,
702 "client_secret" => app.client_secret,
703 "id" => app.id |> to_string(),
704 "redirect_uri" => app.redirect_uris,
705 "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
708 assert expected == json_response(conn, 200)
711 test "get a status", %{conn: conn} do
712 activity = insert(:note_activity)
716 |> get("/api/v1/statuses/#{activity.id}")
718 assert %{"id" => id} = json_response(conn, 200)
719 assert id == to_string(activity.id)
722 describe "deleting a status" do
723 test "when you created it", %{conn: conn} do
724 activity = insert(:note_activity)
725 author = User.get_cached_by_ap_id(activity.data["actor"])
729 |> assign(:user, author)
730 |> delete("/api/v1/statuses/#{activity.id}")
732 assert %{} = json_response(conn, 200)
734 refute Activity.get_by_id(activity.id)
737 test "when you didn't create it", %{conn: conn} do
738 activity = insert(:note_activity)
743 |> assign(:user, user)
744 |> delete("/api/v1/statuses/#{activity.id}")
746 assert %{"error" => _} = json_response(conn, 403)
748 assert Activity.get_by_id(activity.id) == activity
751 test "when you're an admin or moderator", %{conn: conn} do
752 activity1 = insert(:note_activity)
753 activity2 = insert(:note_activity)
754 admin = insert(:user, info: %{is_admin: true})
755 moderator = insert(:user, info: %{is_moderator: true})
759 |> assign(:user, admin)
760 |> delete("/api/v1/statuses/#{activity1.id}")
762 assert %{} = json_response(res_conn, 200)
766 |> assign(:user, moderator)
767 |> delete("/api/v1/statuses/#{activity2.id}")
769 assert %{} = json_response(res_conn, 200)
771 refute Activity.get_by_id(activity1.id)
772 refute Activity.get_by_id(activity2.id)
776 describe "filters" do
777 test "creating a filter", %{conn: conn} do
780 filter = %Pleroma.Filter{
787 |> assign(:user, user)
788 |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
790 assert response = json_response(conn, 200)
791 assert response["phrase"] == filter.phrase
792 assert response["context"] == filter.context
793 assert response["irreversible"] == false
794 assert response["id"] != nil
795 assert response["id"] != ""
798 test "fetching a list of filters", %{conn: conn} do
801 query_one = %Pleroma.Filter{
808 query_two = %Pleroma.Filter{
815 {:ok, filter_one} = Pleroma.Filter.create(query_one)
816 {:ok, filter_two} = Pleroma.Filter.create(query_two)
820 |> assign(:user, user)
821 |> get("/api/v1/filters")
822 |> json_response(200)
828 filters: [filter_two, filter_one]
832 test "get a filter", %{conn: conn} do
835 query = %Pleroma.Filter{
842 {:ok, filter} = Pleroma.Filter.create(query)
846 |> assign(:user, user)
847 |> get("/api/v1/filters/#{filter.filter_id}")
849 assert _response = json_response(conn, 200)
852 test "update a filter", %{conn: conn} do
855 query = %Pleroma.Filter{
862 {:ok, _filter} = Pleroma.Filter.create(query)
864 new = %Pleroma.Filter{
871 |> assign(:user, user)
872 |> put("/api/v1/filters/#{query.filter_id}", %{
877 assert response = json_response(conn, 200)
878 assert response["phrase"] == new.phrase
879 assert response["context"] == new.context
882 test "delete a filter", %{conn: conn} do
885 query = %Pleroma.Filter{
892 {:ok, filter} = Pleroma.Filter.create(query)
896 |> assign(:user, user)
897 |> delete("/api/v1/filters/#{filter.filter_id}")
899 assert response = json_response(conn, 200)
900 assert response == %{}
905 test "creating a list", %{conn: conn} do
910 |> assign(:user, user)
911 |> post("/api/v1/lists", %{"title" => "cuties"})
913 assert %{"title" => title} = json_response(conn, 200)
914 assert title == "cuties"
917 test "adding users to a list", %{conn: conn} do
919 other_user = insert(:user)
920 {:ok, list} = Pleroma.List.create("name", user)
924 |> assign(:user, user)
925 |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
927 assert %{} == json_response(conn, 200)
928 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
929 assert following == [other_user.follower_address]
932 test "removing users from a list", %{conn: conn} do
934 other_user = insert(:user)
935 third_user = insert(:user)
936 {:ok, list} = Pleroma.List.create("name", user)
937 {:ok, list} = Pleroma.List.follow(list, other_user)
938 {:ok, list} = Pleroma.List.follow(list, third_user)
942 |> assign(:user, user)
943 |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
945 assert %{} == json_response(conn, 200)
946 %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
947 assert following == [third_user.follower_address]
950 test "listing users in a list", %{conn: conn} do
952 other_user = insert(:user)
953 {:ok, list} = Pleroma.List.create("name", user)
954 {:ok, list} = Pleroma.List.follow(list, other_user)
958 |> assign(:user, user)
959 |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
961 assert [%{"id" => id}] = json_response(conn, 200)
962 assert id == to_string(other_user.id)
965 test "retrieving a list", %{conn: conn} do
967 {:ok, list} = Pleroma.List.create("name", user)
971 |> assign(:user, user)
972 |> get("/api/v1/lists/#{list.id}")
974 assert %{"id" => id} = json_response(conn, 200)
975 assert id == to_string(list.id)
978 test "renaming a list", %{conn: conn} do
980 {:ok, list} = Pleroma.List.create("name", user)
984 |> assign(:user, user)
985 |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
987 assert %{"title" => name} = json_response(conn, 200)
988 assert name == "newname"
991 test "deleting a list", %{conn: conn} do
993 {:ok, list} = Pleroma.List.create("name", user)
997 |> assign(:user, user)
998 |> delete("/api/v1/lists/#{list.id}")
1000 assert %{} = json_response(conn, 200)
1001 assert is_nil(Repo.get(Pleroma.List, list.id))
1004 test "list timeline", %{conn: conn} do
1005 user = insert(:user)
1006 other_user = insert(:user)
1007 {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
1008 {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
1009 {:ok, list} = Pleroma.List.create("name", user)
1010 {:ok, list} = Pleroma.List.follow(list, other_user)
1014 |> assign(:user, user)
1015 |> get("/api/v1/timelines/list/#{list.id}")
1017 assert [%{"id" => id}] = json_response(conn, 200)
1019 assert id == to_string(activity_two.id)
1022 test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
1023 user = insert(:user)
1024 other_user = insert(:user)
1025 {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
1027 {:ok, _activity_two} =
1028 TwitterAPI.create_status(other_user, %{
1029 "status" => "Marisa is cute.",
1030 "visibility" => "private"
1033 {:ok, list} = Pleroma.List.create("name", user)
1034 {:ok, list} = Pleroma.List.follow(list, other_user)
1038 |> assign(:user, user)
1039 |> get("/api/v1/timelines/list/#{list.id}")
1041 assert [%{"id" => id}] = json_response(conn, 200)
1043 assert id == to_string(activity_one.id)
1047 describe "notifications" do
1048 test "list of notifications", %{conn: conn} do
1049 user = insert(:user)
1050 other_user = insert(:user)
1053 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1055 {:ok, [_notification]} = Notification.create_notifications(activity)
1059 |> assign(:user, user)
1060 |> get("/api/v1/notifications")
1063 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
1065 }\">@<span>#{user.nickname}</span></a></span>"
1067 assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
1068 assert response == expected_response
1071 test "getting a single notification", %{conn: conn} do
1072 user = insert(:user)
1073 other_user = insert(:user)
1076 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1078 {:ok, [notification]} = Notification.create_notifications(activity)
1082 |> assign(:user, user)
1083 |> get("/api/v1/notifications/#{notification.id}")
1086 "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
1088 }\">@<span>#{user.nickname}</span></a></span>"
1090 assert %{"status" => %{"content" => response}} = json_response(conn, 200)
1091 assert response == expected_response
1094 test "dismissing a single notification", %{conn: conn} do
1095 user = insert(:user)
1096 other_user = insert(:user)
1099 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1101 {:ok, [notification]} = Notification.create_notifications(activity)
1105 |> assign(:user, user)
1106 |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
1108 assert %{} = json_response(conn, 200)
1111 test "clearing all notifications", %{conn: conn} do
1112 user = insert(:user)
1113 other_user = insert(:user)
1116 TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
1118 {:ok, [_notification]} = Notification.create_notifications(activity)
1122 |> assign(:user, user)
1123 |> post("/api/v1/notifications/clear")
1125 assert %{} = json_response(conn, 200)
1129 |> assign(:user, user)
1130 |> get("/api/v1/notifications")
1132 assert all = json_response(conn, 200)
1136 test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
1137 user = insert(:user)
1138 other_user = insert(:user)
1140 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1141 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1142 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1143 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1145 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1146 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1147 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1148 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1152 |> assign(:user, user)
1157 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
1159 result = json_response(conn_res, 200)
1160 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1165 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
1167 result = json_response(conn_res, 200)
1168 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1173 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
1175 result = json_response(conn_res, 200)
1176 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
1179 test "filters notifications using exclude_types", %{conn: conn} do
1180 user = insert(:user)
1181 other_user = insert(:user)
1183 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
1184 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
1185 {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
1186 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
1187 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
1189 mention_notification_id =
1190 Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
1192 favorite_notification_id =
1193 Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
1195 reblog_notification_id =
1196 Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
1198 follow_notification_id =
1199 Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
1203 |> assign(:user, user)
1206 get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
1208 assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
1211 get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
1213 assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
1216 get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
1218 assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
1221 get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
1223 assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
1226 test "destroy multiple", %{conn: conn} do
1227 user = insert(:user)
1228 other_user = insert(:user)
1230 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1231 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
1232 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1233 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
1235 notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
1236 notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
1237 notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
1238 notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
1242 |> assign(:user, user)
1246 |> get("/api/v1/notifications")
1248 result = json_response(conn_res, 200)
1249 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
1253 |> assign(:user, other_user)
1257 |> get("/api/v1/notifications")
1259 result = json_response(conn_res, 200)
1260 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1264 |> delete("/api/v1/notifications/destroy_multiple", %{
1265 "ids" => [notification1_id, notification2_id]
1268 assert json_response(conn_destroy, 200) == %{}
1272 |> get("/api/v1/notifications")
1274 result = json_response(conn_res, 200)
1275 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
1278 test "doesn't see notifications after muting user with notifications", %{conn: conn} do
1279 user = insert(:user)
1280 user2 = insert(:user)
1282 {:ok, _, _, _} = CommonAPI.follow(user, user2)
1283 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
1285 conn = assign(conn, :user, user)
1287 conn = get(conn, "/api/v1/notifications")
1289 assert length(json_response(conn, 200)) == 1
1291 {:ok, user} = User.mute(user, user2)
1293 conn = assign(build_conn(), :user, user)
1294 conn = get(conn, "/api/v1/notifications")
1296 assert json_response(conn, 200) == []
1299 test "see notifications after muting user without notifications", %{conn: conn} do
1300 user = insert(:user)
1301 user2 = insert(:user)
1303 {:ok, _, _, _} = CommonAPI.follow(user, user2)
1304 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
1306 conn = assign(conn, :user, user)
1308 conn = get(conn, "/api/v1/notifications")
1310 assert length(json_response(conn, 200)) == 1
1312 {:ok, user} = User.mute(user, user2, false)
1314 conn = assign(build_conn(), :user, user)
1315 conn = get(conn, "/api/v1/notifications")
1317 assert length(json_response(conn, 200)) == 1
1320 test "see notifications after muting user with notifications and with_muted parameter", %{
1323 user = insert(:user)
1324 user2 = insert(:user)
1326 {:ok, _, _, _} = CommonAPI.follow(user, user2)
1327 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
1329 conn = assign(conn, :user, user)
1331 conn = get(conn, "/api/v1/notifications")
1333 assert length(json_response(conn, 200)) == 1
1335 {:ok, user} = User.mute(user, user2)
1337 conn = assign(build_conn(), :user, user)
1338 conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
1340 assert length(json_response(conn, 200)) == 1
1344 describe "reblogging" do
1345 test "reblogs and returns the reblogged status", %{conn: conn} do
1346 activity = insert(:note_activity)
1347 user = insert(:user)
1351 |> assign(:user, user)
1352 |> post("/api/v1/statuses/#{activity.id}/reblog")
1355 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
1357 } = json_response(conn, 200)
1359 assert to_string(activity.id) == id
1362 test "reblogged status for another user", %{conn: conn} do
1363 activity = insert(:note_activity)
1364 user1 = insert(:user)
1365 user2 = insert(:user)
1366 user3 = insert(:user)
1367 CommonAPI.favorite(activity.id, user2)
1368 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
1369 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
1370 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
1374 |> assign(:user, user3)
1375 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1378 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
1379 "reblogged" => false,
1380 "favourited" => false,
1381 "bookmarked" => false
1382 } = json_response(conn_res, 200)
1386 |> assign(:user, user2)
1387 |> get("/api/v1/statuses/#{reblog_activity1.id}")
1390 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
1391 "reblogged" => true,
1392 "favourited" => true,
1393 "bookmarked" => true
1394 } = json_response(conn_res, 200)
1396 assert to_string(activity.id) == id
1400 describe "unreblogging" do
1401 test "unreblogs and returns the unreblogged status", %{conn: conn} do
1402 activity = insert(:note_activity)
1403 user = insert(:user)
1405 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
1409 |> assign(:user, user)
1410 |> post("/api/v1/statuses/#{activity.id}/unreblog")
1412 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
1414 assert to_string(activity.id) == id
1418 describe "favoriting" do
1419 test "favs a status and returns it", %{conn: conn} do
1420 activity = insert(:note_activity)
1421 user = insert(:user)
1425 |> assign(:user, user)
1426 |> post("/api/v1/statuses/#{activity.id}/favourite")
1428 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
1429 json_response(conn, 200)
1431 assert to_string(activity.id) == id
1434 test "returns 500 for a wrong id", %{conn: conn} do
1435 user = insert(:user)
1439 |> assign(:user, user)
1440 |> post("/api/v1/statuses/1/favourite")
1441 |> json_response(500)
1443 assert resp == "Something went wrong"
1447 describe "unfavoriting" do
1448 test "unfavorites a status and returns it", %{conn: conn} do
1449 activity = insert(:note_activity)
1450 user = insert(:user)
1452 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1456 |> assign(:user, user)
1457 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
1459 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
1460 json_response(conn, 200)
1462 assert to_string(activity.id) == id
1466 describe "user timelines" do
1467 test "gets a users statuses", %{conn: conn} do
1468 user_one = insert(:user)
1469 user_two = insert(:user)
1470 user_three = insert(:user)
1472 {:ok, user_three} = User.follow(user_three, user_one)
1474 {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
1476 {:ok, direct_activity} =
1477 CommonAPI.post(user_one, %{
1478 "status" => "Hi, @#{user_two.nickname}.",
1479 "visibility" => "direct"
1482 {:ok, private_activity} =
1483 CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
1487 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1489 assert [%{"id" => id}] = json_response(resp, 200)
1490 assert id == to_string(activity.id)
1494 |> assign(:user, user_two)
1495 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1497 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1498 assert id_one == to_string(direct_activity.id)
1499 assert id_two == to_string(activity.id)
1503 |> assign(:user, user_three)
1504 |> get("/api/v1/accounts/#{user_one.id}/statuses")
1506 assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
1507 assert id_one == to_string(private_activity.id)
1508 assert id_two == to_string(activity.id)
1511 test "unimplemented pinned statuses feature", %{conn: conn} do
1512 note = insert(:note_activity)
1513 user = User.get_cached_by_ap_id(note.data["actor"])
1517 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
1519 assert json_response(conn, 200) == []
1522 test "gets an users media", %{conn: conn} do
1523 note = insert(:note_activity)
1524 user = User.get_cached_by_ap_id(note.data["actor"])
1526 file = %Plug.Upload{
1527 content_type: "image/jpg",
1528 path: Path.absname("test/fixtures/image.jpg"),
1529 filename: "an_image.jpg"
1533 TwitterAPI.upload(file, user, "json")
1537 TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
1541 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
1543 assert [%{"id" => id}] = json_response(conn, 200)
1544 assert id == to_string(image_post.id)
1548 |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
1550 assert [%{"id" => id}] = json_response(conn, 200)
1551 assert id == to_string(image_post.id)
1554 test "gets a user's statuses without reblogs", %{conn: conn} do
1555 user = insert(:user)
1556 {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
1557 {:ok, _, _} = CommonAPI.repeat(post.id, user)
1561 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
1563 assert [%{"id" => id}] = json_response(conn, 200)
1564 assert id == to_string(post.id)
1568 |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
1570 assert [%{"id" => id}] = json_response(conn, 200)
1571 assert id == to_string(post.id)
1574 test "filters user's statuses by a hashtag", %{conn: conn} do
1575 user = insert(:user)
1576 {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
1577 {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
1581 |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
1583 assert [%{"id" => id}] = json_response(conn, 200)
1584 assert id == to_string(post.id)
1588 describe "user relationships" do
1589 test "returns the relationships for the current user", %{conn: conn} do
1590 user = insert(:user)
1591 other_user = insert(:user)
1592 {:ok, user} = User.follow(user, other_user)
1596 |> assign(:user, user)
1597 |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
1599 assert [relationship] = json_response(conn, 200)
1601 assert to_string(other_user.id) == relationship["id"]
1605 describe "media upload" do
1607 upload_config = Pleroma.Config.get([Pleroma.Upload])
1608 proxy_config = Pleroma.Config.get([:media_proxy])
1611 Pleroma.Config.put([Pleroma.Upload], upload_config)
1612 Pleroma.Config.put([:media_proxy], proxy_config)
1615 user = insert(:user)
1619 |> assign(:user, user)
1621 image = %Plug.Upload{
1622 content_type: "image/jpg",
1623 path: Path.absname("test/fixtures/image.jpg"),
1624 filename: "an_image.jpg"
1627 [conn: conn, image: image]
1630 test "returns uploaded image", %{conn: conn, image: image} do
1631 desc = "Description of the image"
1635 |> post("/api/v1/media", %{"file" => image, "description" => desc})
1636 |> json_response(:ok)
1638 assert media["type"] == "image"
1639 assert media["description"] == desc
1642 object = Repo.get(Object, media["id"])
1643 assert object.data["actor"] == User.ap_id(conn.assigns[:user])
1646 test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
1647 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
1649 proxy_url = "https://cache.pleroma.social"
1650 Pleroma.Config.put([:media_proxy, :enabled], true)
1651 Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
1655 |> post("/api/v1/media", %{"file" => image})
1656 |> json_response(:ok)
1658 assert String.starts_with?(media["url"], proxy_url)
1661 test "returns media url when proxy is enabled but media url is whitelisted", %{
1665 media_url = "https://media.pleroma.social"
1666 Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
1668 Pleroma.Config.put([:media_proxy, :enabled], true)
1669 Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
1670 Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
1674 |> post("/api/v1/media", %{"file" => image})
1675 |> json_response(:ok)
1677 assert String.starts_with?(media["url"], media_url)
1681 describe "locked accounts" do
1682 test "/api/v1/follow_requests works" do
1683 user = insert(:user, %{info: %User.Info{locked: true}})
1684 other_user = insert(:user)
1686 {:ok, _activity} = ActivityPub.follow(other_user, user)
1688 user = User.get_cached_by_id(user.id)
1689 other_user = User.get_cached_by_id(other_user.id)
1691 assert User.following?(other_user, user) == false
1695 |> assign(:user, user)
1696 |> get("/api/v1/follow_requests")
1698 assert [relationship] = json_response(conn, 200)
1699 assert to_string(other_user.id) == relationship["id"]
1702 test "/api/v1/follow_requests/:id/authorize works" do
1703 user = insert(:user, %{info: %User.Info{locked: true}})
1704 other_user = insert(:user)
1706 {:ok, _activity} = ActivityPub.follow(other_user, user)
1708 user = User.get_cached_by_id(user.id)
1709 other_user = User.get_cached_by_id(other_user.id)
1711 assert User.following?(other_user, user) == false
1715 |> assign(:user, user)
1716 |> post("/api/v1/follow_requests/#{other_user.id}/authorize")
1718 assert relationship = json_response(conn, 200)
1719 assert to_string(other_user.id) == relationship["id"]
1721 user = User.get_cached_by_id(user.id)
1722 other_user = User.get_cached_by_id(other_user.id)
1724 assert User.following?(other_user, user) == true
1727 test "verify_credentials", %{conn: conn} do
1728 user = insert(:user, %{info: %User.Info{default_scope: "private"}})
1732 |> assign(:user, user)
1733 |> get("/api/v1/accounts/verify_credentials")
1735 assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
1736 assert id == to_string(user.id)
1739 test "/api/v1/follow_requests/:id/reject works" do
1740 user = insert(:user, %{info: %User.Info{locked: true}})
1741 other_user = insert(:user)
1743 {:ok, _activity} = ActivityPub.follow(other_user, user)
1745 user = User.get_cached_by_id(user.id)
1749 |> assign(:user, user)
1750 |> post("/api/v1/follow_requests/#{other_user.id}/reject")
1752 assert relationship = json_response(conn, 200)
1753 assert to_string(other_user.id) == relationship["id"]
1755 user = User.get_cached_by_id(user.id)
1756 other_user = User.get_cached_by_id(other_user.id)
1758 assert User.following?(other_user, user) == false
1762 test "account fetching", %{conn: conn} do
1763 user = insert(:user)
1767 |> get("/api/v1/accounts/#{user.id}")
1769 assert %{"id" => id} = json_response(conn, 200)
1770 assert id == to_string(user.id)
1774 |> get("/api/v1/accounts/-1")
1776 assert %{"error" => "Can't find user"} = json_response(conn, 404)
1779 test "account fetching also works nickname", %{conn: conn} do
1780 user = insert(:user)
1784 |> get("/api/v1/accounts/#{user.nickname}")
1786 assert %{"id" => id} = json_response(conn, 200)
1787 assert id == user.id
1790 test "mascot upload", %{conn: conn} do
1791 user = insert(:user)
1793 non_image_file = %Plug.Upload{
1794 content_type: "audio/mpeg",
1795 path: Path.absname("test/fixtures/sound.mp3"),
1796 filename: "sound.mp3"
1801 |> assign(:user, user)
1802 |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
1804 assert json_response(conn, 415)
1806 file = %Plug.Upload{
1807 content_type: "image/jpg",
1808 path: Path.absname("test/fixtures/image.jpg"),
1809 filename: "an_image.jpg"
1814 |> assign(:user, user)
1815 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1817 assert %{"id" => _, "type" => image} = json_response(conn, 200)
1820 test "mascot retrieving", %{conn: conn} do
1821 user = insert(:user)
1822 # When user hasn't set a mascot, we should just get pleroma tan back
1825 |> assign(:user, user)
1826 |> get("/api/v1/pleroma/mascot")
1828 assert %{"url" => url} = json_response(conn, 200)
1829 assert url =~ "pleroma-fox-tan-smol"
1831 # When a user sets their mascot, we should get that back
1832 file = %Plug.Upload{
1833 content_type: "image/jpg",
1834 path: Path.absname("test/fixtures/image.jpg"),
1835 filename: "an_image.jpg"
1840 |> assign(:user, user)
1841 |> put("/api/v1/pleroma/mascot", %{"file" => file})
1843 assert json_response(conn, 200)
1845 user = User.get_cached_by_id(user.id)
1849 |> assign(:user, user)
1850 |> get("/api/v1/pleroma/mascot")
1852 assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
1853 assert url =~ "an_image"
1856 test "hashtag timeline", %{conn: conn} do
1857 following = insert(:user)
1860 {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
1862 {:ok, [_activity]} =
1863 OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
1867 |> get("/api/v1/timelines/tag/2hu")
1869 assert [%{"id" => id}] = json_response(nconn, 200)
1871 assert id == to_string(activity.id)
1873 # works for different capitalization too
1876 |> get("/api/v1/timelines/tag/2HU")
1878 assert [%{"id" => id}] = json_response(nconn, 200)
1880 assert id == to_string(activity.id)
1884 test "multi-hashtag timeline", %{conn: conn} do
1885 user = insert(:user)
1887 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
1888 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
1889 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
1893 |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
1895 [status_none, status_test1, status_test] = json_response(any_test, 200)
1897 assert to_string(activity_test.id) == status_test["id"]
1898 assert to_string(activity_test1.id) == status_test1["id"]
1899 assert to_string(activity_none.id) == status_none["id"]
1903 |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
1905 assert [status_test1] == json_response(restricted_test, 200)
1907 all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
1909 assert [status_none] == json_response(all_test, 200)
1912 test "getting followers", %{conn: conn} do
1913 user = insert(:user)
1914 other_user = insert(:user)
1915 {:ok, user} = User.follow(user, other_user)
1919 |> get("/api/v1/accounts/#{other_user.id}/followers")
1921 assert [%{"id" => id}] = json_response(conn, 200)
1922 assert id == to_string(user.id)
1925 test "getting followers, hide_followers", %{conn: conn} do
1926 user = insert(:user)
1927 other_user = insert(:user, %{info: %{hide_followers: true}})
1928 {:ok, _user} = User.follow(user, other_user)
1932 |> get("/api/v1/accounts/#{other_user.id}/followers")
1934 assert [] == json_response(conn, 200)
1937 test "getting followers, hide_followers, same user requesting", %{conn: conn} do
1938 user = insert(:user)
1939 other_user = insert(:user, %{info: %{hide_followers: true}})
1940 {:ok, _user} = User.follow(user, other_user)
1944 |> assign(:user, other_user)
1945 |> get("/api/v1/accounts/#{other_user.id}/followers")
1947 refute [] == json_response(conn, 200)
1950 test "getting followers, pagination", %{conn: conn} do
1951 user = insert(:user)
1952 follower1 = insert(:user)
1953 follower2 = insert(:user)
1954 follower3 = insert(:user)
1955 {:ok, _} = User.follow(follower1, user)
1956 {:ok, _} = User.follow(follower2, user)
1957 {:ok, _} = User.follow(follower3, user)
1961 |> assign(:user, user)
1965 |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
1967 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
1968 assert id3 == follower3.id
1969 assert id2 == follower2.id
1973 |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
1975 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
1976 assert id2 == follower2.id
1977 assert id1 == follower1.id
1981 |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
1983 assert [%{"id" => id2}] = json_response(res_conn, 200)
1984 assert id2 == follower2.id
1986 assert [link_header] = get_resp_header(res_conn, "link")
1987 assert link_header =~ ~r/min_id=#{follower2.id}/
1988 assert link_header =~ ~r/max_id=#{follower2.id}/
1991 test "getting following", %{conn: conn} do
1992 user = insert(:user)
1993 other_user = insert(:user)
1994 {:ok, user} = User.follow(user, other_user)
1998 |> get("/api/v1/accounts/#{user.id}/following")
2000 assert [%{"id" => id}] = json_response(conn, 200)
2001 assert id == to_string(other_user.id)
2004 test "getting following, hide_follows", %{conn: conn} do
2005 user = insert(:user, %{info: %{hide_follows: true}})
2006 other_user = insert(:user)
2007 {:ok, user} = User.follow(user, other_user)
2011 |> get("/api/v1/accounts/#{user.id}/following")
2013 assert [] == json_response(conn, 200)
2016 test "getting following, hide_follows, same user requesting", %{conn: conn} do
2017 user = insert(:user, %{info: %{hide_follows: true}})
2018 other_user = insert(:user)
2019 {:ok, user} = User.follow(user, other_user)
2023 |> assign(:user, user)
2024 |> get("/api/v1/accounts/#{user.id}/following")
2026 refute [] == json_response(conn, 200)
2029 test "getting following, pagination", %{conn: conn} do
2030 user = insert(:user)
2031 following1 = insert(:user)
2032 following2 = insert(:user)
2033 following3 = insert(:user)
2034 {:ok, _} = User.follow(user, following1)
2035 {:ok, _} = User.follow(user, following2)
2036 {:ok, _} = User.follow(user, following3)
2040 |> assign(:user, user)
2044 |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
2046 assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
2047 assert id3 == following3.id
2048 assert id2 == following2.id
2052 |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
2054 assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
2055 assert id2 == following2.id
2056 assert id1 == following1.id
2060 |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
2062 assert [%{"id" => id2}] = json_response(res_conn, 200)
2063 assert id2 == following2.id
2065 assert [link_header] = get_resp_header(res_conn, "link")
2066 assert link_header =~ ~r/min_id=#{following2.id}/
2067 assert link_header =~ ~r/max_id=#{following2.id}/
2070 test "following / unfollowing a user", %{conn: conn} do
2071 user = insert(:user)
2072 other_user = insert(:user)
2076 |> assign(:user, user)
2077 |> post("/api/v1/accounts/#{other_user.id}/follow")
2079 assert %{"id" => _id, "following" => true} = json_response(conn, 200)
2081 user = User.get_cached_by_id(user.id)
2085 |> assign(:user, user)
2086 |> post("/api/v1/accounts/#{other_user.id}/unfollow")
2088 assert %{"id" => _id, "following" => false} = json_response(conn, 200)
2090 user = User.get_cached_by_id(user.id)
2094 |> assign(:user, user)
2095 |> post("/api/v1/follows", %{"uri" => other_user.nickname})
2097 assert %{"id" => id} = json_response(conn, 200)
2098 assert id == to_string(other_user.id)
2101 test "following without reblogs" do
2102 follower = insert(:user)
2103 followed = insert(:user)
2104 other_user = insert(:user)
2108 |> assign(:user, follower)
2109 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
2111 assert %{"showing_reblogs" => false} = json_response(conn, 200)
2113 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
2114 {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
2118 |> assign(:user, User.get_cached_by_id(follower.id))
2119 |> get("/api/v1/timelines/home")
2121 assert [] == json_response(conn, 200)
2125 |> assign(:user, follower)
2126 |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
2128 assert %{"showing_reblogs" => true} = json_response(conn, 200)
2132 |> assign(:user, User.get_cached_by_id(follower.id))
2133 |> get("/api/v1/timelines/home")
2135 expected_activity_id = reblog.id
2136 assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
2139 test "following / unfollowing errors" do
2140 user = insert(:user)
2144 |> assign(:user, user)
2147 conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
2148 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2151 user = User.get_cached_by_id(user.id)
2152 conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
2153 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2155 # self follow via uri
2156 user = User.get_cached_by_id(user.id)
2157 conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
2158 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2160 # follow non existing user
2161 conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
2162 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2164 # follow non existing user via uri
2165 conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
2166 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2168 # unfollow non existing user
2169 conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
2170 assert %{"error" => "Record not found"} = json_response(conn_res, 404)
2173 describe "mute/unmute" do
2174 test "with notifications", %{conn: conn} do
2175 user = insert(:user)
2176 other_user = insert(:user)
2180 |> assign(:user, user)
2181 |> post("/api/v1/accounts/#{other_user.id}/mute")
2183 response = json_response(conn, 200)
2185 assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
2186 user = User.get_cached_by_id(user.id)
2190 |> assign(:user, user)
2191 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2193 response = json_response(conn, 200)
2194 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
2197 test "without notifications", %{conn: conn} do
2198 user = insert(:user)
2199 other_user = insert(:user)
2203 |> assign(:user, user)
2204 |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
2206 response = json_response(conn, 200)
2208 assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
2209 user = User.get_cached_by_id(user.id)
2213 |> assign(:user, user)
2214 |> post("/api/v1/accounts/#{other_user.id}/unmute")
2216 response = json_response(conn, 200)
2217 assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
2221 test "subscribing / unsubscribing to a user", %{conn: conn} do
2222 user = insert(:user)
2223 subscription_target = insert(:user)
2227 |> assign(:user, user)
2228 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
2230 assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
2234 |> assign(:user, user)
2235 |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
2237 assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
2240 test "getting a list of mutes", %{conn: conn} do
2241 user = insert(:user)
2242 other_user = insert(:user)
2244 {:ok, user} = User.mute(user, other_user)
2248 |> assign(:user, user)
2249 |> get("/api/v1/mutes")
2251 other_user_id = to_string(other_user.id)
2252 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2255 test "blocking / unblocking a user", %{conn: conn} do
2256 user = insert(:user)
2257 other_user = insert(:user)
2261 |> assign(:user, user)
2262 |> post("/api/v1/accounts/#{other_user.id}/block")
2264 assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
2266 user = User.get_cached_by_id(user.id)
2270 |> assign(:user, user)
2271 |> post("/api/v1/accounts/#{other_user.id}/unblock")
2273 assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
2276 test "getting a list of blocks", %{conn: conn} do
2277 user = insert(:user)
2278 other_user = insert(:user)
2280 {:ok, user} = User.block(user, other_user)
2284 |> assign(:user, user)
2285 |> get("/api/v1/blocks")
2287 other_user_id = to_string(other_user.id)
2288 assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
2291 test "blocking / unblocking a domain", %{conn: conn} do
2292 user = insert(:user)
2293 other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
2297 |> assign(:user, user)
2298 |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2300 assert %{} = json_response(conn, 200)
2301 user = User.get_cached_by_ap_id(user.ap_id)
2302 assert User.blocks?(user, other_user)
2306 |> assign(:user, user)
2307 |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
2309 assert %{} = json_response(conn, 200)
2310 user = User.get_cached_by_ap_id(user.ap_id)
2311 refute User.blocks?(user, other_user)
2314 test "getting a list of domain blocks", %{conn: conn} do
2315 user = insert(:user)
2317 {:ok, user} = User.block_domain(user, "bad.site")
2318 {:ok, user} = User.block_domain(user, "even.worse.site")
2322 |> assign(:user, user)
2323 |> get("/api/v1/domain_blocks")
2325 domain_blocks = json_response(conn, 200)
2327 assert "bad.site" in domain_blocks
2328 assert "even.worse.site" in domain_blocks
2331 test "unimplemented follow_requests, blocks, domain blocks" do
2332 user = insert(:user)
2334 ["blocks", "domain_blocks", "follow_requests"]
2335 |> Enum.each(fn endpoint ->
2338 |> assign(:user, user)
2339 |> get("/api/v1/#{endpoint}")
2341 assert [] = json_response(conn, 200)
2345 test "returns the favorites of a user", %{conn: conn} do
2346 user = insert(:user)
2347 other_user = insert(:user)
2349 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
2350 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
2352 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
2356 |> assign(:user, user)
2357 |> get("/api/v1/favourites")
2359 assert [status] = json_response(first_conn, 200)
2360 assert status["id"] == to_string(activity.id)
2362 assert [{"link", _link_header}] =
2363 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
2365 # Honours query params
2366 {:ok, second_activity} =
2367 CommonAPI.post(other_user, %{
2369 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
2372 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
2374 last_like = status["id"]
2378 |> assign(:user, user)
2379 |> get("/api/v1/favourites?since_id=#{last_like}")
2381 assert [second_status] = json_response(second_conn, 200)
2382 assert second_status["id"] == to_string(second_activity.id)
2386 |> assign(:user, user)
2387 |> get("/api/v1/favourites?limit=0")
2389 assert [] = json_response(third_conn, 200)
2392 describe "getting favorites timeline of specified user" do
2394 [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
2395 [current_user: current_user, user: user]
2398 test "returns list of statuses favorited by specified user", %{
2400 current_user: current_user,
2403 [activity | _] = insert_pair(:note_activity)
2404 CommonAPI.favorite(activity.id, user)
2408 |> assign(:user, current_user)
2409 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2410 |> json_response(:ok)
2414 assert length(response) == 1
2415 assert like["id"] == activity.id
2418 test "returns favorites for specified user_id when user is not logged in", %{
2422 activity = insert(:note_activity)
2423 CommonAPI.favorite(activity.id, user)
2427 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2428 |> json_response(:ok)
2430 assert length(response) == 1
2433 test "returns favorited DM only when user is logged in and he is one of recipients", %{
2435 current_user: current_user,
2439 CommonAPI.post(current_user, %{
2440 "status" => "Hi @#{user.nickname}!",
2441 "visibility" => "direct"
2444 CommonAPI.favorite(direct.id, user)
2448 |> assign(:user, current_user)
2449 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2450 |> json_response(:ok)
2452 assert length(response) == 1
2454 anonymous_response =
2456 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2457 |> json_response(:ok)
2459 assert Enum.empty?(anonymous_response)
2462 test "does not return others' favorited DM when user is not one of recipients", %{
2464 current_user: current_user,
2467 user_two = insert(:user)
2470 CommonAPI.post(user_two, %{
2471 "status" => "Hi @#{user.nickname}!",
2472 "visibility" => "direct"
2475 CommonAPI.favorite(direct.id, user)
2479 |> assign(:user, current_user)
2480 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2481 |> json_response(:ok)
2483 assert Enum.empty?(response)
2486 test "paginates favorites using since_id and max_id", %{
2488 current_user: current_user,
2491 activities = insert_list(10, :note_activity)
2493 Enum.each(activities, fn activity ->
2494 CommonAPI.favorite(activity.id, user)
2497 third_activity = Enum.at(activities, 2)
2498 seventh_activity = Enum.at(activities, 6)
2502 |> assign(:user, current_user)
2503 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
2504 since_id: third_activity.id,
2505 max_id: seventh_activity.id
2507 |> json_response(:ok)
2509 assert length(response) == 3
2510 refute third_activity in response
2511 refute seventh_activity in response
2514 test "limits favorites using limit parameter", %{
2516 current_user: current_user,
2520 |> insert_list(:note_activity)
2521 |> Enum.each(fn activity ->
2522 CommonAPI.favorite(activity.id, user)
2527 |> assign(:user, current_user)
2528 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
2529 |> json_response(:ok)
2531 assert length(response) == 3
2534 test "returns empty response when user does not have any favorited statuses", %{
2536 current_user: current_user,
2541 |> assign(:user, current_user)
2542 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2543 |> json_response(:ok)
2545 assert Enum.empty?(response)
2548 test "returns 404 error when specified user is not exist", %{conn: conn} do
2549 conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
2551 assert json_response(conn, 404) == %{"error" => "Record not found"}
2554 test "returns 403 error when user has hidden own favorites", %{
2556 current_user: current_user
2558 user = insert(:user, %{info: %{hide_favorites: true}})
2559 activity = insert(:note_activity)
2560 CommonAPI.favorite(activity.id, user)
2564 |> assign(:user, current_user)
2565 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2567 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2570 test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
2571 user = insert(:user)
2572 activity = insert(:note_activity)
2573 CommonAPI.favorite(activity.id, user)
2577 |> assign(:user, current_user)
2578 |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
2580 assert user.info.hide_favorites
2581 assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
2585 test "get instance information", %{conn: conn} do
2586 conn = get(conn, "/api/v1/instance")
2587 assert result = json_response(conn, 200)
2589 email = Pleroma.Config.get([:instance, :email])
2590 # Note: not checking for "max_toot_chars" since it's optional
2596 "email" => from_config_email,
2598 "streaming_api" => _
2603 "registrations" => _,
2607 assert email == from_config_email
2610 test "get instance stats", %{conn: conn} do
2611 user = insert(:user, %{local: true})
2613 user2 = insert(:user, %{local: true})
2614 {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
2616 insert(:user, %{local: false, nickname: "u@peer1.com"})
2617 insert(:user, %{local: false, nickname: "u@peer2.com"})
2619 {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
2621 # Stats should count users with missing or nil `info.deactivated` value
2622 user = User.get_cached_by_id(user.id)
2623 info_change = Changeset.change(user.info, %{deactivated: nil})
2627 |> Changeset.change()
2628 |> Changeset.put_embed(:info, info_change)
2629 |> User.update_and_set_cache()
2631 Pleroma.Stats.update_stats()
2633 conn = get(conn, "/api/v1/instance")
2635 assert result = json_response(conn, 200)
2637 stats = result["stats"]
2640 assert stats["user_count"] == 1
2641 assert stats["status_count"] == 1
2642 assert stats["domain_count"] == 2
2645 test "get peers", %{conn: conn} do
2646 insert(:user, %{local: false, nickname: "u@peer1.com"})
2647 insert(:user, %{local: false, nickname: "u@peer2.com"})
2649 Pleroma.Stats.update_stats()
2651 conn = get(conn, "/api/v1/instance/peers")
2653 assert result = json_response(conn, 200)
2655 assert ["peer1.com", "peer2.com"] == Enum.sort(result)
2658 test "put settings", %{conn: conn} do
2659 user = insert(:user)
2663 |> assign(:user, user)
2664 |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
2666 assert _result = json_response(conn, 200)
2668 user = User.get_cached_by_ap_id(user.ap_id)
2669 assert user.info.settings == %{"programming" => "socks"}
2672 describe "pinned statuses" do
2674 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
2676 user = insert(:user)
2677 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
2679 [user: user, activity: activity]
2682 test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
2683 {:ok, _} = CommonAPI.pin(activity.id, user)
2687 |> assign(:user, user)
2688 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2689 |> json_response(200)
2691 id_str = to_string(activity.id)
2693 assert [%{"id" => ^id_str, "pinned" => true}] = result
2696 test "pin status", %{conn: conn, user: user, activity: activity} do
2697 id_str = to_string(activity.id)
2699 assert %{"id" => ^id_str, "pinned" => true} =
2701 |> assign(:user, user)
2702 |> post("/api/v1/statuses/#{activity.id}/pin")
2703 |> json_response(200)
2705 assert [%{"id" => ^id_str, "pinned" => true}] =
2707 |> assign(:user, user)
2708 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2709 |> json_response(200)
2712 test "unpin status", %{conn: conn, user: user, activity: activity} do
2713 {:ok, _} = CommonAPI.pin(activity.id, user)
2715 id_str = to_string(activity.id)
2716 user = refresh_record(user)
2718 assert %{"id" => ^id_str, "pinned" => false} =
2720 |> assign(:user, user)
2721 |> post("/api/v1/statuses/#{activity.id}/unpin")
2722 |> json_response(200)
2726 |> assign(:user, user)
2727 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
2728 |> json_response(200)
2731 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
2732 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
2734 id_str_one = to_string(activity_one.id)
2736 assert %{"id" => ^id_str_one, "pinned" => true} =
2738 |> assign(:user, user)
2739 |> post("/api/v1/statuses/#{id_str_one}/pin")
2740 |> json_response(200)
2742 user = refresh_record(user)
2744 assert %{"error" => "You have already pinned the maximum number of statuses"} =
2746 |> assign(:user, user)
2747 |> post("/api/v1/statuses/#{activity_two.id}/pin")
2748 |> json_response(400)
2754 Pleroma.Config.put([:rich_media, :enabled], true)
2757 Pleroma.Config.put([:rich_media, :enabled], false)
2760 user = insert(:user)
2764 test "returns rich-media card", %{conn: conn, user: user} do
2765 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
2768 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2769 "provider_name" => "www.imdb.com",
2770 "provider_url" => "http://www.imdb.com",
2771 "title" => "The Rock",
2773 "url" => "http://www.imdb.com/title/tt0117500/",
2775 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
2778 "image" => "http://ia.media-imdb.com/images/rock.jpg",
2779 "title" => "The Rock",
2780 "type" => "video.movie",
2781 "url" => "http://www.imdb.com/title/tt0117500/",
2783 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
2790 |> get("/api/v1/statuses/#{activity.id}/card")
2791 |> json_response(200)
2793 assert response == card_data
2795 # works with private posts
2797 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
2801 |> assign(:user, user)
2802 |> get("/api/v1/statuses/#{activity.id}/card")
2803 |> json_response(200)
2805 assert response_two == card_data
2808 test "replaces missing description with an empty string", %{conn: conn, user: user} do
2810 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
2814 |> get("/api/v1/statuses/#{activity.id}/card")
2815 |> json_response(:ok)
2817 assert response == %{
2819 "title" => "Pleroma",
2820 "description" => "",
2822 "provider_name" => "pleroma.social",
2823 "provider_url" => "https://pleroma.social",
2824 "url" => "https://pleroma.social/",
2827 "title" => "Pleroma",
2828 "type" => "website",
2829 "url" => "https://pleroma.social/"
2837 user = insert(:user)
2838 for_user = insert(:user)
2841 CommonAPI.post(user, %{
2842 "status" => "heweoo?"
2846 CommonAPI.post(user, %{
2847 "status" => "heweoo!"
2852 |> assign(:user, for_user)
2853 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
2855 assert json_response(response1, 200)["bookmarked"] == true
2859 |> assign(:user, for_user)
2860 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
2862 assert json_response(response2, 200)["bookmarked"] == true
2866 |> assign(:user, for_user)
2867 |> get("/api/v1/bookmarks")
2869 assert [json_response(response2, 200), json_response(response1, 200)] ==
2870 json_response(bookmarks, 200)
2874 |> assign(:user, for_user)
2875 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
2877 assert json_response(response1, 200)["bookmarked"] == false
2881 |> assign(:user, for_user)
2882 |> get("/api/v1/bookmarks")
2884 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
2887 describe "conversation muting" do
2889 user = insert(:user)
2890 {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
2892 [user: user, activity: activity]
2895 test "mute conversation", %{conn: conn, user: user, activity: activity} do
2896 id_str = to_string(activity.id)
2898 assert %{"id" => ^id_str, "muted" => true} =
2900 |> assign(:user, user)
2901 |> post("/api/v1/statuses/#{activity.id}/mute")
2902 |> json_response(200)
2905 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
2906 {:ok, _} = CommonAPI.add_mute(user, activity)
2908 id_str = to_string(activity.id)
2909 user = refresh_record(user)
2911 assert %{"id" => ^id_str, "muted" => false} =
2913 |> assign(:user, user)
2914 |> post("/api/v1/statuses/#{activity.id}/unmute")
2915 |> json_response(200)
2919 describe "reports" do
2921 reporter = insert(:user)
2922 target_user = insert(:user)
2924 {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
2926 [reporter: reporter, target_user: target_user, activity: activity]
2929 test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
2930 assert %{"action_taken" => false, "id" => _} =
2932 |> assign(:user, reporter)
2933 |> post("/api/v1/reports", %{"account_id" => target_user.id})
2934 |> json_response(200)
2937 test "submit a report with statuses and comment", %{
2940 target_user: target_user,
2943 assert %{"action_taken" => false, "id" => _} =
2945 |> assign(:user, reporter)
2946 |> post("/api/v1/reports", %{
2947 "account_id" => target_user.id,
2948 "status_ids" => [activity.id],
2949 "comment" => "bad status!"
2951 |> json_response(200)
2954 test "account_id is required", %{
2959 assert %{"error" => "Valid `account_id` required"} =
2961 |> assign(:user, reporter)
2962 |> post("/api/v1/reports", %{"status_ids" => [activity.id]})
2963 |> json_response(400)
2966 test "comment must be up to the size specified in the config", %{
2969 target_user: target_user
2971 max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
2972 comment = String.pad_trailing("a", max_size + 1, "a")
2974 error = %{"error" => "Comment must be up to #{max_size} characters"}
2978 |> assign(:user, reporter)
2979 |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
2980 |> json_response(400)
2984 describe "link headers" do
2985 test "preserves parameters in link headers", %{conn: conn} do
2986 user = insert(:user)
2987 other_user = insert(:user)
2990 CommonAPI.post(other_user, %{
2991 "status" => "hi @#{user.nickname}",
2992 "visibility" => "public"
2996 CommonAPI.post(other_user, %{
2997 "status" => "hi @#{user.nickname}",
2998 "visibility" => "public"
3001 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
3002 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
3006 |> assign(:user, user)
3007 |> get("/api/v1/notifications", %{media_only: true})
3009 assert [link_header] = get_resp_header(conn, "link")
3010 assert link_header =~ ~r/media_only=true/
3011 assert link_header =~ ~r/min_id=#{notification2.id}/
3012 assert link_header =~ ~r/max_id=#{notification1.id}/
3016 test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
3017 # Need to set an old-style integer ID to reproduce the problem
3018 # (these are no longer assigned to new accounts but were preserved
3019 # for existing accounts during the migration to flakeIDs)
3020 user_one = insert(:user, %{id: 1212})
3021 user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
3025 |> get("/api/v1/accounts/#{user_one.id}")
3029 |> get("/api/v1/accounts/#{user_two.nickname}")
3033 |> get("/api/v1/accounts/#{user_two.id}")
3035 acc_one = json_response(resp_one, 200)
3036 acc_two = json_response(resp_two, 200)
3037 acc_three = json_response(resp_three, 200)
3038 refute acc_one == acc_two
3039 assert acc_two == acc_three
3042 describe "custom emoji" do
3043 test "with tags", %{conn: conn} do
3046 |> get("/api/v1/custom_emojis")
3047 |> json_response(200)
3049 assert Map.has_key?(emoji, "shortcode")
3050 assert Map.has_key?(emoji, "static_url")
3051 assert Map.has_key?(emoji, "tags")
3052 assert is_list(emoji["tags"])
3053 assert Map.has_key?(emoji, "category")
3054 assert Map.has_key?(emoji, "url")
3055 assert Map.has_key?(emoji, "visible_in_picker")
3059 describe "index/2 redirections" do
3060 setup %{conn: conn} do
3064 signing_salt: "cooldude"
3069 |> Plug.Session.call(Plug.Session.init(session_opts))
3072 test_path = "/web/statuses/test"
3073 %{conn: conn, path: test_path}
3076 test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
3077 conn = get(conn, path)
3079 assert conn.status == 302
3080 assert redirected_to(conn) == "/web/login"
3083 test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
3084 token = insert(:oauth_token)
3088 |> assign(:user, token.user)
3089 |> put_session(:oauth_token, token.token)
3092 assert conn.status == 200
3095 test "saves referer path to session", %{conn: conn, path: path} do
3096 conn = get(conn, path)
3097 return_to = Plug.Conn.get_session(conn, :return_to)
3099 assert return_to == path
3102 test "redirects to the saved path after log in", %{conn: conn, path: path} do
3103 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3104 auth = insert(:oauth_authorization, app: app)
3108 |> put_session(:return_to, path)
3109 |> get("/web/login", %{code: auth.token})
3111 assert conn.status == 302
3112 assert redirected_to(conn) == path
3115 test "redirects to the getting-started page when referer is not present", %{conn: conn} do
3116 app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
3117 auth = insert(:oauth_authorization, app: app)
3119 conn = get(conn, "/web/login", %{code: auth.token})
3121 assert conn.status == 302
3122 assert redirected_to(conn) == "/web/getting-started"
3126 describe "scheduled activities" do
3127 test "creates a scheduled activity", %{conn: conn} do
3128 user = insert(:user)
3129 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3133 |> assign(:user, user)
3134 |> post("/api/v1/statuses", %{
3135 "status" => "scheduled",
3136 "scheduled_at" => scheduled_at
3139 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
3140 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
3141 assert [] == Repo.all(Activity)
3144 test "creates a scheduled activity with a media attachment", %{conn: conn} do
3145 user = insert(:user)
3146 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3148 file = %Plug.Upload{
3149 content_type: "image/jpg",
3150 path: Path.absname("test/fixtures/image.jpg"),
3151 filename: "an_image.jpg"
3154 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
3158 |> assign(:user, user)
3159 |> post("/api/v1/statuses", %{
3160 "media_ids" => [to_string(upload.id)],
3161 "status" => "scheduled",
3162 "scheduled_at" => scheduled_at
3165 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
3166 assert %{"type" => "image"} = media_attachment
3169 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
3171 user = insert(:user)
3174 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
3178 |> assign(:user, user)
3179 |> post("/api/v1/statuses", %{
3180 "status" => "not scheduled",
3181 "scheduled_at" => scheduled_at
3184 assert %{"content" => "not scheduled"} = json_response(conn, 200)
3185 assert [] == Repo.all(ScheduledActivity)
3188 test "returns error when daily user limit is exceeded", %{conn: conn} do
3189 user = insert(:user)
3192 NaiveDateTime.utc_now()
3193 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3194 |> NaiveDateTime.to_iso8601()
3196 attrs = %{params: %{}, scheduled_at: today}
3197 {:ok, _} = ScheduledActivity.create(user, attrs)
3198 {:ok, _} = ScheduledActivity.create(user, attrs)
3202 |> assign(:user, user)
3203 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
3205 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
3208 test "returns error when total user limit is exceeded", %{conn: conn} do
3209 user = insert(:user)
3212 NaiveDateTime.utc_now()
3213 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
3214 |> NaiveDateTime.to_iso8601()
3217 NaiveDateTime.utc_now()
3218 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
3219 |> NaiveDateTime.to_iso8601()
3221 attrs = %{params: %{}, scheduled_at: today}
3222 {:ok, _} = ScheduledActivity.create(user, attrs)
3223 {:ok, _} = ScheduledActivity.create(user, attrs)
3224 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
3228 |> assign(:user, user)
3229 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
3231 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
3234 test "shows scheduled activities", %{conn: conn} do
3235 user = insert(:user)
3236 scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
3237 scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
3238 scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
3239 scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
3243 |> assign(:user, user)
3248 |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
3250 result = json_response(conn_res, 200)
3251 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3256 |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
3258 result = json_response(conn_res, 200)
3259 assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
3264 |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
3266 result = json_response(conn_res, 200)
3267 assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
3270 test "shows a scheduled activity", %{conn: conn} do
3271 user = insert(:user)
3272 scheduled_activity = insert(:scheduled_activity, user: user)
3276 |> assign(:user, user)
3277 |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3279 assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
3280 assert scheduled_activity_id == scheduled_activity.id |> to_string()
3284 |> assign(:user, user)
3285 |> get("/api/v1/scheduled_statuses/404")
3287 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3290 test "updates a scheduled activity", %{conn: conn} do
3291 user = insert(:user)
3292 scheduled_activity = insert(:scheduled_activity, user: user)
3295 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
3299 |> assign(:user, user)
3300 |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
3301 scheduled_at: new_scheduled_at
3304 assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
3305 assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
3309 |> assign(:user, user)
3310 |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
3312 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3315 test "deletes a scheduled activity", %{conn: conn} do
3316 user = insert(:user)
3317 scheduled_activity = insert(:scheduled_activity, user: user)
3321 |> assign(:user, user)
3322 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3324 assert %{} = json_response(res_conn, 200)
3325 assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
3329 |> assign(:user, user)
3330 |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
3332 assert %{"error" => "Record not found"} = json_response(res_conn, 404)
3336 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
3337 user1 = insert(:user)
3338 user2 = insert(:user)
3339 user3 = insert(:user)
3341 {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
3343 # Reply to status from another user
3346 |> assign(:user, user2)
3347 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
3349 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
3351 activity = Activity.get_by_id_with_object(id)
3353 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
3354 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
3356 # Reblog from the third user
3359 |> assign(:user, user3)
3360 |> post("/api/v1/statuses/#{activity.id}/reblog")
3362 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
3363 json_response(conn2, 200)
3365 assert to_string(activity.id) == id
3367 # Getting third user status
3370 |> assign(:user, user3)
3371 |> get("api/v1/timelines/home")
3373 [reblogged_activity] = json_response(conn3, 200)
3375 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
3377 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
3378 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
3381 describe "create account by app" do
3382 test "Account registration via Application", %{conn: conn} do
3385 |> post("/api/v1/apps", %{
3386 client_name: "client_name",
3387 redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
3388 scopes: "read, write, follow"
3392 "client_id" => client_id,
3393 "client_secret" => client_secret,
3395 "name" => "client_name",
3396 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
3399 } = json_response(conn, 200)
3403 |> post("/oauth/token", %{
3404 grant_type: "client_credentials",
3405 client_id: client_id,
3406 client_secret: client_secret
3409 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
3410 json_response(conn, 200)
3413 token_from_db = Repo.get_by(Token, token: token)
3414 assert token_from_db
3416 assert scope == "read write follow"
3420 |> put_req_header("authorization", "Bearer " <> token)
3421 |> post("/api/v1/accounts", %{
3423 email: "lain@example.org",
3424 password: "PlzDontHackLain",
3429 "access_token" => token,
3430 "created_at" => _created_at,
3432 "token_type" => "Bearer"
3433 } = json_response(conn, 200)
3435 token_from_db = Repo.get_by(Token, token: token)
3436 assert token_from_db
3437 token_from_db = Repo.preload(token_from_db, :user)
3438 assert token_from_db.user
3440 assert token_from_db.user.info.confirmation_pending
3443 test "rate limit", %{conn: conn} do
3444 app_token = insert(:oauth_token, user: nil)
3447 put_req_header(conn, "authorization", "Bearer " <> app_token.token)
3448 |> Map.put(:remote_ip, {15, 15, 15, 15})
3453 |> post("/api/v1/accounts", %{
3454 username: "#{i}lain",
3455 email: "#{i}lain@example.org",
3456 password: "PlzDontHackLain",
3461 "access_token" => token,
3462 "created_at" => _created_at,
3464 "token_type" => "Bearer"
3465 } = json_response(conn, 200)
3467 token_from_db = Repo.get_by(Token, token: token)
3468 assert token_from_db
3469 token_from_db = Repo.preload(token_from_db, :user)
3470 assert token_from_db.user
3472 assert token_from_db.user.info.confirmation_pending
3477 |> post("/api/v1/accounts", %{
3479 email: "6lain@example.org",
3480 password: "PlzDontHackLain",
3484 assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
3488 describe "GET /api/v1/polls/:id" do
3489 test "returns poll entity for object id", %{conn: conn} do
3490 user = insert(:user)
3493 CommonAPI.post(user, %{
3494 "status" => "Pleroma does",
3495 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
3498 object = Object.normalize(activity)
3502 |> assign(:user, user)
3503 |> get("/api/v1/polls/#{object.id}")
3505 response = json_response(conn, 200)
3507 assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
3510 test "does not expose polls for private statuses", %{conn: conn} do
3511 user = insert(:user)
3512 other_user = insert(:user)
3515 CommonAPI.post(user, %{
3516 "status" => "Pleroma does",
3517 "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
3518 "visibility" => "private"
3521 object = Object.normalize(activity)
3525 |> assign(:user, other_user)
3526 |> get("/api/v1/polls/#{object.id}")
3528 assert json_response(conn, 404)
3532 describe "POST /api/v1/polls/:id/votes" do
3533 test "votes are added to the poll", %{conn: conn} do
3534 user = insert(:user)
3535 other_user = insert(:user)
3538 CommonAPI.post(user, %{
3539 "status" => "A very delicious sandwich",
3541 "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
3547 object = Object.normalize(activity)
3551 |> assign(:user, other_user)
3552 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
3554 assert json_response(conn, 200)
3555 object = Object.get_by_id(object.id)
3557 assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
3562 test "author can't vote", %{conn: conn} do
3563 user = insert(:user)
3566 CommonAPI.post(user, %{
3567 "status" => "Am I cute?",
3568 "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
3571 object = Object.normalize(activity)
3574 |> assign(:user, user)
3575 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
3576 |> json_response(422) == %{"error" => "Poll's author can't vote"}
3578 object = Object.get_by_id(object.id)
3580 refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
3583 test "does not allow multiple choices on a single-choice question", %{conn: conn} do
3584 user = insert(:user)
3585 other_user = insert(:user)
3588 CommonAPI.post(user, %{
3589 "status" => "The glass is",
3590 "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
3593 object = Object.normalize(activity)
3596 |> assign(:user, other_user)
3597 |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
3598 |> json_response(422) == %{"error" => "Too many choices"}
3600 object = Object.get_by_id(object.id)
3602 refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->