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.StatusControllerTest do
6 use Pleroma.Web.ConnCase
9 alias Pleroma.ActivityExpiration
11 alias Pleroma.Conversation.Participation
14 alias Pleroma.ScheduledActivity
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.CommonAPI
19 import Pleroma.Factory
20 import ExUnit.CaptureLog
22 describe "posting statuses" do
28 |> assign(:user, user)
33 test "posting a status", %{conn: conn} do
34 idempotency_key = "Pikachu rocks!"
38 |> put_req_header("idempotency-key", idempotency_key)
39 |> post("/api/v1/statuses", %{
41 "spoiler_text" => "2hu",
42 "sensitive" => "false"
45 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
47 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
49 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
50 json_response(conn_one, 200)
52 assert Activity.get_by_id(id)
56 |> put_req_header("idempotency-key", idempotency_key)
57 |> post("/api/v1/statuses", %{
59 "spoiler_text" => "2hu",
60 "sensitive" => "false"
63 assert %{"id" => second_id} = json_response(conn_two, 200)
64 assert id == second_id
68 |> post("/api/v1/statuses", %{
70 "spoiler_text" => "2hu",
71 "sensitive" => "false"
74 assert %{"id" => third_id} = json_response(conn_three, 200)
77 # An activity that will expire:
83 |> post("api/v1/statuses", %{
85 "expires_in" => expires_in
88 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
89 assert activity = Activity.get_by_id(fourth_id)
90 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
92 estimated_expires_at =
93 NaiveDateTime.utc_now()
94 |> NaiveDateTime.add(expires_in)
95 |> NaiveDateTime.truncate(:second)
97 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
98 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
100 assert fourth_response["pleroma"]["expires_at"] ==
101 NaiveDateTime.to_iso8601(expiration.scheduled_at)
104 test "posting an undefined status with an attachment", %{conn: conn} do
108 content_type: "image/jpg",
109 path: Path.absname("test/fixtures/image.jpg"),
110 filename: "an_image.jpg"
113 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
117 |> assign(:user, user)
118 |> post("/api/v1/statuses", %{
119 "media_ids" => [to_string(upload.id)]
122 assert json_response(conn, 200)
125 test "replying to a status", %{conn: conn} do
127 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
131 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
133 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
135 activity = Activity.get_by_id(id)
137 assert activity.data["context"] == replied_to.data["context"]
138 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
141 test "replying to a direct message with visibility other than direct", %{conn: conn} do
143 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
145 Enum.each(["public", "private", "unlisted"], fn visibility ->
148 |> post("/api/v1/statuses", %{
149 "status" => "@#{user.nickname} hey",
150 "in_reply_to_id" => replied_to.id,
151 "visibility" => visibility
154 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
158 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
161 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
163 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
164 assert Activity.get_by_id(id)
167 test "posting a sensitive status", %{conn: conn} do
170 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
172 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
173 assert Activity.get_by_id(id)
176 test "posting a fake status", %{conn: conn} do
179 |> post("/api/v1/statuses", %{
181 "\"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"
184 real_status = json_response(real_conn, 200)
187 assert Object.get_by_ap_id(real_status["uri"])
191 |> Map.put("id", nil)
192 |> Map.put("url", nil)
193 |> Map.put("uri", nil)
194 |> Map.put("created_at", nil)
195 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
199 |> post("/api/v1/statuses", %{
201 "\"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",
205 fake_status = json_response(fake_conn, 200)
208 refute Object.get_by_ap_id(fake_status["uri"])
212 |> Map.put("id", nil)
213 |> Map.put("url", nil)
214 |> Map.put("uri", nil)
215 |> Map.put("created_at", nil)
216 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
218 assert real_status == fake_status
221 test "posting a status with OGP link preview", %{conn: conn} do
222 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
223 Config.put([:rich_media, :enabled], true)
227 |> post("/api/v1/statuses", %{
228 "status" => "https://example.com/ogp"
231 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
232 assert Activity.get_by_id(id)
235 test "posting a direct status", %{conn: conn} do
236 user2 = insert(:user)
237 content = "direct cofe @#{user2.nickname}"
241 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
243 assert %{"id" => id} = response = json_response(conn, 200)
244 assert response["visibility"] == "direct"
245 assert response["pleroma"]["direct_conversation_id"]
246 assert activity = Activity.get_by_id(id)
247 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
248 assert activity.data["to"] == [user2.ap_id]
249 assert activity.data["cc"] == []
253 describe "posting scheduled statuses" do
254 test "creates a scheduled activity", %{conn: conn} do
256 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
260 |> assign(:user, user)
261 |> post("/api/v1/statuses", %{
262 "status" => "scheduled",
263 "scheduled_at" => scheduled_at
266 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
267 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
268 assert [] == Repo.all(Activity)
271 test "creates a scheduled activity with a media attachment", %{conn: conn} do
273 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
276 content_type: "image/jpg",
277 path: Path.absname("test/fixtures/image.jpg"),
278 filename: "an_image.jpg"
281 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
285 |> assign(:user, user)
286 |> post("/api/v1/statuses", %{
287 "media_ids" => [to_string(upload.id)],
288 "status" => "scheduled",
289 "scheduled_at" => scheduled_at
292 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
293 assert %{"type" => "image"} = media_attachment
296 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
301 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
305 |> assign(:user, user)
306 |> post("/api/v1/statuses", %{
307 "status" => "not scheduled",
308 "scheduled_at" => scheduled_at
311 assert %{"content" => "not scheduled"} = json_response(conn, 200)
312 assert [] == Repo.all(ScheduledActivity)
315 test "returns error when daily user limit is exceeded", %{conn: conn} do
319 NaiveDateTime.utc_now()
320 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
321 |> NaiveDateTime.to_iso8601()
323 attrs = %{params: %{}, scheduled_at: today}
324 {:ok, _} = ScheduledActivity.create(user, attrs)
325 {:ok, _} = ScheduledActivity.create(user, attrs)
329 |> assign(:user, user)
330 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
332 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
335 test "returns error when total user limit is exceeded", %{conn: conn} do
339 NaiveDateTime.utc_now()
340 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
341 |> NaiveDateTime.to_iso8601()
344 NaiveDateTime.utc_now()
345 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
346 |> NaiveDateTime.to_iso8601()
348 attrs = %{params: %{}, scheduled_at: today}
349 {:ok, _} = ScheduledActivity.create(user, attrs)
350 {:ok, _} = ScheduledActivity.create(user, attrs)
351 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
355 |> assign(:user, user)
356 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
358 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
362 describe "posting polls" do
363 test "posting a poll", %{conn: conn} do
365 time = NaiveDateTime.utc_now()
369 |> assign(:user, user)
370 |> post("/api/v1/statuses", %{
371 "status" => "Who is the #bestgrill?",
372 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
375 response = json_response(conn, 200)
377 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
378 title in ["Rei", "Asuka", "Misato"]
381 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
382 refute response["poll"]["expred"]
385 test "option limit is enforced", %{conn: conn} do
387 limit = Config.get([:instance, :poll_limits, :max_options])
391 |> assign(:user, user)
392 |> post("/api/v1/statuses", %{
394 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
397 %{"error" => error} = json_response(conn, 422)
398 assert error == "Poll can't contain more than #{limit} options"
401 test "option character limit is enforced", %{conn: conn} do
403 limit = Config.get([:instance, :poll_limits, :max_option_chars])
407 |> assign(:user, user)
408 |> post("/api/v1/statuses", %{
411 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
416 %{"error" => error} = json_response(conn, 422)
417 assert error == "Poll options cannot be longer than #{limit} characters each"
420 test "minimal date limit is enforced", %{conn: conn} do
422 limit = Config.get([:instance, :poll_limits, :min_expiration])
426 |> assign(:user, user)
427 |> post("/api/v1/statuses", %{
428 "status" => "imagine arbitrary limits",
430 "options" => ["this post was made by pleroma gang"],
431 "expires_in" => limit - 1
435 %{"error" => error} = json_response(conn, 422)
436 assert error == "Expiration date is too soon"
439 test "maximum date limit is enforced", %{conn: conn} do
441 limit = Config.get([:instance, :poll_limits, :max_expiration])
445 |> assign(:user, user)
446 |> post("/api/v1/statuses", %{
447 "status" => "imagine arbitrary limits",
449 "options" => ["this post was made by pleroma gang"],
450 "expires_in" => limit + 1
454 %{"error" => error} = json_response(conn, 422)
455 assert error == "Expiration date is too far in the future"
459 test "get a status", %{conn: conn} do
460 activity = insert(:note_activity)
464 |> get("/api/v1/statuses/#{activity.id}")
466 assert %{"id" => id} = json_response(conn, 200)
467 assert id == to_string(activity.id)
470 test "get a direct status", %{conn: conn} do
472 other_user = insert(:user)
475 CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"})
479 |> assign(:user, user)
480 |> get("/api/v1/statuses/#{activity.id}")
482 [participation] = Participation.for_user(user)
484 res = json_response(conn, 200)
485 assert res["pleroma"]["direct_conversation_id"] == participation.id
488 test "get statuses by IDs", %{conn: conn} do
489 %{id: id1} = insert(:note_activity)
490 %{id: id2} = insert(:note_activity)
492 query_string = "ids[]=#{id1}&ids[]=#{id2}"
493 conn = get(conn, "/api/v1/statuses/?#{query_string}")
495 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
498 describe "deleting a status" do
499 test "when you created it", %{conn: conn} do
500 activity = insert(:note_activity)
501 author = User.get_cached_by_ap_id(activity.data["actor"])
505 |> assign(:user, author)
506 |> delete("/api/v1/statuses/#{activity.id}")
508 assert %{} = json_response(conn, 200)
510 refute Activity.get_by_id(activity.id)
513 test "when you didn't create it", %{conn: conn} do
514 activity = insert(:note_activity)
519 |> assign(:user, user)
520 |> delete("/api/v1/statuses/#{activity.id}")
522 assert %{"error" => _} = json_response(conn, 403)
524 assert Activity.get_by_id(activity.id) == activity
527 test "when you're an admin or moderator", %{conn: conn} do
528 activity1 = insert(:note_activity)
529 activity2 = insert(:note_activity)
530 admin = insert(:user, info: %{is_admin: true})
531 moderator = insert(:user, info: %{is_moderator: true})
535 |> assign(:user, admin)
536 |> delete("/api/v1/statuses/#{activity1.id}")
538 assert %{} = json_response(res_conn, 200)
542 |> assign(:user, moderator)
543 |> delete("/api/v1/statuses/#{activity2.id}")
545 assert %{} = json_response(res_conn, 200)
547 refute Activity.get_by_id(activity1.id)
548 refute Activity.get_by_id(activity2.id)
552 describe "reblogging" do
553 test "reblogs and returns the reblogged status", %{conn: conn} do
554 activity = insert(:note_activity)
559 |> assign(:user, user)
560 |> post("/api/v1/statuses/#{activity.id}/reblog")
563 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
565 } = json_response(conn, 200)
567 assert to_string(activity.id) == id
570 test "reblogs privately and returns the reblogged status", %{conn: conn} do
571 activity = insert(:note_activity)
576 |> assign(:user, user)
577 |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
580 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
582 "visibility" => "private"
583 } = json_response(conn, 200)
585 assert to_string(activity.id) == id
588 test "reblogged status for another user", %{conn: conn} do
589 activity = insert(:note_activity)
590 user1 = insert(:user)
591 user2 = insert(:user)
592 user3 = insert(:user)
593 {:ok, _} = CommonAPI.favorite(user2, activity.id)
594 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
595 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
596 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
600 |> assign(:user, user3)
601 |> get("/api/v1/statuses/#{reblog_activity1.id}")
604 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
605 "reblogged" => false,
606 "favourited" => false,
607 "bookmarked" => false
608 } = json_response(conn_res, 200)
612 |> assign(:user, user2)
613 |> get("/api/v1/statuses/#{reblog_activity1.id}")
616 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
618 "favourited" => true,
620 } = json_response(conn_res, 200)
622 assert to_string(activity.id) == id
625 test "returns 400 error when activity is not exist", %{conn: conn} do
630 |> assign(:user, user)
631 |> post("/api/v1/statuses/foo/reblog")
633 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
637 describe "unreblogging" do
638 test "unreblogs and returns the unreblogged status", %{conn: conn} do
639 activity = insert(:note_activity)
642 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
646 |> assign(:user, user)
647 |> post("/api/v1/statuses/#{activity.id}/unreblog")
649 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
651 assert to_string(activity.id) == id
654 test "returns 400 error when activity is not exist", %{conn: conn} do
659 |> assign(:user, user)
660 |> post("/api/v1/statuses/foo/unreblog")
662 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
666 describe "favoriting" do
667 test "favs a status and returns it", %{conn: conn} do
668 activity = insert(:note_activity)
673 |> assign(:user, user)
674 |> post("/api/v1/statuses/#{activity.id}/favourite")
676 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
677 json_response(conn, 200)
679 assert to_string(activity.id) == id
682 test "returns 400 error for a wrong id", %{conn: conn} do
685 assert capture_log(fn ->
688 |> assign(:user, user)
689 |> post("/api/v1/statuses/1/favourite")
691 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
696 describe "unfavoriting" do
697 test "unfavorites a status and returns it", %{conn: conn} do
698 activity = insert(:note_activity)
701 {:ok, _} = CommonAPI.favorite(user, activity.id)
705 |> assign(:user, user)
706 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
708 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
709 json_response(conn, 200)
711 assert to_string(activity.id) == id
714 test "returns 400 error for a wrong id", %{conn: conn} do
719 |> assign(:user, user)
720 |> post("/api/v1/statuses/1/unfavourite")
722 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
726 describe "pinned statuses" do
729 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
731 [user: user, activity: activity]
734 clear_config([:instance, :max_pinned_statuses]) do
735 Config.put([:instance, :max_pinned_statuses], 1)
738 test "pin status", %{conn: conn, user: user, activity: activity} do
739 id_str = to_string(activity.id)
741 assert %{"id" => ^id_str, "pinned" => true} =
743 |> assign(:user, user)
744 |> post("/api/v1/statuses/#{activity.id}/pin")
745 |> json_response(200)
747 assert [%{"id" => ^id_str, "pinned" => true}] =
749 |> assign(:user, user)
750 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
751 |> json_response(200)
754 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
755 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
759 |> assign(:user, user)
760 |> post("/api/v1/statuses/#{dm.id}/pin")
762 assert json_response(conn, 400) == %{"error" => "Could not pin"}
765 test "unpin status", %{conn: conn, user: user, activity: activity} do
766 {:ok, _} = CommonAPI.pin(activity.id, user)
768 id_str = to_string(activity.id)
769 user = refresh_record(user)
771 assert %{"id" => ^id_str, "pinned" => false} =
773 |> assign(:user, user)
774 |> post("/api/v1/statuses/#{activity.id}/unpin")
775 |> json_response(200)
779 |> assign(:user, user)
780 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
781 |> json_response(200)
784 test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
787 |> assign(:user, user)
788 |> post("/api/v1/statuses/1/unpin")
790 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
793 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
794 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
796 id_str_one = to_string(activity_one.id)
798 assert %{"id" => ^id_str_one, "pinned" => true} =
800 |> assign(:user, user)
801 |> post("/api/v1/statuses/#{id_str_one}/pin")
802 |> json_response(200)
804 user = refresh_record(user)
806 assert %{"error" => "You have already pinned the maximum number of statuses"} =
808 |> assign(:user, user)
809 |> post("/api/v1/statuses/#{activity_two.id}/pin")
810 |> json_response(400)
816 Config.put([:rich_media, :enabled], true)
822 test "returns rich-media card", %{conn: conn, user: user} do
823 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
825 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
828 "image" => "http://ia.media-imdb.com/images/rock.jpg",
829 "provider_name" => "example.com",
830 "provider_url" => "https://example.com",
831 "title" => "The Rock",
833 "url" => "https://example.com/ogp",
835 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
838 "image" => "http://ia.media-imdb.com/images/rock.jpg",
839 "title" => "The Rock",
840 "type" => "video.movie",
841 "url" => "https://example.com/ogp",
843 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
850 |> get("/api/v1/statuses/#{activity.id}/card")
851 |> json_response(200)
853 assert response == card_data
855 # works with private posts
857 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
861 |> assign(:user, user)
862 |> get("/api/v1/statuses/#{activity.id}/card")
863 |> json_response(200)
865 assert response_two == card_data
868 test "replaces missing description with an empty string", %{conn: conn, user: user} do
869 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
872 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
876 |> get("/api/v1/statuses/#{activity.id}/card")
877 |> json_response(:ok)
879 assert response == %{
881 "title" => "Pleroma",
884 "provider_name" => "example.com",
885 "provider_url" => "https://example.com",
886 "url" => "https://example.com/ogp-missing-data",
889 "title" => "Pleroma",
891 "url" => "https://example.com/ogp-missing-data"
900 for_user = insert(:user)
903 CommonAPI.post(user, %{
904 "status" => "heweoo?"
908 CommonAPI.post(user, %{
909 "status" => "heweoo!"
914 |> assign(:user, for_user)
915 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
917 assert json_response(response1, 200)["bookmarked"] == true
921 |> assign(:user, for_user)
922 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
924 assert json_response(response2, 200)["bookmarked"] == true
928 |> assign(:user, for_user)
929 |> get("/api/v1/bookmarks")
931 assert [json_response(response2, 200), json_response(response1, 200)] ==
932 json_response(bookmarks, 200)
936 |> assign(:user, for_user)
937 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
939 assert json_response(response1, 200)["bookmarked"] == false
943 |> assign(:user, for_user)
944 |> get("/api/v1/bookmarks")
946 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
949 describe "conversation muting" do
951 post_user = insert(:user)
954 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
956 [user: user, activity: activity]
959 test "mute conversation", %{conn: conn, user: user, activity: activity} do
960 id_str = to_string(activity.id)
962 assert %{"id" => ^id_str, "muted" => true} =
964 |> assign(:user, user)
965 |> post("/api/v1/statuses/#{activity.id}/mute")
966 |> json_response(200)
969 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
970 {:ok, _} = CommonAPI.add_mute(user, activity)
974 |> assign(:user, user)
975 |> post("/api/v1/statuses/#{activity.id}/mute")
977 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
980 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
981 {:ok, _} = CommonAPI.add_mute(user, activity)
983 id_str = to_string(activity.id)
984 user = refresh_record(user)
986 assert %{"id" => ^id_str, "muted" => false} =
988 |> assign(:user, user)
989 |> post("/api/v1/statuses/#{activity.id}/unmute")
990 |> json_response(200)
994 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
995 user1 = insert(:user)
996 user2 = insert(:user)
997 user3 = insert(:user)
999 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
1001 # Reply to status from another user
1004 |> assign(:user, user2)
1005 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1007 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
1009 activity = Activity.get_by_id_with_object(id)
1011 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
1012 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1014 # Reblog from the third user
1017 |> assign(:user, user3)
1018 |> post("/api/v1/statuses/#{activity.id}/reblog")
1020 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1021 json_response(conn2, 200)
1023 assert to_string(activity.id) == id
1025 # Getting third user status
1028 |> assign(:user, user3)
1029 |> get("api/v1/timelines/home")
1031 [reblogged_activity] = json_response(conn3, 200)
1033 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1035 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1036 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1039 describe "GET /api/v1/statuses/:id/favourited_by" do
1041 user = insert(:user)
1042 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1046 |> assign(:user, user)
1048 [conn: conn, activity: activity, user: user]
1051 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1052 other_user = insert(:user)
1053 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1057 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1058 |> json_response(:ok)
1060 [%{"id" => id}] = response
1062 assert id == other_user.id
1065 test "returns empty array when status has not been favorited yet", %{
1071 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1072 |> json_response(:ok)
1074 assert Enum.empty?(response)
1077 test "does not return users who have favorited the status but are blocked", %{
1078 conn: %{assigns: %{user: user}} = conn,
1081 other_user = insert(:user)
1082 {:ok, user} = User.block(user, other_user)
1084 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1088 |> assign(:user, user)
1089 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1090 |> json_response(:ok)
1092 assert Enum.empty?(response)
1095 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1096 other_user = insert(:user)
1097 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1101 |> assign(:user, nil)
1102 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1103 |> json_response(:ok)
1105 [%{"id" => id}] = response
1106 assert id == other_user.id
1109 test "requires authentification for private posts", %{conn: conn, user: user} do
1110 other_user = insert(:user)
1113 CommonAPI.post(user, %{
1114 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1115 "visibility" => "direct"
1118 {:ok, _} = CommonAPI.favorite(other_user, activity.id)
1121 |> assign(:user, nil)
1122 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1123 |> json_response(404)
1127 |> assign(:user, other_user)
1128 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1129 |> json_response(200)
1131 [%{"id" => id}] = response
1132 assert id == other_user.id
1136 describe "GET /api/v1/statuses/:id/reblogged_by" do
1138 user = insert(:user)
1139 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1143 |> assign(:user, user)
1145 [conn: conn, activity: activity, user: user]
1148 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1149 other_user = insert(:user)
1150 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1154 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1155 |> json_response(:ok)
1157 [%{"id" => id}] = response
1159 assert id == other_user.id
1162 test "returns empty array when status has not been reblogged yet", %{
1168 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1169 |> json_response(:ok)
1171 assert Enum.empty?(response)
1174 test "does not return users who have reblogged the status but are blocked", %{
1175 conn: %{assigns: %{user: user}} = conn,
1178 other_user = insert(:user)
1179 {:ok, user} = User.block(user, other_user)
1181 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1185 |> assign(:user, user)
1186 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1187 |> json_response(:ok)
1189 assert Enum.empty?(response)
1192 test "does not return users who have reblogged the status privately", %{
1193 conn: %{assigns: %{user: user}} = conn,
1196 other_user = insert(:user)
1198 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
1202 |> assign(:user, user)
1203 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1204 |> json_response(:ok)
1206 assert Enum.empty?(response)
1209 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1210 other_user = insert(:user)
1211 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1215 |> assign(:user, nil)
1216 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1217 |> json_response(:ok)
1219 [%{"id" => id}] = response
1220 assert id == other_user.id
1223 test "requires authentification for private posts", %{conn: conn, user: user} do
1224 other_user = insert(:user)
1227 CommonAPI.post(user, %{
1228 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1229 "visibility" => "direct"
1233 |> assign(:user, nil)
1234 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1235 |> json_response(404)
1239 |> assign(:user, other_user)
1240 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1241 |> json_response(200)
1243 assert [] == response
1248 user = insert(:user)
1250 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1251 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1252 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1253 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1254 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1258 |> assign(:user, nil)
1259 |> get("/api/v1/statuses/#{id3}/context")
1260 |> json_response(:ok)
1263 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1264 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1268 test "returns the favorites of a user", %{conn: conn} do
1269 user = insert(:user)
1270 other_user = insert(:user)
1272 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1273 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1275 {:ok, _} = CommonAPI.favorite(user, activity.id)
1279 |> assign(:user, user)
1280 |> get("/api/v1/favourites")
1282 assert [status] = json_response(first_conn, 200)
1283 assert status["id"] == to_string(activity.id)
1285 assert [{"link", _link_header}] =
1286 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1288 # Honours query params
1289 {:ok, second_activity} =
1290 CommonAPI.post(other_user, %{
1292 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1295 {:ok, _} = CommonAPI.favorite(user, second_activity.id)
1297 last_like = status["id"]
1301 |> assign(:user, user)
1302 |> get("/api/v1/favourites?since_id=#{last_like}")
1304 assert [second_status] = json_response(second_conn, 200)
1305 assert second_status["id"] == to_string(second_activity.id)
1309 |> assign(:user, user)
1310 |> get("/api/v1/favourites?limit=0")
1312 assert [] = json_response(third_conn, 200)