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
13 alias Pleroma.ScheduledActivity
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.CommonAPI
18 import Pleroma.Factory
20 describe "posting statuses" do
26 |> assign(:user, user)
31 test "posting a status", %{conn: conn} do
32 idempotency_key = "Pikachu rocks!"
36 |> put_req_header("idempotency-key", idempotency_key)
37 |> post("/api/v1/statuses", %{
39 "spoiler_text" => "2hu",
40 "sensitive" => "false"
43 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
45 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
47 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
48 json_response(conn_one, 200)
50 assert Activity.get_by_id(id)
54 |> put_req_header("idempotency-key", idempotency_key)
55 |> post("/api/v1/statuses", %{
57 "spoiler_text" => "2hu",
58 "sensitive" => "false"
61 assert %{"id" => second_id} = json_response(conn_two, 200)
62 assert id == second_id
66 |> post("/api/v1/statuses", %{
68 "spoiler_text" => "2hu",
69 "sensitive" => "false"
72 assert %{"id" => third_id} = json_response(conn_three, 200)
75 # An activity that will expire:
81 |> post("api/v1/statuses", %{
83 "expires_in" => expires_in
86 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
87 assert activity = Activity.get_by_id(fourth_id)
88 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
90 estimated_expires_at =
91 NaiveDateTime.utc_now()
92 |> NaiveDateTime.add(expires_in)
93 |> NaiveDateTime.truncate(:second)
95 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
96 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
98 assert fourth_response["pleroma"]["expires_at"] ==
99 NaiveDateTime.to_iso8601(expiration.scheduled_at)
102 test "posting an empty status with an attachment", %{conn: conn} do
106 content_type: "image/jpg",
107 path: Path.absname("test/fixtures/image.jpg"),
108 filename: "an_image.jpg"
111 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
115 |> assign(:user, user)
116 |> post("/api/v1/statuses", %{
117 "media_ids" => [to_string(upload.id)],
121 assert json_response(conn, 200)
124 test "replying to a status", %{conn: conn} do
126 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
130 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
132 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
134 activity = Activity.get_by_id(id)
136 assert activity.data["context"] == replied_to.data["context"]
137 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
140 test "replying to a direct message with visibility other than direct", %{conn: conn} do
142 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
144 Enum.each(["public", "private", "unlisted"], fn visibility ->
147 |> post("/api/v1/statuses", %{
148 "status" => "@#{user.nickname} hey",
149 "in_reply_to_id" => replied_to.id,
150 "visibility" => visibility
153 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
157 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
160 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
162 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
163 assert Activity.get_by_id(id)
166 test "posting a sensitive status", %{conn: conn} do
169 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
171 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
172 assert Activity.get_by_id(id)
175 test "posting a fake status", %{conn: conn} do
178 |> post("/api/v1/statuses", %{
180 "\"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"
183 real_status = json_response(real_conn, 200)
186 assert Object.get_by_ap_id(real_status["uri"])
190 |> Map.put("id", nil)
191 |> Map.put("url", nil)
192 |> Map.put("uri", nil)
193 |> Map.put("created_at", nil)
194 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
198 |> post("/api/v1/statuses", %{
200 "\"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",
204 fake_status = json_response(fake_conn, 200)
207 refute Object.get_by_ap_id(fake_status["uri"])
211 |> Map.put("id", nil)
212 |> Map.put("url", nil)
213 |> Map.put("uri", nil)
214 |> Map.put("created_at", nil)
215 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
217 assert real_status == fake_status
220 test "posting a status with OGP link preview", %{conn: conn} do
221 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
222 Config.put([:rich_media, :enabled], true)
226 |> post("/api/v1/statuses", %{
227 "status" => "https://example.com/ogp"
230 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
231 assert Activity.get_by_id(id)
234 test "posting a direct status", %{conn: conn} do
235 user2 = insert(:user)
236 content = "direct cofe @#{user2.nickname}"
240 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
242 assert %{"id" => id} = response = json_response(conn, 200)
243 assert response["visibility"] == "direct"
244 assert response["pleroma"]["direct_conversation_id"]
245 assert activity = Activity.get_by_id(id)
246 assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
247 assert activity.data["to"] == [user2.ap_id]
248 assert activity.data["cc"] == []
252 describe "posting scheduled statuses" do
253 test "creates a scheduled activity", %{conn: conn} do
255 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
259 |> assign(:user, user)
260 |> post("/api/v1/statuses", %{
261 "status" => "scheduled",
262 "scheduled_at" => scheduled_at
265 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
266 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
267 assert [] == Repo.all(Activity)
270 test "creates a scheduled activity with a media attachment", %{conn: conn} do
272 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
275 content_type: "image/jpg",
276 path: Path.absname("test/fixtures/image.jpg"),
277 filename: "an_image.jpg"
280 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
284 |> assign(:user, user)
285 |> post("/api/v1/statuses", %{
286 "media_ids" => [to_string(upload.id)],
287 "status" => "scheduled",
288 "scheduled_at" => scheduled_at
291 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
292 assert %{"type" => "image"} = media_attachment
295 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
300 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
304 |> assign(:user, user)
305 |> post("/api/v1/statuses", %{
306 "status" => "not scheduled",
307 "scheduled_at" => scheduled_at
310 assert %{"content" => "not scheduled"} = json_response(conn, 200)
311 assert [] == Repo.all(ScheduledActivity)
314 test "returns error when daily user limit is exceeded", %{conn: conn} do
318 NaiveDateTime.utc_now()
319 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
320 |> NaiveDateTime.to_iso8601()
322 attrs = %{params: %{}, scheduled_at: today}
323 {:ok, _} = ScheduledActivity.create(user, attrs)
324 {:ok, _} = ScheduledActivity.create(user, attrs)
328 |> assign(:user, user)
329 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
331 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
334 test "returns error when total user limit is exceeded", %{conn: conn} do
338 NaiveDateTime.utc_now()
339 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
340 |> NaiveDateTime.to_iso8601()
343 NaiveDateTime.utc_now()
344 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
345 |> NaiveDateTime.to_iso8601()
347 attrs = %{params: %{}, scheduled_at: today}
348 {:ok, _} = ScheduledActivity.create(user, attrs)
349 {:ok, _} = ScheduledActivity.create(user, attrs)
350 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
354 |> assign(:user, user)
355 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
357 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
361 describe "posting polls" do
362 test "posting a poll", %{conn: conn} do
364 time = NaiveDateTime.utc_now()
368 |> assign(:user, user)
369 |> post("/api/v1/statuses", %{
370 "status" => "Who is the #bestgrill?",
371 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
374 response = json_response(conn, 200)
376 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
377 title in ["Rei", "Asuka", "Misato"]
380 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
381 refute response["poll"]["expred"]
384 test "option limit is enforced", %{conn: conn} do
386 limit = Config.get([:instance, :poll_limits, :max_options])
390 |> assign(:user, user)
391 |> post("/api/v1/statuses", %{
393 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
396 %{"error" => error} = json_response(conn, 422)
397 assert error == "Poll can't contain more than #{limit} options"
400 test "option character limit is enforced", %{conn: conn} do
402 limit = Config.get([:instance, :poll_limits, :max_option_chars])
406 |> assign(:user, user)
407 |> post("/api/v1/statuses", %{
410 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
415 %{"error" => error} = json_response(conn, 422)
416 assert error == "Poll options cannot be longer than #{limit} characters each"
419 test "minimal date limit is enforced", %{conn: conn} do
421 limit = Config.get([:instance, :poll_limits, :min_expiration])
425 |> assign(:user, user)
426 |> post("/api/v1/statuses", %{
427 "status" => "imagine arbitrary limits",
429 "options" => ["this post was made by pleroma gang"],
430 "expires_in" => limit - 1
434 %{"error" => error} = json_response(conn, 422)
435 assert error == "Expiration date is too soon"
438 test "maximum date limit is enforced", %{conn: conn} do
440 limit = Config.get([:instance, :poll_limits, :max_expiration])
444 |> assign(:user, user)
445 |> post("/api/v1/statuses", %{
446 "status" => "imagine arbitrary limits",
448 "options" => ["this post was made by pleroma gang"],
449 "expires_in" => limit + 1
453 %{"error" => error} = json_response(conn, 422)
454 assert error == "Expiration date is too far in the future"
458 test "get a status", %{conn: conn} do
459 activity = insert(:note_activity)
463 |> get("/api/v1/statuses/#{activity.id}")
465 assert %{"id" => id} = json_response(conn, 200)
466 assert id == to_string(activity.id)
469 test "get statuses by IDs", %{conn: conn} do
470 %{id: id1} = insert(:note_activity)
471 %{id: id2} = insert(:note_activity)
473 query_string = "ids[]=#{id1}&ids[]=#{id2}"
474 conn = get(conn, "/api/v1/statuses/?#{query_string}")
476 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
479 describe "deleting a status" do
480 test "when you created it", %{conn: conn} do
481 activity = insert(:note_activity)
482 author = User.get_cached_by_ap_id(activity.data["actor"])
486 |> assign(:user, author)
487 |> delete("/api/v1/statuses/#{activity.id}")
489 assert %{} = json_response(conn, 200)
491 refute Activity.get_by_id(activity.id)
494 test "when you didn't create it", %{conn: conn} do
495 activity = insert(:note_activity)
500 |> assign(:user, user)
501 |> delete("/api/v1/statuses/#{activity.id}")
503 assert %{"error" => _} = json_response(conn, 403)
505 assert Activity.get_by_id(activity.id) == activity
508 test "when you're an admin or moderator", %{conn: conn} do
509 activity1 = insert(:note_activity)
510 activity2 = insert(:note_activity)
511 admin = insert(:user, info: %{is_admin: true})
512 moderator = insert(:user, info: %{is_moderator: true})
516 |> assign(:user, admin)
517 |> delete("/api/v1/statuses/#{activity1.id}")
519 assert %{} = json_response(res_conn, 200)
523 |> assign(:user, moderator)
524 |> delete("/api/v1/statuses/#{activity2.id}")
526 assert %{} = json_response(res_conn, 200)
528 refute Activity.get_by_id(activity1.id)
529 refute Activity.get_by_id(activity2.id)
533 describe "reblogging" do
534 test "reblogs and returns the reblogged status", %{conn: conn} do
535 activity = insert(:note_activity)
540 |> assign(:user, user)
541 |> post("/api/v1/statuses/#{activity.id}/reblog")
544 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
546 } = json_response(conn, 200)
548 assert to_string(activity.id) == id
551 test "reblogged status for another user", %{conn: conn} do
552 activity = insert(:note_activity)
553 user1 = insert(:user)
554 user2 = insert(:user)
555 user3 = insert(:user)
556 CommonAPI.favorite(activity.id, user2)
557 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
558 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
559 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
563 |> assign(:user, user3)
564 |> get("/api/v1/statuses/#{reblog_activity1.id}")
567 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
568 "reblogged" => false,
569 "favourited" => false,
570 "bookmarked" => false
571 } = json_response(conn_res, 200)
575 |> assign(:user, user2)
576 |> get("/api/v1/statuses/#{reblog_activity1.id}")
579 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
581 "favourited" => true,
583 } = json_response(conn_res, 200)
585 assert to_string(activity.id) == id
588 test "returns 400 error when activity is not exist", %{conn: conn} do
593 |> assign(:user, user)
594 |> post("/api/v1/statuses/foo/reblog")
596 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
600 describe "unreblogging" do
601 test "unreblogs and returns the unreblogged status", %{conn: conn} do
602 activity = insert(:note_activity)
605 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
609 |> assign(:user, user)
610 |> post("/api/v1/statuses/#{activity.id}/unreblog")
612 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
614 assert to_string(activity.id) == id
617 test "returns 400 error when activity is not exist", %{conn: conn} do
622 |> assign(:user, user)
623 |> post("/api/v1/statuses/foo/unreblog")
625 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
629 describe "favoriting" do
630 test "favs a status and returns it", %{conn: conn} do
631 activity = insert(:note_activity)
636 |> assign(:user, user)
637 |> post("/api/v1/statuses/#{activity.id}/favourite")
639 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
640 json_response(conn, 200)
642 assert to_string(activity.id) == id
645 test "returns 400 error for a wrong id", %{conn: conn} do
650 |> assign(:user, user)
651 |> post("/api/v1/statuses/1/favourite")
653 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
657 describe "unfavoriting" do
658 test "unfavorites a status and returns it", %{conn: conn} do
659 activity = insert(:note_activity)
662 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
666 |> assign(:user, user)
667 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
669 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
670 json_response(conn, 200)
672 assert to_string(activity.id) == id
675 test "returns 400 error for a wrong id", %{conn: conn} do
680 |> assign(:user, user)
681 |> post("/api/v1/statuses/1/unfavourite")
683 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
687 describe "pinned statuses" do
690 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
692 [user: user, activity: activity]
695 clear_config([:instance, :max_pinned_statuses]) do
696 Config.put([:instance, :max_pinned_statuses], 1)
699 test "pin status", %{conn: conn, user: user, activity: activity} do
700 id_str = to_string(activity.id)
702 assert %{"id" => ^id_str, "pinned" => true} =
704 |> assign(:user, user)
705 |> post("/api/v1/statuses/#{activity.id}/pin")
706 |> json_response(200)
708 assert [%{"id" => ^id_str, "pinned" => true}] =
710 |> assign(:user, user)
711 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
712 |> json_response(200)
715 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
716 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
720 |> assign(:user, user)
721 |> post("/api/v1/statuses/#{dm.id}/pin")
723 assert json_response(conn, 400) == %{"error" => "Could not pin"}
726 test "unpin status", %{conn: conn, user: user, activity: activity} do
727 {:ok, _} = CommonAPI.pin(activity.id, user)
729 id_str = to_string(activity.id)
730 user = refresh_record(user)
732 assert %{"id" => ^id_str, "pinned" => false} =
734 |> assign(:user, user)
735 |> post("/api/v1/statuses/#{activity.id}/unpin")
736 |> json_response(200)
740 |> assign(:user, user)
741 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
742 |> json_response(200)
745 test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
748 |> assign(:user, user)
749 |> post("/api/v1/statuses/1/unpin")
751 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
754 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
755 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
757 id_str_one = to_string(activity_one.id)
759 assert %{"id" => ^id_str_one, "pinned" => true} =
761 |> assign(:user, user)
762 |> post("/api/v1/statuses/#{id_str_one}/pin")
763 |> json_response(200)
765 user = refresh_record(user)
767 assert %{"error" => "You have already pinned the maximum number of statuses"} =
769 |> assign(:user, user)
770 |> post("/api/v1/statuses/#{activity_two.id}/pin")
771 |> json_response(400)
777 Config.put([:rich_media, :enabled], true)
783 test "returns rich-media card", %{conn: conn, user: user} do
784 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
786 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
789 "image" => "http://ia.media-imdb.com/images/rock.jpg",
790 "provider_name" => "example.com",
791 "provider_url" => "https://example.com",
792 "title" => "The Rock",
794 "url" => "https://example.com/ogp",
796 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
799 "image" => "http://ia.media-imdb.com/images/rock.jpg",
800 "title" => "The Rock",
801 "type" => "video.movie",
802 "url" => "https://example.com/ogp",
804 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
811 |> get("/api/v1/statuses/#{activity.id}/card")
812 |> json_response(200)
814 assert response == card_data
816 # works with private posts
818 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
822 |> assign(:user, user)
823 |> get("/api/v1/statuses/#{activity.id}/card")
824 |> json_response(200)
826 assert response_two == card_data
829 test "replaces missing description with an empty string", %{conn: conn, user: user} do
830 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
833 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
837 |> get("/api/v1/statuses/#{activity.id}/card")
838 |> json_response(:ok)
840 assert response == %{
842 "title" => "Pleroma",
845 "provider_name" => "example.com",
846 "provider_url" => "https://example.com",
847 "url" => "https://example.com/ogp-missing-data",
850 "title" => "Pleroma",
852 "url" => "https://example.com/ogp-missing-data"
861 for_user = insert(:user)
864 CommonAPI.post(user, %{
865 "status" => "heweoo?"
869 CommonAPI.post(user, %{
870 "status" => "heweoo!"
875 |> assign(:user, for_user)
876 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
878 assert json_response(response1, 200)["bookmarked"] == true
882 |> assign(:user, for_user)
883 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
885 assert json_response(response2, 200)["bookmarked"] == true
889 |> assign(:user, for_user)
890 |> get("/api/v1/bookmarks")
892 assert [json_response(response2, 200), json_response(response1, 200)] ==
893 json_response(bookmarks, 200)
897 |> assign(:user, for_user)
898 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
900 assert json_response(response1, 200)["bookmarked"] == false
904 |> assign(:user, for_user)
905 |> get("/api/v1/bookmarks")
907 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
910 describe "conversation muting" do
912 post_user = insert(:user)
915 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
917 [user: user, activity: activity]
920 test "mute conversation", %{conn: conn, user: user, activity: activity} do
921 id_str = to_string(activity.id)
923 assert %{"id" => ^id_str, "muted" => true} =
925 |> assign(:user, user)
926 |> post("/api/v1/statuses/#{activity.id}/mute")
927 |> json_response(200)
930 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
931 {:ok, _} = CommonAPI.add_mute(user, activity)
935 |> assign(:user, user)
936 |> post("/api/v1/statuses/#{activity.id}/mute")
938 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
941 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
942 {:ok, _} = CommonAPI.add_mute(user, activity)
944 id_str = to_string(activity.id)
945 user = refresh_record(user)
947 assert %{"id" => ^id_str, "muted" => false} =
949 |> assign(:user, user)
950 |> post("/api/v1/statuses/#{activity.id}/unmute")
951 |> json_response(200)
955 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
956 user1 = insert(:user)
957 user2 = insert(:user)
958 user3 = insert(:user)
960 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
962 # Reply to status from another user
965 |> assign(:user, user2)
966 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
968 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
970 activity = Activity.get_by_id_with_object(id)
972 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
973 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
975 # Reblog from the third user
978 |> assign(:user, user3)
979 |> post("/api/v1/statuses/#{activity.id}/reblog")
981 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
982 json_response(conn2, 200)
984 assert to_string(activity.id) == id
986 # Getting third user status
989 |> assign(:user, user3)
990 |> get("api/v1/timelines/home")
992 [reblogged_activity] = json_response(conn3, 200)
994 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
996 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
997 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1000 describe "GET /api/v1/statuses/:id/favourited_by" do
1002 user = insert(:user)
1003 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1007 |> assign(:user, user)
1009 [conn: conn, activity: activity, user: user]
1012 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1013 other_user = insert(:user)
1014 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1018 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1019 |> json_response(:ok)
1021 [%{"id" => id}] = response
1023 assert id == other_user.id
1026 test "returns empty array when status has not been favorited yet", %{
1032 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1033 |> json_response(:ok)
1035 assert Enum.empty?(response)
1038 test "does not return users who have favorited the status but are blocked", %{
1039 conn: %{assigns: %{user: user}} = conn,
1042 other_user = insert(:user)
1043 {:ok, user} = User.block(user, other_user)
1045 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1049 |> assign(:user, user)
1050 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1051 |> json_response(:ok)
1053 assert Enum.empty?(response)
1056 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1057 other_user = insert(:user)
1058 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1062 |> assign(:user, nil)
1063 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1064 |> json_response(:ok)
1066 [%{"id" => id}] = response
1067 assert id == other_user.id
1070 test "requires authentification for private posts", %{conn: conn, user: user} do
1071 other_user = insert(:user)
1074 CommonAPI.post(user, %{
1075 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1076 "visibility" => "direct"
1079 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1082 |> assign(:user, nil)
1083 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1084 |> json_response(404)
1088 |> assign(:user, other_user)
1089 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1090 |> json_response(200)
1092 [%{"id" => id}] = response
1093 assert id == other_user.id
1097 describe "GET /api/v1/statuses/:id/reblogged_by" do
1099 user = insert(:user)
1100 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1104 |> assign(:user, user)
1106 [conn: conn, activity: activity, user: user]
1109 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1110 other_user = insert(:user)
1111 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1115 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1116 |> json_response(:ok)
1118 [%{"id" => id}] = response
1120 assert id == other_user.id
1123 test "returns empty array when status has not been reblogged yet", %{
1129 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1130 |> json_response(:ok)
1132 assert Enum.empty?(response)
1135 test "does not return users who have reblogged the status but are blocked", %{
1136 conn: %{assigns: %{user: user}} = conn,
1139 other_user = insert(:user)
1140 {:ok, user} = User.block(user, other_user)
1142 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1146 |> assign(:user, user)
1147 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1148 |> json_response(:ok)
1150 assert Enum.empty?(response)
1153 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1154 other_user = insert(:user)
1155 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1159 |> assign(:user, nil)
1160 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1161 |> json_response(:ok)
1163 [%{"id" => id}] = response
1164 assert id == other_user.id
1167 test "requires authentification for private posts", %{conn: conn, user: user} do
1168 other_user = insert(:user)
1171 CommonAPI.post(user, %{
1172 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1173 "visibility" => "direct"
1177 |> assign(:user, nil)
1178 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1179 |> json_response(404)
1183 |> assign(:user, other_user)
1184 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1185 |> json_response(200)
1187 assert [] == response
1192 user = insert(:user)
1194 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1195 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1196 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1197 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1198 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1202 |> assign(:user, nil)
1203 |> get("/api/v1/statuses/#{id3}/context")
1204 |> json_response(:ok)
1207 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1208 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]