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
15 alias Pleroma.Tests.ObanHelpers
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.CommonAPI
20 import Pleroma.Factory
22 describe "posting statuses" do
28 |> assign(:user, user)
33 test "posting a status and checks reblog count after perform all backroud job", %{conn: conn} do
34 Pleroma.Config.put([:instance, :federating], true)
35 Pleroma.Config.get([:instance, :allow_relay], true)
40 |> assign(:user, user)
41 |> post("api/v1/statuses", %{
42 "content_type" => "text/plain",
43 "source" => "Pleroma FE",
44 "status" => "Hello world",
45 "visibility" => "public"
49 assert response["reblogs_count"] == 0
50 ObanHelpers.perform_all()
54 |> assign(:user, user)
55 |> get("api/v1/statuses/#{response["id"]}", %{})
58 assert response["reblogs_count"] == 0
61 test "posting a status", %{conn: conn} do
62 idempotency_key = "Pikachu rocks!"
66 |> put_req_header("idempotency-key", idempotency_key)
67 |> post("/api/v1/statuses", %{
69 "spoiler_text" => "2hu",
70 "sensitive" => "false"
73 {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
75 assert ttl > :timer.seconds(6 * 60 * 60 - 1)
77 assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
78 json_response(conn_one, 200)
80 assert Activity.get_by_id(id)
84 |> put_req_header("idempotency-key", idempotency_key)
85 |> post("/api/v1/statuses", %{
87 "spoiler_text" => "2hu",
88 "sensitive" => "false"
91 assert %{"id" => second_id} = json_response(conn_two, 200)
92 assert id == second_id
96 |> post("/api/v1/statuses", %{
98 "spoiler_text" => "2hu",
99 "sensitive" => "false"
102 assert %{"id" => third_id} = json_response(conn_three, 200)
103 refute id == third_id
105 # An activity that will expire:
107 expires_in = 120 * 60
111 |> post("api/v1/statuses", %{
112 "status" => "oolong",
113 "expires_in" => expires_in
116 assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200)
117 assert activity = Activity.get_by_id(fourth_id)
118 assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
120 estimated_expires_at =
121 NaiveDateTime.utc_now()
122 |> NaiveDateTime.add(expires_in)
123 |> NaiveDateTime.truncate(:second)
125 # This assert will fail if the test takes longer than a minute. I sure hope it never does:
126 assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
128 assert fourth_response["pleroma"]["expires_at"] ==
129 NaiveDateTime.to_iso8601(expiration.scheduled_at)
132 test "posting an undefined status with an attachment", %{conn: conn} do
136 content_type: "image/jpg",
137 path: Path.absname("test/fixtures/image.jpg"),
138 filename: "an_image.jpg"
141 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
145 |> assign(:user, user)
146 |> post("/api/v1/statuses", %{
147 "media_ids" => [to_string(upload.id)]
150 assert json_response(conn, 200)
153 test "replying to a status", %{conn: conn} do
155 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
159 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
161 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
163 activity = Activity.get_by_id(id)
165 assert activity.data["context"] == replied_to.data["context"]
166 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
169 test "replying to a direct message with visibility other than direct", %{conn: conn} do
171 {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
173 Enum.each(["public", "private", "unlisted"], fn visibility ->
176 |> post("/api/v1/statuses", %{
177 "status" => "@#{user.nickname} hey",
178 "in_reply_to_id" => replied_to.id,
179 "visibility" => visibility
182 assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
186 test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
189 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
191 assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
192 assert Activity.get_by_id(id)
195 test "posting a sensitive status", %{conn: conn} do
198 |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
200 assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
201 assert Activity.get_by_id(id)
204 test "posting a fake status", %{conn: conn} do
207 |> post("/api/v1/statuses", %{
209 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
212 real_status = json_response(real_conn, 200)
215 assert Object.get_by_ap_id(real_status["uri"])
219 |> Map.put("id", nil)
220 |> Map.put("url", nil)
221 |> Map.put("uri", nil)
222 |> Map.put("created_at", nil)
223 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
227 |> post("/api/v1/statuses", %{
229 "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
233 fake_status = json_response(fake_conn, 200)
236 refute Object.get_by_ap_id(fake_status["uri"])
240 |> Map.put("id", nil)
241 |> Map.put("url", nil)
242 |> Map.put("uri", nil)
243 |> Map.put("created_at", nil)
244 |> Kernel.put_in(["pleroma", "conversation_id"], nil)
246 assert real_status == fake_status
249 test "posting a status with OGP link preview", %{conn: conn} do
250 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
251 Config.put([:rich_media, :enabled], true)
255 |> post("/api/v1/statuses", %{
256 "status" => "https://example.com/ogp"
259 assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
260 assert Activity.get_by_id(id)
263 test "posting a direct status", %{conn: conn} do
264 user2 = insert(:user)
265 content = "direct cofe @#{user2.nickname}"
269 |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
271 assert %{"id" => id} = response = json_response(conn, 200)
272 assert response["visibility"] == "direct"
273 assert response["pleroma"]["direct_conversation_id"]
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 scheduled statuses" do
282 test "creates a scheduled activity", %{conn: conn} do
284 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
288 |> assign(:user, user)
289 |> post("/api/v1/statuses", %{
290 "status" => "scheduled",
291 "scheduled_at" => scheduled_at
294 assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
295 assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
296 assert [] == Repo.all(Activity)
299 test "creates a scheduled activity with a media attachment", %{conn: conn} do
301 scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
304 content_type: "image/jpg",
305 path: Path.absname("test/fixtures/image.jpg"),
306 filename: "an_image.jpg"
309 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
313 |> assign(:user, user)
314 |> post("/api/v1/statuses", %{
315 "media_ids" => [to_string(upload.id)],
316 "status" => "scheduled",
317 "scheduled_at" => scheduled_at
320 assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
321 assert %{"type" => "image"} = media_attachment
324 test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
329 NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
333 |> assign(:user, user)
334 |> post("/api/v1/statuses", %{
335 "status" => "not scheduled",
336 "scheduled_at" => scheduled_at
339 assert %{"content" => "not scheduled"} = json_response(conn, 200)
340 assert [] == Repo.all(ScheduledActivity)
343 test "returns error when daily user limit is exceeded", %{conn: conn} do
347 NaiveDateTime.utc_now()
348 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
349 |> NaiveDateTime.to_iso8601()
351 attrs = %{params: %{}, scheduled_at: today}
352 {:ok, _} = ScheduledActivity.create(user, attrs)
353 {:ok, _} = ScheduledActivity.create(user, attrs)
357 |> assign(:user, user)
358 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
360 assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
363 test "returns error when total user limit is exceeded", %{conn: conn} do
367 NaiveDateTime.utc_now()
368 |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
369 |> NaiveDateTime.to_iso8601()
372 NaiveDateTime.utc_now()
373 |> NaiveDateTime.add(:timer.hours(36), :millisecond)
374 |> NaiveDateTime.to_iso8601()
376 attrs = %{params: %{}, scheduled_at: today}
377 {:ok, _} = ScheduledActivity.create(user, attrs)
378 {:ok, _} = ScheduledActivity.create(user, attrs)
379 {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
383 |> assign(:user, user)
384 |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
386 assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
390 describe "posting polls" do
391 test "posting a poll", %{conn: conn} do
393 time = NaiveDateTime.utc_now()
397 |> assign(:user, user)
398 |> post("/api/v1/statuses", %{
399 "status" => "Who is the #bestgrill?",
400 "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
403 response = json_response(conn, 200)
405 assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
406 title in ["Rei", "Asuka", "Misato"]
409 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
410 refute response["poll"]["expred"]
413 test "option limit is enforced", %{conn: conn} do
415 limit = Config.get([:instance, :poll_limits, :max_options])
419 |> assign(:user, user)
420 |> post("/api/v1/statuses", %{
422 "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
425 %{"error" => error} = json_response(conn, 422)
426 assert error == "Poll can't contain more than #{limit} options"
429 test "option character limit is enforced", %{conn: conn} do
431 limit = Config.get([:instance, :poll_limits, :max_option_chars])
435 |> assign(:user, user)
436 |> post("/api/v1/statuses", %{
439 "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
444 %{"error" => error} = json_response(conn, 422)
445 assert error == "Poll options cannot be longer than #{limit} characters each"
448 test "minimal date limit is enforced", %{conn: conn} do
450 limit = Config.get([:instance, :poll_limits, :min_expiration])
454 |> assign(:user, user)
455 |> post("/api/v1/statuses", %{
456 "status" => "imagine arbitrary limits",
458 "options" => ["this post was made by pleroma gang"],
459 "expires_in" => limit - 1
463 %{"error" => error} = json_response(conn, 422)
464 assert error == "Expiration date is too soon"
467 test "maximum date limit is enforced", %{conn: conn} do
469 limit = Config.get([:instance, :poll_limits, :max_expiration])
473 |> assign(:user, user)
474 |> post("/api/v1/statuses", %{
475 "status" => "imagine arbitrary limits",
477 "options" => ["this post was made by pleroma gang"],
478 "expires_in" => limit + 1
482 %{"error" => error} = json_response(conn, 422)
483 assert error == "Expiration date is too far in the future"
487 test "get a status", %{conn: conn} do
488 activity = insert(:note_activity)
492 |> get("/api/v1/statuses/#{activity.id}")
494 assert %{"id" => id} = json_response(conn, 200)
495 assert id == to_string(activity.id)
498 test "get a direct status", %{conn: conn} do
500 other_user = insert(:user)
503 CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"})
507 |> assign(:user, user)
508 |> get("/api/v1/statuses/#{activity.id}")
510 [participation] = Participation.for_user(user)
512 res = json_response(conn, 200)
513 assert res["pleroma"]["direct_conversation_id"] == participation.id
516 test "get statuses by IDs", %{conn: conn} do
517 %{id: id1} = insert(:note_activity)
518 %{id: id2} = insert(:note_activity)
520 query_string = "ids[]=#{id1}&ids[]=#{id2}"
521 conn = get(conn, "/api/v1/statuses/?#{query_string}")
523 assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"])
526 describe "deleting a status" do
527 test "when you created it", %{conn: conn} do
528 activity = insert(:note_activity)
529 author = User.get_cached_by_ap_id(activity.data["actor"])
533 |> assign(:user, author)
534 |> delete("/api/v1/statuses/#{activity.id}")
536 assert %{} = json_response(conn, 200)
538 refute Activity.get_by_id(activity.id)
541 test "when you didn't create it", %{conn: conn} do
542 activity = insert(:note_activity)
547 |> assign(:user, user)
548 |> delete("/api/v1/statuses/#{activity.id}")
550 assert %{"error" => _} = json_response(conn, 403)
552 assert Activity.get_by_id(activity.id) == activity
555 test "when you're an admin or moderator", %{conn: conn} do
556 activity1 = insert(:note_activity)
557 activity2 = insert(:note_activity)
558 admin = insert(:user, info: %{is_admin: true})
559 moderator = insert(:user, info: %{is_moderator: true})
563 |> assign(:user, admin)
564 |> delete("/api/v1/statuses/#{activity1.id}")
566 assert %{} = json_response(res_conn, 200)
570 |> assign(:user, moderator)
571 |> delete("/api/v1/statuses/#{activity2.id}")
573 assert %{} = json_response(res_conn, 200)
575 refute Activity.get_by_id(activity1.id)
576 refute Activity.get_by_id(activity2.id)
580 describe "reblogging" do
581 test "reblogs and returns the reblogged status", %{conn: conn} do
582 activity = insert(:note_activity)
587 |> assign(:user, user)
588 |> post("/api/v1/statuses/#{activity.id}/reblog")
591 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
593 } = json_response(conn, 200)
595 assert to_string(activity.id) == id
598 test "reblogs privately and returns the reblogged status", %{conn: conn} do
599 activity = insert(:note_activity)
604 |> assign(:user, user)
605 |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
608 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
610 "visibility" => "private"
611 } = json_response(conn, 200)
613 assert to_string(activity.id) == id
616 test "reblogged status for another user", %{conn: conn} do
617 activity = insert(:note_activity)
618 user1 = insert(:user)
619 user2 = insert(:user)
620 user3 = insert(:user)
621 CommonAPI.favorite(activity.id, user2)
622 {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
623 {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
624 {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
628 |> assign(:user, user3)
629 |> get("/api/v1/statuses/#{reblog_activity1.id}")
632 "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
633 "reblogged" => false,
634 "favourited" => false,
635 "bookmarked" => false
636 } = json_response(conn_res, 200)
640 |> assign(:user, user2)
641 |> get("/api/v1/statuses/#{reblog_activity1.id}")
644 "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
646 "favourited" => true,
648 } = json_response(conn_res, 200)
650 assert to_string(activity.id) == id
653 test "returns 400 error when activity is not exist", %{conn: conn} do
658 |> assign(:user, user)
659 |> post("/api/v1/statuses/foo/reblog")
661 assert json_response(conn, 400) == %{"error" => "Could not repeat"}
665 describe "unreblogging" do
666 test "unreblogs and returns the unreblogged status", %{conn: conn} do
667 activity = insert(:note_activity)
670 {:ok, _, _} = CommonAPI.repeat(activity.id, user)
674 |> assign(:user, user)
675 |> post("/api/v1/statuses/#{activity.id}/unreblog")
677 assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
679 assert to_string(activity.id) == id
682 test "returns 400 error when activity is not exist", %{conn: conn} do
687 |> assign(:user, user)
688 |> post("/api/v1/statuses/foo/unreblog")
690 assert json_response(conn, 400) == %{"error" => "Could not unrepeat"}
694 describe "favoriting" do
695 test "favs a status and returns it", %{conn: conn} do
696 activity = insert(:note_activity)
701 |> assign(:user, user)
702 |> post("/api/v1/statuses/#{activity.id}/favourite")
704 assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
705 json_response(conn, 200)
707 assert to_string(activity.id) == id
710 test "returns 400 error for a wrong id", %{conn: conn} do
715 |> assign(:user, user)
716 |> post("/api/v1/statuses/1/favourite")
718 assert json_response(conn, 400) == %{"error" => "Could not favorite"}
722 describe "unfavoriting" do
723 test "unfavorites a status and returns it", %{conn: conn} do
724 activity = insert(:note_activity)
727 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
731 |> assign(:user, user)
732 |> post("/api/v1/statuses/#{activity.id}/unfavourite")
734 assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
735 json_response(conn, 200)
737 assert to_string(activity.id) == id
740 test "returns 400 error for a wrong id", %{conn: conn} do
745 |> assign(:user, user)
746 |> post("/api/v1/statuses/1/unfavourite")
748 assert json_response(conn, 400) == %{"error" => "Could not unfavorite"}
752 describe "pinned statuses" do
755 {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
757 [user: user, activity: activity]
760 clear_config([:instance, :max_pinned_statuses]) do
761 Config.put([:instance, :max_pinned_statuses], 1)
764 test "pin status", %{conn: conn, user: user, activity: activity} do
765 id_str = to_string(activity.id)
767 assert %{"id" => ^id_str, "pinned" => true} =
769 |> assign(:user, user)
770 |> post("/api/v1/statuses/#{activity.id}/pin")
771 |> json_response(200)
773 assert [%{"id" => ^id_str, "pinned" => true}] =
775 |> assign(:user, user)
776 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
777 |> json_response(200)
780 test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
781 {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
785 |> assign(:user, user)
786 |> post("/api/v1/statuses/#{dm.id}/pin")
788 assert json_response(conn, 400) == %{"error" => "Could not pin"}
791 test "unpin status", %{conn: conn, user: user, activity: activity} do
792 {:ok, _} = CommonAPI.pin(activity.id, user)
794 id_str = to_string(activity.id)
795 user = refresh_record(user)
797 assert %{"id" => ^id_str, "pinned" => false} =
799 |> assign(:user, user)
800 |> post("/api/v1/statuses/#{activity.id}/unpin")
801 |> json_response(200)
805 |> assign(:user, user)
806 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
807 |> json_response(200)
810 test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do
813 |> assign(:user, user)
814 |> post("/api/v1/statuses/1/unpin")
816 assert json_response(conn, 400) == %{"error" => "Could not unpin"}
819 test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
820 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
822 id_str_one = to_string(activity_one.id)
824 assert %{"id" => ^id_str_one, "pinned" => true} =
826 |> assign(:user, user)
827 |> post("/api/v1/statuses/#{id_str_one}/pin")
828 |> json_response(200)
830 user = refresh_record(user)
832 assert %{"error" => "You have already pinned the maximum number of statuses"} =
834 |> assign(:user, user)
835 |> post("/api/v1/statuses/#{activity_two.id}/pin")
836 |> json_response(400)
842 Config.put([:rich_media, :enabled], true)
848 test "returns rich-media card", %{conn: conn, user: user} do
849 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
851 {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
854 "image" => "http://ia.media-imdb.com/images/rock.jpg",
855 "provider_name" => "example.com",
856 "provider_url" => "https://example.com",
857 "title" => "The Rock",
859 "url" => "https://example.com/ogp",
861 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
864 "image" => "http://ia.media-imdb.com/images/rock.jpg",
865 "title" => "The Rock",
866 "type" => "video.movie",
867 "url" => "https://example.com/ogp",
869 "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
876 |> get("/api/v1/statuses/#{activity.id}/card")
877 |> json_response(200)
879 assert response == card_data
881 # works with private posts
883 CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
887 |> assign(:user, user)
888 |> get("/api/v1/statuses/#{activity.id}/card")
889 |> json_response(200)
891 assert response_two == card_data
894 test "replaces missing description with an empty string", %{conn: conn, user: user} do
895 Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
898 CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
902 |> get("/api/v1/statuses/#{activity.id}/card")
903 |> json_response(:ok)
905 assert response == %{
907 "title" => "Pleroma",
910 "provider_name" => "example.com",
911 "provider_url" => "https://example.com",
912 "url" => "https://example.com/ogp-missing-data",
915 "title" => "Pleroma",
917 "url" => "https://example.com/ogp-missing-data"
926 for_user = insert(:user)
929 CommonAPI.post(user, %{
930 "status" => "heweoo?"
934 CommonAPI.post(user, %{
935 "status" => "heweoo!"
940 |> assign(:user, for_user)
941 |> post("/api/v1/statuses/#{activity1.id}/bookmark")
943 assert json_response(response1, 200)["bookmarked"] == true
947 |> assign(:user, for_user)
948 |> post("/api/v1/statuses/#{activity2.id}/bookmark")
950 assert json_response(response2, 200)["bookmarked"] == true
954 |> assign(:user, for_user)
955 |> get("/api/v1/bookmarks")
957 assert [json_response(response2, 200), json_response(response1, 200)] ==
958 json_response(bookmarks, 200)
962 |> assign(:user, for_user)
963 |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
965 assert json_response(response1, 200)["bookmarked"] == false
969 |> assign(:user, for_user)
970 |> get("/api/v1/bookmarks")
972 assert [json_response(response2, 200)] == json_response(bookmarks, 200)
975 describe "conversation muting" do
977 post_user = insert(:user)
980 {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
982 [user: user, activity: activity]
985 test "mute conversation", %{conn: conn, user: user, activity: activity} do
986 id_str = to_string(activity.id)
988 assert %{"id" => ^id_str, "muted" => true} =
990 |> assign(:user, user)
991 |> post("/api/v1/statuses/#{activity.id}/mute")
992 |> json_response(200)
995 test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
996 {:ok, _} = CommonAPI.add_mute(user, activity)
1000 |> assign(:user, user)
1001 |> post("/api/v1/statuses/#{activity.id}/mute")
1003 assert json_response(conn, 400) == %{"error" => "conversation is already muted"}
1006 test "unmute conversation", %{conn: conn, user: user, activity: activity} do
1007 {:ok, _} = CommonAPI.add_mute(user, activity)
1009 id_str = to_string(activity.id)
1010 user = refresh_record(user)
1012 assert %{"id" => ^id_str, "muted" => false} =
1014 |> assign(:user, user)
1015 |> post("/api/v1/statuses/#{activity.id}/unmute")
1016 |> json_response(200)
1020 test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
1021 user1 = insert(:user)
1022 user2 = insert(:user)
1023 user3 = insert(:user)
1025 {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})
1027 # Reply to status from another user
1030 |> assign(:user, user2)
1031 |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
1033 assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
1035 activity = Activity.get_by_id_with_object(id)
1037 assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
1038 assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
1040 # Reblog from the third user
1043 |> assign(:user, user3)
1044 |> post("/api/v1/statuses/#{activity.id}/reblog")
1046 assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
1047 json_response(conn2, 200)
1049 assert to_string(activity.id) == id
1051 # Getting third user status
1054 |> assign(:user, user3)
1055 |> get("api/v1/timelines/home")
1057 [reblogged_activity] = json_response(conn3, 200)
1059 assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
1061 replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
1062 assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
1065 describe "GET /api/v1/statuses/:id/favourited_by" do
1067 user = insert(:user)
1068 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1072 |> assign(:user, user)
1074 [conn: conn, activity: activity, user: user]
1077 test "returns users who have favorited the status", %{conn: conn, activity: activity} do
1078 other_user = insert(:user)
1079 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1083 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1084 |> json_response(:ok)
1086 [%{"id" => id}] = response
1088 assert id == other_user.id
1091 test "returns empty array when status has not been favorited yet", %{
1097 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1098 |> json_response(:ok)
1100 assert Enum.empty?(response)
1103 test "does not return users who have favorited the status but are blocked", %{
1104 conn: %{assigns: %{user: user}} = conn,
1107 other_user = insert(:user)
1108 {:ok, user} = User.block(user, other_user)
1110 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1114 |> assign(:user, user)
1115 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1116 |> json_response(:ok)
1118 assert Enum.empty?(response)
1121 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1122 other_user = insert(:user)
1123 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1127 |> assign(:user, nil)
1128 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1129 |> json_response(:ok)
1131 [%{"id" => id}] = response
1132 assert id == other_user.id
1135 test "requires authentification for private posts", %{conn: conn, user: user} do
1136 other_user = insert(:user)
1139 CommonAPI.post(user, %{
1140 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1141 "visibility" => "direct"
1144 {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
1147 |> assign(:user, nil)
1148 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1149 |> json_response(404)
1153 |> assign(:user, other_user)
1154 |> get("/api/v1/statuses/#{activity.id}/favourited_by")
1155 |> json_response(200)
1157 [%{"id" => id}] = response
1158 assert id == other_user.id
1162 describe "GET /api/v1/statuses/:id/reblogged_by" do
1164 user = insert(:user)
1165 {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
1169 |> assign(:user, user)
1171 [conn: conn, activity: activity, user: user]
1174 test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
1175 other_user = insert(:user)
1176 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1180 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1181 |> json_response(:ok)
1183 [%{"id" => id}] = response
1185 assert id == other_user.id
1188 test "returns empty array when status has not been reblogged yet", %{
1194 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1195 |> json_response(:ok)
1197 assert Enum.empty?(response)
1200 test "does not return users who have reblogged the status but are blocked", %{
1201 conn: %{assigns: %{user: user}} = conn,
1204 other_user = insert(:user)
1205 {:ok, user} = User.block(user, other_user)
1207 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1211 |> assign(:user, user)
1212 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1213 |> json_response(:ok)
1215 assert Enum.empty?(response)
1218 test "does not return users who have reblogged the status privately", %{
1219 conn: %{assigns: %{user: user}} = conn,
1222 other_user = insert(:user)
1224 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
1228 |> assign(:user, user)
1229 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1230 |> json_response(:ok)
1232 assert Enum.empty?(response)
1235 test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
1236 other_user = insert(:user)
1237 {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
1241 |> assign(:user, nil)
1242 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1243 |> json_response(:ok)
1245 [%{"id" => id}] = response
1246 assert id == other_user.id
1249 test "requires authentification for private posts", %{conn: conn, user: user} do
1250 other_user = insert(:user)
1253 CommonAPI.post(user, %{
1254 "status" => "@#{other_user.nickname} wanna get some #cofe together?",
1255 "visibility" => "direct"
1259 |> assign(:user, nil)
1260 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1261 |> json_response(404)
1265 |> assign(:user, other_user)
1266 |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
1267 |> json_response(200)
1269 assert [] == response
1274 user = insert(:user)
1276 {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"})
1277 {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1})
1278 {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2})
1279 {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3})
1280 {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4})
1284 |> assign(:user, nil)
1285 |> get("/api/v1/statuses/#{id3}/context")
1286 |> json_response(:ok)
1289 "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
1290 "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
1294 test "returns the favorites of a user", %{conn: conn} do
1295 user = insert(:user)
1296 other_user = insert(:user)
1298 {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
1299 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
1301 {:ok, _, _} = CommonAPI.favorite(activity.id, user)
1305 |> assign(:user, user)
1306 |> get("/api/v1/favourites")
1308 assert [status] = json_response(first_conn, 200)
1309 assert status["id"] == to_string(activity.id)
1311 assert [{"link", _link_header}] =
1312 Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
1314 # Honours query params
1315 {:ok, second_activity} =
1316 CommonAPI.post(other_user, %{
1318 "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
1321 {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
1323 last_like = status["id"]
1327 |> assign(:user, user)
1328 |> get("/api/v1/favourites?since_id=#{last_like}")
1330 assert [second_status] = json_response(second_conn, 200)
1331 assert second_status["id"] == to_string(second_activity.id)
1335 |> assign(:user, user)
1336 |> get("/api/v1/favourites?limit=0")
1338 assert [] = json_response(third_conn, 200)